##// END OF EJS Templates
Remove init_widget_js, use require.js for everything...
Jonathan Frederic -
Show More
@@ -1,127 +1,128 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2011 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 // On document ready
10 10 //============================================================================
11 11 "use strict";
12 12
13 13 // for the time beeing, we have to pass marked as a parameter here,
14 14 // as injecting require.js make marked not to put itself in the globals,
15 15 // which make both this file fail at setting marked configuration, and textcell.js
16 16 // which search marked into global.
17 require(['components/marked/lib/marked'],
17 require(['components/marked/lib/marked',
18 'notebook/js/widgets/basic_widgets'],
18 19
19 20 function (marked) {
20 21
21 22 window.marked = marked
22 23
23 24 // monkey patch CM to be able to syntax highlight cell magics
24 25 // bug reported upstream,
25 26 // see https://github.com/marijnh/CodeMirror2/issues/670
26 27 if(CodeMirror.getMode(1,'text/plain').indent == undefined ){
27 28 console.log('patching CM for undefined indent');
28 29 CodeMirror.modes.null = function() {
29 30 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0}}
30 31 }
31 32 }
32 33
33 34 CodeMirror.patchedGetMode = function(config, mode){
34 35 var cmmode = CodeMirror.getMode(config, mode);
35 36 if(cmmode.indent == null)
36 37 {
37 38 console.log('patch mode "' , mode, '" on the fly');
38 39 cmmode.indent = function(){return 0};
39 40 }
40 41 return cmmode;
41 42 }
42 43 // end monkey patching CodeMirror
43 44
44 45 IPython.mathjaxutils.init();
45 46
46 47 $('#ipython-main-app').addClass('border-box-sizing');
47 48 $('div#notebook_panel').addClass('border-box-sizing');
48 49
49 50 var baseProjectUrl = $('body').data('baseProjectUrl');
50 51 var notebookPath = $('body').data('notebookPath');
51 52 var notebookName = $('body').data('notebookName');
52 53 notebookName = decodeURIComponent(notebookName);
53 54 notebookPath = decodeURIComponent(notebookPath);
54 55 console.log(notebookName);
55 56 if (notebookPath == 'None'){
56 57 notebookPath = "";
57 58 }
58 59
59 60 IPython.page = new IPython.Page();
60 61 IPython.layout_manager = new IPython.LayoutManager();
61 62 IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
62 63 IPython.quick_help = new IPython.QuickHelp();
63 64 IPython.login_widget = new IPython.LoginWidget('span#login_widget',{baseProjectUrl:baseProjectUrl});
64 65 IPython.notebook = new IPython.Notebook('div#notebook',{baseProjectUrl:baseProjectUrl, notebookPath:notebookPath, notebookName:notebookName});
65 66 IPython.keyboard_manager = new IPython.KeyboardManager();
66 67 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
67 68 IPython.menubar = new IPython.MenuBar('#menubar',{baseProjectUrl:baseProjectUrl, notebookPath: notebookPath})
68 69 IPython.toolbar = new IPython.MainToolBar('#maintoolbar-container')
69 70 IPython.tooltip = new IPython.Tooltip()
70 71 IPython.notification_area = new IPython.NotificationArea('#notification_area')
71 72 IPython.notification_area.init_notification_widgets();
72 73
73 74 IPython.layout_manager.do_resize();
74 75
75 76 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
76 77 '<span id="test2" style="font-weight: bold;">x</span>'+
77 78 '<span id="test3" style="font-style: italic;">x</span></pre></div>')
78 79 var nh = $('#test1').innerHeight();
79 80 var bh = $('#test2').innerHeight();
80 81 var ih = $('#test3').innerHeight();
81 82 if(nh != bh || nh != ih) {
82 83 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
83 84 }
84 85 $('#fonttest').remove();
85 86
86 87 IPython.page.show();
87 88
88 89 IPython.layout_manager.do_resize();
89 90 var first_load = function () {
90 91 IPython.layout_manager.do_resize();
91 92 var hash = document.location.hash;
92 93 if (hash) {
93 94 document.location.hash = '';
94 95 document.location.hash = hash;
95 96 }
96 97 IPython.notebook.set_autosave_interval(IPython.notebook.minimum_autosave_interval);
97 98 // only do this once
98 99 $([IPython.events]).off('notebook_loaded.Notebook', first_load);
99 100 };
100 101
101 102 $([IPython.events]).on('notebook_loaded.Notebook', first_load);
102 103 $([IPython.events]).trigger('app_initialized.NotebookApp');
103 104 IPython.notebook.load_notebook(notebookName, notebookPath);
104 105
105 106 if (marked) {
106 107 marked.setOptions({
107 108 gfm : true,
108 109 tables: true,
109 110 langPrefix: "language-",
110 111 highlight: function(code, lang) {
111 112 if (!lang) {
112 113 // no language, no highlight
113 114 return code;
114 115 }
115 116 var highlighted;
116 117 try {
117 118 highlighted = hljs.highlight(lang, code, false);
118 119 } catch(err) {
119 120 highlighted = hljs.highlightAuto(code);
120 121 }
121 122 return highlighted.value;
122 123 }
123 124 })
124 125 }
125 126 }
126 127
127 128 );
@@ -1,2213 +1,2215 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2011 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 // Notebook
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 13 "use strict";
14 14
15 15 var utils = IPython.utils;
16 16
17 17 /**
18 18 * A notebook contains and manages cells.
19 19 *
20 20 * @class Notebook
21 21 * @constructor
22 22 * @param {String} selector A jQuery selector for the notebook's DOM element
23 23 * @param {Object} [options] A config object
24 24 */
25 25 var Notebook = function (selector, options) {
26 26 var options = options || {};
27 27 this._baseProjectUrl = options.baseProjectUrl;
28 28 this.notebook_path = options.notebookPath;
29 29 this.notebook_name = options.notebookName;
30 30 this.element = $(selector);
31 31 this.element.scroll();
32 32 this.element.data("notebook", this);
33 33 this.next_prompt_number = 1;
34 34 this.session = null;
35 35 this.kernel = null;
36 36 this.clipboard = null;
37 37 this.undelete_backup = null;
38 38 this.undelete_index = null;
39 39 this.undelete_below = false;
40 40 this.paste_enabled = false;
41 41 // It is important to start out in command mode to match the intial mode
42 42 // of the KeyboardManager.
43 43 this.mode = 'command';
44 44 this.set_dirty(false);
45 45 this.metadata = {};
46 46 this._checkpoint_after_save = false;
47 47 this.last_checkpoint = null;
48 48 this.checkpoints = [];
49 49 this.autosave_interval = 0;
50 50 this.autosave_timer = null;
51 51 // autosave *at most* every two minutes
52 52 this.minimum_autosave_interval = 120000;
53 53 // single worksheet for now
54 54 this.worksheet_metadata = {};
55 55 this.notebook_name_blacklist_re = /[\/\\:]/;
56 56 this.nbformat = 3 // Increment this when changing the nbformat
57 57 this.nbformat_minor = 0 // Increment this when changing the nbformat
58 58 this.style();
59 59 this.create_elements();
60 60 this.bind_events();
61 61 };
62 62
63 63 /**
64 64 * Tweak the notebook's CSS style.
65 65 *
66 66 * @method style
67 67 */
68 68 Notebook.prototype.style = function () {
69 69 $('div#notebook').addClass('border-box-sizing');
70 70 };
71 71
72 72 /**
73 73 * Get the root URL of the notebook server.
74 74 *
75 75 * @method baseProjectUrl
76 76 * @return {String} The base project URL
77 77 */
78 78 Notebook.prototype.baseProjectUrl = function() {
79 79 return this._baseProjectUrl || $('body').data('baseProjectUrl');
80 80 };
81 81
82 82 Notebook.prototype.notebookName = function() {
83 83 return $('body').data('notebookName');
84 84 };
85 85
86 86 Notebook.prototype.notebookPath = function() {
87 87 return $('body').data('notebookPath');
88 88 };
89 89
90 90 /**
91 91 * Create an HTML and CSS representation of the notebook.
92 92 *
93 93 * @method create_elements
94 94 */
95 95 Notebook.prototype.create_elements = function () {
96 96 var that = this;
97 97 this.element.attr('tabindex','-1');
98 98 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
99 99 // We add this end_space div to the end of the notebook div to:
100 100 // i) provide a margin between the last cell and the end of the notebook
101 101 // ii) to prevent the div from scrolling up when the last cell is being
102 102 // edited, but is too low on the page, which browsers will do automatically.
103 103 var end_space = $('<div/>').addClass('end_space');
104 104 end_space.dblclick(function (e) {
105 105 var ncells = that.ncells();
106 106 that.insert_cell_below('code',ncells-1);
107 107 });
108 108 this.element.append(this.container);
109 109 this.container.append(end_space);
110 110 };
111 111
112 112 /**
113 113 * Bind JavaScript events: key presses and custom IPython events.
114 114 *
115 115 * @method bind_events
116 116 */
117 117 Notebook.prototype.bind_events = function () {
118 118 var that = this;
119 119
120 120 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
121 121 var index = that.find_cell_index(data.cell);
122 122 var new_cell = that.insert_cell_below('code',index);
123 123 new_cell.set_text(data.text);
124 124 that.dirty = true;
125 125 });
126 126
127 127 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
128 128 that.dirty = data.value;
129 129 });
130 130
131 131 $([IPython.events]).on('select.Cell', function (event, data) {
132 132 var index = that.find_cell_index(data.cell);
133 133 that.select(index);
134 134 });
135 135
136 136 $([IPython.events]).on('edit_mode.Cell', function (event, data) {
137 137 var index = that.find_cell_index(data.cell);
138 138 that.select(index);
139 139 that.edit_mode();
140 140 });
141 141
142 142 $([IPython.events]).on('command_mode.Cell', function (event, data) {
143 143 that.command_mode();
144 144 });
145 145
146 146 $([IPython.events]).on('status_autorestarting.Kernel', function () {
147 147 IPython.dialog.modal({
148 148 title: "Kernel Restarting",
149 149 body: "The kernel appears to have died. It will restart automatically.",
150 150 buttons: {
151 151 OK : {
152 152 class : "btn-primary"
153 153 }
154 154 }
155 155 });
156 156 });
157 157
158 158 var collapse_time = function (time) {
159 159 var app_height = $('#ipython-main-app').height(); // content height
160 160 var splitter_height = $('div#pager_splitter').outerHeight(true);
161 161 var new_height = app_height - splitter_height;
162 162 that.element.animate({height : new_height + 'px'}, time);
163 163 };
164 164
165 165 this.element.bind('collapse_pager', function (event, extrap) {
166 166 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
167 167 collapse_time(time);
168 168 });
169 169
170 170 var expand_time = function (time) {
171 171 var app_height = $('#ipython-main-app').height(); // content height
172 172 var splitter_height = $('div#pager_splitter').outerHeight(true);
173 173 var pager_height = $('div#pager').outerHeight(true);
174 174 var new_height = app_height - pager_height - splitter_height;
175 175 that.element.animate({height : new_height + 'px'}, time);
176 176 };
177 177
178 178 this.element.bind('expand_pager', function (event, extrap) {
179 179 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
180 180 expand_time(time);
181 181 });
182 182
183 183 // Firefox 22 broke $(window).on("beforeunload")
184 184 // I'm not sure why or how.
185 185 window.onbeforeunload = function (e) {
186 186 // TODO: Make killing the kernel configurable.
187 187 var kill_kernel = false;
188 188 if (kill_kernel) {
189 189 that.session.kill_kernel();
190 190 }
191 191 // if we are autosaving, trigger an autosave on nav-away.
192 192 // still warn, because if we don't the autosave may fail.
193 193 if (that.dirty) {
194 194 if ( that.autosave_interval ) {
195 195 // schedule autosave in a timeout
196 196 // this gives you a chance to forcefully discard changes
197 197 // by reloading the page if you *really* want to.
198 198 // the timer doesn't start until you *dismiss* the dialog.
199 199 setTimeout(function () {
200 200 if (that.dirty) {
201 201 that.save_notebook();
202 202 }
203 203 }, 1000);
204 204 return "Autosave in progress, latest changes may be lost.";
205 205 } else {
206 206 return "Unsaved changes will be lost.";
207 207 }
208 208 };
209 209 // Null is the *only* return value that will make the browser not
210 210 // pop up the "don't leave" dialog.
211 211 return null;
212 212 };
213 213 };
214 214
215 215 /**
216 216 * Set the dirty flag, and trigger the set_dirty.Notebook event
217 217 *
218 218 * @method set_dirty
219 219 */
220 220 Notebook.prototype.set_dirty = function (value) {
221 221 if (value === undefined) {
222 222 value = true;
223 223 }
224 224 if (this.dirty == value) {
225 225 return;
226 226 }
227 227 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
228 228 };
229 229
230 230 /**
231 231 * Scroll the top of the page to a given cell.
232 232 *
233 233 * @method scroll_to_cell
234 234 * @param {Number} cell_number An index of the cell to view
235 235 * @param {Number} time Animation time in milliseconds
236 236 * @return {Number} Pixel offset from the top of the container
237 237 */
238 238 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
239 239 var cells = this.get_cells();
240 240 var time = time || 0;
241 241 cell_number = Math.min(cells.length-1,cell_number);
242 242 cell_number = Math.max(0 ,cell_number);
243 243 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
244 244 this.element.animate({scrollTop:scroll_value}, time);
245 245 return scroll_value;
246 246 };
247 247
248 248 /**
249 249 * Scroll to the bottom of the page.
250 250 *
251 251 * @method scroll_to_bottom
252 252 */
253 253 Notebook.prototype.scroll_to_bottom = function () {
254 254 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
255 255 };
256 256
257 257 /**
258 258 * Scroll to the top of the page.
259 259 *
260 260 * @method scroll_to_top
261 261 */
262 262 Notebook.prototype.scroll_to_top = function () {
263 263 this.element.animate({scrollTop:0}, 0);
264 264 };
265 265
266 266 // Edit Notebook metadata
267 267
268 268 Notebook.prototype.edit_metadata = function () {
269 269 var that = this;
270 270 IPython.dialog.edit_metadata(this.metadata, function (md) {
271 271 that.metadata = md;
272 272 }, 'Notebook');
273 273 };
274 274
275 275 // Cell indexing, retrieval, etc.
276 276
277 277 /**
278 278 * Get all cell elements in the notebook.
279 279 *
280 280 * @method get_cell_elements
281 281 * @return {jQuery} A selector of all cell elements
282 282 */
283 283 Notebook.prototype.get_cell_elements = function () {
284 284 return this.container.children("div.cell");
285 285 };
286 286
287 287 /**
288 288 * Get a particular cell element.
289 289 *
290 290 * @method get_cell_element
291 291 * @param {Number} index An index of a cell to select
292 292 * @return {jQuery} A selector of the given cell.
293 293 */
294 294 Notebook.prototype.get_cell_element = function (index) {
295 295 var result = null;
296 296 var e = this.get_cell_elements().eq(index);
297 297 if (e.length !== 0) {
298 298 result = e;
299 299 }
300 300 return result;
301 301 };
302 302
303 303 /**
304 304 * Count the cells in this notebook.
305 305 *
306 306 * @method ncells
307 307 * @return {Number} The number of cells in this notebook
308 308 */
309 309 Notebook.prototype.ncells = function () {
310 310 return this.get_cell_elements().length;
311 311 };
312 312
313 313 /**
314 314 * Get all Cell objects in this notebook.
315 315 *
316 316 * @method get_cells
317 317 * @return {Array} This notebook's Cell objects
318 318 */
319 319 // TODO: we are often calling cells as cells()[i], which we should optimize
320 320 // to cells(i) or a new method.
321 321 Notebook.prototype.get_cells = function () {
322 322 return this.get_cell_elements().toArray().map(function (e) {
323 323 return $(e).data("cell");
324 324 });
325 325 };
326 326
327 327 /**
328 328 * Get a Cell object from this notebook.
329 329 *
330 330 * @method get_cell
331 331 * @param {Number} index An index of a cell to retrieve
332 332 * @return {Cell} A particular cell
333 333 */
334 334 Notebook.prototype.get_cell = function (index) {
335 335 var result = null;
336 336 var ce = this.get_cell_element(index);
337 337 if (ce !== null) {
338 338 result = ce.data('cell');
339 339 }
340 340 return result;
341 341 }
342 342
343 343 /**
344 344 * Get the cell below a given cell.
345 345 *
346 346 * @method get_next_cell
347 347 * @param {Cell} cell The provided cell
348 348 * @return {Cell} The next cell
349 349 */
350 350 Notebook.prototype.get_next_cell = function (cell) {
351 351 var result = null;
352 352 var index = this.find_cell_index(cell);
353 353 if (this.is_valid_cell_index(index+1)) {
354 354 result = this.get_cell(index+1);
355 355 }
356 356 return result;
357 357 }
358 358
359 359 /**
360 360 * Get the cell above a given cell.
361 361 *
362 362 * @method get_prev_cell
363 363 * @param {Cell} cell The provided cell
364 364 * @return {Cell} The previous cell
365 365 */
366 366 Notebook.prototype.get_prev_cell = function (cell) {
367 367 // TODO: off-by-one
368 368 // nb.get_prev_cell(nb.get_cell(1)) is null
369 369 var result = null;
370 370 var index = this.find_cell_index(cell);
371 371 if (index !== null && index > 1) {
372 372 result = this.get_cell(index-1);
373 373 }
374 374 return result;
375 375 }
376 376
377 377 /**
378 378 * Get the numeric index of a given cell.
379 379 *
380 380 * @method find_cell_index
381 381 * @param {Cell} cell The provided cell
382 382 * @return {Number} The cell's numeric index
383 383 */
384 384 Notebook.prototype.find_cell_index = function (cell) {
385 385 var result = null;
386 386 this.get_cell_elements().filter(function (index) {
387 387 if ($(this).data("cell") === cell) {
388 388 result = index;
389 389 };
390 390 });
391 391 return result;
392 392 };
393 393
394 394 /**
395 395 * Get a given index , or the selected index if none is provided.
396 396 *
397 397 * @method index_or_selected
398 398 * @param {Number} index A cell's index
399 399 * @return {Number} The given index, or selected index if none is provided.
400 400 */
401 401 Notebook.prototype.index_or_selected = function (index) {
402 402 var i;
403 403 if (index === undefined || index === null) {
404 404 i = this.get_selected_index();
405 405 if (i === null) {
406 406 i = 0;
407 407 }
408 408 } else {
409 409 i = index;
410 410 }
411 411 return i;
412 412 };
413 413
414 414 /**
415 415 * Get the currently selected cell.
416 416 * @method get_selected_cell
417 417 * @return {Cell} The selected cell
418 418 */
419 419 Notebook.prototype.get_selected_cell = function () {
420 420 var index = this.get_selected_index();
421 421 return this.get_cell(index);
422 422 };
423 423
424 424 /**
425 425 * Check whether a cell index is valid.
426 426 *
427 427 * @method is_valid_cell_index
428 428 * @param {Number} index A cell index
429 429 * @return True if the index is valid, false otherwise
430 430 */
431 431 Notebook.prototype.is_valid_cell_index = function (index) {
432 432 if (index !== null && index >= 0 && index < this.ncells()) {
433 433 return true;
434 434 } else {
435 435 return false;
436 436 };
437 437 }
438 438
439 439 /**
440 440 * Get the index of the currently selected cell.
441 441
442 442 * @method get_selected_index
443 443 * @return {Number} The selected cell's numeric index
444 444 */
445 445 Notebook.prototype.get_selected_index = function () {
446 446 var result = null;
447 447 this.get_cell_elements().filter(function (index) {
448 448 if ($(this).data("cell").selected === true) {
449 449 result = index;
450 450 };
451 451 });
452 452 return result;
453 453 };
454 454
455 455
456 456 // Cell selection.
457 457
458 458 /**
459 459 * Programmatically select a cell.
460 460 *
461 461 * @method select
462 462 * @param {Number} index A cell's index
463 463 * @return {Notebook} This notebook
464 464 */
465 465 Notebook.prototype.select = function (index) {
466 466 if (this.is_valid_cell_index(index)) {
467 467 var sindex = this.get_selected_index()
468 468 if (sindex !== null && index !== sindex) {
469 469 this.command_mode();
470 470 this.get_cell(sindex).unselect();
471 471 };
472 472 var cell = this.get_cell(index);
473 473 cell.select();
474 474 if (cell.cell_type === 'heading') {
475 475 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
476 476 {'cell_type':cell.cell_type,level:cell.level}
477 477 );
478 478 } else {
479 479 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
480 480 {'cell_type':cell.cell_type}
481 481 );
482 482 };
483 483 };
484 484 return this;
485 485 };
486 486
487 487 /**
488 488 * Programmatically select the next cell.
489 489 *
490 490 * @method select_next
491 491 * @return {Notebook} This notebook
492 492 */
493 493 Notebook.prototype.select_next = function () {
494 494 var index = this.get_selected_index();
495 495 this.select(index+1);
496 496 return this;
497 497 };
498 498
499 499 /**
500 500 * Programmatically select the previous cell.
501 501 *
502 502 * @method select_prev
503 503 * @return {Notebook} This notebook
504 504 */
505 505 Notebook.prototype.select_prev = function () {
506 506 var index = this.get_selected_index();
507 507 this.select(index-1);
508 508 return this;
509 509 };
510 510
511 511
512 512 // Edit/Command mode
513 513
514 514 Notebook.prototype.get_edit_index = function () {
515 515 var result = null;
516 516 this.get_cell_elements().filter(function (index) {
517 517 if ($(this).data("cell").mode === 'edit') {
518 518 result = index;
519 519 };
520 520 });
521 521 return result;
522 522 };
523 523
524 524 Notebook.prototype.command_mode = function () {
525 525 if (this.mode !== 'command') {
526 526 var index = this.get_edit_index();
527 527 var cell = this.get_cell(index);
528 528 if (cell) {
529 529 cell.command_mode();
530 530 };
531 531 this.mode = 'command';
532 532 IPython.keyboard_manager.command_mode();
533 533 };
534 534 };
535 535
536 536 Notebook.prototype.edit_mode = function () {
537 537 if (this.mode !== 'edit') {
538 538 var cell = this.get_selected_cell();
539 539 if (cell === null) {return;} // No cell is selected
540 540 // We need to set the mode to edit to prevent reentering this method
541 541 // when cell.edit_mode() is called below.
542 542 this.mode = 'edit';
543 543 IPython.keyboard_manager.edit_mode();
544 544 cell.edit_mode();
545 545 };
546 546 };
547 547
548 548 Notebook.prototype.focus_cell = function () {
549 549 var cell = this.get_selected_cell();
550 550 if (cell === null) {return;} // No cell is selected
551 551 cell.focus_cell();
552 552 };
553 553
554 554 // Cell movement
555 555
556 556 /**
557 557 * Move given (or selected) cell up and select it.
558 558 *
559 559 * @method move_cell_up
560 560 * @param [index] {integer} cell index
561 561 * @return {Notebook} This notebook
562 562 **/
563 563 Notebook.prototype.move_cell_up = function (index) {
564 564 var i = this.index_or_selected(index);
565 565 if (this.is_valid_cell_index(i) && i > 0) {
566 566 var pivot = this.get_cell_element(i-1);
567 567 var tomove = this.get_cell_element(i);
568 568 if (pivot !== null && tomove !== null) {
569 569 tomove.detach();
570 570 pivot.before(tomove);
571 571 this.select(i-1);
572 572 var cell = this.get_selected_cell();
573 573 cell.focus_cell();
574 574 };
575 575 this.set_dirty(true);
576 576 };
577 577 return this;
578 578 };
579 579
580 580
581 581 /**
582 582 * Move given (or selected) cell down and select it
583 583 *
584 584 * @method move_cell_down
585 585 * @param [index] {integer} cell index
586 586 * @return {Notebook} This notebook
587 587 **/
588 588 Notebook.prototype.move_cell_down = function (index) {
589 589 var i = this.index_or_selected(index);
590 590 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
591 591 var pivot = this.get_cell_element(i+1);
592 592 var tomove = this.get_cell_element(i);
593 593 if (pivot !== null && tomove !== null) {
594 594 tomove.detach();
595 595 pivot.after(tomove);
596 596 this.select(i+1);
597 597 var cell = this.get_selected_cell();
598 598 cell.focus_cell();
599 599 };
600 600 };
601 601 this.set_dirty();
602 602 return this;
603 603 };
604 604
605 605
606 606 // Insertion, deletion.
607 607
608 608 /**
609 609 * Delete a cell from the notebook.
610 610 *
611 611 * @method delete_cell
612 612 * @param [index] A cell's numeric index
613 613 * @return {Notebook} This notebook
614 614 */
615 615 Notebook.prototype.delete_cell = function (index) {
616 616 var i = this.index_or_selected(index);
617 617 var cell = this.get_selected_cell();
618 618 this.undelete_backup = cell.toJSON();
619 619 $('#undelete_cell').removeClass('disabled');
620 620 if (this.is_valid_cell_index(i)) {
621 621 var old_ncells = this.ncells();
622 622 var ce = this.get_cell_element(i);
623 623 ce.remove();
624 624 if (i === 0) {
625 625 // Always make sure we have at least one cell.
626 626 if (old_ncells === 1) {
627 627 this.insert_cell_below('code');
628 628 }
629 629 this.select(0);
630 630 this.undelete_index = 0;
631 631 this.undelete_below = false;
632 632 } else if (i === old_ncells-1 && i !== 0) {
633 633 this.select(i-1);
634 634 this.undelete_index = i - 1;
635 635 this.undelete_below = true;
636 636 } else {
637 637 this.select(i);
638 638 this.undelete_index = i;
639 639 this.undelete_below = false;
640 640 };
641 641 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
642 642 this.set_dirty(true);
643 643 };
644 644 return this;
645 645 };
646 646
647 647 /**
648 648 * Restore the most recently deleted cell.
649 649 *
650 650 * @method undelete
651 651 */
652 652 Notebook.prototype.undelete_cell = function() {
653 653 if (this.undelete_backup !== null && this.undelete_index !== null) {
654 654 var current_index = this.get_selected_index();
655 655 if (this.undelete_index < current_index) {
656 656 current_index = current_index + 1;
657 657 }
658 658 if (this.undelete_index >= this.ncells()) {
659 659 this.select(this.ncells() - 1);
660 660 }
661 661 else {
662 662 this.select(this.undelete_index);
663 663 }
664 664 var cell_data = this.undelete_backup;
665 665 var new_cell = null;
666 666 if (this.undelete_below) {
667 667 new_cell = this.insert_cell_below(cell_data.cell_type);
668 668 } else {
669 669 new_cell = this.insert_cell_above(cell_data.cell_type);
670 670 }
671 671 new_cell.fromJSON(cell_data);
672 672 if (this.undelete_below) {
673 673 this.select(current_index+1);
674 674 } else {
675 675 this.select(current_index);
676 676 }
677 677 this.undelete_backup = null;
678 678 this.undelete_index = null;
679 679 }
680 680 $('#undelete_cell').addClass('disabled');
681 681 }
682 682
683 683 /**
684 684 * Insert a cell so that after insertion the cell is at given index.
685 685 *
686 686 * Similar to insert_above, but index parameter is mandatory
687 687 *
688 688 * Index will be brought back into the accissible range [0,n]
689 689 *
690 690 * @method insert_cell_at_index
691 691 * @param type {string} in ['code','markdown','heading']
692 692 * @param [index] {int} a valid index where to inser cell
693 693 *
694 694 * @return cell {cell|null} created cell or null
695 695 **/
696 696 Notebook.prototype.insert_cell_at_index = function(type, index){
697 697
698 698 var ncells = this.ncells();
699 699 var index = Math.min(index,ncells);
700 700 index = Math.max(index,0);
701 701 var cell = null;
702 702
703 703 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
704 704 if (type === 'code') {
705 705 cell = new IPython.CodeCell(this.kernel);
706 706 cell.set_input_prompt();
707 707 } else if (type === 'markdown') {
708 708 cell = new IPython.MarkdownCell();
709 709 } else if (type === 'raw') {
710 710 cell = new IPython.RawCell();
711 711 } else if (type === 'heading') {
712 712 cell = new IPython.HeadingCell();
713 713 }
714 714
715 715 if(this._insert_element_at_index(cell.element,index)) {
716 716 cell.render();
717 717 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
718 718 cell.refresh();
719 719 // We used to select the cell after we refresh it, but there
720 720 // are now cases were this method is called where select is
721 721 // not appropriate. The selection logic should be handled by the
722 722 // caller of the the top level insert_cell methods.
723 723 this.set_dirty(true);
724 724 }
725 725 }
726 726 return cell;
727 727
728 728 };
729 729
730 730 /**
731 731 * Insert an element at given cell index.
732 732 *
733 733 * @method _insert_element_at_index
734 734 * @param element {dom element} a cell element
735 735 * @param [index] {int} a valid index where to inser cell
736 736 * @private
737 737 *
738 738 * return true if everything whent fine.
739 739 **/
740 740 Notebook.prototype._insert_element_at_index = function(element, index){
741 741 if (element === undefined){
742 742 return false;
743 743 }
744 744
745 745 var ncells = this.ncells();
746 746
747 747 if (ncells === 0) {
748 748 // special case append if empty
749 749 this.element.find('div.end_space').before(element);
750 750 } else if ( ncells === index ) {
751 751 // special case append it the end, but not empty
752 752 this.get_cell_element(index-1).after(element);
753 753 } else if (this.is_valid_cell_index(index)) {
754 754 // otherwise always somewhere to append to
755 755 this.get_cell_element(index).before(element);
756 756 } else {
757 757 return false;
758 758 }
759 759
760 760 if (this.undelete_index !== null && index <= this.undelete_index) {
761 761 this.undelete_index = this.undelete_index + 1;
762 762 this.set_dirty(true);
763 763 }
764 764 return true;
765 765 };
766 766
767 767 /**
768 768 * Insert a cell of given type above given index, or at top
769 769 * of notebook if index smaller than 0.
770 770 *
771 771 * default index value is the one of currently selected cell
772 772 *
773 773 * @method insert_cell_above
774 774 * @param type {string} cell type
775 775 * @param [index] {integer}
776 776 *
777 777 * @return handle to created cell or null
778 778 **/
779 779 Notebook.prototype.insert_cell_above = function (type, index) {
780 780 index = this.index_or_selected(index);
781 781 return this.insert_cell_at_index(type, index);
782 782 };
783 783
784 784 /**
785 785 * Insert a cell of given type below given index, or at bottom
786 786 * of notebook if index greater thatn number of cell
787 787 *
788 788 * default index value is the one of currently selected cell
789 789 *
790 790 * @method insert_cell_below
791 791 * @param type {string} cell type
792 792 * @param [index] {integer}
793 793 *
794 794 * @return handle to created cell or null
795 795 *
796 796 **/
797 797 Notebook.prototype.insert_cell_below = function (type, index) {
798 798 index = this.index_or_selected(index);
799 799 return this.insert_cell_at_index(type, index+1);
800 800 };
801 801
802 802
803 803 /**
804 804 * Insert cell at end of notebook
805 805 *
806 806 * @method insert_cell_at_bottom
807 807 * @param {String} type cell type
808 808 *
809 809 * @return the added cell; or null
810 810 **/
811 811 Notebook.prototype.insert_cell_at_bottom = function (type){
812 812 var len = this.ncells();
813 813 return this.insert_cell_below(type,len-1);
814 814 };
815 815
816 816 /**
817 817 * Turn a cell into a code cell.
818 818 *
819 819 * @method to_code
820 820 * @param {Number} [index] A cell's index
821 821 */
822 822 Notebook.prototype.to_code = function (index) {
823 823 var i = this.index_or_selected(index);
824 824 if (this.is_valid_cell_index(i)) {
825 825 var source_element = this.get_cell_element(i);
826 826 var source_cell = source_element.data("cell");
827 827 if (!(source_cell instanceof IPython.CodeCell)) {
828 828 var target_cell = this.insert_cell_below('code',i);
829 829 var text = source_cell.get_text();
830 830 if (text === source_cell.placeholder) {
831 831 text = '';
832 832 }
833 833 target_cell.set_text(text);
834 834 // make this value the starting point, so that we can only undo
835 835 // to this state, instead of a blank cell
836 836 target_cell.code_mirror.clearHistory();
837 837 source_element.remove();
838 838 this.select(i);
839 839 this.edit_mode();
840 840 this.set_dirty(true);
841 841 };
842 842 };
843 843 };
844 844
845 845 /**
846 846 * Turn a cell into a Markdown cell.
847 847 *
848 848 * @method to_markdown
849 849 * @param {Number} [index] A cell's index
850 850 */
851 851 Notebook.prototype.to_markdown = function (index) {
852 852 var i = this.index_or_selected(index);
853 853 if (this.is_valid_cell_index(i)) {
854 854 var source_element = this.get_cell_element(i);
855 855 var source_cell = source_element.data("cell");
856 856 if (!(source_cell instanceof IPython.MarkdownCell)) {
857 857 var target_cell = this.insert_cell_below('markdown',i);
858 858 var text = source_cell.get_text();
859 859 if (text === source_cell.placeholder) {
860 860 text = '';
861 861 };
862 862 // We must show the editor before setting its contents
863 863 target_cell.unrender();
864 864 target_cell.set_text(text);
865 865 // make this value the starting point, so that we can only undo
866 866 // to this state, instead of a blank cell
867 867 target_cell.code_mirror.clearHistory();
868 868 source_element.remove();
869 869 this.select(i);
870 870 this.edit_mode();
871 871 this.set_dirty(true);
872 872 };
873 873 };
874 874 };
875 875
876 876 /**
877 877 * Turn a cell into a raw text cell.
878 878 *
879 879 * @method to_raw
880 880 * @param {Number} [index] A cell's index
881 881 */
882 882 Notebook.prototype.to_raw = function (index) {
883 883 var i = this.index_or_selected(index);
884 884 if (this.is_valid_cell_index(i)) {
885 885 var source_element = this.get_cell_element(i);
886 886 var source_cell = source_element.data("cell");
887 887 var target_cell = null;
888 888 if (!(source_cell instanceof IPython.RawCell)) {
889 889 target_cell = this.insert_cell_below('raw',i);
890 890 var text = source_cell.get_text();
891 891 if (text === source_cell.placeholder) {
892 892 text = '';
893 893 };
894 894 // We must show the editor before setting its contents
895 895 target_cell.unrender();
896 896 target_cell.set_text(text);
897 897 // make this value the starting point, so that we can only undo
898 898 // to this state, instead of a blank cell
899 899 target_cell.code_mirror.clearHistory();
900 900 source_element.remove();
901 901 this.select(i);
902 902 this.edit_mode();
903 903 this.set_dirty(true);
904 904 };
905 905 };
906 906 };
907 907
908 908 /**
909 909 * Turn a cell into a heading cell.
910 910 *
911 911 * @method to_heading
912 912 * @param {Number} [index] A cell's index
913 913 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
914 914 */
915 915 Notebook.prototype.to_heading = function (index, level) {
916 916 level = level || 1;
917 917 var i = this.index_or_selected(index);
918 918 if (this.is_valid_cell_index(i)) {
919 919 var source_element = this.get_cell_element(i);
920 920 var source_cell = source_element.data("cell");
921 921 var target_cell = null;
922 922 if (source_cell instanceof IPython.HeadingCell) {
923 923 source_cell.set_level(level);
924 924 } else {
925 925 target_cell = this.insert_cell_below('heading',i);
926 926 var text = source_cell.get_text();
927 927 if (text === source_cell.placeholder) {
928 928 text = '';
929 929 };
930 930 // We must show the editor before setting its contents
931 931 target_cell.set_level(level);
932 932 target_cell.unrender();
933 933 target_cell.set_text(text);
934 934 // make this value the starting point, so that we can only undo
935 935 // to this state, instead of a blank cell
936 936 target_cell.code_mirror.clearHistory();
937 937 source_element.remove();
938 938 this.select(i);
939 939 };
940 940 this.edit_mode();
941 941 this.set_dirty(true);
942 942 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
943 943 {'cell_type':'heading',level:level}
944 944 );
945 945 };
946 946 };
947 947
948 948
949 949 // Cut/Copy/Paste
950 950
951 951 /**
952 952 * Enable UI elements for pasting cells.
953 953 *
954 954 * @method enable_paste
955 955 */
956 956 Notebook.prototype.enable_paste = function () {
957 957 var that = this;
958 958 if (!this.paste_enabled) {
959 959 $('#paste_cell_replace').removeClass('disabled')
960 960 .on('click', function () {that.paste_cell_replace();});
961 961 $('#paste_cell_above').removeClass('disabled')
962 962 .on('click', function () {that.paste_cell_above();});
963 963 $('#paste_cell_below').removeClass('disabled')
964 964 .on('click', function () {that.paste_cell_below();});
965 965 this.paste_enabled = true;
966 966 };
967 967 };
968 968
969 969 /**
970 970 * Disable UI elements for pasting cells.
971 971 *
972 972 * @method disable_paste
973 973 */
974 974 Notebook.prototype.disable_paste = function () {
975 975 if (this.paste_enabled) {
976 976 $('#paste_cell_replace').addClass('disabled').off('click');
977 977 $('#paste_cell_above').addClass('disabled').off('click');
978 978 $('#paste_cell_below').addClass('disabled').off('click');
979 979 this.paste_enabled = false;
980 980 };
981 981 };
982 982
983 983 /**
984 984 * Cut a cell.
985 985 *
986 986 * @method cut_cell
987 987 */
988 988 Notebook.prototype.cut_cell = function () {
989 989 this.copy_cell();
990 990 this.delete_cell();
991 991 }
992 992
993 993 /**
994 994 * Copy a cell.
995 995 *
996 996 * @method copy_cell
997 997 */
998 998 Notebook.prototype.copy_cell = function () {
999 999 var cell = this.get_selected_cell();
1000 1000 this.clipboard = cell.toJSON();
1001 1001 this.enable_paste();
1002 1002 };
1003 1003
1004 1004 /**
1005 1005 * Replace the selected cell with a cell in the clipboard.
1006 1006 *
1007 1007 * @method paste_cell_replace
1008 1008 */
1009 1009 Notebook.prototype.paste_cell_replace = function () {
1010 1010 if (this.clipboard !== null && this.paste_enabled) {
1011 1011 var cell_data = this.clipboard;
1012 1012 var new_cell = this.insert_cell_above(cell_data.cell_type);
1013 1013 new_cell.fromJSON(cell_data);
1014 1014 var old_cell = this.get_next_cell(new_cell);
1015 1015 this.delete_cell(this.find_cell_index(old_cell));
1016 1016 this.select(this.find_cell_index(new_cell));
1017 1017 };
1018 1018 };
1019 1019
1020 1020 /**
1021 1021 * Paste a cell from the clipboard above the selected cell.
1022 1022 *
1023 1023 * @method paste_cell_above
1024 1024 */
1025 1025 Notebook.prototype.paste_cell_above = function () {
1026 1026 if (this.clipboard !== null && this.paste_enabled) {
1027 1027 var cell_data = this.clipboard;
1028 1028 var new_cell = this.insert_cell_above(cell_data.cell_type);
1029 1029 new_cell.fromJSON(cell_data);
1030 1030 };
1031 1031 };
1032 1032
1033 1033 /**
1034 1034 * Paste a cell from the clipboard below the selected cell.
1035 1035 *
1036 1036 * @method paste_cell_below
1037 1037 */
1038 1038 Notebook.prototype.paste_cell_below = function () {
1039 1039 if (this.clipboard !== null && this.paste_enabled) {
1040 1040 var cell_data = this.clipboard;
1041 1041 var new_cell = this.insert_cell_below(cell_data.cell_type);
1042 1042 new_cell.fromJSON(cell_data);
1043 1043 };
1044 1044 };
1045 1045
1046 1046 // Split/merge
1047 1047
1048 1048 /**
1049 1049 * Split the selected cell into two, at the cursor.
1050 1050 *
1051 1051 * @method split_cell
1052 1052 */
1053 1053 Notebook.prototype.split_cell = function () {
1054 1054 var mdc = IPython.MarkdownCell;
1055 1055 var rc = IPython.RawCell;
1056 1056 var cell = this.get_selected_cell();
1057 1057 if (cell.is_splittable()) {
1058 1058 var texta = cell.get_pre_cursor();
1059 1059 var textb = cell.get_post_cursor();
1060 1060 if (cell instanceof IPython.CodeCell) {
1061 1061 // In this case the operations keep the notebook in its existing mode
1062 1062 // so we don't need to do any post-op mode changes.
1063 1063 cell.set_text(textb);
1064 1064 var new_cell = this.insert_cell_above('code');
1065 1065 new_cell.set_text(texta);
1066 1066 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1067 1067 // We know cell is !rendered so we can use set_text.
1068 1068 cell.set_text(textb);
1069 1069 var new_cell = this.insert_cell_above(cell.cell_type);
1070 1070 // Unrender the new cell so we can call set_text.
1071 1071 new_cell.unrender();
1072 1072 new_cell.set_text(texta);
1073 1073 }
1074 1074 };
1075 1075 };
1076 1076
1077 1077 /**
1078 1078 * Combine the selected cell into the cell above it.
1079 1079 *
1080 1080 * @method merge_cell_above
1081 1081 */
1082 1082 Notebook.prototype.merge_cell_above = function () {
1083 1083 var mdc = IPython.MarkdownCell;
1084 1084 var rc = IPython.RawCell;
1085 1085 var index = this.get_selected_index();
1086 1086 var cell = this.get_cell(index);
1087 1087 var render = cell.rendered;
1088 1088 if (!cell.is_mergeable()) {
1089 1089 return;
1090 1090 }
1091 1091 if (index > 0) {
1092 1092 var upper_cell = this.get_cell(index-1);
1093 1093 if (!upper_cell.is_mergeable()) {
1094 1094 return;
1095 1095 }
1096 1096 var upper_text = upper_cell.get_text();
1097 1097 var text = cell.get_text();
1098 1098 if (cell instanceof IPython.CodeCell) {
1099 1099 cell.set_text(upper_text+'\n'+text);
1100 1100 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1101 1101 cell.unrender(); // Must unrender before we set_text.
1102 1102 cell.set_text(upper_text+'\n\n'+text);
1103 1103 if (render) {
1104 1104 // The rendered state of the final cell should match
1105 1105 // that of the original selected cell;
1106 1106 cell.render();
1107 1107 }
1108 1108 };
1109 1109 this.delete_cell(index-1);
1110 1110 this.select(this.find_cell_index(cell));
1111 1111 };
1112 1112 };
1113 1113
1114 1114 /**
1115 1115 * Combine the selected cell into the cell below it.
1116 1116 *
1117 1117 * @method merge_cell_below
1118 1118 */
1119 1119 Notebook.prototype.merge_cell_below = function () {
1120 1120 var mdc = IPython.MarkdownCell;
1121 1121 var rc = IPython.RawCell;
1122 1122 var index = this.get_selected_index();
1123 1123 var cell = this.get_cell(index);
1124 1124 var render = cell.rendered;
1125 1125 if (!cell.is_mergeable()) {
1126 1126 return;
1127 1127 }
1128 1128 if (index < this.ncells()-1) {
1129 1129 var lower_cell = this.get_cell(index+1);
1130 1130 if (!lower_cell.is_mergeable()) {
1131 1131 return;
1132 1132 }
1133 1133 var lower_text = lower_cell.get_text();
1134 1134 var text = cell.get_text();
1135 1135 if (cell instanceof IPython.CodeCell) {
1136 1136 cell.set_text(text+'\n'+lower_text);
1137 1137 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1138 1138 cell.unrender(); // Must unrender before we set_text.
1139 1139 cell.set_text(text+'\n\n'+lower_text);
1140 1140 if (render) {
1141 1141 // The rendered state of the final cell should match
1142 1142 // that of the original selected cell;
1143 1143 cell.render();
1144 1144 }
1145 1145 };
1146 1146 this.delete_cell(index+1);
1147 1147 this.select(this.find_cell_index(cell));
1148 1148 };
1149 1149 };
1150 1150
1151 1151
1152 1152 // Cell collapsing and output clearing
1153 1153
1154 1154 /**
1155 1155 * Hide a cell's output.
1156 1156 *
1157 1157 * @method collapse
1158 1158 * @param {Number} index A cell's numeric index
1159 1159 */
1160 1160 Notebook.prototype.collapse = function (index) {
1161 1161 var i = this.index_or_selected(index);
1162 1162 this.get_cell(i).collapse();
1163 1163 this.set_dirty(true);
1164 1164 };
1165 1165
1166 1166 /**
1167 1167 * Show a cell's output.
1168 1168 *
1169 1169 * @method expand
1170 1170 * @param {Number} index A cell's numeric index
1171 1171 */
1172 1172 Notebook.prototype.expand = function (index) {
1173 1173 var i = this.index_or_selected(index);
1174 1174 this.get_cell(i).expand();
1175 1175 this.set_dirty(true);
1176 1176 };
1177 1177
1178 1178 /** Toggle whether a cell's output is collapsed or expanded.
1179 1179 *
1180 1180 * @method toggle_output
1181 1181 * @param {Number} index A cell's numeric index
1182 1182 */
1183 1183 Notebook.prototype.toggle_output = function (index) {
1184 1184 var i = this.index_or_selected(index);
1185 1185 this.get_cell(i).toggle_output();
1186 1186 this.set_dirty(true);
1187 1187 };
1188 1188
1189 1189 /**
1190 1190 * Toggle a scrollbar for long cell outputs.
1191 1191 *
1192 1192 * @method toggle_output_scroll
1193 1193 * @param {Number} index A cell's numeric index
1194 1194 */
1195 1195 Notebook.prototype.toggle_output_scroll = function (index) {
1196 1196 var i = this.index_or_selected(index);
1197 1197 this.get_cell(i).toggle_output_scroll();
1198 1198 };
1199 1199
1200 1200 /**
1201 1201 * Hide each code cell's output area.
1202 1202 *
1203 1203 * @method collapse_all_output
1204 1204 */
1205 1205 Notebook.prototype.collapse_all_output = function () {
1206 1206 var ncells = this.ncells();
1207 1207 var cells = this.get_cells();
1208 1208 for (var i=0; i<ncells; i++) {
1209 1209 if (cells[i] instanceof IPython.CodeCell) {
1210 1210 cells[i].output_area.collapse();
1211 1211 }
1212 1212 };
1213 1213 // this should not be set if the `collapse` key is removed from nbformat
1214 1214 this.set_dirty(true);
1215 1215 };
1216 1216
1217 1217 /**
1218 1218 * Expand each code cell's output area, and add a scrollbar for long output.
1219 1219 *
1220 1220 * @method scroll_all_output
1221 1221 */
1222 1222 Notebook.prototype.scroll_all_output = function () {
1223 1223 var ncells = this.ncells();
1224 1224 var cells = this.get_cells();
1225 1225 for (var i=0; i<ncells; i++) {
1226 1226 if (cells[i] instanceof IPython.CodeCell) {
1227 1227 cells[i].output_area.expand();
1228 1228 cells[i].output_area.scroll_if_long();
1229 1229 }
1230 1230 };
1231 1231 // this should not be set if the `collapse` key is removed from nbformat
1232 1232 this.set_dirty(true);
1233 1233 };
1234 1234
1235 1235 /**
1236 1236 * Expand each code cell's output area, and remove scrollbars.
1237 1237 *
1238 1238 * @method expand_all_output
1239 1239 */
1240 1240 Notebook.prototype.expand_all_output = function () {
1241 1241 var ncells = this.ncells();
1242 1242 var cells = this.get_cells();
1243 1243 for (var i=0; i<ncells; i++) {
1244 1244 if (cells[i] instanceof IPython.CodeCell) {
1245 1245 cells[i].output_area.expand();
1246 1246 cells[i].output_area.unscroll_area();
1247 1247 }
1248 1248 };
1249 1249 // this should not be set if the `collapse` key is removed from nbformat
1250 1250 this.set_dirty(true);
1251 1251 };
1252 1252
1253 1253 /**
1254 1254 * Clear each code cell's output area.
1255 1255 *
1256 1256 * @method clear_all_output
1257 1257 */
1258 1258 Notebook.prototype.clear_all_output = function () {
1259 1259 var ncells = this.ncells();
1260 1260 var cells = this.get_cells();
1261 1261 for (var i=0; i<ncells; i++) {
1262 1262 if (cells[i] instanceof IPython.CodeCell) {
1263 1263 cells[i].clear_output();
1264 1264 // Make all In[] prompts blank, as well
1265 1265 // TODO: make this configurable (via checkbox?)
1266 1266 cells[i].set_input_prompt();
1267 1267 }
1268 1268 };
1269 1269 this.set_dirty(true);
1270 1270 };
1271 1271
1272 1272
1273 1273 // Other cell functions: line numbers, ...
1274 1274
1275 1275 /**
1276 1276 * Toggle line numbers in the selected cell's input area.
1277 1277 *
1278 1278 * @method cell_toggle_line_numbers
1279 1279 */
1280 1280 Notebook.prototype.cell_toggle_line_numbers = function() {
1281 1281 this.get_selected_cell().toggle_line_numbers();
1282 1282 };
1283 1283
1284 1284 // Session related things
1285 1285
1286 1286 /**
1287 1287 * Start a new session and set it on each code cell.
1288 1288 *
1289 1289 * @method start_session
1290 1290 */
1291 1291 Notebook.prototype.start_session = function () {
1292 1292 this.session = new IPython.Session(this.notebook_name, this.notebook_path, this);
1293 1293 this.session.start($.proxy(this._session_started, this));
1294 1294 };
1295 1295
1296 1296
1297 1297 /**
1298 * Once a session is started, link the code cells to the kernel
1298 * Once a session is started, link the code cells to the kernel and pass the
1299 * comm manager to the widget manager
1299 1300 *
1300 1301 */
1301 1302 Notebook.prototype._session_started = function(){
1302 1303 this.kernel = this.session.kernel;
1304 IPython.widget_manager.attach_comm_manager(this.kernel.comm_manager);
1303 1305 var ncells = this.ncells();
1304 1306 for (var i=0; i<ncells; i++) {
1305 1307 var cell = this.get_cell(i);
1306 1308 if (cell instanceof IPython.CodeCell) {
1307 1309 cell.set_kernel(this.session.kernel);
1308 1310 };
1309 1311 };
1310 1312 };
1311 1313
1312 1314 /**
1313 1315 * Prompt the user to restart the IPython kernel.
1314 1316 *
1315 1317 * @method restart_kernel
1316 1318 */
1317 1319 Notebook.prototype.restart_kernel = function () {
1318 1320 var that = this;
1319 1321 IPython.dialog.modal({
1320 1322 title : "Restart kernel or continue running?",
1321 1323 body : $("<p/>").html(
1322 1324 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1323 1325 ),
1324 1326 buttons : {
1325 1327 "Continue running" : {},
1326 1328 "Restart" : {
1327 1329 "class" : "btn-danger",
1328 1330 "click" : function() {
1329 1331 that.session.restart_kernel();
1330 1332 }
1331 1333 }
1332 1334 }
1333 1335 });
1334 1336 };
1335 1337
1336 1338 /**
1337 1339 * Execute or render cell outputs and go into command mode.
1338 1340 *
1339 1341 * @method execute_cell
1340 1342 */
1341 1343 Notebook.prototype.execute_cell = function () {
1342 1344 // mode = shift, ctrl, alt
1343 1345 var cell = this.get_selected_cell();
1344 1346 var cell_index = this.find_cell_index(cell);
1345 1347
1346 1348 cell.execute();
1347 1349 this.command_mode();
1348 1350 cell.focus_cell();
1349 1351 this.set_dirty(true);
1350 1352 }
1351 1353
1352 1354 /**
1353 1355 * Execute or render cell outputs and insert a new cell below.
1354 1356 *
1355 1357 * @method execute_cell_and_insert_below
1356 1358 */
1357 1359 Notebook.prototype.execute_cell_and_insert_below = function () {
1358 1360 var cell = this.get_selected_cell();
1359 1361 var cell_index = this.find_cell_index(cell);
1360 1362
1361 1363 cell.execute();
1362 1364
1363 1365 // If we are at the end always insert a new cell and return
1364 1366 if (cell_index === (this.ncells()-1)) {
1365 1367 this.insert_cell_below('code');
1366 1368 this.select(cell_index+1);
1367 1369 this.edit_mode();
1368 1370 this.scroll_to_bottom();
1369 1371 this.set_dirty(true);
1370 1372 return;
1371 1373 }
1372 1374
1373 1375 // Only insert a new cell, if we ended up in an already populated cell
1374 1376 var next_text = this.get_cell(cell_index+1).get_text();
1375 1377 if (/\S/.test(next_text) === true) {
1376 1378 this.insert_cell_below('code');
1377 1379 }
1378 1380 this.select(cell_index+1);
1379 1381 this.edit_mode();
1380 1382 this.set_dirty(true);
1381 1383 };
1382 1384
1383 1385 /**
1384 1386 * Execute or render cell outputs and select the next cell.
1385 1387 *
1386 1388 * @method execute_cell_and_select_below
1387 1389 */
1388 1390 Notebook.prototype.execute_cell_and_select_below = function () {
1389 1391
1390 1392 var cell = this.get_selected_cell();
1391 1393 var cell_index = this.find_cell_index(cell);
1392 1394
1393 1395 cell.execute();
1394 1396
1395 1397 // If we are at the end always insert a new cell and return
1396 1398 if (cell_index === (this.ncells()-1)) {
1397 1399 this.insert_cell_below('code');
1398 1400 this.select(cell_index+1);
1399 1401 this.edit_mode();
1400 1402 this.scroll_to_bottom();
1401 1403 this.set_dirty(true);
1402 1404 return;
1403 1405 }
1404 1406
1405 1407 this.select(cell_index+1);
1406 1408 this.get_cell(cell_index+1).focus_cell();
1407 1409 this.set_dirty(true);
1408 1410 };
1409 1411
1410 1412 /**
1411 1413 * Execute all cells below the selected cell.
1412 1414 *
1413 1415 * @method execute_cells_below
1414 1416 */
1415 1417 Notebook.prototype.execute_cells_below = function () {
1416 1418 this.execute_cell_range(this.get_selected_index(), this.ncells());
1417 1419 this.scroll_to_bottom();
1418 1420 };
1419 1421
1420 1422 /**
1421 1423 * Execute all cells above the selected cell.
1422 1424 *
1423 1425 * @method execute_cells_above
1424 1426 */
1425 1427 Notebook.prototype.execute_cells_above = function () {
1426 1428 this.execute_cell_range(0, this.get_selected_index());
1427 1429 };
1428 1430
1429 1431 /**
1430 1432 * Execute all cells.
1431 1433 *
1432 1434 * @method execute_all_cells
1433 1435 */
1434 1436 Notebook.prototype.execute_all_cells = function () {
1435 1437 this.execute_cell_range(0, this.ncells());
1436 1438 this.scroll_to_bottom();
1437 1439 };
1438 1440
1439 1441 /**
1440 1442 * Execute a contiguous range of cells.
1441 1443 *
1442 1444 * @method execute_cell_range
1443 1445 * @param {Number} start Index of the first cell to execute (inclusive)
1444 1446 * @param {Number} end Index of the last cell to execute (exclusive)
1445 1447 */
1446 1448 Notebook.prototype.execute_cell_range = function (start, end) {
1447 1449 for (var i=start; i<end; i++) {
1448 1450 this.select(i);
1449 1451 this.execute_cell();
1450 1452 };
1451 1453 };
1452 1454
1453 1455 // Persistance and loading
1454 1456
1455 1457 /**
1456 1458 * Getter method for this notebook's name.
1457 1459 *
1458 1460 * @method get_notebook_name
1459 1461 * @return {String} This notebook's name
1460 1462 */
1461 1463 Notebook.prototype.get_notebook_name = function () {
1462 1464 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1463 1465 return nbname;
1464 1466 };
1465 1467
1466 1468 /**
1467 1469 * Setter method for this notebook's name.
1468 1470 *
1469 1471 * @method set_notebook_name
1470 1472 * @param {String} name A new name for this notebook
1471 1473 */
1472 1474 Notebook.prototype.set_notebook_name = function (name) {
1473 1475 this.notebook_name = name;
1474 1476 };
1475 1477
1476 1478 /**
1477 1479 * Check that a notebook's name is valid.
1478 1480 *
1479 1481 * @method test_notebook_name
1480 1482 * @param {String} nbname A name for this notebook
1481 1483 * @return {Boolean} True if the name is valid, false if invalid
1482 1484 */
1483 1485 Notebook.prototype.test_notebook_name = function (nbname) {
1484 1486 nbname = nbname || '';
1485 1487 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1486 1488 return true;
1487 1489 } else {
1488 1490 return false;
1489 1491 };
1490 1492 };
1491 1493
1492 1494 /**
1493 1495 * Load a notebook from JSON (.ipynb).
1494 1496 *
1495 1497 * This currently handles one worksheet: others are deleted.
1496 1498 *
1497 1499 * @method fromJSON
1498 1500 * @param {Object} data JSON representation of a notebook
1499 1501 */
1500 1502 Notebook.prototype.fromJSON = function (data) {
1501 1503 var content = data.content;
1502 1504 var ncells = this.ncells();
1503 1505 var i;
1504 1506 for (i=0; i<ncells; i++) {
1505 1507 // Always delete cell 0 as they get renumbered as they are deleted.
1506 1508 this.delete_cell(0);
1507 1509 };
1508 1510 // Save the metadata and name.
1509 1511 this.metadata = content.metadata;
1510 1512 this.notebook_name = data.name;
1511 1513 // Only handle 1 worksheet for now.
1512 1514 var worksheet = content.worksheets[0];
1513 1515 if (worksheet !== undefined) {
1514 1516 if (worksheet.metadata) {
1515 1517 this.worksheet_metadata = worksheet.metadata;
1516 1518 }
1517 1519 var new_cells = worksheet.cells;
1518 1520 ncells = new_cells.length;
1519 1521 var cell_data = null;
1520 1522 var new_cell = null;
1521 1523 for (i=0; i<ncells; i++) {
1522 1524 cell_data = new_cells[i];
1523 1525 // VERSIONHACK: plaintext -> raw
1524 1526 // handle never-released plaintext name for raw cells
1525 1527 if (cell_data.cell_type === 'plaintext'){
1526 1528 cell_data.cell_type = 'raw';
1527 1529 }
1528 1530
1529 1531 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1530 1532 new_cell.fromJSON(cell_data);
1531 1533 };
1532 1534 };
1533 1535 if (content.worksheets.length > 1) {
1534 1536 IPython.dialog.modal({
1535 1537 title : "Multiple worksheets",
1536 1538 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1537 1539 "but this version of IPython can only handle the first. " +
1538 1540 "If you save this notebook, worksheets after the first will be lost.",
1539 1541 buttons : {
1540 1542 OK : {
1541 1543 class : "btn-danger"
1542 1544 }
1543 1545 }
1544 1546 });
1545 1547 }
1546 1548 };
1547 1549
1548 1550 /**
1549 1551 * Dump this notebook into a JSON-friendly object.
1550 1552 *
1551 1553 * @method toJSON
1552 1554 * @return {Object} A JSON-friendly representation of this notebook.
1553 1555 */
1554 1556 Notebook.prototype.toJSON = function () {
1555 1557 var cells = this.get_cells();
1556 1558 var ncells = cells.length;
1557 1559 var cell_array = new Array(ncells);
1558 1560 for (var i=0; i<ncells; i++) {
1559 1561 cell_array[i] = cells[i].toJSON();
1560 1562 };
1561 1563 var data = {
1562 1564 // Only handle 1 worksheet for now.
1563 1565 worksheets : [{
1564 1566 cells: cell_array,
1565 1567 metadata: this.worksheet_metadata
1566 1568 }],
1567 1569 metadata : this.metadata
1568 1570 };
1569 1571 return data;
1570 1572 };
1571 1573
1572 1574 /**
1573 1575 * Start an autosave timer, for periodically saving the notebook.
1574 1576 *
1575 1577 * @method set_autosave_interval
1576 1578 * @param {Integer} interval the autosave interval in milliseconds
1577 1579 */
1578 1580 Notebook.prototype.set_autosave_interval = function (interval) {
1579 1581 var that = this;
1580 1582 // clear previous interval, so we don't get simultaneous timers
1581 1583 if (this.autosave_timer) {
1582 1584 clearInterval(this.autosave_timer);
1583 1585 }
1584 1586
1585 1587 this.autosave_interval = this.minimum_autosave_interval = interval;
1586 1588 if (interval) {
1587 1589 this.autosave_timer = setInterval(function() {
1588 1590 if (that.dirty) {
1589 1591 that.save_notebook();
1590 1592 }
1591 1593 }, interval);
1592 1594 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1593 1595 } else {
1594 1596 this.autosave_timer = null;
1595 1597 $([IPython.events]).trigger("autosave_disabled.Notebook");
1596 1598 };
1597 1599 };
1598 1600
1599 1601 /**
1600 1602 * Save this notebook on the server.
1601 1603 *
1602 1604 * @method save_notebook
1603 1605 */
1604 1606 Notebook.prototype.save_notebook = function (extra_settings) {
1605 1607 // Create a JSON model to be sent to the server.
1606 1608 var model = {};
1607 1609 model.name = this.notebook_name;
1608 1610 model.path = this.notebook_path;
1609 1611 model.content = this.toJSON();
1610 1612 model.content.nbformat = this.nbformat;
1611 1613 model.content.nbformat_minor = this.nbformat_minor;
1612 1614 // time the ajax call for autosave tuning purposes.
1613 1615 var start = new Date().getTime();
1614 1616 // We do the call with settings so we can set cache to false.
1615 1617 var settings = {
1616 1618 processData : false,
1617 1619 cache : false,
1618 1620 type : "PUT",
1619 1621 data : JSON.stringify(model),
1620 1622 headers : {'Content-Type': 'application/json'},
1621 1623 success : $.proxy(this.save_notebook_success, this, start),
1622 1624 error : $.proxy(this.save_notebook_error, this)
1623 1625 };
1624 1626 if (extra_settings) {
1625 1627 for (var key in extra_settings) {
1626 1628 settings[key] = extra_settings[key];
1627 1629 }
1628 1630 }
1629 1631 $([IPython.events]).trigger('notebook_saving.Notebook');
1630 1632 var url = utils.url_join_encode(
1631 1633 this._baseProjectUrl,
1632 1634 'api/notebooks',
1633 1635 this.notebook_path,
1634 1636 this.notebook_name
1635 1637 );
1636 1638 $.ajax(url, settings);
1637 1639 };
1638 1640
1639 1641 /**
1640 1642 * Success callback for saving a notebook.
1641 1643 *
1642 1644 * @method save_notebook_success
1643 1645 * @param {Integer} start the time when the save request started
1644 1646 * @param {Object} data JSON representation of a notebook
1645 1647 * @param {String} status Description of response status
1646 1648 * @param {jqXHR} xhr jQuery Ajax object
1647 1649 */
1648 1650 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1649 1651 this.set_dirty(false);
1650 1652 $([IPython.events]).trigger('notebook_saved.Notebook');
1651 1653 this._update_autosave_interval(start);
1652 1654 if (this._checkpoint_after_save) {
1653 1655 this.create_checkpoint();
1654 1656 this._checkpoint_after_save = false;
1655 1657 };
1656 1658 };
1657 1659
1658 1660 /**
1659 1661 * update the autosave interval based on how long the last save took
1660 1662 *
1661 1663 * @method _update_autosave_interval
1662 1664 * @param {Integer} timestamp when the save request started
1663 1665 */
1664 1666 Notebook.prototype._update_autosave_interval = function (start) {
1665 1667 var duration = (new Date().getTime() - start);
1666 1668 if (this.autosave_interval) {
1667 1669 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1668 1670 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1669 1671 // round to 10 seconds, otherwise we will be setting a new interval too often
1670 1672 interval = 10000 * Math.round(interval / 10000);
1671 1673 // set new interval, if it's changed
1672 1674 if (interval != this.autosave_interval) {
1673 1675 this.set_autosave_interval(interval);
1674 1676 }
1675 1677 }
1676 1678 };
1677 1679
1678 1680 /**
1679 1681 * Failure callback for saving a notebook.
1680 1682 *
1681 1683 * @method save_notebook_error
1682 1684 * @param {jqXHR} xhr jQuery Ajax object
1683 1685 * @param {String} status Description of response status
1684 1686 * @param {String} error HTTP error message
1685 1687 */
1686 1688 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1687 1689 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1688 1690 };
1689 1691
1690 1692 Notebook.prototype.new_notebook = function(){
1691 1693 var path = this.notebook_path;
1692 1694 var base_project_url = this._baseProjectUrl;
1693 1695 var settings = {
1694 1696 processData : false,
1695 1697 cache : false,
1696 1698 type : "POST",
1697 1699 dataType : "json",
1698 1700 async : false,
1699 1701 success : function (data, status, xhr){
1700 1702 var notebook_name = data.name;
1701 1703 window.open(
1702 1704 utils.url_join_encode(
1703 1705 base_project_url,
1704 1706 'notebooks',
1705 1707 path,
1706 1708 notebook_name
1707 1709 ),
1708 1710 '_blank'
1709 1711 );
1710 1712 }
1711 1713 };
1712 1714 var url = utils.url_join_encode(
1713 1715 base_project_url,
1714 1716 'api/notebooks',
1715 1717 path
1716 1718 );
1717 1719 $.ajax(url,settings);
1718 1720 };
1719 1721
1720 1722
1721 1723 Notebook.prototype.copy_notebook = function(){
1722 1724 var path = this.notebook_path;
1723 1725 var base_project_url = this._baseProjectUrl;
1724 1726 var settings = {
1725 1727 processData : false,
1726 1728 cache : false,
1727 1729 type : "POST",
1728 1730 dataType : "json",
1729 1731 data : JSON.stringify({copy_from : this.notebook_name}),
1730 1732 async : false,
1731 1733 success : function (data, status, xhr) {
1732 1734 window.open(utils.url_join_encode(
1733 1735 base_project_url,
1734 1736 'notebooks',
1735 1737 data.path,
1736 1738 data.name
1737 1739 ), '_blank');
1738 1740 }
1739 1741 };
1740 1742 var url = utils.url_join_encode(
1741 1743 base_project_url,
1742 1744 'api/notebooks',
1743 1745 path
1744 1746 );
1745 1747 $.ajax(url,settings);
1746 1748 };
1747 1749
1748 1750 Notebook.prototype.rename = function (nbname) {
1749 1751 var that = this;
1750 1752 var data = {name: nbname + '.ipynb'};
1751 1753 var settings = {
1752 1754 processData : false,
1753 1755 cache : false,
1754 1756 type : "PATCH",
1755 1757 data : JSON.stringify(data),
1756 1758 dataType: "json",
1757 1759 headers : {'Content-Type': 'application/json'},
1758 1760 success : $.proxy(that.rename_success, this),
1759 1761 error : $.proxy(that.rename_error, this)
1760 1762 };
1761 1763 $([IPython.events]).trigger('rename_notebook.Notebook', data);
1762 1764 var url = utils.url_join_encode(
1763 1765 this._baseProjectUrl,
1764 1766 'api/notebooks',
1765 1767 this.notebook_path,
1766 1768 this.notebook_name
1767 1769 );
1768 1770 $.ajax(url, settings);
1769 1771 };
1770 1772
1771 1773
1772 1774 Notebook.prototype.rename_success = function (json, status, xhr) {
1773 1775 this.notebook_name = json.name;
1774 1776 var name = this.notebook_name;
1775 1777 var path = json.path;
1776 1778 this.session.rename_notebook(name, path);
1777 1779 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
1778 1780 }
1779 1781
1780 1782 Notebook.prototype.rename_error = function (xhr, status, error) {
1781 1783 var that = this;
1782 1784 var dialog = $('<div/>').append(
1783 1785 $("<p/>").addClass("rename-message")
1784 1786 .html('This notebook name already exists.')
1785 1787 )
1786 1788 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1787 1789 IPython.dialog.modal({
1788 1790 title: "Notebook Rename Error!",
1789 1791 body: dialog,
1790 1792 buttons : {
1791 1793 "Cancel": {},
1792 1794 "OK": {
1793 1795 class: "btn-primary",
1794 1796 click: function () {
1795 1797 IPython.save_widget.rename_notebook();
1796 1798 }}
1797 1799 },
1798 1800 open : function (event, ui) {
1799 1801 var that = $(this);
1800 1802 // Upon ENTER, click the OK button.
1801 1803 that.find('input[type="text"]').keydown(function (event, ui) {
1802 1804 if (event.which === utils.keycodes.ENTER) {
1803 1805 that.find('.btn-primary').first().click();
1804 1806 }
1805 1807 });
1806 1808 that.find('input[type="text"]').focus();
1807 1809 }
1808 1810 });
1809 1811 }
1810 1812
1811 1813 /**
1812 1814 * Request a notebook's data from the server.
1813 1815 *
1814 1816 * @method load_notebook
1815 1817 * @param {String} notebook_name and path A notebook to load
1816 1818 */
1817 1819 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
1818 1820 var that = this;
1819 1821 this.notebook_name = notebook_name;
1820 1822 this.notebook_path = notebook_path;
1821 1823 // We do the call with settings so we can set cache to false.
1822 1824 var settings = {
1823 1825 processData : false,
1824 1826 cache : false,
1825 1827 type : "GET",
1826 1828 dataType : "json",
1827 1829 success : $.proxy(this.load_notebook_success,this),
1828 1830 error : $.proxy(this.load_notebook_error,this),
1829 1831 };
1830 1832 $([IPython.events]).trigger('notebook_loading.Notebook');
1831 1833 var url = utils.url_join_encode(
1832 1834 this._baseProjectUrl,
1833 1835 'api/notebooks',
1834 1836 this.notebook_path,
1835 1837 this.notebook_name
1836 1838 );
1837 1839 $.ajax(url, settings);
1838 1840 };
1839 1841
1840 1842 /**
1841 1843 * Success callback for loading a notebook from the server.
1842 1844 *
1843 1845 * Load notebook data from the JSON response.
1844 1846 *
1845 1847 * @method load_notebook_success
1846 1848 * @param {Object} data JSON representation of a notebook
1847 1849 * @param {String} status Description of response status
1848 1850 * @param {jqXHR} xhr jQuery Ajax object
1849 1851 */
1850 1852 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1851 1853 this.fromJSON(data);
1852 1854 if (this.ncells() === 0) {
1853 1855 this.insert_cell_below('code');
1854 1856 this.select(0);
1855 1857 this.edit_mode();
1856 1858 } else {
1857 1859 this.select(0);
1858 1860 this.command_mode();
1859 1861 };
1860 1862 this.set_dirty(false);
1861 1863 this.scroll_to_top();
1862 1864 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1863 1865 var msg = "This notebook has been converted from an older " +
1864 1866 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1865 1867 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1866 1868 "newer notebook format will be used and older versions of IPython " +
1867 1869 "may not be able to read it. To keep the older version, close the " +
1868 1870 "notebook without saving it.";
1869 1871 IPython.dialog.modal({
1870 1872 title : "Notebook converted",
1871 1873 body : msg,
1872 1874 buttons : {
1873 1875 OK : {
1874 1876 class : "btn-primary"
1875 1877 }
1876 1878 }
1877 1879 });
1878 1880 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1879 1881 var that = this;
1880 1882 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1881 1883 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1882 1884 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1883 1885 this_vs + ". You can still work with this notebook, but some features " +
1884 1886 "introduced in later notebook versions may not be available."
1885 1887
1886 1888 IPython.dialog.modal({
1887 1889 title : "Newer Notebook",
1888 1890 body : msg,
1889 1891 buttons : {
1890 1892 OK : {
1891 1893 class : "btn-danger"
1892 1894 }
1893 1895 }
1894 1896 });
1895 1897
1896 1898 }
1897 1899
1898 1900 // Create the session after the notebook is completely loaded to prevent
1899 1901 // code execution upon loading, which is a security risk.
1900 1902 if (this.session == null) {
1901 1903 this.start_session();
1902 1904 }
1903 1905 // load our checkpoint list
1904 1906 this.list_checkpoints();
1905 1907
1906 1908 // load toolbar state
1907 1909 if (this.metadata.celltoolbar) {
1908 1910 IPython.CellToolbar.global_show();
1909 1911 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
1910 1912 }
1911 1913
1912 1914 $([IPython.events]).trigger('notebook_loaded.Notebook');
1913 1915 };
1914 1916
1915 1917 /**
1916 1918 * Failure callback for loading a notebook from the server.
1917 1919 *
1918 1920 * @method load_notebook_error
1919 1921 * @param {jqXHR} xhr jQuery Ajax object
1920 1922 * @param {String} status Description of response status
1921 1923 * @param {String} error HTTP error message
1922 1924 */
1923 1925 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
1924 1926 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
1925 1927 if (xhr.status === 400) {
1926 1928 var msg = error;
1927 1929 } else if (xhr.status === 500) {
1928 1930 var msg = "An unknown error occurred while loading this notebook. " +
1929 1931 "This version can load notebook formats " +
1930 1932 "v" + this.nbformat + " or earlier.";
1931 1933 }
1932 1934 IPython.dialog.modal({
1933 1935 title: "Error loading notebook",
1934 1936 body : msg,
1935 1937 buttons : {
1936 1938 "OK": {}
1937 1939 }
1938 1940 });
1939 1941 }
1940 1942
1941 1943 /********************* checkpoint-related *********************/
1942 1944
1943 1945 /**
1944 1946 * Save the notebook then immediately create a checkpoint.
1945 1947 *
1946 1948 * @method save_checkpoint
1947 1949 */
1948 1950 Notebook.prototype.save_checkpoint = function () {
1949 1951 this._checkpoint_after_save = true;
1950 1952 this.save_notebook();
1951 1953 };
1952 1954
1953 1955 /**
1954 1956 * Add a checkpoint for this notebook.
1955 1957 * for use as a callback from checkpoint creation.
1956 1958 *
1957 1959 * @method add_checkpoint
1958 1960 */
1959 1961 Notebook.prototype.add_checkpoint = function (checkpoint) {
1960 1962 var found = false;
1961 1963 for (var i = 0; i < this.checkpoints.length; i++) {
1962 1964 var existing = this.checkpoints[i];
1963 1965 if (existing.id == checkpoint.id) {
1964 1966 found = true;
1965 1967 this.checkpoints[i] = checkpoint;
1966 1968 break;
1967 1969 }
1968 1970 }
1969 1971 if (!found) {
1970 1972 this.checkpoints.push(checkpoint);
1971 1973 }
1972 1974 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
1973 1975 };
1974 1976
1975 1977 /**
1976 1978 * List checkpoints for this notebook.
1977 1979 *
1978 1980 * @method list_checkpoints
1979 1981 */
1980 1982 Notebook.prototype.list_checkpoints = function () {
1981 1983 var url = utils.url_join_encode(
1982 1984 this._baseProjectUrl,
1983 1985 'api/notebooks',
1984 1986 this.notebook_path,
1985 1987 this.notebook_name,
1986 1988 'checkpoints'
1987 1989 );
1988 1990 $.get(url).done(
1989 1991 $.proxy(this.list_checkpoints_success, this)
1990 1992 ).fail(
1991 1993 $.proxy(this.list_checkpoints_error, this)
1992 1994 );
1993 1995 };
1994 1996
1995 1997 /**
1996 1998 * Success callback for listing checkpoints.
1997 1999 *
1998 2000 * @method list_checkpoint_success
1999 2001 * @param {Object} data JSON representation of a checkpoint
2000 2002 * @param {String} status Description of response status
2001 2003 * @param {jqXHR} xhr jQuery Ajax object
2002 2004 */
2003 2005 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2004 2006 var data = $.parseJSON(data);
2005 2007 this.checkpoints = data;
2006 2008 if (data.length) {
2007 2009 this.last_checkpoint = data[data.length - 1];
2008 2010 } else {
2009 2011 this.last_checkpoint = null;
2010 2012 }
2011 2013 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
2012 2014 };
2013 2015
2014 2016 /**
2015 2017 * Failure callback for listing a checkpoint.
2016 2018 *
2017 2019 * @method list_checkpoint_error
2018 2020 * @param {jqXHR} xhr jQuery Ajax object
2019 2021 * @param {String} status Description of response status
2020 2022 * @param {String} error_msg HTTP error message
2021 2023 */
2022 2024 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2023 2025 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
2024 2026 };
2025 2027
2026 2028 /**
2027 2029 * Create a checkpoint of this notebook on the server from the most recent save.
2028 2030 *
2029 2031 * @method create_checkpoint
2030 2032 */
2031 2033 Notebook.prototype.create_checkpoint = function () {
2032 2034 var url = utils.url_join_encode(
2033 2035 this._baseProjectUrl,
2034 2036 'api/notebooks',
2035 2037 this.notebookPath(),
2036 2038 this.notebook_name,
2037 2039 'checkpoints'
2038 2040 );
2039 2041 $.post(url).done(
2040 2042 $.proxy(this.create_checkpoint_success, this)
2041 2043 ).fail(
2042 2044 $.proxy(this.create_checkpoint_error, this)
2043 2045 );
2044 2046 };
2045 2047
2046 2048 /**
2047 2049 * Success callback for creating a checkpoint.
2048 2050 *
2049 2051 * @method create_checkpoint_success
2050 2052 * @param {Object} data JSON representation of a checkpoint
2051 2053 * @param {String} status Description of response status
2052 2054 * @param {jqXHR} xhr jQuery Ajax object
2053 2055 */
2054 2056 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2055 2057 var data = $.parseJSON(data);
2056 2058 this.add_checkpoint(data);
2057 2059 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2058 2060 };
2059 2061
2060 2062 /**
2061 2063 * Failure callback for creating a checkpoint.
2062 2064 *
2063 2065 * @method create_checkpoint_error
2064 2066 * @param {jqXHR} xhr jQuery Ajax object
2065 2067 * @param {String} status Description of response status
2066 2068 * @param {String} error_msg HTTP error message
2067 2069 */
2068 2070 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2069 2071 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2070 2072 };
2071 2073
2072 2074 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2073 2075 var that = this;
2074 2076 var checkpoint = checkpoint || this.last_checkpoint;
2075 2077 if ( ! checkpoint ) {
2076 2078 console.log("restore dialog, but no checkpoint to restore to!");
2077 2079 return;
2078 2080 }
2079 2081 var body = $('<div/>').append(
2080 2082 $('<p/>').addClass("p-space").text(
2081 2083 "Are you sure you want to revert the notebook to " +
2082 2084 "the latest checkpoint?"
2083 2085 ).append(
2084 2086 $("<strong/>").text(
2085 2087 " This cannot be undone."
2086 2088 )
2087 2089 )
2088 2090 ).append(
2089 2091 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2090 2092 ).append(
2091 2093 $('<p/>').addClass("p-space").text(
2092 2094 Date(checkpoint.last_modified)
2093 2095 ).css("text-align", "center")
2094 2096 );
2095 2097
2096 2098 IPython.dialog.modal({
2097 2099 title : "Revert notebook to checkpoint",
2098 2100 body : body,
2099 2101 buttons : {
2100 2102 Revert : {
2101 2103 class : "btn-danger",
2102 2104 click : function () {
2103 2105 that.restore_checkpoint(checkpoint.id);
2104 2106 }
2105 2107 },
2106 2108 Cancel : {}
2107 2109 }
2108 2110 });
2109 2111 }
2110 2112
2111 2113 /**
2112 2114 * Restore the notebook to a checkpoint state.
2113 2115 *
2114 2116 * @method restore_checkpoint
2115 2117 * @param {String} checkpoint ID
2116 2118 */
2117 2119 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2118 2120 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2119 2121 var url = utils.url_join_encode(
2120 2122 this._baseProjectUrl,
2121 2123 'api/notebooks',
2122 2124 this.notebookPath(),
2123 2125 this.notebook_name,
2124 2126 'checkpoints',
2125 2127 checkpoint
2126 2128 );
2127 2129 $.post(url).done(
2128 2130 $.proxy(this.restore_checkpoint_success, this)
2129 2131 ).fail(
2130 2132 $.proxy(this.restore_checkpoint_error, this)
2131 2133 );
2132 2134 };
2133 2135
2134 2136 /**
2135 2137 * Success callback for restoring a notebook to a checkpoint.
2136 2138 *
2137 2139 * @method restore_checkpoint_success
2138 2140 * @param {Object} data (ignored, should be empty)
2139 2141 * @param {String} status Description of response status
2140 2142 * @param {jqXHR} xhr jQuery Ajax object
2141 2143 */
2142 2144 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2143 2145 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2144 2146 this.load_notebook(this.notebook_name, this.notebook_path);
2145 2147 };
2146 2148
2147 2149 /**
2148 2150 * Failure callback for restoring a notebook to a checkpoint.
2149 2151 *
2150 2152 * @method restore_checkpoint_error
2151 2153 * @param {jqXHR} xhr jQuery Ajax object
2152 2154 * @param {String} status Description of response status
2153 2155 * @param {String} error_msg HTTP error message
2154 2156 */
2155 2157 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2156 2158 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2157 2159 };
2158 2160
2159 2161 /**
2160 2162 * Delete a notebook checkpoint.
2161 2163 *
2162 2164 * @method delete_checkpoint
2163 2165 * @param {String} checkpoint ID
2164 2166 */
2165 2167 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2166 2168 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2167 2169 var url = utils.url_join_encode(
2168 2170 this._baseProjectUrl,
2169 2171 'api/notebooks',
2170 2172 this.notebookPath(),
2171 2173 this.notebook_name,
2172 2174 'checkpoints',
2173 2175 checkpoint
2174 2176 );
2175 2177 $.ajax(url, {
2176 2178 type: 'DELETE',
2177 2179 success: $.proxy(this.delete_checkpoint_success, this),
2178 2180 error: $.proxy(this.delete_notebook_error,this)
2179 2181 });
2180 2182 };
2181 2183
2182 2184 /**
2183 2185 * Success callback for deleting a notebook checkpoint
2184 2186 *
2185 2187 * @method delete_checkpoint_success
2186 2188 * @param {Object} data (ignored, should be empty)
2187 2189 * @param {String} status Description of response status
2188 2190 * @param {jqXHR} xhr jQuery Ajax object
2189 2191 */
2190 2192 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2191 2193 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2192 2194 this.load_notebook(this.notebook_name, this.notebook_path);
2193 2195 };
2194 2196
2195 2197 /**
2196 2198 * Failure callback for deleting a notebook checkpoint.
2197 2199 *
2198 2200 * @method delete_checkpoint_error
2199 2201 * @param {jqXHR} xhr jQuery Ajax object
2200 2202 * @param {String} status Description of response status
2201 2203 * @param {String} error_msg HTTP error message
2202 2204 */
2203 2205 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2204 2206 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2205 2207 };
2206 2208
2207 2209
2208 2210 IPython.Notebook = Notebook;
2209 2211
2210 2212
2211 2213 return IPython;
2212 2214
2213 2215 }(IPython)); No newline at end of file
@@ -1,468 +1,476 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2013 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 // WidgetModel, WidgetView, and WidgetManager
10 10 //============================================================================
11 11 /**
12 12 * Base Widget classes
13 13 * @module IPython
14 14 * @namespace IPython
15 15 * @submodule widget
16 16 */
17 17
18 18 "use strict";
19 19
20 20 // Use require.js 'define' method so that require.js is intelligent enough to
21 21 // syncronously load everything within this file when it is being 'required'
22 22 // elsewhere.
23 23 define(["components/underscore/underscore-min",
24 24 "components/backbone/backbone-min",
25 25 ], function(){
26 26
27 // Only run once on a notebook.
28 if (IPython.notebook.widget_manager == undefined) {
29 27
30 28 //--------------------------------------------------------------------
31 29 // WidgetModel class
32 30 //--------------------------------------------------------------------
33 31 var WidgetModel = Backbone.Model.extend({
34 32 constructor: function(comm_manager, comm, widget_view_types) {
35 33 this.comm_manager = comm_manager;
36 34 this.widget_view_types = widget_view_types;
37 35 this.pending_msgs = 0;
38 36 this.msg_throttle = 3;
39 37 this.msg_buffer = null;
40 38 this.views = {};
41 39
42 40 // Remember comm associated with the model.
43 41 this.comm = comm;
44 42 comm.model = this;
45 43
46 44 // Hook comm messages up to model.
47 45 comm.on_close($.proxy(this.handle_comm_closed, this));
48 46 comm.on_msg($.proxy(this.handle_comm_msg, this));
49 47
50 48 return Backbone.Model.apply(this);
51 49 },
52 50
53 51
54 52 update_other_views: function(caller) {
55 53 this.last_modified_view = caller;
56 54 this.save(this.changedAttributes(), {patch: true});
57 55
58 56 for (var output_area in this.views) {
59 57 var views = this.views[output_area];
60 58 for (var view_index in views) {
61 59 var view = views[view_index];
62 60 if (view !== caller) {
63 61 view.update();
64 62 }
65 63 }
66 64 }
67 65 },
68 66
69 67
70 68 handle_status: function (output_area, msg) {
71 69 //execution_state : ('busy', 'idle', 'starting')
72 70 if (msg.content.execution_state=='idle') {
73 71
74 72 // Send buffer if this message caused another message to be
75 73 // throttled.
76 if (this.msg_buffer != null) {
77 if (this.msg_throttle == this.pending_msgs &&
74 if (this.msg_buffer != null &&
75 this.msg_throttle == this.pending_msgs &&
78 76 this.msg_buffer.length > 0) {
79 77
80 78 var output_area = this._get_msg_output_area(msg);
81 79 var callbacks = this._make_callbacks(output_area);
82 80 var data = {sync_method: 'update', sync_data: this.msg_buffer};
83 81 comm.send(data, callbacks);
84 82 this.msg_buffer = null;
85 83 } else {
86 84
87 85 // Only decrease the pending message count if the buffer
88 86 // doesn't get flushed (sent).
89 87 --this.pending_msgs;
90 88 }
91 89 }
92 }
93 90 },
94 91
95 92
96 93 // Custom syncronization logic.
97 94 handle_sync: function (method, options) {
98 95 var model_json = this.toJSON();
99 96
100 97 // Only send updated state if the state hasn't been changed
101 98 // during an update.
102 99 if (!this.updating) {
103 100 if (this.pending_msgs >= this.msg_throttle) {
104 101 // The throttle has been exceeded, buffer the current msg so
105 102 // it can be sent once the kernel has finished processing
106 103 // some of the existing messages.
107 104 if (method=='patch') {
108 105 if (this.msg_buffer == null) {
109 106 this.msg_buffer = $.extend({}, model_json); // Copy
110 107 }
111 108 for (var attr in options.attrs) {
112 109 this.msg_buffer[attr] = options.attrs[attr];
113 110 }
114 111 } else {
115 112 this.msg_buffer = $.extend({}, model_json); // Copy
116 113 }
117 114
118 115 } else {
119 116 // We haven't exceeded the throttle, send the message like
120 117 // normal. If this is a patch operation, just send the
121 118 // changes.
122 119 var send_json = model_json;
123 120 if (method=='patch') {
124 121 send_json = {};
125 122 for (var attr in options.attrs) {
126 123 send_json[attr] = options.attrs[attr];
127 124 }
128 125 }
129 126
130 127 var data = {sync_method: method, sync_data: send_json};
131 128 var output_area = this.last_modified_view.output_area;
132 129 var callbacks = this._make_callbacks(output_area);
133 130 this.comm.send(data, callbacks);
134 131 this.pending_msgs++;
135 132 }
136 133 }
137 134
138 135 // Since the comm is a one-way communication, assume the message
139 136 // arrived.
140 137 return model_json;
141 138 },
142 139
143 140
144 141 // Handle incomming comm msg.
145 142 handle_comm_msg: function (msg) {
146 143 var method = msg.content.data.method;
147 144 switch (method){
148 145 case 'display':
149 146
150 147 // Try to get the cell index.
151 148 var output_area = this._get_output_area(msg.parent_header.msg_id);
152 149 if (output_area == null) {
153 150 console.log("Could not determine where the display" +
154 151 " message was from. Widget will not be displayed")
155 152 } else {
156 153 this.display_view(msg.content.data.view_name,
157 154 msg.content.data.parent,
158 155 output_area);
159 156 }
160 157 break;
161 158 case 'update':
162 159 this.handle_update(msg.content.data.state);
163 160 break;
164 161 }
165 162 },
166 163
167 164
168 165 // Handle when a widget is updated via the python side.
169 166 handle_update: function (state) {
170 167 this.updating = true;
171 168 try {
172 169 for (var key in state) {
173 170 if (state.hasOwnProperty(key)) {
174 171 if (key == "_css"){
175 172 this.css = state[key];
176 173 } else {
177 174 this.set(key, state[key]);
178 175 }
179 176 }
180 177 }
181 178 this.id = this.comm.comm_id;
182 179 this.save();
183 180 } finally {
184 181 this.updating = false;
185 182 }
186 183 },
187 184
188 185
189 186 // Handle when a widget is closed.
190 187 handle_comm_closed: function (msg) {
191 188 for (var output_area in this.views) {
192 189 var views = this.views[output_area];
193 190 for (var view_index in views) {
194 191 var view = views[view_index];
195 192 view.remove();
196 193 }
197 194 }
198 195 },
199 196
200 197
201 198 // Create view that represents the model.
202 199 display_view: function (view_name, parent_comm_id, output_area) {
203 200 var new_views = [];
204 201
205 202 var displayed = false;
206 203 if (parent_comm_id != undefined) {
207 204 var parent_comm = this.comm_manager.comms[parent_comm_id];
208 205 var parent_model = parent_comm.model;
209 206 var parent_views = parent_model.views[output_area];
210 207 for (var parent_view_index in parent_views) {
211 208 var parent_view = parent_views[parent_view_index];
212 209 if (parent_view.display_child != undefined) {
213 210 var view = this._create_view(view_name, output_area);
214 211 new_views.push(view);
215 212 parent_view.display_child(view);
216 213 displayed = true;
217 214 }
218 215 }
219 216 }
220 217
221 218 if (!displayed) {
222 219 // No parent view is defined or exists. Add the view's
223 220 // element to cell's widget div.
224 221 var view = this._create_view(view_name, output_area);
225 222 new_views.push(view);
226 223 this._get_widget_area_element(output_area, true)
227 224 .append(view.$el);
228 225
229 226 }
230 227
231 228 for (var view_index in new_views) {
232 229 var view = new_views[view_index];
233 230 view.update();
234 231 }
235 232 },
236 233
237 234
238 235 // Create a view
239 236 _create_view: function (view_name, output_area) {
240 237 var view = new this.widget_view_types[view_name]({model: this});
241 238 view.render();
242 239 if (this.views[output_area]==undefined) {
243 240 this.views[output_area] = []
244 241 }
245 242 this.views[output_area].push(view);
246 243 view.output_area = output_area;
247 244
248 245 // Handle when the view element is remove from the page.
249 246 var that = this;
250 247 view.$el.on("remove", function(){
251 248 var index = that.views[output_area].indexOf(view);
252 249 if (index > -1) {
253 250 that.views[output_area].splice(index, 1);
254 251 }
255 252 view.remove(); // Clean-up view
256 253 if (that.views[output_area].length()==0) {
257 254 delete that.views[output_area];
258 255 }
259 256
260 257 // Close the comm if there are no views left.
261 258 if (that.views.length()==0) {
262 259 that.comm.close();
263 260 }
264 261 });
265 262 return view;
266 263 },
267 264
268 265
269 266 // Build a callback dict.
270 267 _make_callbacks: function (output_area) {
271 268 var callbacks = {};
272 269 if (output_area != null) {
273 270 var that = this;
274 271 callbacks = {
275 272 iopub : {
276 273 output : $.proxy(output_area.handle_output, output_area),
277 274 clear_output : $.proxy(output_area.handle_clear_output, output_area),
278 275 status : function(msg){
279 276 that.handle_status(output_area, msg);
280 277 },
281 278 get_output_area : function() {
282 279 if (that.last_modified_view != undefined &&
283 280 that.last_modified_view.output_area != undefined) {
284 281 return that.last_modified_view.output_area;
285 282 } else {
286 283 return null
287 284 }
288 285 },
289 286 },
290 287 };
291 288 }
292 289 return callbacks;
293 290 },
294 291
295 292
296 293 // Get the output area corresponding to the msg_id.
297 294 // output_area is an instance of Ipython.OutputArea
298 295 _get_output_area: function (msg_id) {
299 296
300 297 // First, guess cell.execute triggered
301 298 var cells = IPython.notebook.get_cells();
302 299 for (var cell_index in cells) {
303 300 if (cells[cell_index].last_msg_id == msg_id) {
304 301 var cell = IPython.notebook.get_cell(cell_index)
305 302 return cell.output_area;
306 303 }
307 304 }
308 305
309 306 // Second, guess widget triggered
310 307 var callbacks = this.comm_manager.kernel.get_callbacks_for_msg(msg_id)
311 308 if (callbacks != undefined && callbacks.iopub != undefined && callbacks.iopub.get_output_area != undefined) {
312 309 var output_area = callbacks.iopub.get_output_area();
313 310 if (output_area != null) {
314 311 return output_area;
315 312 }
316 313 }
317 314
318 315 // Not triggered by a widget or a cell
319 316 return null;
320 317 },
321 318
322 319 // Gets widget output area (as a JQuery element) from the
323 320 // output_area (Ipython.OutputArea instance)
324 321 _get_widget_area_element: function (output_area, show) {
325 322 var widget_area = output_area.element
326 323 .parent() // output_wrapper
327 324 .parent() // cell
328 325 .find('.widget-area');
329 326 if (show) { widget_area.show(); }
330 327 return widget_area.find('.widget-subarea');
331 328 },
332 329
333 330 });
334 331
335 332
336 333 //--------------------------------------------------------------------
337 334 // WidgetView class
338 335 //--------------------------------------------------------------------
339 336 var WidgetView = Backbone.View.extend({
340 337
341 338 initialize: function() {
342 339 this.visible = true;
343 340 this.model.on('change',this.update,this);
344 341 this._add_class_calls = this.model.get('_add_class')[0];
345 342 this._remove_class_calls = this.model.get('_remove_class')[0];
346 343 },
347 344
348 345 update: function() {
349 346 if (this.model.get('visible') != undefined) {
350 347 if (this.visible != this.model.get('visible')) {
351 348 this.visible = this.model.get('visible');
352 349 if (this.visible) {
353 350 this.$el.show();
354 351 } else {
355 352 this.$el.hide();
356 353 }
357 354 }
358 355 }
359 356
360 357 if (this.model.css != undefined) {
361 358 for (var selector in this.model.css) {
362 359 if (this.model.css.hasOwnProperty(selector)) {
363 360
364 361 // Apply the css traits to all elements that match the selector.
365 362 var elements = this.get_selector_element(selector);
366 363 if (elements.length > 0) {
367 364 var css_traits = this.model.css[selector];
368 365 for (var css_key in css_traits) {
369 366 if (css_traits.hasOwnProperty(css_key)) {
370 367 elements.css(css_key, css_traits[css_key]);
371 368 }
372 369 }
373 370 }
374 371 }
375 372 }
376 373 }
377 374
378 375 var add_class = this.model.get('_add_class');
379 376 if (add_class != undefined){
380 377 var add_class_calls = add_class[0];
381 378 if (add_class_calls > this._add_class_calls) {
382 379 this._add_class_calls = add_class_calls;
383 380 var elements = this.get_selector_element(add_class[1]);
384 381 if (elements.length > 0) {
385 382 elements.addClass(add_class[2]);
386 383 }
387 384 }
388 385 }
389 386
390 387 var remove_class = this.model.get('_remove_class');
391 388 if (remove_class != undefined){
392 389 var remove_class_calls = remove_class[0];
393 390 if (remove_class_calls > this._remove_class_calls) {
394 391 this._remove_class_calls = remove_class_calls;
395 392 var elements = this.get_selector_element(remove_class[1]);
396 393 if (elements.length > 0) {
397 394 elements.removeClass(remove_class[2]);
398 395 }
399 396 }
400 397 }
401 398 },
402 399
403 400 get_selector_element: function(selector) {
404 401 // Get the elements via the css selector. If the selector is
405 402 // blank, apply the style to the $el_to_style element. If
406 403 // the $el_to_style element is not defined, use apply the
407 404 // style to the view's element.
408 405 var elements = this.$el.find(selector);
409 406 if (selector=='') {
410 407 if (this.$el_to_style == undefined) {
411 408 elements = this.$el;
412 409 } else {
413 410 elements = this.$el_to_style;
414 411 }
415 412 }
416 413 return elements;
417 414 },
418 415 });
419 416
420 417
421 418 //--------------------------------------------------------------------
422 419 // WidgetManager class
423 420 //--------------------------------------------------------------------
424 var WidgetManager = function(comm_manager){
425 this.comm_manager = comm_manager;
421 var WidgetManager = function(){
422 this.comm_manager = null;
426 423 this.widget_model_types = {};
427 424 this.widget_view_types = {};
428 425
429 426 var that = this;
430 427 Backbone.sync = function(method, model, options, error) {
431 428 var result = model.handle_sync(method, options);
432 429 if (options.success) {
433 430 options.success(result);
434 431 }
435 432 };
436 433 }
437 434
438 435
436 WidgetManager.prototype.attach_comm_manager = function (comm_manager) {
437 this.comm_manager = comm_manager;
438
439 // Register already register widget model types with the comm manager.
440 for (var widget_model_name in this.widget_model_types) {
441 this.comm_manager.register_target(widget_model_name, $.proxy(this.handle_com_open, this));
442 }
443 }
444
445
439 446 WidgetManager.prototype.register_widget_model = function (widget_model_name, widget_model_type) {
440 447 // Register the widget with the comm manager. Make sure to pass this object's context
441 448 // in so `this` works in the call back.
449 if (this.comm_manager!=null) {
442 450 this.comm_manager.register_target(widget_model_name, $.proxy(this.handle_com_open, this));
451 }
443 452 this.widget_model_types[widget_model_name] = widget_model_type;
444 453 }
445 454
446 455
447 456 WidgetManager.prototype.register_widget_view = function (widget_view_name, widget_view_type) {
448 457 this.widget_view_types[widget_view_name] = widget_view_type;
449 458 }
450 459
451 460
452 461 WidgetManager.prototype.handle_com_open = function (comm, msg) {
453 462 var widget_type_name = msg.content.target_name;
454 463 var widget_model = new this.widget_model_types[widget_type_name](this.comm_manager, comm, this.widget_view_types);
455 464 }
456 465
457 466
458 467 //--------------------------------------------------------------------
459 468 // Init code
460 469 //--------------------------------------------------------------------
461 470 IPython.WidgetManager = WidgetManager;
462 471 IPython.WidgetModel = WidgetModel;
463 472 IPython.WidgetView = WidgetView;
464 473
465 IPython.notebook.widget_manager = new WidgetManager(IPython.notebook.kernel.comm_manager);
474 IPython.widget_manager = new WidgetManager();
466 475
467 };
468 476 });
@@ -1,109 +1,109 b''
1 1
2 require(["notebook/js/widget"], function(){
2 define(["notebook/js/widget"], function(){
3 3
4 4 var BoolWidgetModel = IPython.WidgetModel.extend({});
5 IPython.notebook.widget_manager.register_widget_model('BoolWidgetModel', BoolWidgetModel);
5 IPython.widget_manager.register_widget_model('BoolWidgetModel', BoolWidgetModel);
6 6
7 7 var CheckboxView = IPython.WidgetView.extend({
8 8
9 9 // Called when view is rendered.
10 10 render : function(){
11 11 this.$el = $('<div />')
12 12 .addClass('widget-hbox-single');
13 13 this.$label = $('<div />')
14 14 .addClass('widget-hlabel')
15 15 .appendTo(this.$el)
16 16 .hide();
17 17 var that = this;
18 18 this.$checkbox = $('<input />')
19 19 .attr('type', 'checkbox')
20 20 .click(function(el) {
21 21 that.user_invoked_update = true;
22 22 that.model.set('value', that.$checkbox.prop('checked'));
23 23 that.model.update_other_views(that);
24 24 that.user_invoked_update = false;
25 25 })
26 26 .appendTo(this.$el);
27 27
28 28 this.$el_to_style = this.$checkbox; // Set default element to style
29 29 this.update(); // Set defaults.
30 30 },
31 31
32 32 // Handles: Backend -> Frontend Sync
33 33 // Frontent -> Frontend Sync
34 34 update : function(){
35 35 if (!this.user_invoked_update) {
36 36 this.$checkbox.prop('checked', this.model.get('value'));
37 37
38 38 var disabled = this.model.get('disabled');
39 39 this.$checkbox.prop('disabled', disabled);
40 40
41 41 var description = this.model.get('description');
42 42 if (description.length == 0) {
43 43 this.$label.hide();
44 44 } else {
45 45 this.$label.html(description);
46 46 this.$label.show();
47 47 }
48 48 }
49 49 return IPython.WidgetView.prototype.update.call(this);
50 50 },
51 51
52 52 });
53 53
54 IPython.notebook.widget_manager.register_widget_view('CheckboxView', CheckboxView);
54 IPython.widget_manager.register_widget_view('CheckboxView', CheckboxView);
55 55
56 56 var ToggleButtonView = IPython.WidgetView.extend({
57 57
58 58 // Called when view is rendered.
59 59 render : function(){
60 60 this.$el
61 61 .html('');
62 62
63 63 this.$button = $('<button />')
64 64 .addClass('btn')
65 65 .attr('type', 'button')
66 66 .attr('data-toggle', 'button')
67 67 .appendTo(this.$el);
68 68 this.$el_to_style = this.$button; // Set default element to style
69 69
70 70 this.update(); // Set defaults.
71 71 },
72 72
73 73 // Handles: Backend -> Frontend Sync
74 74 // Frontent -> Frontend Sync
75 75 update : function(){
76 76 if (!this.user_invoked_update) {
77 77 if (this.model.get('value')) {
78 78 this.$button.addClass('active');
79 79 } else {
80 80 this.$button.removeClass('active');
81 81 }
82 82
83 83 var disabled = this.model.get('disabled');
84 84 this.$button.prop('disabled', disabled);
85 85
86 86 var description = this.model.get('description');
87 87 if (description.length == 0) {
88 88 this.$button.html(' '); // Preserve button height
89 89 } else {
90 90 this.$button.html(description);
91 91 }
92 92 }
93 93 return IPython.WidgetView.prototype.update.call(this);
94 94 },
95 95
96 96 events: {"click button" : "handleClick"},
97 97
98 98 // Handles and validates user input.
99 99 handleClick: function(e) {
100 100 this.user_invoked_update = true;
101 101 this.model.set('value', ! $(e.target).hasClass('active'));
102 102 this.model.update_other_views(this);
103 103 this.user_invoked_update = false;
104 104 },
105 105 });
106 106
107 IPython.notebook.widget_manager.register_widget_view('ToggleButtonView', ToggleButtonView);
107 IPython.widget_manager.register_widget_view('ToggleButtonView', ToggleButtonView);
108 108
109 109 });
@@ -1,39 +1,39 b''
1 1
2 require(["notebook/js/widget"], function(){
2 define(["notebook/js/widget"], function(){
3 3
4 4 var ButtonWidgetModel = IPython.WidgetModel.extend({});
5 IPython.notebook.widget_manager.register_widget_model('ButtonWidgetModel', ButtonWidgetModel);
5 IPython.widget_manager.register_widget_model('ButtonWidgetModel', ButtonWidgetModel);
6 6
7 7 var ButtonView = IPython.WidgetView.extend({
8 8
9 9 // Called when view is rendered.
10 10 render : function(){
11 11 var that = this;
12 12 this.$el = $("<button />")
13 13 .addClass('btn')
14 14 .click(function() {
15 15 that.model.set('clicks', that.model.get('clicks') + 1);
16 16 that.model.update_other_views(that);
17 17 });
18 18
19 19 this.update(); // Set defaults.
20 20 },
21 21
22 22 // Handles: Backend -> Frontend Sync
23 23 // Frontent -> Frontend Sync
24 24 update : function(){
25 25 var description = this.model.get('description');
26 26 if (description.length==0) {
27 27 this.$el.html(' '); // Preserve button height
28 28 } else {
29 29 this.$el.html(description);
30 30 }
31 31
32 32 return IPython.WidgetView.prototype.update.call(this);
33 33 },
34 34
35 35 });
36 36
37 IPython.notebook.widget_manager.register_widget_view('ButtonView', ButtonView);
37 IPython.widget_manager.register_widget_view('ButtonView', ButtonView);
38 38
39 39 });
@@ -1,46 +1,46 b''
1 require(["notebook/js/widget"], function(){
1 define(["notebook/js/widget"], function(){
2 2 var ContainerModel = IPython.WidgetModel.extend({});
3 IPython.notebook.widget_manager.register_widget_model('ContainerWidgetModel', ContainerModel);
3 IPython.widget_manager.register_widget_model('ContainerWidgetModel', ContainerModel);
4 4
5 5 var ContainerView = IPython.WidgetView.extend({
6 6
7 7 render: function(){
8 8 this.$el = $('<div />')
9 9 .addClass('widget-container');
10 10 },
11 11
12 12 update: function(){
13 13
14 14 // Apply flexible box model properties by adding and removing
15 15 // corrosponding CSS classes.
16 16 // Defined in IPython/html/static/base/less/flexbox.less
17 17 this.set_flex_property('vbox', this.model.get('_vbox'));
18 18 this.set_flex_property('hbox', this.model.get('_hbox'));
19 19 this.set_flex_property('start', this.model.get('_pack_start'));
20 20 this.set_flex_property('center', this.model.get('_pack_center'));
21 21 this.set_flex_property('end', this.model.get('_pack_end'));
22 22 this.set_flex_property('align-start', this.model.get('_align_start'));
23 23 this.set_flex_property('align-center', this.model.get('_align_center'));
24 24 this.set_flex_property('align-end', this.model.get('_align_end'));
25 25 this.set_flex_property('box-flex0', this.model.get('_flex0'));
26 26 this.set_flex_property('box-flex1', this.model.get('_flex1'));
27 27 this.set_flex_property('box-flex2', this.model.get('_flex2'));
28 28
29 29 return IPython.WidgetView.prototype.update.call(this);
30 30 },
31 31
32 32 set_flex_property: function(property_name, enabled) {
33 33 if (enabled) {
34 34 this.$el.addClass(property_name);
35 35 } else {
36 36 this.$el.removeClass(property_name);
37 37 }
38 38 },
39 39
40 40 display_child: function(view) {
41 41 this.$el.append(view.$el);
42 42 },
43 43 });
44 44
45 IPython.notebook.widget_manager.register_widget_view('ContainerView', ContainerView);
45 IPython.widget_manager.register_widget_view('ContainerView', ContainerView);
46 46 }); No newline at end of file
@@ -1,4 +1,4 b''
1 require(["notebook/js/widget"], function(){
1 define(["notebook/js/widget"], function(){
2 2 var FloatWidgetModel = IPython.WidgetModel.extend({});
3 IPython.notebook.widget_manager.register_widget_model('FloatWidgetModel', FloatWidgetModel);
3 IPython.widget_manager.register_widget_model('FloatWidgetModel', FloatWidgetModel);
4 4 }); No newline at end of file
@@ -1,239 +1,239 b''
1 require(["notebook/js/widget"], function(){
1 define(["notebook/js/widget"], function(){
2 2 var FloatRangeWidgetModel = IPython.WidgetModel.extend({});
3 IPython.notebook.widget_manager.register_widget_model('FloatRangeWidgetModel', FloatRangeWidgetModel);
3 IPython.widget_manager.register_widget_model('FloatRangeWidgetModel', FloatRangeWidgetModel);
4 4
5 5 var FloatSliderView = IPython.WidgetView.extend({
6 6
7 7 // Called when view is rendered.
8 8 render : function(){
9 9 this.$el
10 10 .addClass('widget-hbox-single')
11 11 .html('');
12 12 this.$label = $('<div />')
13 13 .appendTo(this.$el)
14 14 .addClass('widget-hlabel')
15 15 .hide();
16 16 this.$slider = $('<div />')
17 17 .slider({})
18 18 .addClass('slider');
19 19
20 20 // Put the slider in a container
21 21 this.$slider_container = $('<div />')
22 22 .addClass('widget-hslider')
23 23 .append(this.$slider);
24 24 this.$el_to_style = this.$slider_container; // Set default element to style
25 25 this.$el.append(this.$slider_container);
26 26
27 27 // Set defaults.
28 28 this.update();
29 29 },
30 30
31 31 // Handles: Backend -> Frontend Sync
32 32 // Frontent -> Frontend Sync
33 33 update : function(){
34 34 // Slider related keys.
35 35 var _keys = ['step', 'max', 'min', 'disabled'];
36 36 for (var index in _keys) {
37 37 var key = _keys[index];
38 38 if (this.model.get(key) != undefined) {
39 39 this.$slider.slider("option", key, this.model.get(key));
40 40 }
41 41 }
42 42
43 43 // WORKAROUND FOR JQUERY SLIDER BUG.
44 44 // The horizontal position of the slider handle
45 45 // depends on the value of the slider at the time
46 46 // of orientation change. Before applying the new
47 47 // workaround, we set the value to the minimum to
48 48 // make sure that the horizontal placement of the
49 49 // handle in the vertical slider is always
50 50 // consistent.
51 51 var orientation = this.model.get('orientation');
52 52 var value = this.model.get('min');
53 53 this.$slider.slider('option', 'value', value);
54 54 this.$slider.slider('option', 'orientation', orientation);
55 55 var value = this.model.get('value');
56 56 this.$slider.slider('option', 'value', value);
57 57
58 58 // Use the right CSS classes for vertical & horizontal sliders
59 59 if (orientation=='vertical') {
60 60 this.$slider_container
61 61 .removeClass('widget-hslider')
62 62 .addClass('widget-vslider');
63 63 this.$el
64 64 .removeClass('widget-hbox-single')
65 65 .addClass('widget-vbox-single');
66 66 this.$label
67 67 .removeClass('widget-hlabel')
68 68 .addClass('widget-vlabel');
69 69
70 70 } else {
71 71 this.$slider_container
72 72 .removeClass('widget-vslider')
73 73 .addClass('widget-hslider');
74 74 this.$el
75 75 .removeClass('widget-vbox-single')
76 76 .addClass('widget-hbox-single');
77 77 this.$label
78 78 .removeClass('widget-vlabel')
79 79 .addClass('widget-hlabel');
80 80 }
81 81
82 82 var description = this.model.get('description');
83 83 if (description.length == 0) {
84 84 this.$label.hide();
85 85 } else {
86 86 this.$label.html(description);
87 87 this.$label.show();
88 88 }
89 89 return IPython.WidgetView.prototype.update.call(this);
90 90 },
91 91
92 92 // Handles: User input
93 93 events: { "slide" : "handleSliderChange" },
94 94 handleSliderChange: function(e, ui) {
95 95 this.model.set('value', ui.value);
96 96 this.model.update_other_views(this);
97 97 },
98 98 });
99 99
100 IPython.notebook.widget_manager.register_widget_view('FloatSliderView', FloatSliderView);
100 IPython.widget_manager.register_widget_view('FloatSliderView', FloatSliderView);
101 101
102 102
103 103 var FloatTextView = IPython.WidgetView.extend({
104 104
105 105 // Called when view is rendered.
106 106 render : function(){
107 107 this.$el
108 108 .addClass('widget-hbox-single')
109 109 .html('');
110 110 this.$label = $('<div />')
111 111 .appendTo(this.$el)
112 112 .addClass('widget-hlabel')
113 113 .hide();
114 114 this.$textbox = $('<input type="text" />')
115 115 .addClass('input')
116 116 .addClass('widget-numeric-text')
117 117 .appendTo(this.$el);
118 118 this.$el_to_style = this.$textbox; // Set default element to style
119 119 this.update(); // Set defaults.
120 120 },
121 121
122 122 // Handles: Backend -> Frontend Sync
123 123 // Frontent -> Frontend Sync
124 124 update : function(){
125 125 var value = this.model.get('value');
126 126 if (!this.changing && parseFloat(this.$textbox.val()) != value) {
127 127 this.$textbox.val(value);
128 128 }
129 129
130 130 if (this.model.get('disabled')) {
131 131 this.$textbox.attr('disabled','disabled');
132 132 } else {
133 133 this.$textbox.removeAttr('disabled');
134 134 }
135 135
136 136 var description = this.model.get('description');
137 137 if (description.length == 0) {
138 138 this.$label.hide();
139 139 } else {
140 140 this.$label.html(description);
141 141 this.$label.show();
142 142 }
143 143 return IPython.WidgetView.prototype.update.call(this);
144 144 },
145 145
146 146
147 147 events: {"keyup input" : "handleChanging",
148 148 "paste input" : "handleChanging",
149 149 "cut input" : "handleChanging",
150 150 "change input" : "handleChanged"}, // Fires only when control is validated or looses focus.
151 151
152 152 // Handles and validates user input.
153 153 handleChanging: function(e) {
154 154
155 155 // Try to parse value as a float.
156 156 var numericalValue = 0.0;
157 157 if (e.target.value != '') {
158 158 numericalValue = parseFloat(e.target.value);
159 159 }
160 160
161 161 // If parse failed, reset value to value stored in model.
162 162 if (isNaN(numericalValue)) {
163 163 e.target.value = this.model.get('value');
164 164 } else if (!isNaN(numericalValue)) {
165 165 if (this.model.get('max') != undefined) {
166 166 numericalValue = Math.min(this.model.get('max'), numericalValue);
167 167 }
168 168 if (this.model.get('min') != undefined) {
169 169 numericalValue = Math.max(this.model.get('min'), numericalValue);
170 170 }
171 171
172 172 // Apply the value if it has changed.
173 173 if (numericalValue != this.model.get('value')) {
174 174 this.changing = true;
175 175 this.model.set('value', numericalValue);
176 176 this.model.update_other_views(this);
177 177 this.changing = false;
178 178 }
179 179 }
180 180 },
181 181
182 182 // Applies validated input.
183 183 handleChanged: function(e) {
184 184 // Update the textbox
185 185 if (this.model.get('value') != e.target.value) {
186 186 e.target.value = this.model.get('value');
187 187 }
188 188 }
189 189 });
190 190
191 IPython.notebook.widget_manager.register_widget_view('FloatTextView', FloatTextView);
191 IPython.widget_manager.register_widget_view('FloatTextView', FloatTextView);
192 192
193 193
194 194 var ProgressView = IPython.WidgetView.extend({
195 195
196 196 // Called when view is rendered.
197 197 render : function(){
198 198 this.$el
199 199 .addClass('widget-hbox-single')
200 200 .html('');
201 201 this.$label = $('<div />')
202 202 .appendTo(this.$el)
203 203 .addClass('widget-hlabel')
204 204 .hide();
205 205 this.$progress = $('<div />')
206 206 .addClass('progress')
207 207 .addClass('widget-progress')
208 208 .appendTo(this.$el);
209 209 this.$el_to_style = this.$progress; // Set default element to style
210 210 this.$bar = $('<div />')
211 211 .addClass('bar')
212 212 .css('width', '50%')
213 213 .appendTo(this.$progress);
214 214 this.update(); // Set defaults.
215 215 },
216 216
217 217 // Handles: Backend -> Frontend Sync
218 218 // Frontent -> Frontend Sync
219 219 update : function(){
220 220 var value = this.model.get('value');
221 221 var max = this.model.get('max');
222 222 var min = this.model.get('min');
223 223 var percent = 100.0 * (value - min) / (max - min);
224 224 this.$bar.css('width', percent + '%');
225 225
226 226 var description = this.model.get('description');
227 227 if (description.length == 0) {
228 228 this.$label.hide();
229 229 } else {
230 230 this.$label.html(description);
231 231 this.$label.show();
232 232 }
233 233 return IPython.WidgetView.prototype.update.call(this);
234 234 },
235 235
236 236 });
237 237
238 IPython.notebook.widget_manager.register_widget_view('ProgressView', ProgressView);
238 IPython.widget_manager.register_widget_view('ProgressView', ProgressView);
239 239 });
@@ -1,4 +1,4 b''
1 require(["notebook/js/widget"], function(){
1 define(["notebook/js/widget"], function(){
2 2 var IntWidgetModel = IPython.WidgetModel.extend({});
3 IPython.notebook.widget_manager.register_widget_model('IntWidgetModel', IntWidgetModel);
3 IPython.widget_manager.register_widget_model('IntWidgetModel', IntWidgetModel);
4 4 }); No newline at end of file
@@ -1,191 +1,191 b''
1 require(["notebook/js/widget"], function(){
1 define(["notebook/js/widget"], function(){
2 2 var IntRangeWidgetModel = IPython.WidgetModel.extend({});
3 IPython.notebook.widget_manager.register_widget_model('IntRangeWidgetModel', IntRangeWidgetModel);
3 IPython.widget_manager.register_widget_model('IntRangeWidgetModel', IntRangeWidgetModel);
4 4
5 5 var IntSliderView = IPython.WidgetView.extend({
6 6
7 7 // Called when view is rendered.
8 8 render : function(){
9 9 this.$el
10 10 .addClass('widget-hbox-single')
11 11 .html('');
12 12 this.$label = $('<div />')
13 13 .appendTo(this.$el)
14 14 .addClass('widget-hlabel')
15 15 .hide();
16 16 this.$slider = $('<div />')
17 17 .slider({})
18 18 .addClass('slider');
19 19
20 20 // Put the slider in a container
21 21 this.$slider_container = $('<div />')
22 22 .addClass('widget-hslider')
23 23 .append(this.$slider);
24 24 this.$el_to_style = this.$slider_container; // Set default element to style
25 25 this.$el.append(this.$slider_container);
26 26
27 27 // Set defaults.
28 28 this.update();
29 29 },
30 30
31 31 // Handles: Backend -> Frontend Sync
32 32 // Frontent -> Frontend Sync
33 33 update : function(){
34 34 // Slider related keys.
35 35 var _keys = ['step', 'max', 'min', 'disabled'];
36 36 for (var index in _keys) {
37 37 var key = _keys[index];
38 38 if (this.model.get(key) != undefined) {
39 39 this.$slider.slider("option", key, this.model.get(key));
40 40 }
41 41 }
42 42
43 43 // WORKAROUND FOR JQUERY SLIDER BUG.
44 44 // The horizontal position of the slider handle
45 45 // depends on the value of the slider at the time
46 46 // of orientation change. Before applying the new
47 47 // workaround, we set the value to the minimum to
48 48 // make sure that the horizontal placement of the
49 49 // handle in the vertical slider is always
50 50 // consistent.
51 51 var orientation = this.model.get('orientation');
52 52 var value = this.model.get('min');
53 53 this.$slider.slider('option', 'value', value);
54 54 this.$slider.slider('option', 'orientation', orientation);
55 55 var value = this.model.get('value');
56 56 this.$slider.slider('option', 'value', value);
57 57
58 58 // Use the right CSS classes for vertical & horizontal sliders
59 59 if (orientation=='vertical') {
60 60 this.$slider_container
61 61 .removeClass('widget-hslider')
62 62 .addClass('widget-vslider');
63 63 this.$el
64 64 .removeClass('widget-hbox-single')
65 65 .addClass('widget-vbox-single');
66 66 this.$label
67 67 .removeClass('widget-hlabel')
68 68 .addClass('widget-vlabel');
69 69
70 70 } else {
71 71 this.$slider_container
72 72 .removeClass('widget-vslider')
73 73 .addClass('widget-hslider');
74 74 this.$el
75 75 .removeClass('widget-vbox-single')
76 76 .addClass('widget-hbox-single');
77 77 this.$label
78 78 .removeClass('widget-vlabel')
79 79 .addClass('widget-hlabel');
80 80 }
81 81
82 82 var description = this.model.get('description');
83 83 if (description.length == 0) {
84 84 this.$label.hide();
85 85 } else {
86 86 this.$label.html(description);
87 87 this.$label.show();
88 88 }
89 89 return IPython.WidgetView.prototype.update.call(this);
90 90 },
91 91
92 92 // Handles: User input
93 93 events: { "slide" : "handleSliderChange" },
94 94 handleSliderChange: function(e, ui) {
95 95 this.model.set('value', ~~ui.value); // Double bit-wise not to truncate decimel
96 96 this.model.update_other_views(this);
97 97 },
98 98 });
99 99
100 IPython.notebook.widget_manager.register_widget_view('IntSliderView', IntSliderView);
100 IPython.widget_manager.register_widget_view('IntSliderView', IntSliderView);
101 101
102 102 var IntTextView = IPython.WidgetView.extend({
103 103
104 104 // Called when view is rendered.
105 105 render : function(){
106 106 this.$el
107 107 .addClass('widget-hbox-single')
108 108 .html('');
109 109 this.$label = $('<div />')
110 110 .appendTo(this.$el)
111 111 .addClass('widget-hlabel')
112 112 .hide();
113 113 this.$textbox = $('<input type="text" />')
114 114 .addClass('input')
115 115 .addClass('widget-numeric-text')
116 116 .appendTo(this.$el);
117 117 this.$el_to_style = this.$textbox; // Set default element to style
118 118 this.update(); // Set defaults.
119 119 },
120 120
121 121 // Handles: Backend -> Frontend Sync
122 122 // Frontent -> Frontend Sync
123 123 update : function(){
124 124 var value = this.model.get('value');
125 125 if (!this.changing && parseInt(this.$textbox.val()) != value) {
126 126 this.$textbox.val(value);
127 127 }
128 128
129 129 if (this.model.get('disabled')) {
130 130 this.$textbox.attr('disabled','disabled');
131 131 } else {
132 132 this.$textbox.removeAttr('disabled');
133 133 }
134 134
135 135 var description = this.model.get('description');
136 136 if (description.length == 0) {
137 137 this.$label.hide();
138 138 } else {
139 139 this.$label.html(description);
140 140 this.$label.show();
141 141 }
142 142 return IPython.WidgetView.prototype.update.call(this);
143 143 },
144 144
145 145
146 146 events: {"keyup input" : "handleChanging",
147 147 "paste input" : "handleChanging",
148 148 "cut input" : "handleChanging",
149 149 "change input" : "handleChanged"}, // Fires only when control is validated or looses focus.
150 150
151 151 // Handles and validates user input.
152 152 handleChanging: function(e) {
153 153
154 154 // Try to parse value as a float.
155 155 var numericalValue = 0;
156 156 if (e.target.value != '') {
157 157 numericalValue = parseInt(e.target.value);
158 158 }
159 159
160 160 // If parse failed, reset value to value stored in model.
161 161 if (isNaN(numericalValue)) {
162 162 e.target.value = this.model.get('value');
163 163 } else if (!isNaN(numericalValue)) {
164 164 if (this.model.get('max') != undefined) {
165 165 numericalValue = Math.min(this.model.get('max'), numericalValue);
166 166 }
167 167 if (this.model.get('min') != undefined) {
168 168 numericalValue = Math.max(this.model.get('min'), numericalValue);
169 169 }
170 170
171 171 // Apply the value if it has changed.
172 172 if (numericalValue != this.model.get('value')) {
173 173 this.changing = true;
174 174 this.model.set('value', numericalValue);
175 175 this.model.update_other_views(this);
176 176 this.changing = false;
177 177 }
178 178 }
179 179 },
180 180
181 181 // Applies validated input.
182 182 handleChanged: function(e) {
183 183 // Update the textbox
184 184 if (this.model.get('value') != e.target.value) {
185 185 e.target.value = this.model.get('value');
186 186 }
187 187 }
188 188 });
189 189
190 IPython.notebook.widget_manager.register_widget_view('IntTextView', IntTextView);
190 IPython.widget_manager.register_widget_view('IntTextView', IntTextView);
191 191 });
@@ -1,139 +1,139 b''
1 require(["notebook/js/widget"], function(){
1 define(["notebook/js/widget"], function(){
2 2 var MulticontainerModel = IPython.WidgetModel.extend({});
3 IPython.notebook.widget_manager.register_widget_model('MulticontainerWidgetModel', MulticontainerModel);
3 IPython.widget_manager.register_widget_model('MulticontainerWidgetModel', MulticontainerModel);
4 4
5 5 var AccordionView = IPython.WidgetView.extend({
6 6
7 7 render: function(){
8 8 this.$el = $('<div />', {id: IPython.utils.uuid()})
9 9 .addClass('accordion');
10 10 this.containers = [];
11 11 },
12 12
13 13 update: function() {
14 14 // Set tab titles
15 15 var titles = this.model.get('_titles');
16 16 for (var page_index in titles) {
17 17
18 18 var accordian = this.containers[page_index]
19 19 if (accordian != undefined) {
20 20 accordian
21 21 .find('.accordion-heading')
22 22 .find('.accordion-toggle')
23 23 .html(titles[page_index]);
24 24 }
25 25 }
26 26
27 27 return IPython.WidgetView.prototype.update.call(this);
28 28 },
29 29
30 30 display_child: function(view) {
31 31
32 32 var index = this.containers.length;
33 33 var uuid = IPython.utils.uuid();
34 34 var accordion_group = $('<div />')
35 35 .addClass('accordion-group')
36 36 .appendTo(this.$el);
37 37 var accordion_heading = $('<div />')
38 38 .addClass('accordion-heading')
39 39 .appendTo(accordion_group);
40 40 var accordion_toggle = $('<a />')
41 41 .addClass('accordion-toggle')
42 42 .attr('data-toggle', 'collapse')
43 43 .attr('data-parent', '#' + this.$el.attr('id'))
44 44 .attr('href', '#' + uuid)
45 45 .html('Page ' + index)
46 46 .appendTo(accordion_heading);
47 47 var accordion_body = $('<div />', {id: uuid})
48 48 .addClass('accordion-body collapse')
49 49 .appendTo(accordion_group);
50 50 var accordion_inner = $('<div />')
51 51 .addClass('accordion-inner')
52 52 .appendTo(accordion_body);
53 53 this.containers.push(accordion_group);
54 54
55 55 accordion_inner.append(view.$el);
56 56 this.update();
57 57 },
58 58 });
59 59
60 IPython.notebook.widget_manager.register_widget_view('AccordionView', AccordionView);
60 IPython.widget_manager.register_widget_view('AccordionView', AccordionView);
61 61
62 62 var TabView = IPython.WidgetView.extend({
63 63
64 64 render: function(){
65 65 this.$el = $('<div />');
66 66 var uuid = IPython.utils.uuid();
67 67 var that = this;
68 68 this.$tabs = $('<div />', {id: uuid})
69 69 .addClass('nav')
70 70 .addClass('nav-tabs')
71 71 .appendTo(this.$el);
72 72 this.$tab_contents = $('<div />', {id: uuid + 'Content'})
73 73 .addClass('tab-content')
74 74 .appendTo(this.$el);
75 75
76 76 this.containers = [];
77 77 },
78 78
79 79 update: function() {
80 80 // Set tab titles
81 81 var titles = this.model.get('_titles');
82 82 for (var page_index in titles) {
83 83 var tab_text = this.containers[page_index]
84 84 if (tab_text != undefined) {
85 85 tab_text.html(titles[page_index]);
86 86 }
87 87 }
88 88
89 89 var selected_index = this.model.get('selected_index');
90 90 if (0 <= selected_index && selected_index < this.containers.length) {
91 91 this.select_page(selected_index);
92 92 }
93 93
94 94 return IPython.WidgetView.prototype.update.call(this);
95 95 },
96 96
97 97 display_child: function(view) {
98 98
99 99 var index = this.containers.length;
100 100 var uuid = IPython.utils.uuid();
101 101
102 102 var that = this;
103 103 var tab = $('<li />')
104 104 .css('list-style-type', 'none')
105 105 .appendTo(this.$tabs);
106 106 var tab_text = $('<a />')
107 107 .attr('href', '#' + uuid)
108 108 .attr('data-toggle', 'tab')
109 109 .html('Page ' + index)
110 110 .appendTo(tab)
111 111 .click(function (e) {
112 112 that.model.set("selected_index", index);
113 113 that.model.update_other_views(that);
114 114 that.select_page(index);
115 115 });
116 116 this.containers.push(tab_text);
117 117
118 118 var contents_div = $('<div />', {id: uuid})
119 119 .addClass('tab-pane')
120 120 .addClass('fade')
121 121 .append(view.$el)
122 122 .appendTo(this.$tab_contents);
123 123
124 124 if (index==0) {
125 125 tab_text.tab('show');
126 126 }
127 127 this.update();
128 128 },
129 129
130 130 select_page: function(index) {
131 131 this.$tabs.find('li')
132 132 .removeClass('active');
133 133 this.containers[index].tab('show');
134 134 },
135 135 });
136 136
137 IPython.notebook.widget_manager.register_widget_view('TabView', TabView);
137 IPython.widget_manager.register_widget_view('TabView', TabView);
138 138
139 139 });
@@ -1,251 +1,251 b''
1 require(["notebook/js/widget"], function(){
1 define(["notebook/js/widget"], function(){
2 2 var SelectionWidgetModel = IPython.WidgetModel.extend({});
3 IPython.notebook.widget_manager.register_widget_model('SelectionWidgetModel', SelectionWidgetModel);
3 IPython.widget_manager.register_widget_model('SelectionWidgetModel', SelectionWidgetModel);
4 4
5 5 var DropdownView = IPython.WidgetView.extend({
6 6
7 7 // Called when view is rendered.
8 8 render : function(){
9 9
10 10 this.$el
11 11 .addClass('widget-hbox-single')
12 12 .html('');
13 13 this.$label = $('<div />')
14 14 .appendTo(this.$el)
15 15 .addClass('widget-hlabel')
16 16 .hide();
17 17 this.$buttongroup = $('<div />')
18 18 .addClass('widget_item')
19 19 .addClass('btn-group')
20 20 .appendTo(this.$el);
21 21 this.$el_to_style = this.$buttongroup; // Set default element to style
22 22 this.$droplabel = $('<button />')
23 23 .addClass('btn')
24 24 .addClass('widget-combo-btn')
25 25 .appendTo(this.$buttongroup);
26 26 this.$dropbutton = $('<button />')
27 27 .addClass('btn')
28 28 .addClass('dropdown-toggle')
29 29 .attr('data-toggle', 'dropdown')
30 30 .html('<span class="caret"></span>')
31 31 .appendTo(this.$buttongroup);
32 32 this.$droplist = $('<ul />')
33 33 .addClass('dropdown-menu')
34 34 .appendTo(this.$buttongroup);
35 35
36 36 // Set defaults.
37 37 this.update();
38 38 },
39 39
40 40 // Handles: Backend -> Frontend Sync
41 41 // Frontent -> Frontend Sync
42 42 update : function(){
43 43 this.$droplabel.html(this.model.get('value'));
44 44
45 45 var items = this.model.get('values');
46 46 this.$droplist.html('');
47 47 for (var index in items) {
48 48 var that = this;
49 49 var item_button = $('<a href="#"/>')
50 50 .html(items[index])
51 51 .on('click', function(e){
52 52 that.model.set('value', $(e.target).html(), this);
53 53 that.model.update_other_views(that);
54 54 })
55 55
56 56 this.$droplist.append($('<li />').append(item_button))
57 57 }
58 58
59 59 if (this.model.get('disabled')) {
60 60 this.$buttongroup.attr('disabled','disabled');
61 61 this.$droplabel.attr('disabled','disabled');
62 62 this.$dropbutton.attr('disabled','disabled');
63 63 this.$droplist.attr('disabled','disabled');
64 64 } else {
65 65 this.$buttongroup.removeAttr('disabled');
66 66 this.$droplabel.removeAttr('disabled');
67 67 this.$dropbutton.removeAttr('disabled');
68 68 this.$droplist.removeAttr('disabled');
69 69 }
70 70
71 71 var description = this.model.get('description');
72 72 if (description.length == 0) {
73 73 this.$label.hide();
74 74 } else {
75 75 this.$label.html(description);
76 76 this.$label.show();
77 77 }
78 78 return IPython.WidgetView.prototype.update.call(this);
79 79 },
80 80
81 81 });
82 82
83 IPython.notebook.widget_manager.register_widget_view('DropdownView', DropdownView);
83 IPython.widget_manager.register_widget_view('DropdownView', DropdownView);
84 84
85 85 var RadioButtonsView = IPython.WidgetView.extend({
86 86
87 87 // Called when view is rendered.
88 88 render : function(){
89 89 this.$el
90 90 .addClass('widget-hbox')
91 91 .html('');
92 92 this.$label = $('<div />')
93 93 .appendTo(this.$el)
94 94 .addClass('widget-hlabel')
95 95 .hide();
96 96 this.$container = $('<div />')
97 97 .appendTo(this.$el)
98 98 .addClass('widget-container')
99 99 .addClass('vbox');
100 100 this.$el_to_style = this.$container; // Set default element to style
101 101 this.update();
102 102 },
103 103
104 104 // Handles: Backend -> Frontend Sync
105 105 // Frontent -> Frontend Sync
106 106 update : function(){
107 107
108 108 // Add missing items to the DOM.
109 109 var items = this.model.get('values');
110 110 var disabled = this.model.get('disabled');
111 111 for (var index in items) {
112 112 var item_query = ' :input[value="' + items[index] + '"]';
113 113 if (this.$el.find(item_query).length == 0) {
114 114 var $label = $('<label />')
115 115 .addClass('radio')
116 116 .html(items[index])
117 117 .appendTo(this.$container);
118 118
119 119 var that = this;
120 120 $('<input />')
121 121 .attr('type', 'radio')
122 122 .addClass(this.model)
123 123 .val(items[index])
124 124 .prependTo($label)
125 125 .on('click', function(e){
126 126 that.model.set('value', $(e.target).val(), this);
127 127 that.model.update_other_views(that);
128 128 });
129 129 }
130 130
131 131 var $item_element = this.$container.find(item_query);
132 132 if (this.model.get('value') == items[index]) {
133 133 $item_element.prop('checked', true);
134 134 } else {
135 135 $item_element.prop('checked', false);
136 136 }
137 137 $item_element.prop('disabled', disabled);
138 138 }
139 139
140 140 // Remove items that no longer exist.
141 141 this.$container.find('input').each(function(i, obj) {
142 142 var value = $(obj).val();
143 143 var found = false;
144 144 for (var index in items) {
145 145 if (items[index] == value) {
146 146 found = true;
147 147 break;
148 148 }
149 149 }
150 150
151 151 if (!found) {
152 152 $(obj).parent().remove();
153 153 }
154 154 });
155 155
156 156 var description = this.model.get('description');
157 157 if (description.length == 0) {
158 158 this.$label.hide();
159 159 } else {
160 160 this.$label.html(description);
161 161 this.$label.show();
162 162 }
163 163 return IPython.WidgetView.prototype.update.call(this);
164 164 },
165 165
166 166 });
167 167
168 IPython.notebook.widget_manager.register_widget_view('RadioButtonsView', RadioButtonsView);
168 IPython.widget_manager.register_widget_view('RadioButtonsView', RadioButtonsView);
169 169
170 170
171 171 var ToggleButtonsView = IPython.WidgetView.extend({
172 172
173 173 // Called when view is rendered.
174 174 render : function(){
175 175 this.$el
176 176 .addClass('widget-hbox-single')
177 177 .html('');
178 178 this.$label = $('<div />')
179 179 .appendTo(this.$el)
180 180 .addClass('widget-hlabel')
181 181 .hide();
182 182 this.$buttongroup = $('<div />')
183 183 .addClass('btn-group')
184 184 .attr('data-toggle', 'buttons-radio')
185 185 .appendTo(this.$el);
186 186 this.$el_to_style = this.$buttongroup; // Set default element to style
187 187 this.update();
188 188 },
189 189
190 190 // Handles: Backend -> Frontend Sync
191 191 // Frontent -> Frontend Sync
192 192 update : function(){
193 193
194 194 // Add missing items to the DOM.
195 195 var items = this.model.get('values');
196 196 var disabled = this.model.get('disabled');
197 197 for (var index in items) {
198 198 var item_query = ' :contains("' + items[index] + '")';
199 199 if (this.$buttongroup.find(item_query).length == 0) {
200 200
201 201 var that = this;
202 202 $('<button />')
203 203 .attr('type', 'button')
204 204 .addClass('btn')
205 205 .html(items[index])
206 206 .appendTo(this.$buttongroup)
207 207 .on('click', function(e){
208 208 that.model.set('value', $(e.target).html(), this);
209 209 that.model.update_other_views(that);
210 210 });
211 211 }
212 212
213 213 var $item_element = this.$buttongroup.find(item_query);
214 214 if (this.model.get('value') == items[index]) {
215 215 $item_element.addClass('active');
216 216 } else {
217 217 $item_element.removeClass('active');
218 218 }
219 219 $item_element.prop('disabled', disabled);
220 220 }
221 221
222 222 // Remove items that no longer exist.
223 223 this.$buttongroup.find('button').each(function(i, obj) {
224 224 var value = $(obj).html();
225 225 var found = false;
226 226 for (var index in items) {
227 227 if (items[index] == value) {
228 228 found = true;
229 229 break;
230 230 }
231 231 }
232 232
233 233 if (!found) {
234 234 $(obj).remove();
235 235 }
236 236 });
237 237
238 238 var description = this.model.get('description');
239 239 if (description.length == 0) {
240 240 this.$label.hide();
241 241 } else {
242 242 this.$label.html(description);
243 243 this.$label.show();
244 244 }
245 245 return IPython.WidgetView.prototype.update.call(this);
246 246 },
247 247
248 248 });
249 249
250 IPython.notebook.widget_manager.register_widget_view('ToggleButtonsView', ToggleButtonsView);
250 IPython.widget_manager.register_widget_view('ToggleButtonsView', ToggleButtonsView);
251 251 });
@@ -1,131 +1,131 b''
1 require(["notebook/js/widget"], function(){
1 define(["notebook/js/widget"], function(){
2 2 var StringWidgetModel = IPython.WidgetModel.extend({});
3 IPython.notebook.widget_manager.register_widget_model('StringWidgetModel', StringWidgetModel);
3 IPython.widget_manager.register_widget_model('StringWidgetModel', StringWidgetModel);
4 4
5 5 var LabelView = IPython.WidgetView.extend({
6 6
7 7 // Called when view is rendered.
8 8 render : function(){
9 9 this.$el = $('<div />');
10 10 this.update(); // Set defaults.
11 11 },
12 12
13 13 // Handles: Backend -> Frontend Sync
14 14 // Frontent -> Frontend Sync
15 15 update : function(){
16 16 this.$el.html(this.model.get('value'));
17 17 return IPython.WidgetView.prototype.update.call(this);
18 18 },
19 19
20 20 });
21 21
22 IPython.notebook.widget_manager.register_widget_view('LabelView', LabelView);
22 IPython.widget_manager.register_widget_view('LabelView', LabelView);
23 23
24 24 var TextAreaView = IPython.WidgetView.extend({
25 25
26 26 // Called when view is rendered.
27 27 render : function(){
28 28 this.$el
29 29 .addClass('widget-hbox')
30 30 .html('');
31 31 this.$label = $('<div />')
32 32 .appendTo(this.$el)
33 33 .addClass('widget-hlabel')
34 34 .hide();
35 35 this.$textbox = $('<textarea />')
36 36 .attr('rows', 5)
37 37 .addClass('widget-text')
38 38 .appendTo(this.$el);
39 39 this.$el_to_style = this.$textbox; // Set default element to style
40 40 this.update(); // Set defaults.
41 41 },
42 42
43 43 // Handles: Backend -> Frontend Sync
44 44 // Frontent -> Frontend Sync
45 45 update : function(){
46 46 if (!this.user_invoked_update) {
47 47 this.$textbox.val(this.model.get('value'));
48 48 }
49 49
50 50 var disabled = this.model.get('disabled');
51 51 this.$textbox.prop('disabled', disabled);
52 52
53 53 var description = this.model.get('description');
54 54 if (description.length == 0) {
55 55 this.$label.hide();
56 56 } else {
57 57 this.$label.html(description);
58 58 this.$label.show();
59 59 }
60 60 return IPython.WidgetView.prototype.update.call(this);
61 61 },
62 62
63 63 events: {"keyup textarea" : "handleChanging",
64 64 "paste textarea" : "handleChanging",
65 65 "cut textarea" : "handleChanging"},
66 66
67 67 // Handles and validates user input.
68 68 handleChanging: function(e) {
69 69 this.user_invoked_update = true;
70 70 this.model.set('value', e.target.value);
71 71 this.model.update_other_views(this);
72 72 this.user_invoked_update = false;
73 73 },
74 74 });
75 75
76 IPython.notebook.widget_manager.register_widget_view('TextAreaView', TextAreaView);
76 IPython.widget_manager.register_widget_view('TextAreaView', TextAreaView);
77 77
78 78 var TextBoxView = IPython.WidgetView.extend({
79 79
80 80 // Called when view is rendered.
81 81 render : function(){
82 82 this.$el
83 83 .addClass('widget-hbox-single')
84 84 .html('');
85 85 this.$label = $('<div />')
86 86 .addClass('widget-hlabel')
87 87 .appendTo(this.$el)
88 88 .hide();
89 89 this.$textbox = $('<input type="text" />')
90 90 .addClass('input')
91 91 .addClass('widget-text')
92 92 .appendTo(this.$el);
93 93 this.$el_to_style = this.$textbox; // Set default element to style
94 94 this.update(); // Set defaults.
95 95 },
96 96
97 97 // Handles: Backend -> Frontend Sync
98 98 // Frontent -> Frontend Sync
99 99 update : function(){
100 100 if (!this.user_invoked_update) {
101 101 this.$textbox.val(this.model.get('value'));
102 102 }
103 103
104 104 var disabled = this.model.get('disabled');
105 105 this.$textbox.prop('disabled', disabled);
106 106
107 107 var description = this.model.get('description');
108 108 if (description.length == 0) {
109 109 this.$label.hide();
110 110 } else {
111 111 this.$label.html(description);
112 112 this.$label.show();
113 113 }
114 114 return IPython.WidgetView.prototype.update.call(this);
115 115 },
116 116
117 117 events: {"keyup input" : "handleChanging",
118 118 "paste input" : "handleChanging",
119 119 "cut input" : "handleChanging"},
120 120
121 121 // Handles and validates user input.
122 122 handleChanging: function(e) {
123 123 this.user_invoked_update = true;
124 124 this.model.set('value', e.target.value);
125 125 this.model.update_other_views(this);
126 126 this.user_invoked_update = false;
127 127 },
128 128 });
129 129
130 IPython.notebook.widget_manager.register_widget_view('TextBoxView', TextBoxView);
130 IPython.widget_manager.register_widget_view('TextBoxView', TextBoxView);
131 131 });
@@ -1,12 +1,12 b''
1 from .widget import Widget, init_widget_js
1 from .widget import Widget
2 2
3 3 from .widget_bool import BoolWidget
4 4 from .widget_button import ButtonWidget
5 5 from .widget_container import ContainerWidget
6 6 from .widget_float import FloatWidget
7 7 from .widget_float_range import FloatRangeWidget
8 8 from .widget_int import IntWidget
9 9 from .widget_int_range import IntRangeWidget
10 10 from .widget_multicontainer import MulticontainerWidget
11 11 from .widget_selection import SelectionWidget
12 12 from .widget_string import StringWidget
@@ -1,365 +1,352 b''
1 1 """Base Widget class. Allows user to create widgets in the backend that render
2 2 in the IPython notebook frontend.
3 3 """
4 4 #-----------------------------------------------------------------------------
5 5 # Copyright (c) 2013, the IPython Development Team.
6 6 #
7 7 # Distributed under the terms of the Modified BSD License.
8 8 #
9 9 # The full license is in the file COPYING.txt, distributed with this software.
10 10 #-----------------------------------------------------------------------------
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Imports
14 14 #-----------------------------------------------------------------------------
15 15 from copy import copy
16 16 from glob import glob
17 17 import uuid
18 18 import sys
19 19 import os
20 20 import inspect
21 21
22 22 import IPython
23 23 from IPython.kernel.comm import Comm
24 24 from IPython.config import LoggingConfigurable
25 25 from IPython.utils.traitlets import Unicode, Dict, List, Instance, Bool
26 26 from IPython.display import Javascript, display
27 27 from IPython.utils.py3compat import string_types
28 28
29 #-----------------------------------------------------------------------------
30 # Shared
31 #-----------------------------------------------------------------------------
32 def init_widget_js():
33 path = os.path.split(os.path.abspath( __file__ ))[0]
34 for filepath in glob(os.path.join(path, "*.py")):
35 filename = os.path.split(filepath)[1]
36 name = filename.rsplit('.', 1)[0]
37 if not (name == 'widget' or name == '__init__') and name.startswith('widget_'):
38 # Remove 'widget_' from the start of the name before compiling the path.
39 js_path = 'static/notebook/js/widgets/%s.js' % name[7:]
40 display(Javascript(data='$.getScript($("body").data("baseProjectUrl") + "%s");' % js_path), exclude="text/plain")
41
42 29
43 30 #-----------------------------------------------------------------------------
44 31 # Classes
45 32 #-----------------------------------------------------------------------------
46 33 class Widget(LoggingConfigurable):
47 34
48 35 # Shared declarations
49 36 _keys = []
50 37
51 38 # Public declarations
52 39 target_name = Unicode('widget', help="""Name of the backbone model
53 40 registered in the frontend to create and sync this widget with.""")
54 41 default_view_name = Unicode(help="""Default view registered in the frontend
55 42 to use to represent the widget.""")
56 43 parent = Instance('IPython.html.widgets.widget.Widget')
57 44 visible = Bool(True, help="Whether or not the widget is visible.")
58 45
59 46 def _parent_changed(self, name, old, new):
60 47 if self._displayed:
61 48 raise Exception('Parent cannot be set because widget has been displayed.')
62 49 elif new == self:
63 50 raise Exception('Parent cannot be set to self.')
64 51 else:
65 52
66 53 # Parent/child association
67 54 if new is not None and not self in new._children:
68 55 new._children.append(self)
69 56 if old is not None and self in old._children:
70 57 old._children.remove(self)
71 58
72 59 # Private/protected declarations
73 60 _property_lock = False
74 61 _css = Dict() # Internal CSS property dict
75 62 _add_class = List() # Used to add a js class to a DOM element (call#, selector, class_name)
76 63 _remove_class = List() # Used to remove a js class from a DOM element (call#, selector, class_name)
77 64 _displayed = False
78 65 _comm = None
79 66
80 67
81 68 def __init__(self, **kwargs):
82 69 """Public constructor
83 70
84 71 Parameters
85 72 ----------
86 73 parent : Widget instance (optional)
87 74 Widget that this widget instance is child of. When the widget is
88 75 displayed in the frontend, it's corresponding view will be made
89 76 child of the parent's view if the parent's view exists already. If
90 77 the parent's view is displayed, it will automatically display this
91 78 widget's default view as it's child. The default view can be set
92 79 via the default_view_name property.
93 80 """
94 81 self._children = []
95 82 self._add_class = [0]
96 83 self._remove_class = [0]
97 84 self._display_callbacks = []
98 85 super(Widget, self).__init__(**kwargs)
99 86
100 87 # Register after init to allow default values to be specified
101 88 self.on_trait_change(self._handle_property_changed, self.keys)
102 89
103 90
104 91 def __del__(self):
105 92 """Object disposal"""
106 93 self.close()
107 94
108 95
109 96 def close(self):
110 97 """Close method. Closes the widget which closes the underlying comm.
111 98 When the comm is closed, all of the widget views are automatically
112 99 removed from the frontend."""
113 100 self._comm.close()
114 101 del self._comm
115 102
116 103
117 104 # Properties
118 105 def _get_keys(self):
119 106 keys = ['visible', '_css', '_add_class', '_remove_class']
120 107 keys.extend(self._keys)
121 108 return keys
122 109 keys = property(_get_keys)
123 110
124 111
125 112 # Event handlers
126 113 def _handle_msg(self, msg):
127 114 """Called when a msg is recieved from the frontend"""
128 115 # Handle backbone sync methods CREATE, PATCH, and UPDATE
129 116 sync_method = msg['content']['data']['sync_method']
130 117 sync_data = msg['content']['data']['sync_data']
131 118 self._handle_recieve_state(sync_data) # handles all methods
132 119
133 120
134 121 def _handle_recieve_state(self, sync_data):
135 122 """Called when a state is recieved from the frontend."""
136 123 self._property_lock = True
137 124 try:
138 125
139 126 # Use _keys instead of keys - Don't get retrieve the css from the client side.
140 127 for name in self._keys:
141 128 if name in sync_data:
142 129 setattr(self, name, sync_data[name])
143 130 finally:
144 131 self._property_lock = False
145 132
146 133
147 134 def _handle_property_changed(self, name, old, new):
148 135 """Called when a proeprty has been changed."""
149 136 if not self._property_lock and self._comm is not None:
150 137 # TODO: Validate properties.
151 138 # Send new state to frontend
152 139 self.send_state(key=name)
153 140
154 141
155 142 def _handle_close(self):
156 143 """Called when the comm is closed by the frontend."""
157 144 self._comm = None
158 145
159 146
160 147 # Public methods
161 148 def send_state(self, key=None):
162 149 """Sends the widget state, or a piece of it, to the frontend.
163 150
164 151 Parameters
165 152 ----------
166 153 key : unicode (optional)
167 154 A single property's name to sync with the frontend.
168 155 """
169 156 if self._comm is not None:
170 157 state = {}
171 158
172 159 # If a key is provided, just send the state of that key.
173 160 keys = []
174 161 if key is None:
175 162 keys.extend(self.keys)
176 163 else:
177 164 keys.append(key)
178 165 for key in self.keys:
179 166 try:
180 167 state[key] = getattr(self, key)
181 168 except Exception as e:
182 169 pass # Eat errors, nom nom nom
183 170 self._comm.send({"method": "update",
184 171 "state": state})
185 172
186 173
187 174 def get_css(self, key, selector=""):
188 175 """Get a CSS property of the widget. Note, this function does not
189 176 actually request the CSS from the front-end; Only properties that have
190 177 been set with set_css can be read.
191 178
192 179 Parameters
193 180 ----------
194 181 key: unicode
195 182 CSS key
196 183 selector: unicode (optional)
197 184 JQuery selector used when the CSS key/value was set.
198 185 """
199 186 if selector in self._css and key in self._css[selector]:
200 187 return self._css[selector][key]
201 188 else:
202 189 return None
203 190
204 191
205 192 def set_css(self, *args, **kwargs):
206 193 """Set one or more CSS properties of the widget (shared among all of the
207 194 views). This function has two signatures:
208 195 - set_css(css_dict, [selector=''])
209 196 - set_css(key, value, [selector=''])
210 197
211 198 Parameters
212 199 ----------
213 200 css_dict : dict
214 201 CSS key/value pairs to apply
215 202 key: unicode
216 203 CSS key
217 204 value
218 205 CSS value
219 206 selector: unicode (optional)
220 207 JQuery selector to use to apply the CSS key/value.
221 208 """
222 209 selector = kwargs.get('selector', '')
223 210
224 211 # Signature 1: set_css(css_dict, [selector=''])
225 212 if len(args) == 1:
226 213 if isinstance(args[0], dict):
227 214 for (key, value) in args[0].items():
228 215 self.set_css(key, value, selector=selector)
229 216 else:
230 217 raise Exception('css_dict must be a dict.')
231 218
232 219 # Signature 2: set_css(key, value, [selector=''])
233 220 elif len(args) == 2 or len(args) == 3:
234 221
235 222 # Selector can be a positional arg if it's the 3rd value
236 223 if len(args) == 3:
237 224 selector = args[2]
238 225 if selector not in self._css:
239 226 self._css[selector] = {}
240 227
241 228 # Only update the property if it has changed.
242 229 key = args[0]
243 230 value = args[1]
244 231 if not (key in self._css[selector] and value in self._css[selector][key]):
245 232 self._css[selector][key] = value
246 233 self.send_state('_css') # Send new state to client.
247 234 else:
248 235 raise Exception('set_css only accepts 1-3 arguments')
249 236
250 237
251 238 def add_class(self, class_name, selector=""):
252 239 """Add class[es] to a DOM element
253 240
254 241 Parameters
255 242 ----------
256 243 class_name: unicode
257 244 Class name(s) to add to the DOM element(s). Multiple class names
258 245 must be space separated.
259 246 selector: unicode (optional)
260 247 JQuery selector to select the DOM element(s) that the class(es) will
261 248 be added to.
262 249 """
263 250 self._add_class = [self._add_class[0] + 1, selector, class_name]
264 251 self.send_state(key='_add_class')
265 252
266 253
267 254 def remove_class(self, class_name, selector=""):
268 255 """Remove class[es] from a DOM element
269 256
270 257 Parameters
271 258 ----------
272 259 class_name: unicode
273 260 Class name(s) to remove from the DOM element(s). Multiple class
274 261 names must be space separated.
275 262 selector: unicode (optional)
276 263 JQuery selector to select the DOM element(s) that the class(es) will
277 264 be removed from.
278 265 """
279 266 self._remove_class = [self._remove_class[0] + 1, selector, class_name]
280 267 self.send_state(key='_remove_class')
281 268
282 269
283 270 def on_displayed(self, callback, remove=False):
284 271 """Register a callback to be called when the widget has been displayed
285 272
286 273 Parameters
287 274 ----------
288 275 callback: method handler
289 276 Can have a signature of:
290 277 - callback()
291 278 - callback(sender)
292 279 - callback(sender, view_name)
293 280 remove: bool
294 281 True if the callback should be unregistered."""
295 282 if remove:
296 283 self._display_callbacks.remove(callback)
297 284 elif not callback in self._display_callbacks:
298 285 self._display_callbacks.append(callback)
299 286
300 287
301 288 def handle_displayed(self, view_name):
302 289 """Called when a view has been displayed for this widget instance
303 290
304 291 Parameters
305 292 ----------
306 293 view_name: unicode
307 294 Name of the view that was displayed."""
308 295 for handler in self._display_callbacks:
309 296 if callable(handler):
310 297 argspec = inspect.getargspec(handler)
311 298 nargs = len(argspec[0])
312 299
313 300 # Bound methods have an additional 'self' argument
314 301 if isinstance(handler, types.MethodType):
315 302 nargs -= 1
316 303
317 304 # Call the callback
318 305 if nargs == 0:
319 306 handler()
320 307 elif nargs == 1:
321 308 handler(self)
322 309 elif nargs == 2:
323 310 handler(self, view_name)
324 311 else:
325 312 raise TypeError('Widget display callback must ' \
326 313 'accept 0-2 arguments, not %d.' % nargs)
327 314
328 315
329 316 # Support methods
330 317 def _repr_widget_(self, view_name=None):
331 318 """Function that is called when `IPython.display.display` is called on
332 319 the widget.
333 320
334 321 Parameters
335 322 ----------
336 323 view_name: unicode (optional)
337 324 View to display in the frontend. Overrides default_view_name."""
338 325
339 326 if not view_name:
340 327 view_name = self.default_view_name
341 328
342 329 # Create a comm.
343 330 if self._comm is None:
344 331 self._comm = Comm(target_name=self.target_name)
345 332 self._comm.on_msg(self._handle_msg)
346 333 self._comm.on_close(self._handle_close)
347 334
348 335 # Make sure model is syncronized
349 336 self.send_state()
350 337
351 338 # Show view.
352 339 if self.parent is None or self.parent._comm is None:
353 340 self._comm.send({"method": "display", "view_name": view_name})
354 341 else:
355 342 self._comm.send({"method": "display",
356 343 "view_name": view_name,
357 344 "parent": self.parent._comm.comm_id})
358 345 self._displayed = True
359 346 self.handle_displayed(view_name)
360 347
361 348 # Now display children if any.
362 349 for child in self._children:
363 350 if child != self:
364 351 child._repr_widget_()
365 352 return None
@@ -1,295 +1,220 b''
1 1 {
2 2 "metadata": {
3 3 "cell_tags": [
4 4 [
5 5 "<None>",
6 6 null
7 7 ]
8 8 ],
9 9 "name": ""
10 10 },
11 11 "nbformat": 3,
12 12 "nbformat_minor": 0,
13 13 "worksheets": [
14 14 {
15 15 "cells": [
16 16 {
17 17 "cell_type": "code",
18 18 "collapsed": false,
19 19 "input": [
20 20 "from IPython.html import widgets # Widget definitions\n",
21 "from IPython.display import display # Used to display widgets in the notebook\n",
22 "\n",
23 "# Enable widgets in this notebook\n",
24 "widgets.init_widget_js()"
21 "from IPython.display import display # Used to display widgets in the notebook"
25 22 ],
26 23 "language": "python",
27 24 "metadata": {},
28 "outputs": [
29 {
30 "javascript": [
31 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/button.js\");"
32 ],
33 "metadata": {},
34 "output_type": "display_data"
35 },
36 {
37 "javascript": [
38 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/int_range.js\");"
39 ],
40 "metadata": {},
41 "output_type": "display_data"
42 },
43 {
44 "javascript": [
45 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/string.js\");"
46 ],
47 "metadata": {},
48 "output_type": "display_data"
49 },
50 {
51 "javascript": [
52 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/multicontainer.js\");"
53 ],
54 "metadata": {},
55 "output_type": "display_data"
56 },
57 {
58 "javascript": [
59 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/bool.js\");"
60 ],
61 "metadata": {},
62 "output_type": "display_data"
63 },
64 {
65 "javascript": [
66 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/int.js\");"
67 ],
68 "metadata": {},
69 "output_type": "display_data"
70 },
71 {
72 "javascript": [
73 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/selection.js\");"
74 ],
75 "metadata": {},
76 "output_type": "display_data"
77 },
78 {
79 "javascript": [
80 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/float.js\");"
81 ],
82 "metadata": {},
83 "output_type": "display_data"
84 },
85 {
86 "javascript": [
87 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/float_range.js\");"
88 ],
89 "metadata": {},
90 "output_type": "display_data"
91 },
92 {
93 "javascript": [
94 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/container.js\");"
95 ],
96 "metadata": {},
97 "output_type": "display_data"
98 }
99 ],
25 "outputs": [],
100 26 "prompt_number": 1
101 27 },
102 28 {
103 29 "cell_type": "heading",
104 30 "level": 1,
105 31 "metadata": {},
106 32 "source": [
107 33 "Custom Widget"
108 34 ]
109 35 },
110 36 {
111 37 "cell_type": "code",
112 38 "collapsed": false,
113 39 "input": [
114 40 "# Import the base Widget class and the traitlets Unicode class.\n",
115 41 "from IPython.html.widgets import Widget\n",
116 42 "from IPython.utils.traitlets import Unicode, Int\n",
117 43 "\n",
118 44 "# Define our FileWidget and its target model and default view.\n",
119 45 "class FileWidget(Widget):\n",
120 46 " target_name = Unicode('FileWidgetModel')\n",
121 47 " default_view_name = Unicode('FilePickerView')\n",
122 48 " \n",
123 49 " # Define the custom state properties to sync with the front-end\n",
124 50 " _keys = ['value', 'filename']\n",
125 51 " value = Unicode('')\n",
126 52 " filename = Unicode('')\n",
127 53 " on_failed = Int(0)"
128 54 ],
129 55 "language": "python",
130 56 "metadata": {},
131 57 "outputs": [],
132 58 "prompt_number": 2
133 59 },
134 60 {
135 61 "cell_type": "code",
136 62 "collapsed": false,
137 63 "input": [
138 64 "%%javascript\n",
139 65 "\n",
140 66 "require([\"notebook/js/widget\"], function(){\n",
141 67 " \n",
142 68 " // Define the FileModel and register it with the widget manager.\n",
143 69 " var FileModel = IPython.WidgetModel.extend({});\n",
144 " IPython.notebook.widget_manager.register_widget_model('FileWidgetModel', FileModel);\n",
70 " IPython.widget_manager.register_widget_model('FileWidgetModel', FileModel);\n",
145 71 " \n",
146 72 " // Define the FilePickerView\n",
147 73 " var FilePickerView = IPython.WidgetView.extend({\n",
148 74 " \n",
149 75 " render: function(){\n",
150 76 " var that = this;\n",
151 77 " this.$el = $('<input />')\n",
152 78 " .attr('type', 'file')\n",
153 79 " .change(function(evt){ that.handleFileChange(evt) });\n",
154 80 " },\n",
155 81 " \n",
156 82 " // Handles: User input\n",
157 83 " handleFileChange: function(evt) { \n",
158 84 " \n",
159 85 " //Retrieve the first (and only!) File from the FileList object\n",
160 86 " var that = this;\n",
161 87 " var f = evt.target.files[0];\n",
162 88 " if (f) {\n",
163 89 " var r = new FileReader();\n",
164 90 " r.onload = function(e) {\n",
165 91 " that.model.set('value', e.target.result);\n",
166 92 " that.model.update_other_views(that);\n",
167 93 " }\n",
168 94 " r.readAsText(f);\n",
169 95 " } else {\n",
170 96 " this.model.set('on_failed', this.model.get('on_failed') + 1);\n",
171 97 " this.model.update_other_views(this);\n",
172 98 " }\n",
173 99 " this.model.set('filename', f.name);\n",
174 100 " this.model.update_other_views(this);\n",
175 101 " },\n",
176 102 " });\n",
177 103 " \n",
178 104 " // Register the DatePickerView with the widget manager.\n",
179 " IPython.notebook.widget_manager.register_widget_view('FilePickerView', FilePickerView);\n",
105 " IPython.widget_manager.register_widget_view('FilePickerView', FilePickerView);\n",
180 106 "});"
181 107 ],
182 108 "language": "python",
183 109 "metadata": {},
184 110 "outputs": [
185 111 {
186 112 "javascript": [
187 113 "\n",
188 114 "require([\"notebook/js/widget\"], function(){\n",
189 115 " \n",
190 116 " // Define the FileModel and register it with the widget manager.\n",
191 117 " var FileModel = IPython.WidgetModel.extend({});\n",
192 " IPython.notebook.widget_manager.register_widget_model('FileWidgetModel', FileModel);\n",
118 " IPython.widget_manager.register_widget_model('FileWidgetModel', FileModel);\n",
193 119 " \n",
194 120 " // Define the FilePickerView\n",
195 121 " var FilePickerView = IPython.WidgetView.extend({\n",
196 122 " \n",
197 123 " render: function(){\n",
198 124 " var that = this;\n",
199 125 " this.$el = $('<input />')\n",
200 126 " .attr('type', 'file')\n",
201 127 " .change(function(evt){ that.handleFileChange(evt) });\n",
202 128 " },\n",
203 129 " \n",
204 130 " // Handles: User input\n",
205 " events: { \"change\" : \"handleFileChange\" }, \n",
206 131 " handleFileChange: function(evt) { \n",
207 132 " \n",
208 133 " //Retrieve the first (and only!) File from the FileList object\n",
209 134 " var that = this;\n",
210 135 " var f = evt.target.files[0];\n",
211 136 " if (f) {\n",
212 137 " var r = new FileReader();\n",
213 138 " r.onload = function(e) {\n",
214 139 " that.model.set('value', e.target.result);\n",
215 140 " that.model.update_other_views(that);\n",
216 141 " }\n",
217 142 " r.readAsText(f);\n",
218 143 " } else {\n",
219 144 " this.model.set('on_failed', this.model.get('on_failed') + 1);\n",
220 145 " this.model.update_other_views(this);\n",
221 146 " }\n",
222 147 " this.model.set('filename', f.name);\n",
223 148 " this.model.update_other_views(this);\n",
224 149 " },\n",
225 150 " });\n",
226 151 " \n",
227 152 " // Register the DatePickerView with the widget manager.\n",
228 " IPython.notebook.widget_manager.register_widget_view('FilePickerView', FilePickerView);\n",
153 " IPython.widget_manager.register_widget_view('FilePickerView', FilePickerView);\n",
229 154 "});"
230 155 ],
231 156 "metadata": {},
232 157 "output_type": "display_data",
233 158 "text": [
234 "<IPython.core.display.Javascript at 0x2fa80d0>"
159 "<IPython.core.display.Javascript at 0x319fe90>"
235 160 ]
236 161 }
237 162 ],
238 163 "prompt_number": 3
239 164 },
240 165 {
241 166 "cell_type": "heading",
242 167 "level": 1,
243 168 "metadata": {},
244 169 "source": [
245 170 "Usage"
246 171 ]
247 172 },
248 173 {
249 174 "cell_type": "code",
250 175 "collapsed": false,
251 176 "input": [
252 177 "file_widget = FileWidget()\n",
253 178 "display(file_widget)\n",
254 179 "\n",
255 180 "def file_loading():\n",
256 181 " print \"Loading %s\" % file_widget.filename\n",
257 182 "\n",
258 183 "def file_loaded():\n",
259 184 " print \"Loaded, file contents: %s\" % file_widget.value\n",
260 185 "\n",
261 186 "def file_failed(name, old_value, new_value):\n",
262 187 " if new_value > old_value:\n",
263 188 " print \"Could not load file contents of %s\" % file_widget.filename\n",
264 189 "\n",
265 190 "\n",
266 191 "file_widget.on_trait_change(file_loading, 'filename')\n",
267 192 "file_widget.on_trait_change(file_loaded, 'value')\n",
268 193 "file_widget.on_trait_change(file_failed, 'on_failed')"
269 194 ],
270 195 "language": "python",
271 196 "metadata": {},
272 197 "outputs": [
273 198 {
274 199 "output_type": "stream",
275 200 "stream": "stdout",
276 201 "text": [
277 202 "Loading test.txt\n"
278 203 ]
279 204 },
280 205 {
281 206 "output_type": "stream",
282 207 "stream": "stdout",
283 208 "text": [
284 209 "Loaded, file contents: \n",
285 210 "hello world!\n"
286 211 ]
287 212 }
288 213 ],
289 214 "prompt_number": 4
290 215 }
291 216 ],
292 217 "metadata": {}
293 218 }
294 219 ]
295 220 } No newline at end of file
@@ -1,391 +1,323 b''
1 1 {
2 2 "metadata": {
3 "cell_tags": [
4 [
5 "<None>",
6 null
7 ]
8 ],
3 9 "name": ""
4 10 },
5 11 "nbformat": 3,
6 12 "nbformat_minor": 0,
7 13 "worksheets": [
8 14 {
9 15 "cells": [
10 16 {
11 17 "cell_type": "markdown",
12 18 "metadata": {},
13 19 "source": [
14 "To enable the use IPython widgets in the notebook, the widget namespace and display function need to be imported. The Javascript dependencies need to be loaded via `IPython.html.widgets.init_widget_js()`. This method needs to be called each time the notebook webpage is refreshed."
20 "To use IPython widgets in the notebook, the widget namespace and display function need to be imported."
15 21 ]
16 22 },
17 23 {
18 24 "cell_type": "code",
19 25 "collapsed": false,
20 26 "input": [
21 27 "from IPython.html import widgets # Widget definitions\n",
22 "from IPython.display import display # Used to display widgets in the notebook\n",
23 "\n",
24 "# Enable widgets in this notebook\n",
25 "widgets.init_widget_js()"
28 "from IPython.display import display # Used to display widgets in the notebook"
26 29 ],
27 30 "language": "python",
28 31 "metadata": {},
29 "outputs": [
30 {
31 "javascript": [
32 "$.getScript(\"/static/notebook/js/widgets/bool.js\");"
33 ],
34 "metadata": {},
35 "output_type": "display_data"
36 },
37 {
38 "javascript": [
39 "$.getScript(\"/static/notebook/js/widgets/int_range.js\");"
40 ],
41 "metadata": {},
42 "output_type": "display_data"
43 },
44 {
45 "javascript": [
46 "$.getScript(\"/static/notebook/js/widgets/int.js\");"
47 ],
48 "metadata": {},
49 "output_type": "display_data"
50 },
51 {
52 "javascript": [
53 "$.getScript(\"/static/notebook/js/widgets/selection.js\");"
54 ],
55 "metadata": {},
56 "output_type": "display_data"
57 },
58 {
59 "javascript": [
60 "$.getScript(\"/static/notebook/js/widgets/string.js\");"
61 ],
62 "metadata": {},
63 "output_type": "display_data"
64 },
65 {
66 "javascript": [
67 "$.getScript(\"/static/notebook/js/widgets/float.js\");"
68 ],
69 "metadata": {},
70 "output_type": "display_data"
71 },
72 {
73 "javascript": [
74 "$.getScript(\"/static/notebook/js/widgets/container.js\");"
75 ],
76 "metadata": {},
77 "output_type": "display_data"
78 },
79 {
80 "javascript": [
81 "$.getScript(\"/static/notebook/js/widgets/multicontainer.js\");"
82 ],
83 "metadata": {},
84 "output_type": "display_data"
85 },
86 {
87 "javascript": [
88 "$.getScript(\"/static/notebook/js/widgets/button.js\");"
89 ],
90 "metadata": {},
91 "output_type": "display_data"
92 },
93 {
94 "javascript": [
95 "$.getScript(\"/static/notebook/js/widgets/float_range.js\");"
96 ],
97 "metadata": {},
98 "output_type": "display_data"
99 }
100 ],
32 "outputs": [],
101 33 "prompt_number": 1
102 34 },
103 35 {
104 36 "cell_type": "heading",
105 37 "level": 1,
106 38 "metadata": {},
107 39 "source": [
108 40 "Basic Widgets"
109 41 ]
110 42 },
111 43 {
112 44 "cell_type": "markdown",
113 45 "metadata": {},
114 46 "source": [
115 47 "The IPython notebook comes preloaded with basic widgets that represent common data types. These widgets are\n",
116 48 "\n",
117 49 "- BoolWidget : boolean \n",
118 50 "- FloatRangeWidget : bounded float \n",
119 51 "- FloatWidget : unbounded float \n",
120 52 "- IntRangeWidget : bounded integer \n",
121 53 "- IntWidget : unbounded integer \n",
122 54 "- SelectionWidget : enumeration \n",
123 55 "- StringWidget : string \n",
124 56 "\n",
125 57 "A few special widgets are also included, that can be used to capture events and change how other widgets are displayed. These widgets are\n",
126 58 "\n",
127 59 "- ButtonWidget \n",
128 60 "- ContainerWidget \n",
129 61 "- MulticontainerWidget \n",
130 62 "\n",
131 63 "To see the complete list of widgets, one can execute the following"
132 64 ]
133 65 },
134 66 {
135 67 "cell_type": "code",
136 68 "collapsed": false,
137 69 "input": [
138 70 "[widget for widget in dir(widgets) if widget.endswith('Widget')]"
139 71 ],
140 72 "language": "python",
141 73 "metadata": {},
142 74 "outputs": [
143 75 {
144 76 "metadata": {},
145 77 "output_type": "pyout",
146 78 "prompt_number": 2,
147 79 "text": [
148 80 "['BoolWidget',\n",
149 81 " 'ButtonWidget',\n",
150 82 " 'ContainerWidget',\n",
151 83 " 'FloatRangeWidget',\n",
152 84 " 'FloatWidget',\n",
153 85 " 'IntRangeWidget',\n",
154 86 " 'IntWidget',\n",
155 87 " 'MulticontainerWidget',\n",
156 88 " 'SelectionWidget',\n",
157 89 " 'StringWidget',\n",
158 90 " 'Widget']"
159 91 ]
160 92 }
161 93 ],
162 94 "prompt_number": 2
163 95 },
164 96 {
165 97 "cell_type": "markdown",
166 98 "metadata": {},
167 99 "source": [
168 100 "The basic widgets can all be constructed without arguments. The following creates a FloatRangeWidget without displaying it"
169 101 ]
170 102 },
171 103 {
172 104 "cell_type": "code",
173 105 "collapsed": false,
174 106 "input": [
175 107 "mywidget = widgets.FloatRangeWidget()"
176 108 ],
177 109 "language": "python",
178 110 "metadata": {},
179 111 "outputs": [],
180 112 "prompt_number": 3
181 113 },
182 114 {
183 115 "cell_type": "markdown",
184 116 "metadata": {},
185 117 "source": [
186 118 "Constructing a widget does not display it on the page. To display a widget, the widget must be passed to the IPython `display(object)` method. `mywidget` is displayed by"
187 119 ]
188 120 },
189 121 {
190 122 "cell_type": "code",
191 123 "collapsed": false,
192 124 "input": [
193 125 "display(mywidget)"
194 126 ],
195 127 "language": "python",
196 128 "metadata": {},
197 129 "outputs": [],
198 130 "prompt_number": 4
199 131 },
200 132 {
201 133 "cell_type": "markdown",
202 134 "metadata": {},
203 135 "source": [
204 136 "It's important to realize that widgets are not the same as output, even though they are displayed with `display`. Widgets are drawn in a special widget area. That area is marked with a close button which allows you to collapse the widgets. Widgets cannot be interleaved with output. Doing so would break the ability to make simple animations using `clear_output`.\n",
205 137 "\n",
206 138 "Widgets are manipulated via special instance properties (traitlets). The names of these instance properties are listed in the widget's `keys` property (as seen below). A few of these properties are common to most, if not all, widgets. The common properties are `value`, `description`, `visible`, and `disabled`. `_css`, `_add_class`, and `_remove_class` are internal properties that exist in all widgets and should not be modified."
207 139 ]
208 140 },
209 141 {
210 142 "cell_type": "code",
211 143 "collapsed": false,
212 144 "input": [
213 145 "mywidget.keys"
214 146 ],
215 147 "language": "python",
216 148 "metadata": {},
217 149 "outputs": [
218 150 {
219 151 "metadata": {},
220 152 "output_type": "pyout",
221 153 "prompt_number": 5,
222 154 "text": [
223 155 "['visible',\n",
224 156 " '_css',\n",
225 157 " '_add_class',\n",
226 158 " '_remove_class',\n",
227 159 " 'value',\n",
228 160 " 'step',\n",
229 161 " 'max',\n",
230 162 " 'min',\n",
231 163 " 'disabled',\n",
232 164 " 'orientation',\n",
233 165 " 'description']"
234 166 ]
235 167 }
236 168 ],
237 169 "prompt_number": 5
238 170 },
239 171 {
240 172 "cell_type": "markdown",
241 173 "metadata": {},
242 174 "source": [
243 175 "Changing a widget's property value will automatically update that widget everywhere it is displayed in the notebook. Here the value of `mywidget` is set. The slider shown above (after input 4) updates automatically to the new value. In reverse, changing the value of the displayed widget will update the property's value."
244 176 ]
245 177 },
246 178 {
247 179 "cell_type": "code",
248 180 "collapsed": false,
249 181 "input": [
250 182 "mywidget.value = 25.0"
251 183 ],
252 184 "language": "python",
253 185 "metadata": {},
254 186 "outputs": [],
255 187 "prompt_number": 6
256 188 },
257 189 {
258 190 "cell_type": "markdown",
259 191 "metadata": {},
260 192 "source": [
261 193 "After changing the widget's value in the notebook by hand to 0.0 (sliding the bar to the far left)."
262 194 ]
263 195 },
264 196 {
265 197 "cell_type": "code",
266 198 "collapsed": false,
267 199 "input": [
268 200 "mywidget.value"
269 201 ],
270 202 "language": "python",
271 203 "metadata": {},
272 204 "outputs": [
273 205 {
274 206 "metadata": {},
275 207 "output_type": "pyout",
276 208 "prompt_number": 7,
277 209 "text": [
278 210 "0.0"
279 211 ]
280 212 }
281 213 ],
282 214 "prompt_number": 7
283 215 },
284 216 {
285 217 "cell_type": "markdown",
286 218 "metadata": {},
287 219 "source": [
288 220 "Widget property values can also be set with kwargs during the construction of the widget (as seen below)."
289 221 ]
290 222 },
291 223 {
292 224 "cell_type": "code",
293 225 "collapsed": false,
294 226 "input": [
295 227 "mysecondwidget = widgets.SelectionWidget(values=[\"Item A\", \"Item B\", \"Item C\"], value=\"Nothing Selected\")\n",
296 228 "display(mysecondwidget)"
297 229 ],
298 230 "language": "python",
299 231 "metadata": {},
300 232 "outputs": [],
301 233 "prompt_number": 8
302 234 },
303 235 {
304 236 "cell_type": "heading",
305 237 "level": 1,
306 238 "metadata": {},
307 239 "source": [
308 240 "Views"
309 241 ]
310 242 },
311 243 {
312 244 "cell_type": "markdown",
313 245 "metadata": {},
314 246 "source": [
315 247 "The data types that most of the widgets represent can be displayed more than one way. A `view` is a visual representation of a widget in the notebook. In the example in the section above, the default `view` for the `FloatRangeWidget` is used. The default view is set in the widgets `default_view_name` instance property (as seen below)."
316 248 ]
317 249 },
318 250 {
319 251 "cell_type": "code",
320 252 "collapsed": false,
321 253 "input": [
322 254 "mywidget.default_view_name"
323 255 ],
324 256 "language": "python",
325 257 "metadata": {},
326 258 "outputs": [
327 259 {
328 260 "metadata": {},
329 261 "output_type": "pyout",
330 262 "prompt_number": 9,
331 263 "text": [
332 264 "u'FloatSliderView'"
333 265 ]
334 266 }
335 267 ],
336 268 "prompt_number": 9
337 269 },
338 270 {
339 271 "cell_type": "markdown",
340 272 "metadata": {},
341 273 "source": [
342 274 "When a widget is displayed using `display(...)`, the `default_view_name` is used to determine what view type should be used to display the widget. View names are case sensitive. Sometimes the default view isn't the best view to represent a piece of data. To change what view is used, either the `default_view_name` can be changed or the `view_name` kwarg of `display` can be set. This also can be used to display one widget multiple ways in one output (as seen below)."
343 275 ]
344 276 },
345 277 {
346 278 "cell_type": "code",
347 279 "collapsed": false,
348 280 "input": [
349 281 "display(mywidget)\n",
350 282 "display(mywidget, view_name=\"FloatTextView\")"
351 283 ],
352 284 "language": "python",
353 285 "metadata": {},
354 286 "outputs": [],
355 287 "prompt_number": 10
356 288 },
357 289 {
358 290 "cell_type": "markdown",
359 291 "metadata": {},
360 292 "source": [
361 293 "Some views work with multiple different widget types and some views only work with one. The complete list of views and supported widgets is below. The default views are italicized.\n",
362 294 "\n",
363 295 "| Widget Name | View Names |\n",
364 296 "|:-----------------------|:--------------------|\n",
365 297 "| BoolWidget | *CheckboxView* |\n",
366 298 "| | ToggleButtonView |\n",
367 299 "| ButtonWidget | *ButtonView* |\n",
368 300 "| ContainerWidget | *ContainerView* |\n",
369 301 "| FloatRangeWidget | *FloatSliderView* |\n",
370 302 "| | FloatTextView |\n",
371 303 "| | ProgressView |\n",
372 304 "| FloatWidget | *FloatTextView* |\n",
373 305 "| IntRangeWidget | *IntSliderView* |\n",
374 306 "| | IntTextView |\n",
375 307 "| | ProgressView |\n",
376 308 "| IntWidget | *IntTextView* |\n",
377 309 "| MulticontainerWidget | AccordionView |\n",
378 310 "| | *TabView* |\n",
379 311 "| SelectionWidget | ToggleButtonsView |\n",
380 312 "| | RadioButtonsView |\n",
381 313 "| | *DropdownView* |\n",
382 314 "| StringWidget | LabelView |\n",
383 315 "| | TextAreaView |\n",
384 316 "| | *TextBoxView* |\n"
385 317 ]
386 318 }
387 319 ],
388 320 "metadata": {}
389 321 }
390 322 ]
391 323 } No newline at end of file
@@ -1,310 +1,242 b''
1 1 {
2 2 "metadata": {
3 "cell_tags": [
4 [
5 "<None>",
6 null
7 ]
8 ],
3 9 "name": ""
4 10 },
5 11 "nbformat": 3,
6 12 "nbformat_minor": 0,
7 13 "worksheets": [
8 14 {
9 15 "cells": [
10 16 {
11 17 "cell_type": "code",
12 18 "collapsed": false,
13 19 "input": [
14 20 "from IPython.html import widgets # Widget definitions\n",
15 "from IPython.display import display # Used to display widgets in the notebook\n",
16 "\n",
17 "# Enable widgets in this notebook\n",
18 "widgets.init_widget_js()"
21 "from IPython.display import display # Used to display widgets in the notebook"
19 22 ],
20 23 "language": "python",
21 24 "metadata": {},
22 "outputs": [
23 {
24 "javascript": [
25 "$.getScript(\"/static/notebook/js/widgets/bool.js\");"
26 ],
27 "metadata": {},
28 "output_type": "display_data"
29 },
30 {
31 "javascript": [
32 "$.getScript(\"/static/notebook/js/widgets/int_range.js\");"
33 ],
34 "metadata": {},
35 "output_type": "display_data"
36 },
37 {
38 "javascript": [
39 "$.getScript(\"/static/notebook/js/widgets/int.js\");"
40 ],
41 "metadata": {},
42 "output_type": "display_data"
43 },
44 {
45 "javascript": [
46 "$.getScript(\"/static/notebook/js/widgets/selection.js\");"
47 ],
48 "metadata": {},
49 "output_type": "display_data"
50 },
51 {
52 "javascript": [
53 "$.getScript(\"/static/notebook/js/widgets/string.js\");"
54 ],
55 "metadata": {},
56 "output_type": "display_data"
57 },
58 {
59 "javascript": [
60 "$.getScript(\"/static/notebook/js/widgets/float.js\");"
61 ],
62 "metadata": {},
63 "output_type": "display_data"
64 },
65 {
66 "javascript": [
67 "$.getScript(\"/static/notebook/js/widgets/container.js\");"
68 ],
69 "metadata": {},
70 "output_type": "display_data"
71 },
72 {
73 "javascript": [
74 "$.getScript(\"/static/notebook/js/widgets/multicontainer.js\");"
75 ],
76 "metadata": {},
77 "output_type": "display_data"
78 },
79 {
80 "javascript": [
81 "$.getScript(\"/static/notebook/js/widgets/button.js\");"
82 ],
83 "metadata": {},
84 "output_type": "display_data"
85 },
86 {
87 "javascript": [
88 "$.getScript(\"/static/notebook/js/widgets/float_range.js\");"
89 ],
90 "metadata": {},
91 "output_type": "display_data"
92 }
93 ],
25 "outputs": [],
94 26 "prompt_number": 1
95 27 },
96 28 {
97 29 "cell_type": "heading",
98 30 "level": 1,
99 31 "metadata": {},
100 32 "source": [
101 33 "Traitlet Events"
102 34 ]
103 35 },
104 36 {
105 37 "cell_type": "markdown",
106 38 "metadata": {},
107 39 "source": [
108 40 "The widget properties are IPython traitlets. Traitlets are eventful. To handle property value changes, the `on_trait_change` method of the widget can be used to register an event handling callback. The doc string for `on_trait_change` can be seen below. Both the `name` and `remove` properties are optional."
109 41 ]
110 42 },
111 43 {
112 44 "cell_type": "code",
113 45 "collapsed": false,
114 46 "input": [
115 47 "print(widgets.Widget.on_trait_change.__doc__)"
116 48 ],
117 49 "language": "python",
118 50 "metadata": {},
119 51 "outputs": [
120 52 {
121 53 "output_type": "stream",
122 54 "stream": "stdout",
123 55 "text": [
124 56 "Setup a handler to be called when a trait changes.\n",
125 57 "\n",
126 58 " This is used to setup dynamic notifications of trait changes.\n",
127 59 "\n",
128 60 " Static handlers can be created by creating methods on a HasTraits\n",
129 61 " subclass with the naming convention '_[traitname]_changed'. Thus,\n",
130 62 " to create static handler for the trait 'a', create the method\n",
131 63 " _a_changed(self, name, old, new) (fewer arguments can be used, see\n",
132 64 " below).\n",
133 65 "\n",
134 66 " Parameters\n",
135 67 " ----------\n",
136 68 " handler : callable\n",
137 69 " A callable that is called when a trait changes. Its\n",
138 70 " signature can be handler(), handler(name), handler(name, new)\n",
139 71 " or handler(name, old, new).\n",
140 72 " name : list, str, None\n",
141 73 " If None, the handler will apply to all traits. If a list\n",
142 74 " of str, handler will apply to all names in the list. If a\n",
143 75 " str, the handler will apply just to that name.\n",
144 76 " remove : bool\n",
145 77 " If False (the default), then install the handler. If True\n",
146 78 " then unintall it.\n",
147 79 " \n"
148 80 ]
149 81 }
150 82 ],
151 83 "prompt_number": 2
152 84 },
153 85 {
154 86 "cell_type": "markdown",
155 87 "metadata": {},
156 88 "source": [
157 89 "Mentioned in the doc string, the callback registered can have 4 possible signatures:\n",
158 90 "\n",
159 91 "- callback()\n",
160 92 "- callback(trait_name)\n",
161 93 "- callback(trait_name, new_value)\n",
162 94 "- callback(trait_name, old_value, new_value)\n",
163 95 "\n",
164 96 "An example of how to output an IntRangeWiget's value as it is changed can be seen below."
165 97 ]
166 98 },
167 99 {
168 100 "cell_type": "code",
169 101 "collapsed": false,
170 102 "input": [
171 103 "intrange = widgets.IntRangeWidget()\n",
172 104 "display(intrange)\n",
173 105 "\n",
174 106 "def on_value_change(name, value):\n",
175 107 " print value\n",
176 108 "\n",
177 109 "intrange.on_trait_change(on_value_change, 'value')"
178 110 ],
179 111 "language": "python",
180 112 "metadata": {},
181 113 "outputs": [],
182 114 "prompt_number": 3
183 115 },
184 116 {
185 117 "cell_type": "heading",
186 118 "level": 1,
187 119 "metadata": {},
188 120 "source": [
189 121 "Specialized Events"
190 122 ]
191 123 },
192 124 {
193 125 "cell_type": "heading",
194 126 "level": 2,
195 127 "metadata": {},
196 128 "source": [
197 129 "Button On Click Event"
198 130 ]
199 131 },
200 132 {
201 133 "cell_type": "markdown",
202 134 "metadata": {},
203 135 "source": [
204 136 "The `ButtonWidget` is a special widget, like the `ContainerWidget` and `MulticontainerWidget`, that isn't used to represent a data type. Instead the button widget is used to handle mouse clicks. The `on_click` method of the `ButtonWidget` can be used to register a click even handler. The doc string of the `on_click` can be seen below."
205 137 ]
206 138 },
207 139 {
208 140 "cell_type": "code",
209 141 "collapsed": false,
210 142 "input": [
211 143 "print(widgets.ButtonWidget.on_click.__doc__)"
212 144 ],
213 145 "language": "python",
214 146 "metadata": {},
215 147 "outputs": [
216 148 {
217 149 "output_type": "stream",
218 150 "stream": "stdout",
219 151 "text": [
220 152 "Register a callback to execute when the button is clicked. The\n",
221 153 " callback can either accept no parameters or one sender parameter:\n",
222 154 " - callback()\n",
223 155 " - callback(sender)\n",
224 156 " If the callback has a sender parameter, the ButtonWidget instance that\n",
225 157 " called the callback will be passed into the method as the sender.\n",
226 158 "\n",
227 159 " Parameters\n",
228 160 " ----------\n",
229 161 " remove : bool (optional)\n",
230 162 " Set to true to remove the callback from the list of callbacks.\n"
231 163 ]
232 164 }
233 165 ],
234 166 "prompt_number": 4
235 167 },
236 168 {
237 169 "cell_type": "markdown",
238 170 "metadata": {},
239 171 "source": [
240 172 "Button clicks are tracked by the `clicks` property of the button widget. By using the `on_click` method and the `clicks` property, a button that outputs how many times it has been clicked is shown below."
241 173 ]
242 174 },
243 175 {
244 176 "cell_type": "code",
245 177 "collapsed": false,
246 178 "input": [
247 179 "button = widgets.ButtonWidget(description=\"Click Me!\")\n",
248 180 "display(button)\n",
249 181 "\n",
250 182 "def on_button_clicked(sender):\n",
251 183 " print(\"Button clicked %d times.\" % sender.clicks)\n",
252 184 "\n",
253 185 "button.on_click(on_button_clicked)"
254 186 ],
255 187 "language": "python",
256 188 "metadata": {},
257 189 "outputs": [
258 190 {
259 191 "output_type": "stream",
260 192 "stream": "stdout",
261 193 "text": [
262 194 "Button clicked 1 times.\n"
263 195 ]
264 196 },
265 197 {
266 198 "output_type": "stream",
267 199 "stream": "stdout",
268 200 "text": [
269 201 "Button clicked 2 times.\n"
270 202 ]
271 203 },
272 204 {
273 205 "output_type": "stream",
274 206 "stream": "stdout",
275 207 "text": [
276 208 "Button clicked 3 times.\n"
277 209 ]
278 210 }
279 211 ],
280 212 "prompt_number": 5
281 213 },
282 214 {
283 215 "cell_type": "markdown",
284 216 "metadata": {},
285 217 "source": [
286 218 "Event handlers can also be used to create widgets. In the example below, clicking a button spawns another button with a description equal to how many times the parent button had been clicked at the time."
287 219 ]
288 220 },
289 221 {
290 222 "cell_type": "code",
291 223 "collapsed": false,
292 224 "input": [
293 225 "def show_button(sender=None):\n",
294 226 " button = widgets.ButtonWidget()\n",
295 227 " button.description = \"%d\" % (sender.clicks if sender is not None else 0)\n",
296 228 " display(button)\n",
297 229 " button.on_click(show_button)\n",
298 230 "show_button()\n",
299 231 " "
300 232 ],
301 233 "language": "python",
302 234 "metadata": {},
303 235 "outputs": [],
304 236 "prompt_number": 6
305 237 }
306 238 ],
307 239 "metadata": {}
308 240 }
309 241 ]
310 242 } No newline at end of file
@@ -1,326 +1,258 b''
1 1 {
2 2 "metadata": {
3 "cell_tags": [
4 [
5 "<None>",
6 null
7 ]
8 ],
3 9 "name": ""
4 10 },
5 11 "nbformat": 3,
6 12 "nbformat_minor": 0,
7 13 "worksheets": [
8 14 {
9 15 "cells": [
10 16 {
11 17 "cell_type": "code",
12 18 "collapsed": false,
13 19 "input": [
14 20 "from IPython.html import widgets # Widget definitions\n",
15 "from IPython.display import display # Used to display widgets in the notebook\n",
16 "\n",
17 "# Enable widgets in this notebook\n",
18 "widgets.init_widget_js()"
21 "from IPython.display import display # Used to display widgets in the notebook"
19 22 ],
20 23 "language": "python",
21 24 "metadata": {},
22 "outputs": [
23 {
24 "javascript": [
25 "$.getScript(\"/static/notebook/js/widgets/bool.js\");"
26 ],
27 "metadata": {},
28 "output_type": "display_data"
29 },
30 {
31 "javascript": [
32 "$.getScript(\"/static/notebook/js/widgets/int_range.js\");"
33 ],
34 "metadata": {},
35 "output_type": "display_data"
36 },
37 {
38 "javascript": [
39 "$.getScript(\"/static/notebook/js/widgets/int.js\");"
40 ],
41 "metadata": {},
42 "output_type": "display_data"
43 },
44 {
45 "javascript": [
46 "$.getScript(\"/static/notebook/js/widgets/selection.js\");"
47 ],
48 "metadata": {},
49 "output_type": "display_data"
50 },
51 {
52 "javascript": [
53 "$.getScript(\"/static/notebook/js/widgets/string.js\");"
54 ],
55 "metadata": {},
56 "output_type": "display_data"
57 },
58 {
59 "javascript": [
60 "$.getScript(\"/static/notebook/js/widgets/float.js\");"
61 ],
62 "metadata": {},
63 "output_type": "display_data"
64 },
65 {
66 "javascript": [
67 "$.getScript(\"/static/notebook/js/widgets/container.js\");"
68 ],
69 "metadata": {},
70 "output_type": "display_data"
71 },
72 {
73 "javascript": [
74 "$.getScript(\"/static/notebook/js/widgets/multicontainer.js\");"
75 ],
76 "metadata": {},
77 "output_type": "display_data"
78 },
79 {
80 "javascript": [
81 "$.getScript(\"/static/notebook/js/widgets/button.js\");"
82 ],
83 "metadata": {},
84 "output_type": "display_data"
85 },
86 {
87 "javascript": [
88 "$.getScript(\"/static/notebook/js/widgets/float_range.js\");"
89 ],
90 "metadata": {},
91 "output_type": "display_data"
92 }
93 ],
25 "outputs": [],
94 26 "prompt_number": 1
95 27 },
96 28 {
97 29 "cell_type": "heading",
98 30 "level": 1,
99 31 "metadata": {},
100 32 "source": [
101 33 "Parent/Child Relationships"
102 34 ]
103 35 },
104 36 {
105 37 "cell_type": "markdown",
106 38 "metadata": {},
107 39 "source": [
108 40 "To display widget A inside widget B, widget A must be a child of widget B. With IPython widgets, the widgets are instances that live in the back-end (usally Python). There can be multiple views displayed in the front-end that represent one widget in the backend. Each view can be displayed at a different time, or even displayed two or more times in the same output. Because of this, the parent of a widget can only be set before the widget has been displayed.\n",
109 41 "\n",
110 42 "Every widget has a `parent` property. This property can be set via a kwarg in the widget's constructor or after construction, but before display. Calling display on an object with children automatically displays those children too (as seen below)."
111 43 ]
112 44 },
113 45 {
114 46 "cell_type": "code",
115 47 "collapsed": false,
116 48 "input": [
117 49 "container = widgets.MulticontainerWidget()\n",
118 50 "\n",
119 51 "floatrange = widgets.FloatRangeWidget(parent=container) # You can set the parent in the constructor,\n",
120 52 "\n",
121 53 "string = widgets.StringWidget()\n",
122 54 "string.parent = container # or after the widget has been created.\n",
123 55 "\n",
124 56 "display(container) # Displays the `container` and all of it's children."
125 57 ],
126 58 "language": "python",
127 59 "metadata": {},
128 60 "outputs": [],
129 61 "prompt_number": 2
130 62 },
131 63 {
132 64 "cell_type": "markdown",
133 65 "metadata": {},
134 66 "source": [
135 67 "Children can also be added to parents after the parent has been displayed. If the children are added after the parent has already been displayed, the children must be displayed themselves.\n",
136 68 "\n",
137 69 "In the example below, the IntRangeWidget is never rendered since display was called on the parent before the parent/child relationship was established."
138 70 ]
139 71 },
140 72 {
141 73 "cell_type": "code",
142 74 "collapsed": false,
143 75 "input": [
144 76 "container = widgets.MulticontainerWidget()\n",
145 77 "display(container)\n",
146 78 "\n",
147 79 "intrange = widgets.IntRangeWidget(parent=container) # Never gets displayed."
148 80 ],
149 81 "language": "python",
150 82 "metadata": {},
151 83 "outputs": [],
152 84 "prompt_number": 3
153 85 },
154 86 {
155 87 "cell_type": "markdown",
156 88 "metadata": {},
157 89 "source": [
158 90 "Calling display on the child fixes the problem."
159 91 ]
160 92 },
161 93 {
162 94 "cell_type": "code",
163 95 "collapsed": false,
164 96 "input": [
165 97 "container = widgets.MulticontainerWidget()\n",
166 98 "display(container)\n",
167 99 "\n",
168 100 "intrange = widgets.IntRangeWidget(parent=container)\n",
169 101 "display(intrange) # This line is needed since the `container` has already been displayed."
170 102 ],
171 103 "language": "python",
172 104 "metadata": {},
173 105 "outputs": [],
174 106 "prompt_number": 4
175 107 },
176 108 {
177 109 "cell_type": "heading",
178 110 "level": 1,
179 111 "metadata": {},
180 112 "source": [
181 113 "Changing Child Views"
182 114 ]
183 115 },
184 116 {
185 117 "cell_type": "markdown",
186 118 "metadata": {},
187 119 "source": [
188 120 "The view used to display a widget must defined by the time the widget is displayed. If children widgets are to be displayed along with the parent in one call, their `view_name`s can't be set since all of the widgets are sharing the same display call. Instead, their `default_view_name`s must be set (as seen below)."
189 121 ]
190 122 },
191 123 {
192 124 "cell_type": "code",
193 125 "collapsed": false,
194 126 "input": [
195 127 "container = widgets.MulticontainerWidget()\n",
196 128 "\n",
197 129 "floatrange = widgets.FloatRangeWidget(parent=container)\n",
198 130 "floatrange.default_view_name = \"FloatTextView\" # It can be set as a property.\n",
199 131 "\n",
200 132 "string = widgets.StringWidget(default_view_name = \"TextAreaView\") # It can also be set in the constructor.\n",
201 133 "string.parent = container\n",
202 134 "\n",
203 135 "display(container)"
204 136 ],
205 137 "language": "python",
206 138 "metadata": {},
207 139 "outputs": [],
208 140 "prompt_number": 5
209 141 },
210 142 {
211 143 "cell_type": "markdown",
212 144 "metadata": {},
213 145 "source": [
214 146 "However, if the children are displayed after the parent, their `view_name` can also be set like normal. Both methods will work. The code below produces the same output as the code above."
215 147 ]
216 148 },
217 149 {
218 150 "cell_type": "code",
219 151 "collapsed": false,
220 152 "input": [
221 153 "container = widgets.MulticontainerWidget()\n",
222 154 "display(container)\n",
223 155 "\n",
224 156 "floatrange = widgets.FloatRangeWidget()\n",
225 157 "floatrange.parent=container\n",
226 158 "display(floatrange, view_name = \"FloatTextView\") # view_name can be set during display.\n",
227 159 "\n",
228 160 "string = widgets.StringWidget()\n",
229 161 "string.parent = container\n",
230 162 "string.default_view_name = \"TextAreaView\" # Setting default_view_name still works.\n",
231 163 "display(string)\n"
232 164 ],
233 165 "language": "python",
234 166 "metadata": {},
235 167 "outputs": [],
236 168 "prompt_number": 6
237 169 },
238 170 {
239 171 "cell_type": "heading",
240 172 "level": 1,
241 173 "metadata": {},
242 174 "source": [
243 175 "Visibility"
244 176 ]
245 177 },
246 178 {
247 179 "cell_type": "markdown",
248 180 "metadata": {},
249 181 "source": [
250 182 "Sometimes it's necessary to hide/show widget views in place, without ruining the order that they have been displayed on the page. Using the `display` method, the views are always added to the end of their respective containers. Instead the `visibility` property of widgets can be used to hide/show widgets that have already been displayed (as seen below)."
251 183 ]
252 184 },
253 185 {
254 186 "cell_type": "code",
255 187 "collapsed": false,
256 188 "input": [
257 189 "string = widgets.StringWidget(value=\"Hello World!\")\n",
258 190 "display(string, view_name=\"LabelView\") "
259 191 ],
260 192 "language": "python",
261 193 "metadata": {},
262 194 "outputs": [],
263 195 "prompt_number": 7
264 196 },
265 197 {
266 198 "cell_type": "code",
267 199 "collapsed": false,
268 200 "input": [
269 201 "string.visible=False"
270 202 ],
271 203 "language": "python",
272 204 "metadata": {},
273 205 "outputs": [],
274 206 "prompt_number": 8
275 207 },
276 208 {
277 209 "cell_type": "code",
278 210 "collapsed": false,
279 211 "input": [
280 212 "string.visible=True"
281 213 ],
282 214 "language": "python",
283 215 "metadata": {},
284 216 "outputs": [],
285 217 "prompt_number": 9
286 218 },
287 219 {
288 220 "cell_type": "markdown",
289 221 "metadata": {},
290 222 "source": [
291 223 "In the example below, a form is rendered which conditionally displays widgets depending on the state of other widgets. Try toggling the student checkbox."
292 224 ]
293 225 },
294 226 {
295 227 "cell_type": "code",
296 228 "collapsed": false,
297 229 "input": [
298 230 "form = widgets.ContainerWidget()\n",
299 231 "first = widgets.StringWidget(description=\"First Name:\", parent=form)\n",
300 232 "last = widgets.StringWidget(description=\"Last Name:\", parent=form)\n",
301 233 "\n",
302 234 "student = widgets.BoolWidget(description=\"Student:\", value=False, parent=form)\n",
303 235 "school_info = widgets.ContainerWidget(visible=False, parent=form)\n",
304 236 "school = widgets.StringWidget(description=\"School:\", parent=school_info)\n",
305 237 "grade = widgets.IntRangeWidget(description=\"Grade:\", min=0, max=12, default_view_name='IntTextView', parent=school_info)\n",
306 238 "\n",
307 239 "pet = widgets.StringWidget(description=\"Pet's Name:\", parent=form)\n",
308 240 "display(form)\n",
309 241 "\n",
310 242 "def on_student_toggle(name, value):\n",
311 243 " if value:\n",
312 244 " school_info.visible = True\n",
313 245 " else:\n",
314 246 " school_info.visible = False\n",
315 247 "student.on_trait_change(on_student_toggle, 'value')\n"
316 248 ],
317 249 "language": "python",
318 250 "metadata": {},
319 251 "outputs": [],
320 252 "prompt_number": 10
321 253 }
322 254 ],
323 255 "metadata": {}
324 256 }
325 257 ]
326 258 } No newline at end of file
@@ -1,405 +1,337 b''
1 1 {
2 2 "metadata": {
3 "cell_tags": [
4 [
5 "<None>",
6 null
7 ]
8 ],
3 9 "name": ""
4 10 },
5 11 "nbformat": 3,
6 12 "nbformat_minor": 0,
7 13 "worksheets": [
8 14 {
9 15 "cells": [
10 16 {
11 17 "cell_type": "code",
12 18 "collapsed": false,
13 19 "input": [
14 20 "from IPython.html import widgets # Widget definitions\n",
15 "from IPython.display import display # Used to display widgets in the notebook\n",
16 "\n",
17 "# Enable widgets in this notebook\n",
18 "widgets.init_widget_js()"
21 "from IPython.display import display # Used to display widgets in the notebook"
19 22 ],
20 23 "language": "python",
21 24 "metadata": {},
22 "outputs": [
23 {
24 "javascript": [
25 "$.getScript(\"../static/notebook/js/widgets/bool.js\");"
26 ],
27 "metadata": {},
28 "output_type": "display_data"
29 },
30 {
31 "javascript": [
32 "$.getScript(\"../static/notebook/js/widgets/int_range.js\");"
33 ],
34 "metadata": {},
35 "output_type": "display_data"
36 },
37 {
38 "javascript": [
39 "$.getScript(\"../static/notebook/js/widgets/int.js\");"
40 ],
41 "metadata": {},
42 "output_type": "display_data"
43 },
44 {
45 "javascript": [
46 "$.getScript(\"../static/notebook/js/widgets/selection.js\");"
47 ],
48 "metadata": {},
49 "output_type": "display_data"
50 },
51 {
52 "javascript": [
53 "$.getScript(\"../static/notebook/js/widgets/string.js\");"
54 ],
55 "metadata": {},
56 "output_type": "display_data"
57 },
58 {
59 "javascript": [
60 "$.getScript(\"../static/notebook/js/widgets/float.js\");"
61 ],
62 "metadata": {},
63 "output_type": "display_data"
64 },
65 {
66 "javascript": [
67 "$.getScript(\"../static/notebook/js/widgets/container.js\");"
68 ],
69 "metadata": {},
70 "output_type": "display_data"
71 },
72 {
73 "javascript": [
74 "$.getScript(\"../static/notebook/js/widgets/multicontainer.js\");"
75 ],
76 "metadata": {},
77 "output_type": "display_data"
78 },
79 {
80 "javascript": [
81 "$.getScript(\"../static/notebook/js/widgets/button.js\");"
82 ],
83 "metadata": {},
84 "output_type": "display_data"
85 },
86 {
87 "javascript": [
88 "$.getScript(\"../static/notebook/js/widgets/float_range.js\");"
89 ],
90 "metadata": {},
91 "output_type": "display_data"
92 }
93 ],
25 "outputs": [],
94 26 "prompt_number": 1
95 27 },
96 28 {
97 29 "cell_type": "heading",
98 30 "level": 1,
99 31 "metadata": {},
100 32 "source": [
101 33 "CSS"
102 34 ]
103 35 },
104 36 {
105 37 "cell_type": "markdown",
106 38 "metadata": {},
107 39 "source": [
108 40 "When trying to design an attractive widget GUI, styling becomes important. Widget views are DOM (document object model) elements that can be controlled with CSS. There are two helper methods defined on widget that allow the manipulation of the widget's CSS. The first is the `set_css` method, whos doc string is displayed below. This method allows one or more CSS attributes to be set at once. "
109 41 ]
110 42 },
111 43 {
112 44 "cell_type": "code",
113 45 "collapsed": false,
114 46 "input": [
115 47 "print(widgets.Widget.set_css.__doc__)"
116 48 ],
117 49 "language": "python",
118 50 "metadata": {},
119 51 "outputs": [
120 52 {
121 53 "output_type": "stream",
122 54 "stream": "stdout",
123 55 "text": [
124 56 "Set one or more CSS properties of the widget (shared among all of the \n",
125 57 " views). This function has two signatures:\n",
126 58 " - set_css(css_dict, [selector=''])\n",
127 59 " - set_css(key, value, [selector=''])\n",
128 60 "\n",
129 61 " Parameters\n",
130 62 " ----------\n",
131 63 " css_dict : dict\n",
132 64 " CSS key/value pairs to apply\n",
133 65 " key: unicode\n",
134 66 " CSS key\n",
135 67 " value\n",
136 68 " CSS value\n",
137 69 " selector: unicode (optional)\n",
138 70 " JQuery selector to use to apply the CSS key/value.\n",
139 71 " \n"
140 72 ]
141 73 }
142 74 ],
143 75 "prompt_number": 2
144 76 },
145 77 {
146 78 "cell_type": "markdown",
147 79 "metadata": {},
148 80 "source": [
149 81 "The second is `get_css` which allows CSS attributes that have been set to be read. Note that this method will only read CSS attributes that have been set using the `set_css` method. `get_css`'s doc string is displayed below."
150 82 ]
151 83 },
152 84 {
153 85 "cell_type": "code",
154 86 "collapsed": false,
155 87 "input": [
156 88 "print(widgets.Widget.get_css.__doc__)"
157 89 ],
158 90 "language": "python",
159 91 "metadata": {},
160 92 "outputs": [
161 93 {
162 94 "output_type": "stream",
163 95 "stream": "stdout",
164 96 "text": [
165 97 "Get a CSS property of the widget. Note, this function does not \n",
166 98 " actually request the CSS from the front-end; Only properties that have \n",
167 99 " been set with set_css can be read.\n",
168 100 "\n",
169 101 " Parameters\n",
170 102 " ----------\n",
171 103 " key: unicode\n",
172 104 " CSS key\n",
173 105 " selector: unicode (optional)\n",
174 106 " JQuery selector used when the CSS key/value was set.\n",
175 107 " \n"
176 108 ]
177 109 }
178 110 ],
179 111 "prompt_number": 3
180 112 },
181 113 {
182 114 "cell_type": "markdown",
183 115 "metadata": {},
184 116 "source": [
185 117 "Below is an example that applies CSS attributes to a container to emphasize text."
186 118 ]
187 119 },
188 120 {
189 121 "cell_type": "code",
190 122 "collapsed": false,
191 123 "input": [
192 124 "container = widgets.ContainerWidget()\n",
193 125 "\n",
194 126 "# set_css used to set a single CSS attribute.\n",
195 127 "container.set_css('border', '3px solid black') # Border the container\n",
196 128 "\n",
197 129 "# set_css used to set multiple CSS attributes.\n",
198 130 "container.set_css({'padding': '6px', # Add padding to the container\n",
199 131 " 'background': 'yellow'}) # Fill the container yellow\n",
200 132 "\n",
201 133 "label = widgets.StringWidget(default_view_name=\"LabelView\", parent=container)\n",
202 134 "label.value = \"<strong>ALERT: </strong> Hello World!\"\n",
203 135 "\n",
204 136 "display(container)"
205 137 ],
206 138 "language": "python",
207 139 "metadata": {},
208 140 "outputs": [],
209 141 "prompt_number": 4
210 142 },
211 143 {
212 144 "cell_type": "heading",
213 145 "level": 1,
214 146 "metadata": {},
215 147 "source": [
216 148 "DOM Classes"
217 149 ]
218 150 },
219 151 {
220 152 "cell_type": "markdown",
221 153 "metadata": {},
222 154 "source": [
223 155 "In some cases it's necessary to apply DOM classes to your widgets. DOM classes allow DOM elements to be indentified by Javascript and CSS. The notebook defines its own set of classes to stylize its elements. The `add_class` widget method allows you to add DOM classes to your widget's definition. The `add_class` method's doc string can be seen below."
224 156 ]
225 157 },
226 158 {
227 159 "cell_type": "code",
228 160 "collapsed": false,
229 161 "input": [
230 162 "print(widgets.Widget.add_class.__doc__)"
231 163 ],
232 164 "language": "python",
233 165 "metadata": {},
234 166 "outputs": [
235 167 {
236 168 "output_type": "stream",
237 169 "stream": "stdout",
238 170 "text": [
239 171 "Add class[es] to a DOM element\n",
240 172 "\n",
241 173 " Parameters\n",
242 174 " ----------\n",
243 175 " class_name: unicode\n",
244 176 " Class name(s) to add to the DOM element(s). Multiple class names \n",
245 177 " must be space separated.\n",
246 178 " selector: unicode (optional)\n",
247 179 " JQuery selector to select the DOM element(s) that the class(es) will \n",
248 180 " be added to.\n",
249 181 " \n"
250 182 ]
251 183 }
252 184 ],
253 185 "prompt_number": 5
254 186 },
255 187 {
256 188 "cell_type": "markdown",
257 189 "metadata": {},
258 190 "source": [
259 191 "Since `add_class` if a DOM operation, it will only affect widgets that have been displayed. `add_class` must be called after the widget has been displayed. Extending the example above, the corners of the container can be rounded by adding the `corner-all` notebook class to the container (as seen below). "
260 192 ]
261 193 },
262 194 {
263 195 "cell_type": "code",
264 196 "collapsed": false,
265 197 "input": [
266 198 "container = widgets.ContainerWidget()\n",
267 199 "container.set_css({'border': '3px solid black',\n",
268 200 " 'padding': '6px',\n",
269 201 " 'background': 'yellow'}) \n",
270 202 "\n",
271 203 "label = widgets.StringWidget(default_view_name=\"LabelView\", parent=container) \n",
272 204 "label.value = \"<strong>ALERT: </strong> Hello World!\"\n",
273 205 "\n",
274 206 "display(container)\n",
275 207 "container.add_class('corner-all') # Must be called AFTER display"
276 208 ],
277 209 "language": "python",
278 210 "metadata": {},
279 211 "outputs": [],
280 212 "prompt_number": 6
281 213 },
282 214 {
283 215 "cell_type": "markdown",
284 216 "metadata": {},
285 217 "source": [
286 218 "The IPython notebook uses bootstrap for styling. The example above can be simplified by using a bootstrap class (as seen below). Bootstrap documentation can be found at http://getbootstrap.com/\u200e ."
287 219 ]
288 220 },
289 221 {
290 222 "cell_type": "code",
291 223 "collapsed": false,
292 224 "input": [
293 225 "label = widgets.StringWidget(value = \"<strong>ALERT: </strong> Hello World!\")\n",
294 226 "display(label, view_name=\"LabelView\")\n",
295 227 "\n",
296 228 "# Apply twitter bootstrap alert class to the label.\n",
297 229 "label.add_class(\"alert\")"
298 230 ],
299 231 "language": "python",
300 232 "metadata": {},
301 233 "outputs": [],
302 234 "prompt_number": 7
303 235 },
304 236 {
305 237 "cell_type": "markdown",
306 238 "metadata": {},
307 239 "source": [
308 240 "The example below shows how bootstrap classes can be used to change button apearance."
309 241 ]
310 242 },
311 243 {
312 244 "cell_type": "code",
313 245 "collapsed": false,
314 246 "input": [
315 247 "# List of the bootstrap button styles\n",
316 248 "button_classes = ['Default', 'btn-primary', 'btn-info', 'btn-success', \n",
317 249 " 'btn-warning', 'btn-danger', 'btn-inverse', 'btn-link']\n",
318 250 "\n",
319 251 "# Create each button and apply the style. Also add margin to the buttons so they space\n",
320 252 "# themselves nicely.\n",
321 253 "for i in range(8):\n",
322 254 " button = widgets.ButtonWidget(description=button_classes[i])\n",
323 255 " button.set_css(\"margin\", \"5px\")\n",
324 256 " display(button)\n",
325 257 " if i > 0: # Don't add a class the first button.\n",
326 258 " button.add_class(button_classes[i])\n",
327 259 " "
328 260 ],
329 261 "language": "python",
330 262 "metadata": {},
331 263 "outputs": [],
332 264 "prompt_number": 8
333 265 },
334 266 {
335 267 "cell_type": "markdown",
336 268 "metadata": {},
337 269 "source": [
338 270 "It's also useful to be able to remove DOM classes from widgets. The `remove_class` widget method allows you to remove classes from widgets that have been displayed. Like `add_widget`, it must be called after the widget has been displayed. The doc string of `remove_class` can be seen below."
339 271 ]
340 272 },
341 273 {
342 274 "cell_type": "code",
343 275 "collapsed": false,
344 276 "input": [
345 277 "print(widgets.Widget.remove_class.__doc__)"
346 278 ],
347 279 "language": "python",
348 280 "metadata": {},
349 281 "outputs": [
350 282 {
351 283 "output_type": "stream",
352 284 "stream": "stdout",
353 285 "text": [
354 286 "Remove class[es] from a DOM element\n",
355 287 "\n",
356 288 " Parameters\n",
357 289 " ----------\n",
358 290 " class_name: unicode\n",
359 291 " Class name(s) to remove from the DOM element(s). Multiple class \n",
360 292 " names must be space separated.\n",
361 293 " selector: unicode (optional)\n",
362 294 " JQuery selector to select the DOM element(s) that the class(es) will \n",
363 295 " be removed from.\n",
364 296 " \n"
365 297 ]
366 298 }
367 299 ],
368 300 "prompt_number": 9
369 301 },
370 302 {
371 303 "cell_type": "markdown",
372 304 "metadata": {},
373 305 "source": [
374 306 "The example below animates an alert using different bootstrap styles."
375 307 ]
376 308 },
377 309 {
378 310 "cell_type": "code",
379 311 "collapsed": false,
380 312 "input": [
381 313 "import time\n",
382 314 "label = widgets.StringWidget(value = \"<strong>ALERT: </strong> Hello World!\")\n",
383 315 "display(label, view_name=\"LabelView\")\n",
384 316 "\n",
385 317 "# Apply twitter bootstrap alert class to the label.\n",
386 318 "label.add_class(\"alert\")\n",
387 319 "\n",
388 320 "# Animate through additional bootstrap label styles 3 times\n",
389 321 "additional_alert_styles = ['alert-error', 'alert-info', 'alert-success']\n",
390 322 "for i in range(3 * len(additional_alert_styles)):\n",
391 323 " label.add_class(additional_alert_styles[i % 3])\n",
392 324 " label.remove_class(additional_alert_styles[(i-1) % 3])\n",
393 325 " time.sleep(1)\n",
394 326 " "
395 327 ],
396 328 "language": "python",
397 329 "metadata": {},
398 330 "outputs": [],
399 331 "prompt_number": 10
400 332 }
401 333 ],
402 334 "metadata": {}
403 335 }
404 336 ]
405 337 } No newline at end of file
@@ -1,378 +1,304 b''
1 1 {
2 2 "metadata": {
3 3 "cell_tags": [
4 4 [
5 5 "<None>",
6 6 null
7 7 ]
8 8 ],
9 9 "name": ""
10 10 },
11 11 "nbformat": 3,
12 12 "nbformat_minor": 0,
13 13 "worksheets": [
14 14 {
15 15 "cells": [
16 16 {
17 17 "cell_type": "code",
18 18 "collapsed": false,
19 19 "input": [
20 20 "from IPython.html import widgets # Widget definitions\n",
21 "from IPython.display import display # Used to display widgets in the notebook\n",
22 "\n",
23 "# Enable widgets in this notebook\n",
24 "widgets.init_widget_js()"
21 "from IPython.display import display # Used to display widgets in the notebook"
25 22 ],
26 23 "language": "python",
27 24 "metadata": {},
28 "outputs": [
29 {
30 "javascript": [
31 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/button.js\");"
32 ],
33 "metadata": {},
34 "output_type": "display_data"
35 },
36 {
37 "javascript": [
38 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/int_range.js\");"
39 ],
40 "metadata": {},
41 "output_type": "display_data"
42 },
43 {
44 "javascript": [
45 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/string.js\");"
46 ],
47 "metadata": {},
48 "output_type": "display_data"
49 },
50 {
51 "javascript": [
52 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/multicontainer.js\");"
53 ],
54 "metadata": {},
55 "output_type": "display_data"
56 },
57 {
58 "javascript": [
59 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/bool.js\");"
60 ],
61 "metadata": {},
62 "output_type": "display_data"
63 },
64 {
65 "javascript": [
66 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/int.js\");"
67 ],
68 "metadata": {},
69 "output_type": "display_data"
70 },
71 {
72 "javascript": [
73 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/selection.js\");"
74 ],
75 "metadata": {},
76 "output_type": "display_data"
77 },
78 {
79 "javascript": [
80 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/float.js\");"
81 ],
82 "metadata": {},
83 "output_type": "display_data"
84 },
85 {
86 "javascript": [
87 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/float_range.js\");"
88 ],
89 "metadata": {},
90 "output_type": "display_data"
91 },
92 {
93 "javascript": [
94 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/container.js\");"
95 ],
96 "metadata": {},
97 "output_type": "display_data"
98 }
99 ],
25 "outputs": [],
100 26 "prompt_number": 1
101 27 },
102 28 {
103 29 "cell_type": "heading",
104 30 "level": 1,
105 31 "metadata": {},
106 32 "source": [
107 33 "Alignment"
108 34 ]
109 35 },
110 36 {
111 37 "cell_type": "markdown",
112 38 "metadata": {},
113 39 "source": [
114 40 "Most widgets have a `description` property which allows a label for the widget to be defined. The label of the widget has a fixed minimum width. The text of the label is always right aligned and the widget is left aligned (as seen below) "
115 41 ]
116 42 },
117 43 {
118 44 "cell_type": "code",
119 45 "collapsed": false,
120 46 "input": [
121 47 "display(widgets.StringWidget(description=\"a:\"))\n",
122 48 "display(widgets.StringWidget(description=\"aa:\"))\n",
123 49 "display(widgets.StringWidget(description=\"aaa:\"))"
124 50 ],
125 51 "language": "python",
126 52 "metadata": {},
127 53 "outputs": [],
128 54 "prompt_number": 2
129 55 },
130 56 {
131 57 "cell_type": "markdown",
132 58 "metadata": {},
133 59 "source": [
134 60 "If a label is longer than the minimum width, the widget is shifted to the right (as seen below)."
135 61 ]
136 62 },
137 63 {
138 64 "cell_type": "code",
139 65 "collapsed": false,
140 66 "input": [
141 67 "display(widgets.StringWidget(description=\"a:\"))\n",
142 68 "display(widgets.StringWidget(description=\"aa:\"))\n",
143 69 "display(widgets.StringWidget(description=\"aaa:\"))\n",
144 70 "display(widgets.StringWidget(description=\"aaaaaaaaaaaaaaaaaa:\"))"
145 71 ],
146 72 "language": "python",
147 73 "metadata": {},
148 74 "outputs": [],
149 75 "prompt_number": 3
150 76 },
151 77 {
152 78 "cell_type": "markdown",
153 79 "metadata": {},
154 80 "source": [
155 81 "If a `description` is not set for the widget, the label is not displayed (as seen below)."
156 82 ]
157 83 },
158 84 {
159 85 "cell_type": "code",
160 86 "collapsed": false,
161 87 "input": [
162 88 "display(widgets.StringWidget(description=\"a:\"))\n",
163 89 "display(widgets.StringWidget(description=\"aa:\"))\n",
164 90 "display(widgets.StringWidget(description=\"aaa:\"))\n",
165 91 "display(widgets.StringWidget())"
166 92 ],
167 93 "language": "python",
168 94 "metadata": {},
169 95 "outputs": [],
170 96 "prompt_number": 4
171 97 },
172 98 {
173 99 "cell_type": "heading",
174 100 "level": 1,
175 101 "metadata": {},
176 102 "source": [
177 103 "Custom Alignment"
178 104 ]
179 105 },
180 106 {
181 107 "cell_type": "markdown",
182 108 "metadata": {},
183 109 "source": [
184 110 "`ContainerWidget`s allow for custom alignment of widgets. The `hbox` and `vbox` methods (parameterless) cause the `ContainerWidget` to both horizontally and vertically align its children. The following example compares `vbox` to `hbox`."
185 111 ]
186 112 },
187 113 {
188 114 "cell_type": "code",
189 115 "collapsed": false,
190 116 "input": [
191 117 "child_style = {\n",
192 118 " 'background': '#77CC77',\n",
193 119 " 'padding': '25px',\n",
194 120 " 'margin': '5px',\n",
195 121 " 'font-size': 'xx-large',\n",
196 122 " 'color': 'white',\n",
197 123 "}\n",
198 124 "\n",
199 125 "def make_container(title):\n",
200 126 " display(widgets.StringWidget(default_view_name='LabelView', value='<h2><br>' + title + '</h2>'))\n",
201 127 " container = widgets.ContainerWidget()\n",
202 128 " container.set_css('background', '#999999')\n",
203 129 " display(container)\n",
204 130 " return container\n",
205 131 "\n",
206 132 "def fill_container(container):\n",
207 133 " components = []\n",
208 134 " for i in range(3):\n",
209 135 " components.append(widgets.StringWidget(parent=container, default_view_name='LabelView', value=\"ABC\"[i]))\n",
210 136 " components[i].set_css(child_style)\n",
211 137 " display(components[i])\n",
212 138 " \n",
213 139 "container = make_container('VBox')\n",
214 140 "container.vbox()\n",
215 141 "fill_container(container)\n",
216 142 "\n",
217 143 "container = make_container('HBox')\n",
218 144 "container.hbox()\n",
219 145 "fill_container(container)\n"
220 146 ],
221 147 "language": "python",
222 148 "metadata": {},
223 149 "outputs": [],
224 150 "prompt_number": 5
225 151 },
226 152 {
227 153 "cell_type": "markdown",
228 154 "metadata": {},
229 155 "source": [
230 156 "The `ContainerWidget` `pack_start`, `pack_center`, and `pack_end` methods (parameterless) adjust the alignment of the widgets on the axis that they are being rendered on. Below is an example of the different alignments."
231 157 ]
232 158 },
233 159 {
234 160 "cell_type": "code",
235 161 "collapsed": false,
236 162 "input": [
237 163 "container = make_container('HBox Pack Start')\n",
238 164 "container.hbox()\n",
239 165 "container.pack_start()\n",
240 166 "fill_container(container)\n",
241 167 " \n",
242 168 "container = make_container('HBox Pack Center')\n",
243 169 "container.hbox()\n",
244 170 "container.pack_center()\n",
245 171 "fill_container(container)\n",
246 172 " \n",
247 173 "container = make_container('HBox Pack End')\n",
248 174 "container.hbox()\n",
249 175 "container.pack_end()\n",
250 176 "fill_container(container)"
251 177 ],
252 178 "language": "python",
253 179 "metadata": {},
254 180 "outputs": [],
255 181 "prompt_number": 6
256 182 },
257 183 {
258 184 "cell_type": "markdown",
259 185 "metadata": {},
260 186 "source": [
261 187 "The `ContainerWidget` `flex0`, `flex1`, and `flex2` methods (parameterless) modify the containers flexibility. Changing a container flexibility affects how and if the container will occupy the remaining space. Setting `flex0` has the same result as setting no flex. Below is an example of different flex configurations. The number on the boxes correspond to the applied flex."
262 188 ]
263 189 },
264 190 {
265 191 "cell_type": "code",
266 192 "collapsed": false,
267 193 "input": [
268 194 "def fill_container(container, flexes):\n",
269 195 " components = []\n",
270 196 " for i in range(len(flexes)):\n",
271 197 " components.append(widgets.ContainerWidget(parent=container))\n",
272 198 " components[i].set_css(child_style)\n",
273 199 " \n",
274 200 " label = widgets.StringWidget(parent=components[i], default_view_name='LabelView', value=str(flexes[i]))\n",
275 201 " \n",
276 202 " if flexes[i] == 0:\n",
277 203 " components[i].flex0()\n",
278 204 " elif flexes[i] == 1:\n",
279 205 " components[i].flex1()\n",
280 206 " elif flexes[i] == 2:\n",
281 207 " components[i].flex2()\n",
282 208 " display(components[i])\n",
283 209 " \n",
284 210 "container = make_container('Different Flex Configurations')\n",
285 211 "container.hbox()\n",
286 212 "fill_container(container, [0, 0, 0])\n",
287 213 " \n",
288 214 "container = make_container('')\n",
289 215 "container.hbox()\n",
290 216 "fill_container(container, [0, 0, 1])\n",
291 217 " \n",
292 218 "container = make_container('')\n",
293 219 "container.hbox()\n",
294 220 "fill_container(container, [0, 1, 1])\n",
295 221 " \n",
296 222 "container = make_container('')\n",
297 223 "container.hbox()\n",
298 224 "fill_container(container, [0, 2, 2])\n",
299 225 " \n",
300 226 "container = make_container('')\n",
301 227 "container.hbox()\n",
302 228 "fill_container(container, [0, 1, 2])\n",
303 229 " \n",
304 230 "container = make_container('')\n",
305 231 "container.hbox()\n",
306 232 "fill_container(container, [1, 1, 2])"
307 233 ],
308 234 "language": "python",
309 235 "metadata": {},
310 236 "outputs": [],
311 237 "prompt_number": 7
312 238 },
313 239 {
314 240 "cell_type": "markdown",
315 241 "metadata": {},
316 242 "source": [
317 243 "The `ContainerWidget` `align_start`, `align_center`, and `align_end` methods (parameterless) adjust the alignment of the widgets on the axis perpindicular to the one that they are being rendered on. Below is an example of the different alignments."
318 244 ]
319 245 },
320 246 {
321 247 "cell_type": "code",
322 248 "collapsed": false,
323 249 "input": [
324 250 "def fill_container(container):\n",
325 251 " components = []\n",
326 252 " for i in range(3):\n",
327 253 " components.append(widgets.StringWidget(parent=container, default_view_name='LabelView', value=\"ABC\"[i]))\n",
328 254 " components[i].set_css(child_style)\n",
329 255 " components[i].set_css('height', str((i+1) * 50) + 'px')\n",
330 256 " display(components[i])\n",
331 257 "\n",
332 258 "container = make_container('HBox Align Start')\n",
333 259 "container.hbox()\n",
334 260 "container.align_start()\n",
335 261 "fill_container(container)\n",
336 262 " \n",
337 263 "container = make_container('HBox Align Center')\n",
338 264 "container.hbox()\n",
339 265 "container.align_center()\n",
340 266 "fill_container(container)\n",
341 267 " \n",
342 268 "container = make_container('HBox Align End')\n",
343 269 "container.hbox()\n",
344 270 "container.align_end()\n",
345 271 "fill_container(container)"
346 272 ],
347 273 "language": "python",
348 274 "metadata": {},
349 275 "outputs": [],
350 276 "prompt_number": 8
351 277 },
352 278 {
353 279 "cell_type": "markdown",
354 280 "metadata": {},
355 281 "source": [
356 282 "By default the widget area is a `vbox`; however, there are many uses for a `hbox`. The example below uses a `hbox` to display a set of vertical sliders, like an equalizer."
357 283 ]
358 284 },
359 285 {
360 286 "cell_type": "code",
361 287 "collapsed": false,
362 288 "input": [
363 289 "container = widgets.ContainerWidget()\n",
364 290 "container.hbox()\n",
365 291 "for i in range(15):\n",
366 292 " widgets.FloatRangeWidget(orientation='vertical', parent=container, description=str(i+1), value=50.0)\n",
367 293 "display(container)"
368 294 ],
369 295 "language": "python",
370 296 "metadata": {},
371 297 "outputs": [],
372 298 "prompt_number": 9
373 299 }
374 300 ],
375 301 "metadata": {}
376 302 }
377 303 ]
378 304 } No newline at end of file
@@ -1,1278 +1,1203 b''
1 1 {
2 2 "metadata": {
3 3 "cell_tags": [
4 4 [
5 5 "<None>",
6 6 null
7 7 ]
8 8 ],
9 9 "name": ""
10 10 },
11 11 "nbformat": 3,
12 12 "nbformat_minor": 0,
13 13 "worksheets": [
14 14 {
15 15 "cells": [
16 16 {
17 17 "cell_type": "markdown",
18 18 "metadata": {},
19 19 "source": [
20 20 "Before reading, the author recommends the reader to review\n",
21 21 "\n",
22 22 "- [MVC prgramming](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller)\n",
23 23 "- [Backbone.js](https://www.codeschool.com/courses/anatomy-of-backbonejs)\n",
24 24 "- [The widget IPEP](https://github.com/ipython/ipython/wiki/IPEP-23%3A-Backbone.js-Widgets)\n",
25 25 "- [The original widget PR discussion](https://github.com/ipython/ipython/pull/4374)"
26 26 ]
27 27 },
28 28 {
29 29 "cell_type": "code",
30 30 "collapsed": false,
31 31 "input": [
32 32 "from IPython.html import widgets # Widget definitions\n",
33 "from IPython.display import display # Used to display widgets in the notebook\n",
34 "\n",
35 "# Enable widgets in this notebook\n",
36 "widgets.init_widget_js()"
33 "from IPython.display import display # Used to display widgets in the notebook"
37 34 ],
38 35 "language": "python",
39 36 "metadata": {},
40 "outputs": [
41 {
42 "javascript": [
43 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/button.js\");"
44 ],
45 "metadata": {},
46 "output_type": "display_data"
47 },
48 {
49 "javascript": [
50 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/int_range.js\");"
51 ],
52 "metadata": {},
53 "output_type": "display_data"
54 },
55 {
56 "javascript": [
57 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/string.js\");"
58 ],
59 "metadata": {},
60 "output_type": "display_data"
61 },
62 {
63 "javascript": [
64 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/multicontainer.js\");"
65 ],
66 "metadata": {},
67 "output_type": "display_data"
68 },
69 {
70 "javascript": [
71 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/bool.js\");"
72 ],
73 "metadata": {},
74 "output_type": "display_data"
75 },
76 {
77 "javascript": [
78 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/int.js\");"
79 ],
80 "metadata": {},
81 "output_type": "display_data"
82 },
83 {
84 "javascript": [
85 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/selection.js\");"
86 ],
87 "metadata": {},
88 "output_type": "display_data"
89 },
90 {
91 "javascript": [
92 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/float.js\");"
93 ],
94 "metadata": {},
95 "output_type": "display_data"
96 },
97 {
98 "javascript": [
99 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/float_range.js\");"
100 ],
101 "metadata": {},
102 "output_type": "display_data"
103 },
104 {
105 "javascript": [
106 "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/container.js\");"
107 ],
108 "metadata": {},
109 "output_type": "display_data"
110 }
111 ],
37 "outputs": [],
112 38 "prompt_number": 1
113 39 },
114 40 {
115 41 "cell_type": "heading",
116 42 "level": 1,
117 43 "metadata": {},
118 44 "source": [
119 45 "Abstract"
120 46 ]
121 47 },
122 48 {
123 49 "cell_type": "markdown",
124 50 "metadata": {},
125 51 "source": [
126 52 "This notebook implements a custom date picker widget. The purpose of this notebook is to demonstrate the widget creation process. To create a custom widget, custom Python and JavaScript is required."
127 53 ]
128 54 },
129 55 {
130 56 "cell_type": "heading",
131 57 "level": 1,
132 58 "metadata": {},
133 59 "source": [
134 60 "Section 1 - Basics"
135 61 ]
136 62 },
137 63 {
138 64 "cell_type": "heading",
139 65 "level": 2,
140 66 "metadata": {},
141 67 "source": [
142 68 "Python"
143 69 ]
144 70 },
145 71 {
146 72 "cell_type": "markdown",
147 73 "metadata": {},
148 74 "source": [
149 75 "When starting a project like this, it is often easiest to make an overly simplified base to verify that the underlying framework is working as expected. To start we will create an empty widget and make sure that it can be rendered. The first step is to create the widget in Python."
150 76 ]
151 77 },
152 78 {
153 79 "cell_type": "code",
154 80 "collapsed": false,
155 81 "input": [
156 82 "# Import the base Widget class and the traitlets Unicode class.\n",
157 83 "from IPython.html.widgets import Widget\n",
158 84 "from IPython.utils.traitlets import Unicode\n",
159 85 "\n",
160 86 "# Define our DateWidget and its target model and default view.\n",
161 87 "class DateWidget(Widget):\n",
162 88 " target_name = Unicode('DateWidgetModel')\n",
163 89 " default_view_name = Unicode('DatePickerView')"
164 90 ],
165 91 "language": "python",
166 92 "metadata": {},
167 93 "outputs": [],
168 94 "prompt_number": 2
169 95 },
170 96 {
171 97 "cell_type": "markdown",
172 98 "metadata": {},
173 99 "source": [
174 100 "- **target_name** is a special `Widget` property that tells the widget framework which Backbone model in the front-end corresponds to this widget.\n",
175 101 "- **default_view_name** is the default Backbone view to display when the user calls `display` to display an instance of this widget.\n"
176 102 ]
177 103 },
178 104 {
179 105 "cell_type": "heading",
180 106 "level": 2,
181 107 "metadata": {},
182 108 "source": [
183 109 "JavaScript"
184 110 ]
185 111 },
186 112 {
187 113 "cell_type": "markdown",
188 114 "metadata": {},
189 115 "source": [
190 116 "In the IPython notebook [require.js](http://requirejs.org/) is used to load JavaScript dependencies. All IPython widget code depends on `notebook/js/widget.js`. In it the base widget model, base view, and widget manager are defined. We need to use require.js to include this file:"
191 117 ]
192 118 },
193 119 {
194 120 "cell_type": "code",
195 121 "collapsed": false,
196 122 "input": [
197 123 "%%javascript\n",
198 124 "\n",
199 125 "require([\"notebook/js/widget\"], function(){\n",
200 126 "\n",
201 127 "});"
202 128 ],
203 129 "language": "python",
204 130 "metadata": {},
205 131 "outputs": [
206 132 {
207 133 "javascript": [
208 134 "\n",
209 135 "require([\"notebook/js/widget\"], function(){\n",
210 136 "\n",
211 137 "});"
212 138 ],
213 139 "metadata": {},
214 140 "output_type": "display_data",
215 141 "text": [
216 "<IPython.core.display.Javascript at 0x168b0d0>"
142 "<IPython.core.display.Javascript at 0x21f8f10>"
217 143 ]
218 144 }
219 145 ],
220 146 "prompt_number": 3
221 147 },
222 148 {
223 149 "cell_type": "markdown",
224 150 "metadata": {},
225 151 "source": [
226 152 "The next step is to add a definition for the widget's model. It's important to extend the `IPython.WidgetModel` which extends the Backbone.js base model instead of trying to extend the Backbone.js base model directly. After defining the model, it needs to be registed with the widget manager using the `target_name` used in the Python code."
227 153 ]
228 154 },
229 155 {
230 156 "cell_type": "code",
231 157 "collapsed": false,
232 158 "input": [
233 159 "%%javascript\n",
234 160 "\n",
235 161 "require([\"notebook/js/widget\"], function(){\n",
236 162 " \n",
237 163 " // Define the DateModel and register it with the widget manager.\n",
238 164 " var DateModel = IPython.WidgetModel.extend({});\n",
239 " IPython.notebook.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
165 " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
240 166 "});"
241 167 ],
242 168 "language": "python",
243 169 "metadata": {},
244 170 "outputs": [
245 171 {
246 172 "javascript": [
247 173 "\n",
248 174 "require([\"notebook/js/widget\"], function(){\n",
249 175 " \n",
250 176 " // Define the DateModel and register it with the widget manager.\n",
251 177 " var DateModel = IPython.WidgetModel.extend({});\n",
252 " IPython.notebook.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
178 " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
253 179 "});"
254 180 ],
255 181 "metadata": {},
256 182 "output_type": "display_data",
257 183 "text": [
258 "<IPython.core.display.Javascript at 0x168df50>"
184 "<IPython.core.display.Javascript at 0x21f8ed0>"
259 185 ]
260 186 }
261 187 ],
262 188 "prompt_number": 4
263 189 },
264 190 {
265 191 "cell_type": "markdown",
266 192 "metadata": {},
267 193 "source": [
268 194 "Now that the model is defined, we need to define a view that can be used to represent the model. To do this, the `IPython.WidgetView` is extended. A render function must be defined. The render function is used to render a widget view instance to the DOM. For now the render function renders a div that contains the text *Hello World!* Lastly, the view needs to be registered with the widget manager like the model was.\n",
269 195 "\n",
270 196 "**Final JavaScript code below:**"
271 197 ]
272 198 },
273 199 {
274 200 "cell_type": "code",
275 201 "collapsed": false,
276 202 "input": [
277 203 "%%javascript\n",
278 204 "\n",
279 205 "require([\"notebook/js/widget\"], function(){\n",
280 206 " \n",
281 207 " // Define the DateModel and register it with the widget manager.\n",
282 208 " var DateModel = IPython.WidgetModel.extend({});\n",
283 " IPython.notebook.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
209 " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
284 210 " \n",
285 211 " // Define the DatePickerView\n",
286 212 " var DatePickerView = IPython.WidgetView.extend({\n",
287 213 " \n",
288 214 " render: function(){\n",
289 215 " this.$el = $('<div />')\n",
290 216 " .html('Hello World!');\n",
291 217 " },\n",
292 218 " });\n",
293 219 " \n",
294 220 " // Register the DatePickerView with the widget manager.\n",
295 " IPython.notebook.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
221 " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
296 222 "});"
297 223 ],
298 224 "language": "python",
299 225 "metadata": {},
300 226 "outputs": [
301 227 {
302 228 "javascript": [
303 229 "\n",
304 230 "require([\"notebook/js/widget\"], function(){\n",
305 231 " \n",
306 232 " // Define the DateModel and register it with the widget manager.\n",
307 233 " var DateModel = IPython.WidgetModel.extend({});\n",
308 " IPython.notebook.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
234 " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
309 235 " \n",
310 236 " // Define the DatePickerView\n",
311 237 " var DatePickerView = IPython.WidgetView.extend({\n",
312 238 " \n",
313 239 " render: function(){\n",
314 240 " this.$el = $('<div />')\n",
315 241 " .html('Hello World!');\n",
316 242 " },\n",
317 243 " });\n",
318 244 " \n",
319 245 " // Register the DatePickerView with the widget manager.\n",
320 " IPython.notebook.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
246 " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
321 247 "});"
322 248 ],
323 249 "metadata": {},
324 250 "output_type": "display_data",
325 251 "text": [
326 "<IPython.core.display.Javascript at 0x168f050>"
252 "<IPython.core.display.Javascript at 0x21f8cd0>"
327 253 ]
328 254 }
329 255 ],
330 256 "prompt_number": 5
331 257 },
332 258 {
333 259 "cell_type": "heading",
334 260 "level": 2,
335 261 "metadata": {},
336 262 "source": [
337 263 "Test"
338 264 ]
339 265 },
340 266 {
341 267 "cell_type": "markdown",
342 268 "metadata": {},
343 269 "source": [
344 270 "To test, create the widget the same way that the other widgets are created."
345 271 ]
346 272 },
347 273 {
348 274 "cell_type": "code",
349 275 "collapsed": false,
350 276 "input": [
351 277 "my_widget = DateWidget()\n",
352 278 "display(my_widget)"
353 279 ],
354 280 "language": "python",
355 281 "metadata": {},
356 282 "outputs": [],
357 283 "prompt_number": 6
358 284 },
359 285 {
360 286 "cell_type": "heading",
361 287 "level": 1,
362 288 "metadata": {},
363 289 "source": [
364 290 "Section 2 - Something useful"
365 291 ]
366 292 },
367 293 {
368 294 "cell_type": "heading",
369 295 "level": 2,
370 296 "metadata": {},
371 297 "source": [
372 298 "Python"
373 299 ]
374 300 },
375 301 {
376 302 "cell_type": "markdown",
377 303 "metadata": {},
378 304 "source": [
379 305 "In the last section we created a simple widget that displayed *Hello World!* There was no custom state information associated with the widget. To make an actual date widget, we need to add a property that will be synced between the Python model and the JavaScript model. The new property must be a traitlet property so the widget machinery can automatically handle it. The property needs to be added to the the `_keys` list. The `_keys` list tells the widget machinery what traitlets should be synced with the front-end. Adding this to the code from the last section:"
380 306 ]
381 307 },
382 308 {
383 309 "cell_type": "code",
384 310 "collapsed": false,
385 311 "input": [
386 312 "# Import the base Widget class and the traitlets Unicode class.\n",
387 313 "from IPython.html.widgets import Widget\n",
388 314 "from IPython.utils.traitlets import Unicode\n",
389 315 "\n",
390 316 "# Define our DateWidget and its target model and default view.\n",
391 317 "class DateWidget(Widget):\n",
392 318 " target_name = Unicode('DateWidgetModel')\n",
393 319 " default_view_name = Unicode('DatePickerView')\n",
394 320 " \n",
395 321 " # Define the custom state properties to sync with the front-end\n",
396 322 " _keys = ['value']\n",
397 323 " value = Unicode()"
398 324 ],
399 325 "language": "python",
400 326 "metadata": {},
401 327 "outputs": [],
402 328 "prompt_number": 7
403 329 },
404 330 {
405 331 "cell_type": "heading",
406 332 "level": 2,
407 333 "metadata": {},
408 334 "source": [
409 335 "JavaScript"
410 336 ]
411 337 },
412 338 {
413 339 "cell_type": "markdown",
414 340 "metadata": {},
415 341 "source": [
416 342 "In the JavaScript there is no need to define the same properties in the JavaScript model. When the JavaScript model is created for the first time, it copies all of the attributes from the Python model. We need to replace *Hello World!* with an actual HTML date picker widget."
417 343 ]
418 344 },
419 345 {
420 346 "cell_type": "code",
421 347 "collapsed": false,
422 348 "input": [
423 349 "%%javascript\n",
424 350 "\n",
425 351 "require([\"notebook/js/widget\"], function(){\n",
426 352 " \n",
427 353 " // Define the DateModel and register it with the widget manager.\n",
428 354 " var DateModel = IPython.WidgetModel.extend({});\n",
429 " IPython.notebook.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
355 " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
430 356 " \n",
431 357 " // Define the DatePickerView\n",
432 358 " var DatePickerView = IPython.WidgetView.extend({\n",
433 359 " \n",
434 360 " render: function(){\n",
435 361 " \n",
436 362 " // Create a div to hold our widget.\n",
437 363 " this.$el = $('<div />');\n",
438 364 " \n",
439 365 " // Create the date picker control.\n",
440 366 " this.$date = $('<input />')\n",
441 367 " .attr('type', 'date');\n",
442 368 " },\n",
443 369 " });\n",
444 370 " \n",
445 371 " // Register the DatePickerView with the widget manager.\n",
446 " IPython.notebook.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
372 " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
447 373 "});"
448 374 ],
449 375 "language": "python",
450 376 "metadata": {},
451 377 "outputs": [
452 378 {
453 379 "javascript": [
454 380 "\n",
455 381 "require([\"notebook/js/widget\"], function(){\n",
456 382 " \n",
457 383 " // Define the DateModel and register it with the widget manager.\n",
458 384 " var DateModel = IPython.WidgetModel.extend({});\n",
459 " IPython.notebook.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
385 " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
460 386 " \n",
461 387 " // Define the DatePickerView\n",
462 388 " var DatePickerView = IPython.WidgetView.extend({\n",
463 389 " \n",
464 390 " render: function(){\n",
465 391 " \n",
466 392 " // Create a div to hold our widget.\n",
467 393 " this.$el = $('<div />');\n",
468 394 " \n",
469 395 " // Create the date picker control.\n",
470 396 " this.$date = $('<input />')\n",
471 397 " .attr('type', 'date');\n",
472 398 " },\n",
473 399 " });\n",
474 400 " \n",
475 401 " // Register the DatePickerView with the widget manager.\n",
476 " IPython.notebook.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
402 " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
477 403 "});"
478 404 ],
479 405 "metadata": {},
480 406 "output_type": "display_data",
481 407 "text": [
482 "<IPython.core.display.Javascript at 0x17822d0>"
408 "<IPython.core.display.Javascript at 0x21fc310>"
483 409 ]
484 410 }
485 411 ],
486 412 "prompt_number": 8
487 413 },
488 414 {
489 415 "cell_type": "markdown",
490 416 "metadata": {},
491 417 "source": [
492 418 "In order to get the HTML date picker to update itself with the value set in the back-end, we need to implement an `update()` method."
493 419 ]
494 420 },
495 421 {
496 422 "cell_type": "code",
497 423 "collapsed": false,
498 424 "input": [
499 425 "%%javascript\n",
500 426 "\n",
501 427 "require([\"notebook/js/widget\"], function(){\n",
502 428 " \n",
503 429 " // Define the DateModel and register it with the widget manager.\n",
504 430 " var DateModel = IPython.WidgetModel.extend({});\n",
505 " IPython.notebook.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
431 " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
506 432 " \n",
507 433 " // Define the DatePickerView\n",
508 434 " var DatePickerView = IPython.WidgetView.extend({\n",
509 435 " \n",
510 436 " render: function(){\n",
511 437 " \n",
512 438 " // Create a div to hold our widget.\n",
513 439 " this.$el = $('<div />');\n",
514 440 " \n",
515 441 " // Create the date picker control.\n",
516 442 " this.$date = $('<input />')\n",
517 443 " .attr('type', 'date');\n",
518 444 " },\n",
519 445 " \n",
520 446 " update: function() {\n",
521 447 " \n",
522 448 " // Set the value of the date control and then call base.\n",
523 449 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
524 450 " return IPython.WidgetView.prototype.update.call(this);\n",
525 451 " },\n",
526 452 " });\n",
527 453 " \n",
528 454 " // Register the DatePickerView with the widget manager.\n",
529 " IPython.notebook.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
455 " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
530 456 "});"
531 457 ],
532 458 "language": "python",
533 459 "metadata": {},
534 460 "outputs": [
535 461 {
536 462 "javascript": [
537 463 "\n",
538 464 "require([\"notebook/js/widget\"], function(){\n",
539 465 " \n",
540 466 " // Define the DateModel and register it with the widget manager.\n",
541 467 " var DateModel = IPython.WidgetModel.extend({});\n",
542 " IPython.notebook.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
468 " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
543 469 " \n",
544 470 " // Define the DatePickerView\n",
545 471 " var DatePickerView = IPython.WidgetView.extend({\n",
546 472 " \n",
547 473 " render: function(){\n",
548 474 " \n",
549 475 " // Create a div to hold our widget.\n",
550 476 " this.$el = $('<div />');\n",
551 477 " \n",
552 478 " // Create the date picker control.\n",
553 479 " this.$date = $('<input />')\n",
554 480 " .attr('type', 'date');\n",
555 481 " },\n",
556 482 " \n",
557 483 " update: function() {\n",
558 484 " \n",
559 485 " // Set the value of the date control and then call base.\n",
560 486 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
561 487 " return IPython.WidgetView.prototype.update.call(this);\n",
562 488 " },\n",
563 489 " });\n",
564 490 " \n",
565 491 " // Register the DatePickerView with the widget manager.\n",
566 " IPython.notebook.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
492 " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
567 493 "});"
568 494 ],
569 495 "metadata": {},
570 496 "output_type": "display_data",
571 497 "text": [
572 "<IPython.core.display.Javascript at 0x1782390>"
498 "<IPython.core.display.Javascript at 0x21fc290>"
573 499 ]
574 500 }
575 501 ],
576 502 "prompt_number": 9
577 503 },
578 504 {
579 505 "cell_type": "markdown",
580 506 "metadata": {},
581 507 "source": [
582 508 "To get the changed value from the front-end to publish itself to the back-end, we need to listen to the change event triggered by the HTM date control and set the value in the model. By setting the `this.$el` property of the view, we break the Backbone powered event handling. To fix this, a call to `this.delegateEvents()` must be added after `this.$el` is set. \n",
583 509 "\n",
584 510 "After the date change event fires and the new value is set in the model, it's very important that we call `update_other_views(this)` to make the other views on the page update and to let the widget machinery know which view changed the model. This is important because the widget machinery needs to know which cell to route the message callbacks to.\n",
585 511 "\n",
586 512 "**Final JavaScript code below:**"
587 513 ]
588 514 },
589 515 {
590 516 "cell_type": "code",
591 517 "collapsed": false,
592 518 "input": [
593 519 "%%javascript\n",
594 520 "\n",
595 521 "require([\"notebook/js/widget\"], function(){\n",
596 522 " \n",
597 523 " // Define the DateModel and register it with the widget manager.\n",
598 524 " var DateModel = IPython.WidgetModel.extend({});\n",
599 " IPython.notebook.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
525 " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
600 526 " \n",
601 527 " // Define the DatePickerView\n",
602 528 " var DatePickerView = IPython.WidgetView.extend({\n",
603 529 " \n",
604 530 " render: function(){\n",
605 531 " \n",
606 532 " // Create a div to hold our widget.\n",
607 533 " this.$el = $('<div />');\n",
608 534 " this.delegateEvents();\n",
609 535 " \n",
610 536 " // Create the date picker control.\n",
611 537 " this.$date = $('<input />')\n",
612 538 " .attr('type', 'date')\n",
613 539 " .appendTo(this.$el);\n",
614 540 " },\n",
615 541 " \n",
616 542 " update: function() {\n",
617 543 " \n",
618 544 " // Set the value of the date control and then call base.\n",
619 545 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
620 546 " return IPython.WidgetView.prototype.update.call(this);\n",
621 547 " },\n",
622 548 " \n",
623 549 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
624 550 " events: {\"change\": \"handle_date_change\"},\n",
625 551 " \n",
626 552 " // Callback for when the date is changed.\n",
627 553 " handle_date_change: function(event) {\n",
628 554 " this.model.set('value', this.$date.val());\n",
629 555 " this.model.update_other_views(this);\n",
630 556 " },\n",
631 557 " \n",
632 558 " });\n",
633 559 " \n",
634 560 " // Register the DatePickerView with the widget manager.\n",
635 " IPython.notebook.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
561 " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
636 562 "});"
637 563 ],
638 564 "language": "python",
639 565 "metadata": {},
640 566 "outputs": [
641 567 {
642 568 "javascript": [
643 569 "\n",
644 570 "require([\"notebook/js/widget\"], function(){\n",
645 571 " \n",
646 572 " // Define the DateModel and register it with the widget manager.\n",
647 573 " var DateModel = IPython.WidgetModel.extend({});\n",
648 " IPython.notebook.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
574 " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
649 575 " \n",
650 576 " // Define the DatePickerView\n",
651 577 " var DatePickerView = IPython.WidgetView.extend({\n",
652 578 " \n",
653 579 " render: function(){\n",
654 580 " \n",
655 581 " // Create a div to hold our widget.\n",
656 582 " this.$el = $('<div />');\n",
657 583 " this.delegateEvents();\n",
658 584 " \n",
659 585 " // Create the date picker control.\n",
660 586 " this.$date = $('<input />')\n",
661 587 " .attr('type', 'date')\n",
662 588 " .appendTo(this.$el);\n",
663 589 " },\n",
664 590 " \n",
665 591 " update: function() {\n",
666 592 " \n",
667 593 " // Set the value of the date control and then call base.\n",
668 594 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
669 595 " return IPython.WidgetView.prototype.update.call(this);\n",
670 596 " },\n",
671 597 " \n",
672 598 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
673 599 " events: {\"change\": \"handle_date_change\"},\n",
674 600 " \n",
675 601 " // Callback for when the date is changed.\n",
676 602 " handle_date_change: function(event) {\n",
677 603 " this.model.set('value', this.$date.val());\n",
678 604 " this.model.update_other_views(this);\n",
679 605 " },\n",
680 606 " \n",
681 607 " });\n",
682 608 " \n",
683 609 " // Register the DatePickerView with the widget manager.\n",
684 " IPython.notebook.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
610 " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
685 611 "});"
686 612 ],
687 613 "metadata": {},
688 614 "output_type": "display_data",
689 615 "text": [
690 "<IPython.core.display.Javascript at 0x17821d0>"
616 "<IPython.core.display.Javascript at 0x21fc3d0>"
691 617 ]
692 618 }
693 619 ],
694 620 "prompt_number": 10
695 621 },
696 622 {
697 623 "cell_type": "heading",
698 624 "level": 2,
699 625 "metadata": {},
700 626 "source": [
701 627 "Test"
702 628 ]
703 629 },
704 630 {
705 631 "cell_type": "markdown",
706 632 "metadata": {},
707 633 "source": [
708 634 "To test, create the widget the same way that the other widgets are created."
709 635 ]
710 636 },
711 637 {
712 638 "cell_type": "code",
713 639 "collapsed": false,
714 640 "input": [
715 641 "my_widget = DateWidget()\n",
716 642 "display(my_widget)"
717 643 ],
718 644 "language": "python",
719 645 "metadata": {},
720 646 "outputs": [],
721 647 "prompt_number": 11
722 648 },
723 649 {
724 650 "cell_type": "markdown",
725 651 "metadata": {},
726 652 "source": [
727 653 "Display the widget again to make sure that both views remain in sync."
728 654 ]
729 655 },
730 656 {
731 657 "cell_type": "code",
732 658 "collapsed": false,
733 659 "input": [
734 660 "display(my_widget)"
735 661 ],
736 662 "language": "python",
737 663 "metadata": {},
738 664 "outputs": [],
739 665 "prompt_number": 12
740 666 },
741 667 {
742 668 "cell_type": "markdown",
743 669 "metadata": {},
744 670 "source": [
745 671 "Read the date from Python"
746 672 ]
747 673 },
748 674 {
749 675 "cell_type": "code",
750 676 "collapsed": false,
751 677 "input": [
752 678 "my_widget.value"
753 679 ],
754 680 "language": "python",
755 681 "metadata": {},
756 682 "outputs": [
757 683 {
758 684 "metadata": {},
759 685 "output_type": "pyout",
760 686 "prompt_number": 13,
761 687 "text": [
762 "u''"
688 "u'2013-11-14'"
763 689 ]
764 690 }
765 691 ],
766 692 "prompt_number": 13
767 693 },
768 694 {
769 695 "cell_type": "markdown",
770 696 "metadata": {},
771 697 "source": [
772 698 "Set the date from Python"
773 699 ]
774 700 },
775 701 {
776 702 "cell_type": "code",
777 703 "collapsed": false,
778 704 "input": [
779 705 "my_widget.value = \"1999-12-01\" # December 1st, 1999"
780 706 ],
781 707 "language": "python",
782 708 "metadata": {},
783 709 "outputs": [],
784 710 "prompt_number": 14
785 711 },
786 712 {
787 713 "cell_type": "heading",
788 714 "level": 1,
789 715 "metadata": {},
790 716 "source": [
791 717 "Section 3 - Extra credit"
792 718 ]
793 719 },
794 720 {
795 721 "cell_type": "markdown",
796 722 "metadata": {},
797 723 "source": [
798 724 "In the last section we created a fully working date picker widget. Now we will add custom validation and support for labels. Currently only the ISO date format \"YYYY-MM-DD\" is supported. We will add support for all of the date formats recognized by the 3rd party Python dateutil library."
799 725 ]
800 726 },
801 727 {
802 728 "cell_type": "heading",
803 729 "level": 2,
804 730 "metadata": {},
805 731 "source": [
806 732 "Python"
807 733 ]
808 734 },
809 735 {
810 736 "cell_type": "markdown",
811 737 "metadata": {},
812 738 "source": [
813 739 "The traitlet machinery searches the class that the trait is defined in for methods with \"`_changed`\" suffixed onto their names. Any method with the format \"`X_changed`\" will be called when \"`X`\" is modified. We can take advantage of this to perform validation and parsing of different date string formats. Below a method that listens to value has been added to the DateWidget."
814 740 ]
815 741 },
816 742 {
817 743 "cell_type": "code",
818 744 "collapsed": false,
819 745 "input": [
820 746 "# Import the base Widget class and the traitlets Unicode class.\n",
821 747 "from IPython.html.widgets import Widget\n",
822 748 "from IPython.utils.traitlets import Unicode\n",
823 749 "\n",
824 750 "# Define our DateWidget and its target model and default view.\n",
825 751 "class DateWidget(Widget):\n",
826 752 " target_name = Unicode('DateWidgetModel')\n",
827 753 " default_view_name = Unicode('DatePickerView')\n",
828 754 " \n",
829 755 " # Define the custom state properties to sync with the front-end\n",
830 756 " _keys = ['value']\n",
831 757 " value = Unicode()\n",
832 758 " \n",
833 759 " # This function automatically gets called by the traitlet machinery when\n",
834 760 " # value is modified because of this function's name.\n",
835 761 " def _value_changed(self, name, old_value, new_value):\n",
836 762 " pass\n",
837 763 " "
838 764 ],
839 765 "language": "python",
840 766 "metadata": {},
841 767 "outputs": [],
842 768 "prompt_number": 15
843 769 },
844 770 {
845 771 "cell_type": "markdown",
846 772 "metadata": {},
847 773 "source": [
848 774 "Now the function that parses the date string and only sets it in the correct format can be added."
849 775 ]
850 776 },
851 777 {
852 778 "cell_type": "code",
853 779 "collapsed": false,
854 780 "input": [
855 781 "# Import the dateutil library to parse date strings.\n",
856 782 "from dateutil import parser\n",
857 783 "\n",
858 784 "# Import the base Widget class and the traitlets Unicode class.\n",
859 785 "from IPython.html.widgets import Widget\n",
860 786 "from IPython.utils.traitlets import Unicode\n",
861 787 "\n",
862 788 "# Define our DateWidget and its target model and default view.\n",
863 789 "class DateWidget(Widget):\n",
864 790 " target_name = Unicode('DateWidgetModel')\n",
865 791 " default_view_name = Unicode('DatePickerView')\n",
866 792 " \n",
867 793 " # Define the custom state properties to sync with the front-end\n",
868 794 " _keys = ['value']\n",
869 795 " value = Unicode()\n",
870 796 " \n",
871 797 " # This function automatically gets called by the traitlet machinery when\n",
872 798 " # value is modified because of this function's name.\n",
873 799 " def _value_changed(self, name, old_value, new_value):\n",
874 800 " \n",
875 801 " # Parse the date time value.\n",
876 802 " try:\n",
877 803 " parsed_date = parser.parse(new_value)\n",
878 804 " parsed_date_string = parsed_date.strftime(\"%Y-%m-%d\")\n",
879 805 " except:\n",
880 806 " parsed_date_string = ''\n",
881 807 " \n",
882 808 " # Set the parsed date string if the current date string is different.\n",
883 809 " if self.value != parsed_date_string:\n",
884 810 " self.value = parsed_date_string"
885 811 ],
886 812 "language": "python",
887 813 "metadata": {},
888 814 "outputs": [],
889 815 "prompt_number": 16
890 816 },
891 817 {
892 818 "cell_type": "markdown",
893 819 "metadata": {},
894 820 "source": [
895 821 "The standard property name used for widget labels is `description`. In the code block below, `description` has been added to the Python widget."
896 822 ]
897 823 },
898 824 {
899 825 "cell_type": "code",
900 826 "collapsed": false,
901 827 "input": [
902 828 "# Import the dateutil library to parse date strings.\n",
903 829 "from dateutil import parser\n",
904 830 "\n",
905 831 "# Import the base Widget class and the traitlets Unicode class.\n",
906 832 "from IPython.html.widgets import Widget\n",
907 833 "from IPython.utils.traitlets import Unicode\n",
908 834 "\n",
909 835 "# Define our DateWidget and its target model and default view.\n",
910 836 "class DateWidget(Widget):\n",
911 837 " target_name = Unicode('DateWidgetModel')\n",
912 838 " default_view_name = Unicode('DatePickerView')\n",
913 839 " \n",
914 840 " # Define the custom state properties to sync with the front-end\n",
915 841 " _keys = ['value', 'description']\n",
916 842 " value = Unicode()\n",
917 843 " description = Unicode()\n",
918 844 " \n",
919 845 " # This function automatically gets called by the traitlet machinery when\n",
920 846 " # value is modified because of this function's name.\n",
921 847 " def _value_changed(self, name, old_value, new_value):\n",
922 848 " \n",
923 849 " # Parse the date time value.\n",
924 850 " try:\n",
925 851 " parsed_date = parser.parse(new_value)\n",
926 852 " parsed_date_string = parsed_date.strftime(\"%Y-%m-%d\")\n",
927 853 " except:\n",
928 854 " parsed_date_string = ''\n",
929 855 " \n",
930 856 " # Set the parsed date string if the current date string is different.\n",
931 857 " if self.value != parsed_date_string:\n",
932 858 " self.value = parsed_date_string"
933 859 ],
934 860 "language": "python",
935 861 "metadata": {},
936 862 "outputs": [],
937 863 "prompt_number": 17
938 864 },
939 865 {
940 866 "cell_type": "markdown",
941 867 "metadata": {},
942 868 "source": [
943 869 "Finally, a callback list is added so the user can perform custom validation. If any one of the callbacks returns False, the new date time is not set.\n",
944 870 "\n",
945 871 "**Final Python code below:**"
946 872 ]
947 873 },
948 874 {
949 875 "cell_type": "code",
950 876 "collapsed": false,
951 877 "input": [
952 878 "# Import the dateutil library to parse date strings.\n",
953 879 "from dateutil import parser\n",
954 880 "\n",
955 881 "# Import the base Widget class and the traitlets Unicode class.\n",
956 882 "from IPython.html.widgets import Widget\n",
957 883 "from IPython.utils.traitlets import Unicode\n",
958 884 "\n",
959 885 "# Define our DateWidget and its target model and default view.\n",
960 886 "class DateWidget(Widget):\n",
961 887 " target_name = Unicode('DateWidgetModel')\n",
962 888 " default_view_name = Unicode('DatePickerView')\n",
963 889 " \n",
964 890 " # Define the custom state properties to sync with the front-end\n",
965 891 " _keys = ['value', 'description']\n",
966 892 " value = Unicode()\n",
967 893 " description = Unicode()\n",
968 894 " \n",
969 895 " def __init__(self, **kwargs):\n",
970 896 " super(DateWidget, self).__init__(**kwargs)\n",
971 897 " self._validation_callbacks = []\n",
972 898 " \n",
973 899 " # This function automatically gets called by the traitlet machinery when\n",
974 900 " # value is modified because of this function's name.\n",
975 901 " def _value_changed(self, name, old_value, new_value):\n",
976 902 " \n",
977 903 " # Parse the date time value.\n",
978 904 " try:\n",
979 905 " parsed_date = parser.parse(new_value)\n",
980 906 " parsed_date_string = parsed_date.strftime(\"%Y-%m-%d\")\n",
981 907 " except:\n",
982 908 " parsed_date = None\n",
983 909 " parsed_date_string = ''\n",
984 910 " \n",
985 911 " # Set the parsed date string if the current date string is different.\n",
986 912 " if old_value != new_value:\n",
987 913 " if self.handle_validate(parsed_date):\n",
988 914 " self.value = parsed_date_string\n",
989 915 " else:\n",
990 916 " self.value = old_value\n",
991 917 " self.send_state() # The traitlet event won't fire since the value isn't changing.\n",
992 918 " # We need to force the back-end to send the front-end the state\n",
993 919 " # to make sure that the date control date doesn't change.\n",
994 920 " \n",
995 921 " \n",
996 922 " # Allow the user to register custom validation callbacks.\n",
997 923 " # callback(new value as a datetime object)\n",
998 924 " def on_validate(self, callback, remove=False):\n",
999 925 " if remove and callback in self._validation_callbacks:\n",
1000 926 " self._validation_callbacks.remove(callback)\n",
1001 927 " elif (not remove) and (not callback in self._validation_callbacks):\n",
1002 928 " self._validation_callbacks.append(callback)\n",
1003 929 " \n",
1004 930 " # Call user validation callbacks. Return True if valid.\n",
1005 931 " def handle_validate(self, new_value):\n",
1006 932 " for callback in self._validation_callbacks:\n",
1007 933 " if not callback(new_value):\n",
1008 934 " return False\n",
1009 935 " return True\n",
1010 936 " "
1011 937 ],
1012 938 "language": "python",
1013 939 "metadata": {},
1014 940 "outputs": [],
1015 941 "prompt_number": 18
1016 942 },
1017 943 {
1018 944 "cell_type": "heading",
1019 945 "level": 2,
1020 946 "metadata": {},
1021 947 "source": [
1022 948 "JavaScript"
1023 949 ]
1024 950 },
1025 951 {
1026 952 "cell_type": "markdown",
1027 953 "metadata": {},
1028 954 "source": [
1029 955 "Using the Javascript code from the last section, we add a label to the date time object. The label is a div with the `widget-hlabel` class applied to it. The `widget-hlabel` is a class provided by the widget framework that applies special styling to a div to make it look like the rest of the horizontal labels used with the built in widgets. Similar to the `widget-hlabel` class is the `widget-hbox-single` class. The `widget-hbox-single` class applies special styling to widget containers that store a single line horizontal widget. \n",
1030 956 "\n",
1031 957 "We hide the label if the description value is blank."
1032 958 ]
1033 959 },
1034 960 {
1035 961 "cell_type": "code",
1036 962 "collapsed": false,
1037 963 "input": [
1038 964 "%%javascript\n",
1039 965 "\n",
1040 966 "require([\"notebook/js/widget\"], function(){\n",
1041 967 " \n",
1042 968 " // Define the DateModel and register it with the widget manager.\n",
1043 969 " var DateModel = IPython.WidgetModel.extend({});\n",
1044 " IPython.notebook.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
970 " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
1045 971 " \n",
1046 972 " // Define the DatePickerView\n",
1047 973 " var DatePickerView = IPython.WidgetView.extend({\n",
1048 974 " \n",
1049 975 " render: function(){\n",
1050 976 " \n",
1051 977 " // Create a div to hold our widget.\n",
1052 978 " this.$el = $('<div />')\n",
1053 979 " .addClass('widget-hbox-single'); // Apply this class to the widget container to make\n",
1054 980 " // it fit with the other built in widgets.\n",
1055 981 " this.delegateEvents();\n",
1056 982 " \n",
1057 983 " // Create a label.\n",
1058 984 " this.$label = $('<div />')\n",
1059 985 " .addClass('widget-hlabel')\n",
1060 986 " .appendTo(this.$el)\n",
1061 987 " .hide(); // Hide the label by default.\n",
1062 988 " \n",
1063 989 " // Create the date picker control.\n",
1064 990 " this.$date = $('<input />')\n",
1065 991 " .attr('type', 'date')\n",
1066 992 " .appendTo(this.$el);\n",
1067 993 " },\n",
1068 994 " \n",
1069 995 " update: function() {\n",
1070 996 " \n",
1071 997 " // Set the value of the date control and then call base.\n",
1072 998 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
1073 999 " \n",
1074 1000 " // Hide or show the label depending on the existance of a description.\n",
1075 1001 " var description = this.model.get('description');\n",
1076 1002 " if (description == undefined || description == '') {\n",
1077 1003 " this.$label.hide();\n",
1078 1004 " } else {\n",
1079 1005 " this.$label.show();\n",
1080 1006 " this.$label.html(description);\n",
1081 1007 " }\n",
1082 1008 " \n",
1083 1009 " return IPython.WidgetView.prototype.update.call(this);\n",
1084 1010 " },\n",
1085 1011 " \n",
1086 1012 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
1087 1013 " events: {\"change\": \"handle_date_change\"},\n",
1088 1014 " \n",
1089 1015 " // Callback for when the date is changed.\n",
1090 1016 " handle_date_change: function(event) {\n",
1091 1017 " this.model.set('value', this.$date.val());\n",
1092 1018 " this.model.update_other_views(this);\n",
1093 1019 " },\n",
1094 1020 " \n",
1095 1021 " });\n",
1096 1022 " \n",
1097 1023 " // Register the DatePickerView with the widget manager.\n",
1098 " IPython.notebook.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
1024 " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
1099 1025 "});"
1100 1026 ],
1101 1027 "language": "python",
1102 1028 "metadata": {},
1103 1029 "outputs": [
1104 1030 {
1105 1031 "javascript": [
1106 1032 "\n",
1107 1033 "require([\"notebook/js/widget\"], function(){\n",
1108 1034 " \n",
1109 1035 " // Define the DateModel and register it with the widget manager.\n",
1110 1036 " var DateModel = IPython.WidgetModel.extend({});\n",
1111 " IPython.notebook.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
1037 " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
1112 1038 " \n",
1113 1039 " // Define the DatePickerView\n",
1114 1040 " var DatePickerView = IPython.WidgetView.extend({\n",
1115 1041 " \n",
1116 1042 " render: function(){\n",
1117 1043 " \n",
1118 1044 " // Create a div to hold our widget.\n",
1119 1045 " this.$el = $('<div />')\n",
1120 1046 " .addClass('widget-hbox-single'); // Apply this class to the widget container to make\n",
1121 1047 " // it fit with the other built in widgets.\n",
1122 1048 " this.delegateEvents();\n",
1123 1049 " \n",
1124 1050 " // Create a label.\n",
1125 1051 " this.$label = $('<div />')\n",
1126 1052 " .addClass('widget-hlabel')\n",
1127 1053 " .appendTo(this.$el)\n",
1128 1054 " .hide(); // Hide the label by default.\n",
1129 1055 " \n",
1130 1056 " // Create the date picker control.\n",
1131 1057 " this.$date = $('<input />')\n",
1132 1058 " .attr('type', 'date')\n",
1133 1059 " .appendTo(this.$el);\n",
1134 1060 " },\n",
1135 1061 " \n",
1136 1062 " update: function() {\n",
1137 1063 " \n",
1138 1064 " // Set the value of the date control and then call base.\n",
1139 1065 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
1140 1066 " \n",
1141 1067 " // Hide or show the label depending on the existance of a description.\n",
1142 1068 " var description = this.model.get('description');\n",
1143 1069 " if (description == undefined || description == '') {\n",
1144 1070 " this.$label.hide();\n",
1145 1071 " } else {\n",
1146 1072 " this.$label.show();\n",
1147 1073 " this.$label.html(description);\n",
1148 1074 " }\n",
1149 1075 " \n",
1150 1076 " return IPython.WidgetView.prototype.update.call(this);\n",
1151 1077 " },\n",
1152 1078 " \n",
1153 1079 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
1154 1080 " events: {\"change\": \"handle_date_change\"},\n",
1155 1081 " \n",
1156 1082 " // Callback for when the date is changed.\n",
1157 1083 " handle_date_change: function(event) {\n",
1158 1084 " this.model.set('value', this.$date.val());\n",
1159 1085 " this.model.update_other_views(this);\n",
1160 1086 " },\n",
1161 1087 " \n",
1162 1088 " });\n",
1163 1089 " \n",
1164 1090 " // Register the DatePickerView with the widget manager.\n",
1165 " IPython.notebook.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
1091 " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
1166 1092 "});"
1167 1093 ],
1168 1094 "metadata": {},
1169 1095 "output_type": "display_data",
1170 1096 "text": [
1171 "<IPython.core.display.Javascript at 0x179f790>"
1097 "<IPython.core.display.Javascript at 0x221a850>"
1172 1098 ]
1173 1099 }
1174 1100 ],
1175 1101 "prompt_number": 19
1176 1102 },
1177 1103 {
1178 1104 "cell_type": "heading",
1179 1105 "level": 2,
1180 1106 "metadata": {},
1181 1107 "source": [
1182 1108 "Test"
1183 1109 ]
1184 1110 },
1185 1111 {
1186 1112 "cell_type": "markdown",
1187 1113 "metadata": {},
1188 1114 "source": [
1189 1115 "To test the drawing of the label we create the widget like normal but supply the additional description property a value."
1190 1116 ]
1191 1117 },
1192 1118 {
1193 1119 "cell_type": "code",
1194 1120 "collapsed": false,
1195 1121 "input": [
1196 1122 "# Add some additional widgets for aesthetic purpose\n",
1197 1123 "display(widgets.StringWidget(description=\"First:\"))\n",
1198 1124 "display(widgets.StringWidget(description=\"Last:\"))\n",
1199 1125 "\n",
1200 1126 "my_widget = DateWidget(description=\"DOB:\")\n",
1201 1127 "display(my_widget)"
1202 1128 ],
1203 1129 "language": "python",
1204 1130 "metadata": {},
1205 1131 "outputs": [],
1206 1132 "prompt_number": 20
1207 1133 },
1208 1134 {
1209 1135 "cell_type": "markdown",
1210 1136 "metadata": {},
1211 1137 "source": [
1212 1138 "Since the date widget uses `value` and `description`, we can also display its value using a `TextBoxView`. The allows us to look at the raw date value being passed to and from the back-end and front-end."
1213 1139 ]
1214 1140 },
1215 1141 {
1216 1142 "cell_type": "code",
1217 1143 "collapsed": false,
1218 1144 "input": [
1219 "\n",
1220 1145 "display(my_widget, view_name=\"TextBoxView\")"
1221 1146 ],
1222 1147 "language": "python",
1223 1148 "metadata": {},
1224 1149 "outputs": [],
1225 1150 "prompt_number": 21
1226 1151 },
1227 1152 {
1228 1153 "cell_type": "markdown",
1229 1154 "metadata": {},
1230 1155 "source": [
1231 1156 "Now we will try to create a widget that only accepts dates in the year 2013. We render the widget without a description to verify that it can still render without a label."
1232 1157 ]
1233 1158 },
1234 1159 {
1235 1160 "cell_type": "code",
1236 1161 "collapsed": false,
1237 1162 "input": [
1238 1163 "my_widget = DateWidget()\n",
1239 1164 "display(my_widget)\n",
1240 1165 "\n",
1241 1166 "def validate_date(date):\n",
1242 1167 " return not date is None and date.year == 2013\n",
1243 1168 "my_widget.on_validate(validate_date)"
1244 1169 ],
1245 1170 "language": "python",
1246 1171 "metadata": {},
1247 1172 "outputs": [],
1248 1173 "prompt_number": 22
1249 1174 },
1250 1175 {
1251 1176 "cell_type": "code",
1252 1177 "collapsed": false,
1253 1178 "input": [
1254 1179 "# Try setting a valid date\n",
1255 1180 "my_widget.value = \"December 2, 2013\""
1256 1181 ],
1257 1182 "language": "python",
1258 1183 "metadata": {},
1259 1184 "outputs": [],
1260 1185 "prompt_number": 23
1261 1186 },
1262 1187 {
1263 1188 "cell_type": "code",
1264 1189 "collapsed": false,
1265 1190 "input": [
1266 1191 "# Try setting an invalid date\n",
1267 1192 "my_widget.value = \"June 12, 1999\""
1268 1193 ],
1269 1194 "language": "python",
1270 1195 "metadata": {},
1271 1196 "outputs": [],
1272 1197 "prompt_number": 24
1273 1198 }
1274 1199 ],
1275 1200 "metadata": {}
1276 1201 }
1277 1202 ]
1278 1203 } No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now