##// 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 // Copyright (C) 2011 The IPython Development Team
2 // Copyright (C) 2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // On document ready
9 // On document ready
10 //============================================================================
10 //============================================================================
11 "use strict";
11 "use strict";
12
12
13 // for the time beeing, we have to pass marked as a parameter here,
13 // for the time beeing, we have to pass marked as a parameter here,
14 // as injecting require.js make marked not to put itself in the globals,
14 // as injecting require.js make marked not to put itself in the globals,
15 // which make both this file fail at setting marked configuration, and textcell.js
15 // which make both this file fail at setting marked configuration, and textcell.js
16 // which search marked into global.
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 function (marked) {
20 function (marked) {
20
21
21 window.marked = marked
22 window.marked = marked
22
23
23 // monkey patch CM to be able to syntax highlight cell magics
24 // monkey patch CM to be able to syntax highlight cell magics
24 // bug reported upstream,
25 // bug reported upstream,
25 // see https://github.com/marijnh/CodeMirror2/issues/670
26 // see https://github.com/marijnh/CodeMirror2/issues/670
26 if(CodeMirror.getMode(1,'text/plain').indent == undefined ){
27 if(CodeMirror.getMode(1,'text/plain').indent == undefined ){
27 console.log('patching CM for undefined indent');
28 console.log('patching CM for undefined indent');
28 CodeMirror.modes.null = function() {
29 CodeMirror.modes.null = function() {
29 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0}}
30 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0}}
30 }
31 }
31 }
32 }
32
33
33 CodeMirror.patchedGetMode = function(config, mode){
34 CodeMirror.patchedGetMode = function(config, mode){
34 var cmmode = CodeMirror.getMode(config, mode);
35 var cmmode = CodeMirror.getMode(config, mode);
35 if(cmmode.indent == null)
36 if(cmmode.indent == null)
36 {
37 {
37 console.log('patch mode "' , mode, '" on the fly');
38 console.log('patch mode "' , mode, '" on the fly');
38 cmmode.indent = function(){return 0};
39 cmmode.indent = function(){return 0};
39 }
40 }
40 return cmmode;
41 return cmmode;
41 }
42 }
42 // end monkey patching CodeMirror
43 // end monkey patching CodeMirror
43
44
44 IPython.mathjaxutils.init();
45 IPython.mathjaxutils.init();
45
46
46 $('#ipython-main-app').addClass('border-box-sizing');
47 $('#ipython-main-app').addClass('border-box-sizing');
47 $('div#notebook_panel').addClass('border-box-sizing');
48 $('div#notebook_panel').addClass('border-box-sizing');
48
49
49 var baseProjectUrl = $('body').data('baseProjectUrl');
50 var baseProjectUrl = $('body').data('baseProjectUrl');
50 var notebookPath = $('body').data('notebookPath');
51 var notebookPath = $('body').data('notebookPath');
51 var notebookName = $('body').data('notebookName');
52 var notebookName = $('body').data('notebookName');
52 notebookName = decodeURIComponent(notebookName);
53 notebookName = decodeURIComponent(notebookName);
53 notebookPath = decodeURIComponent(notebookPath);
54 notebookPath = decodeURIComponent(notebookPath);
54 console.log(notebookName);
55 console.log(notebookName);
55 if (notebookPath == 'None'){
56 if (notebookPath == 'None'){
56 notebookPath = "";
57 notebookPath = "";
57 }
58 }
58
59
59 IPython.page = new IPython.Page();
60 IPython.page = new IPython.Page();
60 IPython.layout_manager = new IPython.LayoutManager();
61 IPython.layout_manager = new IPython.LayoutManager();
61 IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
62 IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
62 IPython.quick_help = new IPython.QuickHelp();
63 IPython.quick_help = new IPython.QuickHelp();
63 IPython.login_widget = new IPython.LoginWidget('span#login_widget',{baseProjectUrl:baseProjectUrl});
64 IPython.login_widget = new IPython.LoginWidget('span#login_widget',{baseProjectUrl:baseProjectUrl});
64 IPython.notebook = new IPython.Notebook('div#notebook',{baseProjectUrl:baseProjectUrl, notebookPath:notebookPath, notebookName:notebookName});
65 IPython.notebook = new IPython.Notebook('div#notebook',{baseProjectUrl:baseProjectUrl, notebookPath:notebookPath, notebookName:notebookName});
65 IPython.keyboard_manager = new IPython.KeyboardManager();
66 IPython.keyboard_manager = new IPython.KeyboardManager();
66 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
67 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
67 IPython.menubar = new IPython.MenuBar('#menubar',{baseProjectUrl:baseProjectUrl, notebookPath: notebookPath})
68 IPython.menubar = new IPython.MenuBar('#menubar',{baseProjectUrl:baseProjectUrl, notebookPath: notebookPath})
68 IPython.toolbar = new IPython.MainToolBar('#maintoolbar-container')
69 IPython.toolbar = new IPython.MainToolBar('#maintoolbar-container')
69 IPython.tooltip = new IPython.Tooltip()
70 IPython.tooltip = new IPython.Tooltip()
70 IPython.notification_area = new IPython.NotificationArea('#notification_area')
71 IPython.notification_area = new IPython.NotificationArea('#notification_area')
71 IPython.notification_area.init_notification_widgets();
72 IPython.notification_area.init_notification_widgets();
72
73
73 IPython.layout_manager.do_resize();
74 IPython.layout_manager.do_resize();
74
75
75 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
76 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
76 '<span id="test2" style="font-weight: bold;">x</span>'+
77 '<span id="test2" style="font-weight: bold;">x</span>'+
77 '<span id="test3" style="font-style: italic;">x</span></pre></div>')
78 '<span id="test3" style="font-style: italic;">x</span></pre></div>')
78 var nh = $('#test1').innerHeight();
79 var nh = $('#test1').innerHeight();
79 var bh = $('#test2').innerHeight();
80 var bh = $('#test2').innerHeight();
80 var ih = $('#test3').innerHeight();
81 var ih = $('#test3').innerHeight();
81 if(nh != bh || nh != ih) {
82 if(nh != bh || nh != ih) {
82 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
83 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
83 }
84 }
84 $('#fonttest').remove();
85 $('#fonttest').remove();
85
86
86 IPython.page.show();
87 IPython.page.show();
87
88
88 IPython.layout_manager.do_resize();
89 IPython.layout_manager.do_resize();
89 var first_load = function () {
90 var first_load = function () {
90 IPython.layout_manager.do_resize();
91 IPython.layout_manager.do_resize();
91 var hash = document.location.hash;
92 var hash = document.location.hash;
92 if (hash) {
93 if (hash) {
93 document.location.hash = '';
94 document.location.hash = '';
94 document.location.hash = hash;
95 document.location.hash = hash;
95 }
96 }
96 IPython.notebook.set_autosave_interval(IPython.notebook.minimum_autosave_interval);
97 IPython.notebook.set_autosave_interval(IPython.notebook.minimum_autosave_interval);
97 // only do this once
98 // only do this once
98 $([IPython.events]).off('notebook_loaded.Notebook', first_load);
99 $([IPython.events]).off('notebook_loaded.Notebook', first_load);
99 };
100 };
100
101
101 $([IPython.events]).on('notebook_loaded.Notebook', first_load);
102 $([IPython.events]).on('notebook_loaded.Notebook', first_load);
102 $([IPython.events]).trigger('app_initialized.NotebookApp');
103 $([IPython.events]).trigger('app_initialized.NotebookApp');
103 IPython.notebook.load_notebook(notebookName, notebookPath);
104 IPython.notebook.load_notebook(notebookName, notebookPath);
104
105
105 if (marked) {
106 if (marked) {
106 marked.setOptions({
107 marked.setOptions({
107 gfm : true,
108 gfm : true,
108 tables: true,
109 tables: true,
109 langPrefix: "language-",
110 langPrefix: "language-",
110 highlight: function(code, lang) {
111 highlight: function(code, lang) {
111 if (!lang) {
112 if (!lang) {
112 // no language, no highlight
113 // no language, no highlight
113 return code;
114 return code;
114 }
115 }
115 var highlighted;
116 var highlighted;
116 try {
117 try {
117 highlighted = hljs.highlight(lang, code, false);
118 highlighted = hljs.highlight(lang, code, false);
118 } catch(err) {
119 } catch(err) {
119 highlighted = hljs.highlightAuto(code);
120 highlighted = hljs.highlightAuto(code);
120 }
121 }
121 return highlighted.value;
122 return highlighted.value;
122 }
123 }
123 })
124 })
124 }
125 }
125 }
126 }
126
127
127 );
128 );
@@ -1,2213 +1,2215 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
2 // Copyright (C) 2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Notebook
9 // Notebook
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 var utils = IPython.utils;
15 var utils = IPython.utils;
16
16
17 /**
17 /**
18 * A notebook contains and manages cells.
18 * A notebook contains and manages cells.
19 *
19 *
20 * @class Notebook
20 * @class Notebook
21 * @constructor
21 * @constructor
22 * @param {String} selector A jQuery selector for the notebook's DOM element
22 * @param {String} selector A jQuery selector for the notebook's DOM element
23 * @param {Object} [options] A config object
23 * @param {Object} [options] A config object
24 */
24 */
25 var Notebook = function (selector, options) {
25 var Notebook = function (selector, options) {
26 var options = options || {};
26 var options = options || {};
27 this._baseProjectUrl = options.baseProjectUrl;
27 this._baseProjectUrl = options.baseProjectUrl;
28 this.notebook_path = options.notebookPath;
28 this.notebook_path = options.notebookPath;
29 this.notebook_name = options.notebookName;
29 this.notebook_name = options.notebookName;
30 this.element = $(selector);
30 this.element = $(selector);
31 this.element.scroll();
31 this.element.scroll();
32 this.element.data("notebook", this);
32 this.element.data("notebook", this);
33 this.next_prompt_number = 1;
33 this.next_prompt_number = 1;
34 this.session = null;
34 this.session = null;
35 this.kernel = null;
35 this.kernel = null;
36 this.clipboard = null;
36 this.clipboard = null;
37 this.undelete_backup = null;
37 this.undelete_backup = null;
38 this.undelete_index = null;
38 this.undelete_index = null;
39 this.undelete_below = false;
39 this.undelete_below = false;
40 this.paste_enabled = false;
40 this.paste_enabled = false;
41 // It is important to start out in command mode to match the intial mode
41 // It is important to start out in command mode to match the intial mode
42 // of the KeyboardManager.
42 // of the KeyboardManager.
43 this.mode = 'command';
43 this.mode = 'command';
44 this.set_dirty(false);
44 this.set_dirty(false);
45 this.metadata = {};
45 this.metadata = {};
46 this._checkpoint_after_save = false;
46 this._checkpoint_after_save = false;
47 this.last_checkpoint = null;
47 this.last_checkpoint = null;
48 this.checkpoints = [];
48 this.checkpoints = [];
49 this.autosave_interval = 0;
49 this.autosave_interval = 0;
50 this.autosave_timer = null;
50 this.autosave_timer = null;
51 // autosave *at most* every two minutes
51 // autosave *at most* every two minutes
52 this.minimum_autosave_interval = 120000;
52 this.minimum_autosave_interval = 120000;
53 // single worksheet for now
53 // single worksheet for now
54 this.worksheet_metadata = {};
54 this.worksheet_metadata = {};
55 this.notebook_name_blacklist_re = /[\/\\:]/;
55 this.notebook_name_blacklist_re = /[\/\\:]/;
56 this.nbformat = 3 // Increment this when changing the nbformat
56 this.nbformat = 3 // Increment this when changing the nbformat
57 this.nbformat_minor = 0 // Increment this when changing the nbformat
57 this.nbformat_minor = 0 // Increment this when changing the nbformat
58 this.style();
58 this.style();
59 this.create_elements();
59 this.create_elements();
60 this.bind_events();
60 this.bind_events();
61 };
61 };
62
62
63 /**
63 /**
64 * Tweak the notebook's CSS style.
64 * Tweak the notebook's CSS style.
65 *
65 *
66 * @method style
66 * @method style
67 */
67 */
68 Notebook.prototype.style = function () {
68 Notebook.prototype.style = function () {
69 $('div#notebook').addClass('border-box-sizing');
69 $('div#notebook').addClass('border-box-sizing');
70 };
70 };
71
71
72 /**
72 /**
73 * Get the root URL of the notebook server.
73 * Get the root URL of the notebook server.
74 *
74 *
75 * @method baseProjectUrl
75 * @method baseProjectUrl
76 * @return {String} The base project URL
76 * @return {String} The base project URL
77 */
77 */
78 Notebook.prototype.baseProjectUrl = function() {
78 Notebook.prototype.baseProjectUrl = function() {
79 return this._baseProjectUrl || $('body').data('baseProjectUrl');
79 return this._baseProjectUrl || $('body').data('baseProjectUrl');
80 };
80 };
81
81
82 Notebook.prototype.notebookName = function() {
82 Notebook.prototype.notebookName = function() {
83 return $('body').data('notebookName');
83 return $('body').data('notebookName');
84 };
84 };
85
85
86 Notebook.prototype.notebookPath = function() {
86 Notebook.prototype.notebookPath = function() {
87 return $('body').data('notebookPath');
87 return $('body').data('notebookPath');
88 };
88 };
89
89
90 /**
90 /**
91 * Create an HTML and CSS representation of the notebook.
91 * Create an HTML and CSS representation of the notebook.
92 *
92 *
93 * @method create_elements
93 * @method create_elements
94 */
94 */
95 Notebook.prototype.create_elements = function () {
95 Notebook.prototype.create_elements = function () {
96 var that = this;
96 var that = this;
97 this.element.attr('tabindex','-1');
97 this.element.attr('tabindex','-1');
98 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
98 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
99 // We add this end_space div to the end of the notebook div to:
99 // We add this end_space div to the end of the notebook div to:
100 // i) provide a margin between the last cell and the end of the notebook
100 // i) provide a margin between the last cell and the end of the notebook
101 // ii) to prevent the div from scrolling up when the last cell is being
101 // ii) to prevent the div from scrolling up when the last cell is being
102 // edited, but is too low on the page, which browsers will do automatically.
102 // edited, but is too low on the page, which browsers will do automatically.
103 var end_space = $('<div/>').addClass('end_space');
103 var end_space = $('<div/>').addClass('end_space');
104 end_space.dblclick(function (e) {
104 end_space.dblclick(function (e) {
105 var ncells = that.ncells();
105 var ncells = that.ncells();
106 that.insert_cell_below('code',ncells-1);
106 that.insert_cell_below('code',ncells-1);
107 });
107 });
108 this.element.append(this.container);
108 this.element.append(this.container);
109 this.container.append(end_space);
109 this.container.append(end_space);
110 };
110 };
111
111
112 /**
112 /**
113 * Bind JavaScript events: key presses and custom IPython events.
113 * Bind JavaScript events: key presses and custom IPython events.
114 *
114 *
115 * @method bind_events
115 * @method bind_events
116 */
116 */
117 Notebook.prototype.bind_events = function () {
117 Notebook.prototype.bind_events = function () {
118 var that = this;
118 var that = this;
119
119
120 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
120 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
121 var index = that.find_cell_index(data.cell);
121 var index = that.find_cell_index(data.cell);
122 var new_cell = that.insert_cell_below('code',index);
122 var new_cell = that.insert_cell_below('code',index);
123 new_cell.set_text(data.text);
123 new_cell.set_text(data.text);
124 that.dirty = true;
124 that.dirty = true;
125 });
125 });
126
126
127 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
127 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
128 that.dirty = data.value;
128 that.dirty = data.value;
129 });
129 });
130
130
131 $([IPython.events]).on('select.Cell', function (event, data) {
131 $([IPython.events]).on('select.Cell', function (event, data) {
132 var index = that.find_cell_index(data.cell);
132 var index = that.find_cell_index(data.cell);
133 that.select(index);
133 that.select(index);
134 });
134 });
135
135
136 $([IPython.events]).on('edit_mode.Cell', function (event, data) {
136 $([IPython.events]).on('edit_mode.Cell', function (event, data) {
137 var index = that.find_cell_index(data.cell);
137 var index = that.find_cell_index(data.cell);
138 that.select(index);
138 that.select(index);
139 that.edit_mode();
139 that.edit_mode();
140 });
140 });
141
141
142 $([IPython.events]).on('command_mode.Cell', function (event, data) {
142 $([IPython.events]).on('command_mode.Cell', function (event, data) {
143 that.command_mode();
143 that.command_mode();
144 });
144 });
145
145
146 $([IPython.events]).on('status_autorestarting.Kernel', function () {
146 $([IPython.events]).on('status_autorestarting.Kernel', function () {
147 IPython.dialog.modal({
147 IPython.dialog.modal({
148 title: "Kernel Restarting",
148 title: "Kernel Restarting",
149 body: "The kernel appears to have died. It will restart automatically.",
149 body: "The kernel appears to have died. It will restart automatically.",
150 buttons: {
150 buttons: {
151 OK : {
151 OK : {
152 class : "btn-primary"
152 class : "btn-primary"
153 }
153 }
154 }
154 }
155 });
155 });
156 });
156 });
157
157
158 var collapse_time = function (time) {
158 var collapse_time = function (time) {
159 var app_height = $('#ipython-main-app').height(); // content height
159 var app_height = $('#ipython-main-app').height(); // content height
160 var splitter_height = $('div#pager_splitter').outerHeight(true);
160 var splitter_height = $('div#pager_splitter').outerHeight(true);
161 var new_height = app_height - splitter_height;
161 var new_height = app_height - splitter_height;
162 that.element.animate({height : new_height + 'px'}, time);
162 that.element.animate({height : new_height + 'px'}, time);
163 };
163 };
164
164
165 this.element.bind('collapse_pager', function (event, extrap) {
165 this.element.bind('collapse_pager', function (event, extrap) {
166 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
166 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
167 collapse_time(time);
167 collapse_time(time);
168 });
168 });
169
169
170 var expand_time = function (time) {
170 var expand_time = function (time) {
171 var app_height = $('#ipython-main-app').height(); // content height
171 var app_height = $('#ipython-main-app').height(); // content height
172 var splitter_height = $('div#pager_splitter').outerHeight(true);
172 var splitter_height = $('div#pager_splitter').outerHeight(true);
173 var pager_height = $('div#pager').outerHeight(true);
173 var pager_height = $('div#pager').outerHeight(true);
174 var new_height = app_height - pager_height - splitter_height;
174 var new_height = app_height - pager_height - splitter_height;
175 that.element.animate({height : new_height + 'px'}, time);
175 that.element.animate({height : new_height + 'px'}, time);
176 };
176 };
177
177
178 this.element.bind('expand_pager', function (event, extrap) {
178 this.element.bind('expand_pager', function (event, extrap) {
179 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
179 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
180 expand_time(time);
180 expand_time(time);
181 });
181 });
182
182
183 // Firefox 22 broke $(window).on("beforeunload")
183 // Firefox 22 broke $(window).on("beforeunload")
184 // I'm not sure why or how.
184 // I'm not sure why or how.
185 window.onbeforeunload = function (e) {
185 window.onbeforeunload = function (e) {
186 // TODO: Make killing the kernel configurable.
186 // TODO: Make killing the kernel configurable.
187 var kill_kernel = false;
187 var kill_kernel = false;
188 if (kill_kernel) {
188 if (kill_kernel) {
189 that.session.kill_kernel();
189 that.session.kill_kernel();
190 }
190 }
191 // if we are autosaving, trigger an autosave on nav-away.
191 // if we are autosaving, trigger an autosave on nav-away.
192 // still warn, because if we don't the autosave may fail.
192 // still warn, because if we don't the autosave may fail.
193 if (that.dirty) {
193 if (that.dirty) {
194 if ( that.autosave_interval ) {
194 if ( that.autosave_interval ) {
195 // schedule autosave in a timeout
195 // schedule autosave in a timeout
196 // this gives you a chance to forcefully discard changes
196 // this gives you a chance to forcefully discard changes
197 // by reloading the page if you *really* want to.
197 // by reloading the page if you *really* want to.
198 // the timer doesn't start until you *dismiss* the dialog.
198 // the timer doesn't start until you *dismiss* the dialog.
199 setTimeout(function () {
199 setTimeout(function () {
200 if (that.dirty) {
200 if (that.dirty) {
201 that.save_notebook();
201 that.save_notebook();
202 }
202 }
203 }, 1000);
203 }, 1000);
204 return "Autosave in progress, latest changes may be lost.";
204 return "Autosave in progress, latest changes may be lost.";
205 } else {
205 } else {
206 return "Unsaved changes will be lost.";
206 return "Unsaved changes will be lost.";
207 }
207 }
208 };
208 };
209 // Null is the *only* return value that will make the browser not
209 // Null is the *only* return value that will make the browser not
210 // pop up the "don't leave" dialog.
210 // pop up the "don't leave" dialog.
211 return null;
211 return null;
212 };
212 };
213 };
213 };
214
214
215 /**
215 /**
216 * Set the dirty flag, and trigger the set_dirty.Notebook event
216 * Set the dirty flag, and trigger the set_dirty.Notebook event
217 *
217 *
218 * @method set_dirty
218 * @method set_dirty
219 */
219 */
220 Notebook.prototype.set_dirty = function (value) {
220 Notebook.prototype.set_dirty = function (value) {
221 if (value === undefined) {
221 if (value === undefined) {
222 value = true;
222 value = true;
223 }
223 }
224 if (this.dirty == value) {
224 if (this.dirty == value) {
225 return;
225 return;
226 }
226 }
227 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
227 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
228 };
228 };
229
229
230 /**
230 /**
231 * Scroll the top of the page to a given cell.
231 * Scroll the top of the page to a given cell.
232 *
232 *
233 * @method scroll_to_cell
233 * @method scroll_to_cell
234 * @param {Number} cell_number An index of the cell to view
234 * @param {Number} cell_number An index of the cell to view
235 * @param {Number} time Animation time in milliseconds
235 * @param {Number} time Animation time in milliseconds
236 * @return {Number} Pixel offset from the top of the container
236 * @return {Number} Pixel offset from the top of the container
237 */
237 */
238 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
238 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
239 var cells = this.get_cells();
239 var cells = this.get_cells();
240 var time = time || 0;
240 var time = time || 0;
241 cell_number = Math.min(cells.length-1,cell_number);
241 cell_number = Math.min(cells.length-1,cell_number);
242 cell_number = Math.max(0 ,cell_number);
242 cell_number = Math.max(0 ,cell_number);
243 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
243 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
244 this.element.animate({scrollTop:scroll_value}, time);
244 this.element.animate({scrollTop:scroll_value}, time);
245 return scroll_value;
245 return scroll_value;
246 };
246 };
247
247
248 /**
248 /**
249 * Scroll to the bottom of the page.
249 * Scroll to the bottom of the page.
250 *
250 *
251 * @method scroll_to_bottom
251 * @method scroll_to_bottom
252 */
252 */
253 Notebook.prototype.scroll_to_bottom = function () {
253 Notebook.prototype.scroll_to_bottom = function () {
254 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
254 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
255 };
255 };
256
256
257 /**
257 /**
258 * Scroll to the top of the page.
258 * Scroll to the top of the page.
259 *
259 *
260 * @method scroll_to_top
260 * @method scroll_to_top
261 */
261 */
262 Notebook.prototype.scroll_to_top = function () {
262 Notebook.prototype.scroll_to_top = function () {
263 this.element.animate({scrollTop:0}, 0);
263 this.element.animate({scrollTop:0}, 0);
264 };
264 };
265
265
266 // Edit Notebook metadata
266 // Edit Notebook metadata
267
267
268 Notebook.prototype.edit_metadata = function () {
268 Notebook.prototype.edit_metadata = function () {
269 var that = this;
269 var that = this;
270 IPython.dialog.edit_metadata(this.metadata, function (md) {
270 IPython.dialog.edit_metadata(this.metadata, function (md) {
271 that.metadata = md;
271 that.metadata = md;
272 }, 'Notebook');
272 }, 'Notebook');
273 };
273 };
274
274
275 // Cell indexing, retrieval, etc.
275 // Cell indexing, retrieval, etc.
276
276
277 /**
277 /**
278 * Get all cell elements in the notebook.
278 * Get all cell elements in the notebook.
279 *
279 *
280 * @method get_cell_elements
280 * @method get_cell_elements
281 * @return {jQuery} A selector of all cell elements
281 * @return {jQuery} A selector of all cell elements
282 */
282 */
283 Notebook.prototype.get_cell_elements = function () {
283 Notebook.prototype.get_cell_elements = function () {
284 return this.container.children("div.cell");
284 return this.container.children("div.cell");
285 };
285 };
286
286
287 /**
287 /**
288 * Get a particular cell element.
288 * Get a particular cell element.
289 *
289 *
290 * @method get_cell_element
290 * @method get_cell_element
291 * @param {Number} index An index of a cell to select
291 * @param {Number} index An index of a cell to select
292 * @return {jQuery} A selector of the given cell.
292 * @return {jQuery} A selector of the given cell.
293 */
293 */
294 Notebook.prototype.get_cell_element = function (index) {
294 Notebook.prototype.get_cell_element = function (index) {
295 var result = null;
295 var result = null;
296 var e = this.get_cell_elements().eq(index);
296 var e = this.get_cell_elements().eq(index);
297 if (e.length !== 0) {
297 if (e.length !== 0) {
298 result = e;
298 result = e;
299 }
299 }
300 return result;
300 return result;
301 };
301 };
302
302
303 /**
303 /**
304 * Count the cells in this notebook.
304 * Count the cells in this notebook.
305 *
305 *
306 * @method ncells
306 * @method ncells
307 * @return {Number} The number of cells in this notebook
307 * @return {Number} The number of cells in this notebook
308 */
308 */
309 Notebook.prototype.ncells = function () {
309 Notebook.prototype.ncells = function () {
310 return this.get_cell_elements().length;
310 return this.get_cell_elements().length;
311 };
311 };
312
312
313 /**
313 /**
314 * Get all Cell objects in this notebook.
314 * Get all Cell objects in this notebook.
315 *
315 *
316 * @method get_cells
316 * @method get_cells
317 * @return {Array} This notebook's Cell objects
317 * @return {Array} This notebook's Cell objects
318 */
318 */
319 // TODO: we are often calling cells as cells()[i], which we should optimize
319 // TODO: we are often calling cells as cells()[i], which we should optimize
320 // to cells(i) or a new method.
320 // to cells(i) or a new method.
321 Notebook.prototype.get_cells = function () {
321 Notebook.prototype.get_cells = function () {
322 return this.get_cell_elements().toArray().map(function (e) {
322 return this.get_cell_elements().toArray().map(function (e) {
323 return $(e).data("cell");
323 return $(e).data("cell");
324 });
324 });
325 };
325 };
326
326
327 /**
327 /**
328 * Get a Cell object from this notebook.
328 * Get a Cell object from this notebook.
329 *
329 *
330 * @method get_cell
330 * @method get_cell
331 * @param {Number} index An index of a cell to retrieve
331 * @param {Number} index An index of a cell to retrieve
332 * @return {Cell} A particular cell
332 * @return {Cell} A particular cell
333 */
333 */
334 Notebook.prototype.get_cell = function (index) {
334 Notebook.prototype.get_cell = function (index) {
335 var result = null;
335 var result = null;
336 var ce = this.get_cell_element(index);
336 var ce = this.get_cell_element(index);
337 if (ce !== null) {
337 if (ce !== null) {
338 result = ce.data('cell');
338 result = ce.data('cell');
339 }
339 }
340 return result;
340 return result;
341 }
341 }
342
342
343 /**
343 /**
344 * Get the cell below a given cell.
344 * Get the cell below a given cell.
345 *
345 *
346 * @method get_next_cell
346 * @method get_next_cell
347 * @param {Cell} cell The provided cell
347 * @param {Cell} cell The provided cell
348 * @return {Cell} The next cell
348 * @return {Cell} The next cell
349 */
349 */
350 Notebook.prototype.get_next_cell = function (cell) {
350 Notebook.prototype.get_next_cell = function (cell) {
351 var result = null;
351 var result = null;
352 var index = this.find_cell_index(cell);
352 var index = this.find_cell_index(cell);
353 if (this.is_valid_cell_index(index+1)) {
353 if (this.is_valid_cell_index(index+1)) {
354 result = this.get_cell(index+1);
354 result = this.get_cell(index+1);
355 }
355 }
356 return result;
356 return result;
357 }
357 }
358
358
359 /**
359 /**
360 * Get the cell above a given cell.
360 * Get the cell above a given cell.
361 *
361 *
362 * @method get_prev_cell
362 * @method get_prev_cell
363 * @param {Cell} cell The provided cell
363 * @param {Cell} cell The provided cell
364 * @return {Cell} The previous cell
364 * @return {Cell} The previous cell
365 */
365 */
366 Notebook.prototype.get_prev_cell = function (cell) {
366 Notebook.prototype.get_prev_cell = function (cell) {
367 // TODO: off-by-one
367 // TODO: off-by-one
368 // nb.get_prev_cell(nb.get_cell(1)) is null
368 // nb.get_prev_cell(nb.get_cell(1)) is null
369 var result = null;
369 var result = null;
370 var index = this.find_cell_index(cell);
370 var index = this.find_cell_index(cell);
371 if (index !== null && index > 1) {
371 if (index !== null && index > 1) {
372 result = this.get_cell(index-1);
372 result = this.get_cell(index-1);
373 }
373 }
374 return result;
374 return result;
375 }
375 }
376
376
377 /**
377 /**
378 * Get the numeric index of a given cell.
378 * Get the numeric index of a given cell.
379 *
379 *
380 * @method find_cell_index
380 * @method find_cell_index
381 * @param {Cell} cell The provided cell
381 * @param {Cell} cell The provided cell
382 * @return {Number} The cell's numeric index
382 * @return {Number} The cell's numeric index
383 */
383 */
384 Notebook.prototype.find_cell_index = function (cell) {
384 Notebook.prototype.find_cell_index = function (cell) {
385 var result = null;
385 var result = null;
386 this.get_cell_elements().filter(function (index) {
386 this.get_cell_elements().filter(function (index) {
387 if ($(this).data("cell") === cell) {
387 if ($(this).data("cell") === cell) {
388 result = index;
388 result = index;
389 };
389 };
390 });
390 });
391 return result;
391 return result;
392 };
392 };
393
393
394 /**
394 /**
395 * Get a given index , or the selected index if none is provided.
395 * Get a given index , or the selected index if none is provided.
396 *
396 *
397 * @method index_or_selected
397 * @method index_or_selected
398 * @param {Number} index A cell's index
398 * @param {Number} index A cell's index
399 * @return {Number} The given index, or selected index if none is provided.
399 * @return {Number} The given index, or selected index if none is provided.
400 */
400 */
401 Notebook.prototype.index_or_selected = function (index) {
401 Notebook.prototype.index_or_selected = function (index) {
402 var i;
402 var i;
403 if (index === undefined || index === null) {
403 if (index === undefined || index === null) {
404 i = this.get_selected_index();
404 i = this.get_selected_index();
405 if (i === null) {
405 if (i === null) {
406 i = 0;
406 i = 0;
407 }
407 }
408 } else {
408 } else {
409 i = index;
409 i = index;
410 }
410 }
411 return i;
411 return i;
412 };
412 };
413
413
414 /**
414 /**
415 * Get the currently selected cell.
415 * Get the currently selected cell.
416 * @method get_selected_cell
416 * @method get_selected_cell
417 * @return {Cell} The selected cell
417 * @return {Cell} The selected cell
418 */
418 */
419 Notebook.prototype.get_selected_cell = function () {
419 Notebook.prototype.get_selected_cell = function () {
420 var index = this.get_selected_index();
420 var index = this.get_selected_index();
421 return this.get_cell(index);
421 return this.get_cell(index);
422 };
422 };
423
423
424 /**
424 /**
425 * Check whether a cell index is valid.
425 * Check whether a cell index is valid.
426 *
426 *
427 * @method is_valid_cell_index
427 * @method is_valid_cell_index
428 * @param {Number} index A cell index
428 * @param {Number} index A cell index
429 * @return True if the index is valid, false otherwise
429 * @return True if the index is valid, false otherwise
430 */
430 */
431 Notebook.prototype.is_valid_cell_index = function (index) {
431 Notebook.prototype.is_valid_cell_index = function (index) {
432 if (index !== null && index >= 0 && index < this.ncells()) {
432 if (index !== null && index >= 0 && index < this.ncells()) {
433 return true;
433 return true;
434 } else {
434 } else {
435 return false;
435 return false;
436 };
436 };
437 }
437 }
438
438
439 /**
439 /**
440 * Get the index of the currently selected cell.
440 * Get the index of the currently selected cell.
441
441
442 * @method get_selected_index
442 * @method get_selected_index
443 * @return {Number} The selected cell's numeric index
443 * @return {Number} The selected cell's numeric index
444 */
444 */
445 Notebook.prototype.get_selected_index = function () {
445 Notebook.prototype.get_selected_index = function () {
446 var result = null;
446 var result = null;
447 this.get_cell_elements().filter(function (index) {
447 this.get_cell_elements().filter(function (index) {
448 if ($(this).data("cell").selected === true) {
448 if ($(this).data("cell").selected === true) {
449 result = index;
449 result = index;
450 };
450 };
451 });
451 });
452 return result;
452 return result;
453 };
453 };
454
454
455
455
456 // Cell selection.
456 // Cell selection.
457
457
458 /**
458 /**
459 * Programmatically select a cell.
459 * Programmatically select a cell.
460 *
460 *
461 * @method select
461 * @method select
462 * @param {Number} index A cell's index
462 * @param {Number} index A cell's index
463 * @return {Notebook} This notebook
463 * @return {Notebook} This notebook
464 */
464 */
465 Notebook.prototype.select = function (index) {
465 Notebook.prototype.select = function (index) {
466 if (this.is_valid_cell_index(index)) {
466 if (this.is_valid_cell_index(index)) {
467 var sindex = this.get_selected_index()
467 var sindex = this.get_selected_index()
468 if (sindex !== null && index !== sindex) {
468 if (sindex !== null && index !== sindex) {
469 this.command_mode();
469 this.command_mode();
470 this.get_cell(sindex).unselect();
470 this.get_cell(sindex).unselect();
471 };
471 };
472 var cell = this.get_cell(index);
472 var cell = this.get_cell(index);
473 cell.select();
473 cell.select();
474 if (cell.cell_type === 'heading') {
474 if (cell.cell_type === 'heading') {
475 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
475 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
476 {'cell_type':cell.cell_type,level:cell.level}
476 {'cell_type':cell.cell_type,level:cell.level}
477 );
477 );
478 } else {
478 } else {
479 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
479 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
480 {'cell_type':cell.cell_type}
480 {'cell_type':cell.cell_type}
481 );
481 );
482 };
482 };
483 };
483 };
484 return this;
484 return this;
485 };
485 };
486
486
487 /**
487 /**
488 * Programmatically select the next cell.
488 * Programmatically select the next cell.
489 *
489 *
490 * @method select_next
490 * @method select_next
491 * @return {Notebook} This notebook
491 * @return {Notebook} This notebook
492 */
492 */
493 Notebook.prototype.select_next = function () {
493 Notebook.prototype.select_next = function () {
494 var index = this.get_selected_index();
494 var index = this.get_selected_index();
495 this.select(index+1);
495 this.select(index+1);
496 return this;
496 return this;
497 };
497 };
498
498
499 /**
499 /**
500 * Programmatically select the previous cell.
500 * Programmatically select the previous cell.
501 *
501 *
502 * @method select_prev
502 * @method select_prev
503 * @return {Notebook} This notebook
503 * @return {Notebook} This notebook
504 */
504 */
505 Notebook.prototype.select_prev = function () {
505 Notebook.prototype.select_prev = function () {
506 var index = this.get_selected_index();
506 var index = this.get_selected_index();
507 this.select(index-1);
507 this.select(index-1);
508 return this;
508 return this;
509 };
509 };
510
510
511
511
512 // Edit/Command mode
512 // Edit/Command mode
513
513
514 Notebook.prototype.get_edit_index = function () {
514 Notebook.prototype.get_edit_index = function () {
515 var result = null;
515 var result = null;
516 this.get_cell_elements().filter(function (index) {
516 this.get_cell_elements().filter(function (index) {
517 if ($(this).data("cell").mode === 'edit') {
517 if ($(this).data("cell").mode === 'edit') {
518 result = index;
518 result = index;
519 };
519 };
520 });
520 });
521 return result;
521 return result;
522 };
522 };
523
523
524 Notebook.prototype.command_mode = function () {
524 Notebook.prototype.command_mode = function () {
525 if (this.mode !== 'command') {
525 if (this.mode !== 'command') {
526 var index = this.get_edit_index();
526 var index = this.get_edit_index();
527 var cell = this.get_cell(index);
527 var cell = this.get_cell(index);
528 if (cell) {
528 if (cell) {
529 cell.command_mode();
529 cell.command_mode();
530 };
530 };
531 this.mode = 'command';
531 this.mode = 'command';
532 IPython.keyboard_manager.command_mode();
532 IPython.keyboard_manager.command_mode();
533 };
533 };
534 };
534 };
535
535
536 Notebook.prototype.edit_mode = function () {
536 Notebook.prototype.edit_mode = function () {
537 if (this.mode !== 'edit') {
537 if (this.mode !== 'edit') {
538 var cell = this.get_selected_cell();
538 var cell = this.get_selected_cell();
539 if (cell === null) {return;} // No cell is selected
539 if (cell === null) {return;} // No cell is selected
540 // We need to set the mode to edit to prevent reentering this method
540 // We need to set the mode to edit to prevent reentering this method
541 // when cell.edit_mode() is called below.
541 // when cell.edit_mode() is called below.
542 this.mode = 'edit';
542 this.mode = 'edit';
543 IPython.keyboard_manager.edit_mode();
543 IPython.keyboard_manager.edit_mode();
544 cell.edit_mode();
544 cell.edit_mode();
545 };
545 };
546 };
546 };
547
547
548 Notebook.prototype.focus_cell = function () {
548 Notebook.prototype.focus_cell = function () {
549 var cell = this.get_selected_cell();
549 var cell = this.get_selected_cell();
550 if (cell === null) {return;} // No cell is selected
550 if (cell === null) {return;} // No cell is selected
551 cell.focus_cell();
551 cell.focus_cell();
552 };
552 };
553
553
554 // Cell movement
554 // Cell movement
555
555
556 /**
556 /**
557 * Move given (or selected) cell up and select it.
557 * Move given (or selected) cell up and select it.
558 *
558 *
559 * @method move_cell_up
559 * @method move_cell_up
560 * @param [index] {integer} cell index
560 * @param [index] {integer} cell index
561 * @return {Notebook} This notebook
561 * @return {Notebook} This notebook
562 **/
562 **/
563 Notebook.prototype.move_cell_up = function (index) {
563 Notebook.prototype.move_cell_up = function (index) {
564 var i = this.index_or_selected(index);
564 var i = this.index_or_selected(index);
565 if (this.is_valid_cell_index(i) && i > 0) {
565 if (this.is_valid_cell_index(i) && i > 0) {
566 var pivot = this.get_cell_element(i-1);
566 var pivot = this.get_cell_element(i-1);
567 var tomove = this.get_cell_element(i);
567 var tomove = this.get_cell_element(i);
568 if (pivot !== null && tomove !== null) {
568 if (pivot !== null && tomove !== null) {
569 tomove.detach();
569 tomove.detach();
570 pivot.before(tomove);
570 pivot.before(tomove);
571 this.select(i-1);
571 this.select(i-1);
572 var cell = this.get_selected_cell();
572 var cell = this.get_selected_cell();
573 cell.focus_cell();
573 cell.focus_cell();
574 };
574 };
575 this.set_dirty(true);
575 this.set_dirty(true);
576 };
576 };
577 return this;
577 return this;
578 };
578 };
579
579
580
580
581 /**
581 /**
582 * Move given (or selected) cell down and select it
582 * Move given (or selected) cell down and select it
583 *
583 *
584 * @method move_cell_down
584 * @method move_cell_down
585 * @param [index] {integer} cell index
585 * @param [index] {integer} cell index
586 * @return {Notebook} This notebook
586 * @return {Notebook} This notebook
587 **/
587 **/
588 Notebook.prototype.move_cell_down = function (index) {
588 Notebook.prototype.move_cell_down = function (index) {
589 var i = this.index_or_selected(index);
589 var i = this.index_or_selected(index);
590 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
590 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
591 var pivot = this.get_cell_element(i+1);
591 var pivot = this.get_cell_element(i+1);
592 var tomove = this.get_cell_element(i);
592 var tomove = this.get_cell_element(i);
593 if (pivot !== null && tomove !== null) {
593 if (pivot !== null && tomove !== null) {
594 tomove.detach();
594 tomove.detach();
595 pivot.after(tomove);
595 pivot.after(tomove);
596 this.select(i+1);
596 this.select(i+1);
597 var cell = this.get_selected_cell();
597 var cell = this.get_selected_cell();
598 cell.focus_cell();
598 cell.focus_cell();
599 };
599 };
600 };
600 };
601 this.set_dirty();
601 this.set_dirty();
602 return this;
602 return this;
603 };
603 };
604
604
605
605
606 // Insertion, deletion.
606 // Insertion, deletion.
607
607
608 /**
608 /**
609 * Delete a cell from the notebook.
609 * Delete a cell from the notebook.
610 *
610 *
611 * @method delete_cell
611 * @method delete_cell
612 * @param [index] A cell's numeric index
612 * @param [index] A cell's numeric index
613 * @return {Notebook} This notebook
613 * @return {Notebook} This notebook
614 */
614 */
615 Notebook.prototype.delete_cell = function (index) {
615 Notebook.prototype.delete_cell = function (index) {
616 var i = this.index_or_selected(index);
616 var i = this.index_or_selected(index);
617 var cell = this.get_selected_cell();
617 var cell = this.get_selected_cell();
618 this.undelete_backup = cell.toJSON();
618 this.undelete_backup = cell.toJSON();
619 $('#undelete_cell').removeClass('disabled');
619 $('#undelete_cell').removeClass('disabled');
620 if (this.is_valid_cell_index(i)) {
620 if (this.is_valid_cell_index(i)) {
621 var old_ncells = this.ncells();
621 var old_ncells = this.ncells();
622 var ce = this.get_cell_element(i);
622 var ce = this.get_cell_element(i);
623 ce.remove();
623 ce.remove();
624 if (i === 0) {
624 if (i === 0) {
625 // Always make sure we have at least one cell.
625 // Always make sure we have at least one cell.
626 if (old_ncells === 1) {
626 if (old_ncells === 1) {
627 this.insert_cell_below('code');
627 this.insert_cell_below('code');
628 }
628 }
629 this.select(0);
629 this.select(0);
630 this.undelete_index = 0;
630 this.undelete_index = 0;
631 this.undelete_below = false;
631 this.undelete_below = false;
632 } else if (i === old_ncells-1 && i !== 0) {
632 } else if (i === old_ncells-1 && i !== 0) {
633 this.select(i-1);
633 this.select(i-1);
634 this.undelete_index = i - 1;
634 this.undelete_index = i - 1;
635 this.undelete_below = true;
635 this.undelete_below = true;
636 } else {
636 } else {
637 this.select(i);
637 this.select(i);
638 this.undelete_index = i;
638 this.undelete_index = i;
639 this.undelete_below = false;
639 this.undelete_below = false;
640 };
640 };
641 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
641 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
642 this.set_dirty(true);
642 this.set_dirty(true);
643 };
643 };
644 return this;
644 return this;
645 };
645 };
646
646
647 /**
647 /**
648 * Restore the most recently deleted cell.
648 * Restore the most recently deleted cell.
649 *
649 *
650 * @method undelete
650 * @method undelete
651 */
651 */
652 Notebook.prototype.undelete_cell = function() {
652 Notebook.prototype.undelete_cell = function() {
653 if (this.undelete_backup !== null && this.undelete_index !== null) {
653 if (this.undelete_backup !== null && this.undelete_index !== null) {
654 var current_index = this.get_selected_index();
654 var current_index = this.get_selected_index();
655 if (this.undelete_index < current_index) {
655 if (this.undelete_index < current_index) {
656 current_index = current_index + 1;
656 current_index = current_index + 1;
657 }
657 }
658 if (this.undelete_index >= this.ncells()) {
658 if (this.undelete_index >= this.ncells()) {
659 this.select(this.ncells() - 1);
659 this.select(this.ncells() - 1);
660 }
660 }
661 else {
661 else {
662 this.select(this.undelete_index);
662 this.select(this.undelete_index);
663 }
663 }
664 var cell_data = this.undelete_backup;
664 var cell_data = this.undelete_backup;
665 var new_cell = null;
665 var new_cell = null;
666 if (this.undelete_below) {
666 if (this.undelete_below) {
667 new_cell = this.insert_cell_below(cell_data.cell_type);
667 new_cell = this.insert_cell_below(cell_data.cell_type);
668 } else {
668 } else {
669 new_cell = this.insert_cell_above(cell_data.cell_type);
669 new_cell = this.insert_cell_above(cell_data.cell_type);
670 }
670 }
671 new_cell.fromJSON(cell_data);
671 new_cell.fromJSON(cell_data);
672 if (this.undelete_below) {
672 if (this.undelete_below) {
673 this.select(current_index+1);
673 this.select(current_index+1);
674 } else {
674 } else {
675 this.select(current_index);
675 this.select(current_index);
676 }
676 }
677 this.undelete_backup = null;
677 this.undelete_backup = null;
678 this.undelete_index = null;
678 this.undelete_index = null;
679 }
679 }
680 $('#undelete_cell').addClass('disabled');
680 $('#undelete_cell').addClass('disabled');
681 }
681 }
682
682
683 /**
683 /**
684 * Insert a cell so that after insertion the cell is at given index.
684 * Insert a cell so that after insertion the cell is at given index.
685 *
685 *
686 * Similar to insert_above, but index parameter is mandatory
686 * Similar to insert_above, but index parameter is mandatory
687 *
687 *
688 * Index will be brought back into the accissible range [0,n]
688 * Index will be brought back into the accissible range [0,n]
689 *
689 *
690 * @method insert_cell_at_index
690 * @method insert_cell_at_index
691 * @param type {string} in ['code','markdown','heading']
691 * @param type {string} in ['code','markdown','heading']
692 * @param [index] {int} a valid index where to inser cell
692 * @param [index] {int} a valid index where to inser cell
693 *
693 *
694 * @return cell {cell|null} created cell or null
694 * @return cell {cell|null} created cell or null
695 **/
695 **/
696 Notebook.prototype.insert_cell_at_index = function(type, index){
696 Notebook.prototype.insert_cell_at_index = function(type, index){
697
697
698 var ncells = this.ncells();
698 var ncells = this.ncells();
699 var index = Math.min(index,ncells);
699 var index = Math.min(index,ncells);
700 index = Math.max(index,0);
700 index = Math.max(index,0);
701 var cell = null;
701 var cell = null;
702
702
703 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
703 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
704 if (type === 'code') {
704 if (type === 'code') {
705 cell = new IPython.CodeCell(this.kernel);
705 cell = new IPython.CodeCell(this.kernel);
706 cell.set_input_prompt();
706 cell.set_input_prompt();
707 } else if (type === 'markdown') {
707 } else if (type === 'markdown') {
708 cell = new IPython.MarkdownCell();
708 cell = new IPython.MarkdownCell();
709 } else if (type === 'raw') {
709 } else if (type === 'raw') {
710 cell = new IPython.RawCell();
710 cell = new IPython.RawCell();
711 } else if (type === 'heading') {
711 } else if (type === 'heading') {
712 cell = new IPython.HeadingCell();
712 cell = new IPython.HeadingCell();
713 }
713 }
714
714
715 if(this._insert_element_at_index(cell.element,index)) {
715 if(this._insert_element_at_index(cell.element,index)) {
716 cell.render();
716 cell.render();
717 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
717 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
718 cell.refresh();
718 cell.refresh();
719 // We used to select the cell after we refresh it, but there
719 // We used to select the cell after we refresh it, but there
720 // are now cases were this method is called where select is
720 // are now cases were this method is called where select is
721 // not appropriate. The selection logic should be handled by the
721 // not appropriate. The selection logic should be handled by the
722 // caller of the the top level insert_cell methods.
722 // caller of the the top level insert_cell methods.
723 this.set_dirty(true);
723 this.set_dirty(true);
724 }
724 }
725 }
725 }
726 return cell;
726 return cell;
727
727
728 };
728 };
729
729
730 /**
730 /**
731 * Insert an element at given cell index.
731 * Insert an element at given cell index.
732 *
732 *
733 * @method _insert_element_at_index
733 * @method _insert_element_at_index
734 * @param element {dom element} a cell element
734 * @param element {dom element} a cell element
735 * @param [index] {int} a valid index where to inser cell
735 * @param [index] {int} a valid index where to inser cell
736 * @private
736 * @private
737 *
737 *
738 * return true if everything whent fine.
738 * return true if everything whent fine.
739 **/
739 **/
740 Notebook.prototype._insert_element_at_index = function(element, index){
740 Notebook.prototype._insert_element_at_index = function(element, index){
741 if (element === undefined){
741 if (element === undefined){
742 return false;
742 return false;
743 }
743 }
744
744
745 var ncells = this.ncells();
745 var ncells = this.ncells();
746
746
747 if (ncells === 0) {
747 if (ncells === 0) {
748 // special case append if empty
748 // special case append if empty
749 this.element.find('div.end_space').before(element);
749 this.element.find('div.end_space').before(element);
750 } else if ( ncells === index ) {
750 } else if ( ncells === index ) {
751 // special case append it the end, but not empty
751 // special case append it the end, but not empty
752 this.get_cell_element(index-1).after(element);
752 this.get_cell_element(index-1).after(element);
753 } else if (this.is_valid_cell_index(index)) {
753 } else if (this.is_valid_cell_index(index)) {
754 // otherwise always somewhere to append to
754 // otherwise always somewhere to append to
755 this.get_cell_element(index).before(element);
755 this.get_cell_element(index).before(element);
756 } else {
756 } else {
757 return false;
757 return false;
758 }
758 }
759
759
760 if (this.undelete_index !== null && index <= this.undelete_index) {
760 if (this.undelete_index !== null && index <= this.undelete_index) {
761 this.undelete_index = this.undelete_index + 1;
761 this.undelete_index = this.undelete_index + 1;
762 this.set_dirty(true);
762 this.set_dirty(true);
763 }
763 }
764 return true;
764 return true;
765 };
765 };
766
766
767 /**
767 /**
768 * Insert a cell of given type above given index, or at top
768 * Insert a cell of given type above given index, or at top
769 * of notebook if index smaller than 0.
769 * of notebook if index smaller than 0.
770 *
770 *
771 * default index value is the one of currently selected cell
771 * default index value is the one of currently selected cell
772 *
772 *
773 * @method insert_cell_above
773 * @method insert_cell_above
774 * @param type {string} cell type
774 * @param type {string} cell type
775 * @param [index] {integer}
775 * @param [index] {integer}
776 *
776 *
777 * @return handle to created cell or null
777 * @return handle to created cell or null
778 **/
778 **/
779 Notebook.prototype.insert_cell_above = function (type, index) {
779 Notebook.prototype.insert_cell_above = function (type, index) {
780 index = this.index_or_selected(index);
780 index = this.index_or_selected(index);
781 return this.insert_cell_at_index(type, index);
781 return this.insert_cell_at_index(type, index);
782 };
782 };
783
783
784 /**
784 /**
785 * Insert a cell of given type below given index, or at bottom
785 * Insert a cell of given type below given index, or at bottom
786 * of notebook if index greater thatn number of cell
786 * of notebook if index greater thatn number of cell
787 *
787 *
788 * default index value is the one of currently selected cell
788 * default index value is the one of currently selected cell
789 *
789 *
790 * @method insert_cell_below
790 * @method insert_cell_below
791 * @param type {string} cell type
791 * @param type {string} cell type
792 * @param [index] {integer}
792 * @param [index] {integer}
793 *
793 *
794 * @return handle to created cell or null
794 * @return handle to created cell or null
795 *
795 *
796 **/
796 **/
797 Notebook.prototype.insert_cell_below = function (type, index) {
797 Notebook.prototype.insert_cell_below = function (type, index) {
798 index = this.index_or_selected(index);
798 index = this.index_or_selected(index);
799 return this.insert_cell_at_index(type, index+1);
799 return this.insert_cell_at_index(type, index+1);
800 };
800 };
801
801
802
802
803 /**
803 /**
804 * Insert cell at end of notebook
804 * Insert cell at end of notebook
805 *
805 *
806 * @method insert_cell_at_bottom
806 * @method insert_cell_at_bottom
807 * @param {String} type cell type
807 * @param {String} type cell type
808 *
808 *
809 * @return the added cell; or null
809 * @return the added cell; or null
810 **/
810 **/
811 Notebook.prototype.insert_cell_at_bottom = function (type){
811 Notebook.prototype.insert_cell_at_bottom = function (type){
812 var len = this.ncells();
812 var len = this.ncells();
813 return this.insert_cell_below(type,len-1);
813 return this.insert_cell_below(type,len-1);
814 };
814 };
815
815
816 /**
816 /**
817 * Turn a cell into a code cell.
817 * Turn a cell into a code cell.
818 *
818 *
819 * @method to_code
819 * @method to_code
820 * @param {Number} [index] A cell's index
820 * @param {Number} [index] A cell's index
821 */
821 */
822 Notebook.prototype.to_code = function (index) {
822 Notebook.prototype.to_code = function (index) {
823 var i = this.index_or_selected(index);
823 var i = this.index_or_selected(index);
824 if (this.is_valid_cell_index(i)) {
824 if (this.is_valid_cell_index(i)) {
825 var source_element = this.get_cell_element(i);
825 var source_element = this.get_cell_element(i);
826 var source_cell = source_element.data("cell");
826 var source_cell = source_element.data("cell");
827 if (!(source_cell instanceof IPython.CodeCell)) {
827 if (!(source_cell instanceof IPython.CodeCell)) {
828 var target_cell = this.insert_cell_below('code',i);
828 var target_cell = this.insert_cell_below('code',i);
829 var text = source_cell.get_text();
829 var text = source_cell.get_text();
830 if (text === source_cell.placeholder) {
830 if (text === source_cell.placeholder) {
831 text = '';
831 text = '';
832 }
832 }
833 target_cell.set_text(text);
833 target_cell.set_text(text);
834 // make this value the starting point, so that we can only undo
834 // make this value the starting point, so that we can only undo
835 // to this state, instead of a blank cell
835 // to this state, instead of a blank cell
836 target_cell.code_mirror.clearHistory();
836 target_cell.code_mirror.clearHistory();
837 source_element.remove();
837 source_element.remove();
838 this.select(i);
838 this.select(i);
839 this.edit_mode();
839 this.edit_mode();
840 this.set_dirty(true);
840 this.set_dirty(true);
841 };
841 };
842 };
842 };
843 };
843 };
844
844
845 /**
845 /**
846 * Turn a cell into a Markdown cell.
846 * Turn a cell into a Markdown cell.
847 *
847 *
848 * @method to_markdown
848 * @method to_markdown
849 * @param {Number} [index] A cell's index
849 * @param {Number} [index] A cell's index
850 */
850 */
851 Notebook.prototype.to_markdown = function (index) {
851 Notebook.prototype.to_markdown = function (index) {
852 var i = this.index_or_selected(index);
852 var i = this.index_or_selected(index);
853 if (this.is_valid_cell_index(i)) {
853 if (this.is_valid_cell_index(i)) {
854 var source_element = this.get_cell_element(i);
854 var source_element = this.get_cell_element(i);
855 var source_cell = source_element.data("cell");
855 var source_cell = source_element.data("cell");
856 if (!(source_cell instanceof IPython.MarkdownCell)) {
856 if (!(source_cell instanceof IPython.MarkdownCell)) {
857 var target_cell = this.insert_cell_below('markdown',i);
857 var target_cell = this.insert_cell_below('markdown',i);
858 var text = source_cell.get_text();
858 var text = source_cell.get_text();
859 if (text === source_cell.placeholder) {
859 if (text === source_cell.placeholder) {
860 text = '';
860 text = '';
861 };
861 };
862 // We must show the editor before setting its contents
862 // We must show the editor before setting its contents
863 target_cell.unrender();
863 target_cell.unrender();
864 target_cell.set_text(text);
864 target_cell.set_text(text);
865 // make this value the starting point, so that we can only undo
865 // make this value the starting point, so that we can only undo
866 // to this state, instead of a blank cell
866 // to this state, instead of a blank cell
867 target_cell.code_mirror.clearHistory();
867 target_cell.code_mirror.clearHistory();
868 source_element.remove();
868 source_element.remove();
869 this.select(i);
869 this.select(i);
870 this.edit_mode();
870 this.edit_mode();
871 this.set_dirty(true);
871 this.set_dirty(true);
872 };
872 };
873 };
873 };
874 };
874 };
875
875
876 /**
876 /**
877 * Turn a cell into a raw text cell.
877 * Turn a cell into a raw text cell.
878 *
878 *
879 * @method to_raw
879 * @method to_raw
880 * @param {Number} [index] A cell's index
880 * @param {Number} [index] A cell's index
881 */
881 */
882 Notebook.prototype.to_raw = function (index) {
882 Notebook.prototype.to_raw = function (index) {
883 var i = this.index_or_selected(index);
883 var i = this.index_or_selected(index);
884 if (this.is_valid_cell_index(i)) {
884 if (this.is_valid_cell_index(i)) {
885 var source_element = this.get_cell_element(i);
885 var source_element = this.get_cell_element(i);
886 var source_cell = source_element.data("cell");
886 var source_cell = source_element.data("cell");
887 var target_cell = null;
887 var target_cell = null;
888 if (!(source_cell instanceof IPython.RawCell)) {
888 if (!(source_cell instanceof IPython.RawCell)) {
889 target_cell = this.insert_cell_below('raw',i);
889 target_cell = this.insert_cell_below('raw',i);
890 var text = source_cell.get_text();
890 var text = source_cell.get_text();
891 if (text === source_cell.placeholder) {
891 if (text === source_cell.placeholder) {
892 text = '';
892 text = '';
893 };
893 };
894 // We must show the editor before setting its contents
894 // We must show the editor before setting its contents
895 target_cell.unrender();
895 target_cell.unrender();
896 target_cell.set_text(text);
896 target_cell.set_text(text);
897 // make this value the starting point, so that we can only undo
897 // make this value the starting point, so that we can only undo
898 // to this state, instead of a blank cell
898 // to this state, instead of a blank cell
899 target_cell.code_mirror.clearHistory();
899 target_cell.code_mirror.clearHistory();
900 source_element.remove();
900 source_element.remove();
901 this.select(i);
901 this.select(i);
902 this.edit_mode();
902 this.edit_mode();
903 this.set_dirty(true);
903 this.set_dirty(true);
904 };
904 };
905 };
905 };
906 };
906 };
907
907
908 /**
908 /**
909 * Turn a cell into a heading cell.
909 * Turn a cell into a heading cell.
910 *
910 *
911 * @method to_heading
911 * @method to_heading
912 * @param {Number} [index] A cell's index
912 * @param {Number} [index] A cell's index
913 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
913 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
914 */
914 */
915 Notebook.prototype.to_heading = function (index, level) {
915 Notebook.prototype.to_heading = function (index, level) {
916 level = level || 1;
916 level = level || 1;
917 var i = this.index_or_selected(index);
917 var i = this.index_or_selected(index);
918 if (this.is_valid_cell_index(i)) {
918 if (this.is_valid_cell_index(i)) {
919 var source_element = this.get_cell_element(i);
919 var source_element = this.get_cell_element(i);
920 var source_cell = source_element.data("cell");
920 var source_cell = source_element.data("cell");
921 var target_cell = null;
921 var target_cell = null;
922 if (source_cell instanceof IPython.HeadingCell) {
922 if (source_cell instanceof IPython.HeadingCell) {
923 source_cell.set_level(level);
923 source_cell.set_level(level);
924 } else {
924 } else {
925 target_cell = this.insert_cell_below('heading',i);
925 target_cell = this.insert_cell_below('heading',i);
926 var text = source_cell.get_text();
926 var text = source_cell.get_text();
927 if (text === source_cell.placeholder) {
927 if (text === source_cell.placeholder) {
928 text = '';
928 text = '';
929 };
929 };
930 // We must show the editor before setting its contents
930 // We must show the editor before setting its contents
931 target_cell.set_level(level);
931 target_cell.set_level(level);
932 target_cell.unrender();
932 target_cell.unrender();
933 target_cell.set_text(text);
933 target_cell.set_text(text);
934 // make this value the starting point, so that we can only undo
934 // make this value the starting point, so that we can only undo
935 // to this state, instead of a blank cell
935 // to this state, instead of a blank cell
936 target_cell.code_mirror.clearHistory();
936 target_cell.code_mirror.clearHistory();
937 source_element.remove();
937 source_element.remove();
938 this.select(i);
938 this.select(i);
939 };
939 };
940 this.edit_mode();
940 this.edit_mode();
941 this.set_dirty(true);
941 this.set_dirty(true);
942 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
942 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
943 {'cell_type':'heading',level:level}
943 {'cell_type':'heading',level:level}
944 );
944 );
945 };
945 };
946 };
946 };
947
947
948
948
949 // Cut/Copy/Paste
949 // Cut/Copy/Paste
950
950
951 /**
951 /**
952 * Enable UI elements for pasting cells.
952 * Enable UI elements for pasting cells.
953 *
953 *
954 * @method enable_paste
954 * @method enable_paste
955 */
955 */
956 Notebook.prototype.enable_paste = function () {
956 Notebook.prototype.enable_paste = function () {
957 var that = this;
957 var that = this;
958 if (!this.paste_enabled) {
958 if (!this.paste_enabled) {
959 $('#paste_cell_replace').removeClass('disabled')
959 $('#paste_cell_replace').removeClass('disabled')
960 .on('click', function () {that.paste_cell_replace();});
960 .on('click', function () {that.paste_cell_replace();});
961 $('#paste_cell_above').removeClass('disabled')
961 $('#paste_cell_above').removeClass('disabled')
962 .on('click', function () {that.paste_cell_above();});
962 .on('click', function () {that.paste_cell_above();});
963 $('#paste_cell_below').removeClass('disabled')
963 $('#paste_cell_below').removeClass('disabled')
964 .on('click', function () {that.paste_cell_below();});
964 .on('click', function () {that.paste_cell_below();});
965 this.paste_enabled = true;
965 this.paste_enabled = true;
966 };
966 };
967 };
967 };
968
968
969 /**
969 /**
970 * Disable UI elements for pasting cells.
970 * Disable UI elements for pasting cells.
971 *
971 *
972 * @method disable_paste
972 * @method disable_paste
973 */
973 */
974 Notebook.prototype.disable_paste = function () {
974 Notebook.prototype.disable_paste = function () {
975 if (this.paste_enabled) {
975 if (this.paste_enabled) {
976 $('#paste_cell_replace').addClass('disabled').off('click');
976 $('#paste_cell_replace').addClass('disabled').off('click');
977 $('#paste_cell_above').addClass('disabled').off('click');
977 $('#paste_cell_above').addClass('disabled').off('click');
978 $('#paste_cell_below').addClass('disabled').off('click');
978 $('#paste_cell_below').addClass('disabled').off('click');
979 this.paste_enabled = false;
979 this.paste_enabled = false;
980 };
980 };
981 };
981 };
982
982
983 /**
983 /**
984 * Cut a cell.
984 * Cut a cell.
985 *
985 *
986 * @method cut_cell
986 * @method cut_cell
987 */
987 */
988 Notebook.prototype.cut_cell = function () {
988 Notebook.prototype.cut_cell = function () {
989 this.copy_cell();
989 this.copy_cell();
990 this.delete_cell();
990 this.delete_cell();
991 }
991 }
992
992
993 /**
993 /**
994 * Copy a cell.
994 * Copy a cell.
995 *
995 *
996 * @method copy_cell
996 * @method copy_cell
997 */
997 */
998 Notebook.prototype.copy_cell = function () {
998 Notebook.prototype.copy_cell = function () {
999 var cell = this.get_selected_cell();
999 var cell = this.get_selected_cell();
1000 this.clipboard = cell.toJSON();
1000 this.clipboard = cell.toJSON();
1001 this.enable_paste();
1001 this.enable_paste();
1002 };
1002 };
1003
1003
1004 /**
1004 /**
1005 * Replace the selected cell with a cell in the clipboard.
1005 * Replace the selected cell with a cell in the clipboard.
1006 *
1006 *
1007 * @method paste_cell_replace
1007 * @method paste_cell_replace
1008 */
1008 */
1009 Notebook.prototype.paste_cell_replace = function () {
1009 Notebook.prototype.paste_cell_replace = function () {
1010 if (this.clipboard !== null && this.paste_enabled) {
1010 if (this.clipboard !== null && this.paste_enabled) {
1011 var cell_data = this.clipboard;
1011 var cell_data = this.clipboard;
1012 var new_cell = this.insert_cell_above(cell_data.cell_type);
1012 var new_cell = this.insert_cell_above(cell_data.cell_type);
1013 new_cell.fromJSON(cell_data);
1013 new_cell.fromJSON(cell_data);
1014 var old_cell = this.get_next_cell(new_cell);
1014 var old_cell = this.get_next_cell(new_cell);
1015 this.delete_cell(this.find_cell_index(old_cell));
1015 this.delete_cell(this.find_cell_index(old_cell));
1016 this.select(this.find_cell_index(new_cell));
1016 this.select(this.find_cell_index(new_cell));
1017 };
1017 };
1018 };
1018 };
1019
1019
1020 /**
1020 /**
1021 * Paste a cell from the clipboard above the selected cell.
1021 * Paste a cell from the clipboard above the selected cell.
1022 *
1022 *
1023 * @method paste_cell_above
1023 * @method paste_cell_above
1024 */
1024 */
1025 Notebook.prototype.paste_cell_above = function () {
1025 Notebook.prototype.paste_cell_above = function () {
1026 if (this.clipboard !== null && this.paste_enabled) {
1026 if (this.clipboard !== null && this.paste_enabled) {
1027 var cell_data = this.clipboard;
1027 var cell_data = this.clipboard;
1028 var new_cell = this.insert_cell_above(cell_data.cell_type);
1028 var new_cell = this.insert_cell_above(cell_data.cell_type);
1029 new_cell.fromJSON(cell_data);
1029 new_cell.fromJSON(cell_data);
1030 };
1030 };
1031 };
1031 };
1032
1032
1033 /**
1033 /**
1034 * Paste a cell from the clipboard below the selected cell.
1034 * Paste a cell from the clipboard below the selected cell.
1035 *
1035 *
1036 * @method paste_cell_below
1036 * @method paste_cell_below
1037 */
1037 */
1038 Notebook.prototype.paste_cell_below = function () {
1038 Notebook.prototype.paste_cell_below = function () {
1039 if (this.clipboard !== null && this.paste_enabled) {
1039 if (this.clipboard !== null && this.paste_enabled) {
1040 var cell_data = this.clipboard;
1040 var cell_data = this.clipboard;
1041 var new_cell = this.insert_cell_below(cell_data.cell_type);
1041 var new_cell = this.insert_cell_below(cell_data.cell_type);
1042 new_cell.fromJSON(cell_data);
1042 new_cell.fromJSON(cell_data);
1043 };
1043 };
1044 };
1044 };
1045
1045
1046 // Split/merge
1046 // Split/merge
1047
1047
1048 /**
1048 /**
1049 * Split the selected cell into two, at the cursor.
1049 * Split the selected cell into two, at the cursor.
1050 *
1050 *
1051 * @method split_cell
1051 * @method split_cell
1052 */
1052 */
1053 Notebook.prototype.split_cell = function () {
1053 Notebook.prototype.split_cell = function () {
1054 var mdc = IPython.MarkdownCell;
1054 var mdc = IPython.MarkdownCell;
1055 var rc = IPython.RawCell;
1055 var rc = IPython.RawCell;
1056 var cell = this.get_selected_cell();
1056 var cell = this.get_selected_cell();
1057 if (cell.is_splittable()) {
1057 if (cell.is_splittable()) {
1058 var texta = cell.get_pre_cursor();
1058 var texta = cell.get_pre_cursor();
1059 var textb = cell.get_post_cursor();
1059 var textb = cell.get_post_cursor();
1060 if (cell instanceof IPython.CodeCell) {
1060 if (cell instanceof IPython.CodeCell) {
1061 // In this case the operations keep the notebook in its existing mode
1061 // In this case the operations keep the notebook in its existing mode
1062 // so we don't need to do any post-op mode changes.
1062 // so we don't need to do any post-op mode changes.
1063 cell.set_text(textb);
1063 cell.set_text(textb);
1064 var new_cell = this.insert_cell_above('code');
1064 var new_cell = this.insert_cell_above('code');
1065 new_cell.set_text(texta);
1065 new_cell.set_text(texta);
1066 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1066 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1067 // We know cell is !rendered so we can use set_text.
1067 // We know cell is !rendered so we can use set_text.
1068 cell.set_text(textb);
1068 cell.set_text(textb);
1069 var new_cell = this.insert_cell_above(cell.cell_type);
1069 var new_cell = this.insert_cell_above(cell.cell_type);
1070 // Unrender the new cell so we can call set_text.
1070 // Unrender the new cell so we can call set_text.
1071 new_cell.unrender();
1071 new_cell.unrender();
1072 new_cell.set_text(texta);
1072 new_cell.set_text(texta);
1073 }
1073 }
1074 };
1074 };
1075 };
1075 };
1076
1076
1077 /**
1077 /**
1078 * Combine the selected cell into the cell above it.
1078 * Combine the selected cell into the cell above it.
1079 *
1079 *
1080 * @method merge_cell_above
1080 * @method merge_cell_above
1081 */
1081 */
1082 Notebook.prototype.merge_cell_above = function () {
1082 Notebook.prototype.merge_cell_above = function () {
1083 var mdc = IPython.MarkdownCell;
1083 var mdc = IPython.MarkdownCell;
1084 var rc = IPython.RawCell;
1084 var rc = IPython.RawCell;
1085 var index = this.get_selected_index();
1085 var index = this.get_selected_index();
1086 var cell = this.get_cell(index);
1086 var cell = this.get_cell(index);
1087 var render = cell.rendered;
1087 var render = cell.rendered;
1088 if (!cell.is_mergeable()) {
1088 if (!cell.is_mergeable()) {
1089 return;
1089 return;
1090 }
1090 }
1091 if (index > 0) {
1091 if (index > 0) {
1092 var upper_cell = this.get_cell(index-1);
1092 var upper_cell = this.get_cell(index-1);
1093 if (!upper_cell.is_mergeable()) {
1093 if (!upper_cell.is_mergeable()) {
1094 return;
1094 return;
1095 }
1095 }
1096 var upper_text = upper_cell.get_text();
1096 var upper_text = upper_cell.get_text();
1097 var text = cell.get_text();
1097 var text = cell.get_text();
1098 if (cell instanceof IPython.CodeCell) {
1098 if (cell instanceof IPython.CodeCell) {
1099 cell.set_text(upper_text+'\n'+text);
1099 cell.set_text(upper_text+'\n'+text);
1100 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1100 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1101 cell.unrender(); // Must unrender before we set_text.
1101 cell.unrender(); // Must unrender before we set_text.
1102 cell.set_text(upper_text+'\n\n'+text);
1102 cell.set_text(upper_text+'\n\n'+text);
1103 if (render) {
1103 if (render) {
1104 // The rendered state of the final cell should match
1104 // The rendered state of the final cell should match
1105 // that of the original selected cell;
1105 // that of the original selected cell;
1106 cell.render();
1106 cell.render();
1107 }
1107 }
1108 };
1108 };
1109 this.delete_cell(index-1);
1109 this.delete_cell(index-1);
1110 this.select(this.find_cell_index(cell));
1110 this.select(this.find_cell_index(cell));
1111 };
1111 };
1112 };
1112 };
1113
1113
1114 /**
1114 /**
1115 * Combine the selected cell into the cell below it.
1115 * Combine the selected cell into the cell below it.
1116 *
1116 *
1117 * @method merge_cell_below
1117 * @method merge_cell_below
1118 */
1118 */
1119 Notebook.prototype.merge_cell_below = function () {
1119 Notebook.prototype.merge_cell_below = function () {
1120 var mdc = IPython.MarkdownCell;
1120 var mdc = IPython.MarkdownCell;
1121 var rc = IPython.RawCell;
1121 var rc = IPython.RawCell;
1122 var index = this.get_selected_index();
1122 var index = this.get_selected_index();
1123 var cell = this.get_cell(index);
1123 var cell = this.get_cell(index);
1124 var render = cell.rendered;
1124 var render = cell.rendered;
1125 if (!cell.is_mergeable()) {
1125 if (!cell.is_mergeable()) {
1126 return;
1126 return;
1127 }
1127 }
1128 if (index < this.ncells()-1) {
1128 if (index < this.ncells()-1) {
1129 var lower_cell = this.get_cell(index+1);
1129 var lower_cell = this.get_cell(index+1);
1130 if (!lower_cell.is_mergeable()) {
1130 if (!lower_cell.is_mergeable()) {
1131 return;
1131 return;
1132 }
1132 }
1133 var lower_text = lower_cell.get_text();
1133 var lower_text = lower_cell.get_text();
1134 var text = cell.get_text();
1134 var text = cell.get_text();
1135 if (cell instanceof IPython.CodeCell) {
1135 if (cell instanceof IPython.CodeCell) {
1136 cell.set_text(text+'\n'+lower_text);
1136 cell.set_text(text+'\n'+lower_text);
1137 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1137 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1138 cell.unrender(); // Must unrender before we set_text.
1138 cell.unrender(); // Must unrender before we set_text.
1139 cell.set_text(text+'\n\n'+lower_text);
1139 cell.set_text(text+'\n\n'+lower_text);
1140 if (render) {
1140 if (render) {
1141 // The rendered state of the final cell should match
1141 // The rendered state of the final cell should match
1142 // that of the original selected cell;
1142 // that of the original selected cell;
1143 cell.render();
1143 cell.render();
1144 }
1144 }
1145 };
1145 };
1146 this.delete_cell(index+1);
1146 this.delete_cell(index+1);
1147 this.select(this.find_cell_index(cell));
1147 this.select(this.find_cell_index(cell));
1148 };
1148 };
1149 };
1149 };
1150
1150
1151
1151
1152 // Cell collapsing and output clearing
1152 // Cell collapsing and output clearing
1153
1153
1154 /**
1154 /**
1155 * Hide a cell's output.
1155 * Hide a cell's output.
1156 *
1156 *
1157 * @method collapse
1157 * @method collapse
1158 * @param {Number} index A cell's numeric index
1158 * @param {Number} index A cell's numeric index
1159 */
1159 */
1160 Notebook.prototype.collapse = function (index) {
1160 Notebook.prototype.collapse = function (index) {
1161 var i = this.index_or_selected(index);
1161 var i = this.index_or_selected(index);
1162 this.get_cell(i).collapse();
1162 this.get_cell(i).collapse();
1163 this.set_dirty(true);
1163 this.set_dirty(true);
1164 };
1164 };
1165
1165
1166 /**
1166 /**
1167 * Show a cell's output.
1167 * Show a cell's output.
1168 *
1168 *
1169 * @method expand
1169 * @method expand
1170 * @param {Number} index A cell's numeric index
1170 * @param {Number} index A cell's numeric index
1171 */
1171 */
1172 Notebook.prototype.expand = function (index) {
1172 Notebook.prototype.expand = function (index) {
1173 var i = this.index_or_selected(index);
1173 var i = this.index_or_selected(index);
1174 this.get_cell(i).expand();
1174 this.get_cell(i).expand();
1175 this.set_dirty(true);
1175 this.set_dirty(true);
1176 };
1176 };
1177
1177
1178 /** Toggle whether a cell's output is collapsed or expanded.
1178 /** Toggle whether a cell's output is collapsed or expanded.
1179 *
1179 *
1180 * @method toggle_output
1180 * @method toggle_output
1181 * @param {Number} index A cell's numeric index
1181 * @param {Number} index A cell's numeric index
1182 */
1182 */
1183 Notebook.prototype.toggle_output = function (index) {
1183 Notebook.prototype.toggle_output = function (index) {
1184 var i = this.index_or_selected(index);
1184 var i = this.index_or_selected(index);
1185 this.get_cell(i).toggle_output();
1185 this.get_cell(i).toggle_output();
1186 this.set_dirty(true);
1186 this.set_dirty(true);
1187 };
1187 };
1188
1188
1189 /**
1189 /**
1190 * Toggle a scrollbar for long cell outputs.
1190 * Toggle a scrollbar for long cell outputs.
1191 *
1191 *
1192 * @method toggle_output_scroll
1192 * @method toggle_output_scroll
1193 * @param {Number} index A cell's numeric index
1193 * @param {Number} index A cell's numeric index
1194 */
1194 */
1195 Notebook.prototype.toggle_output_scroll = function (index) {
1195 Notebook.prototype.toggle_output_scroll = function (index) {
1196 var i = this.index_or_selected(index);
1196 var i = this.index_or_selected(index);
1197 this.get_cell(i).toggle_output_scroll();
1197 this.get_cell(i).toggle_output_scroll();
1198 };
1198 };
1199
1199
1200 /**
1200 /**
1201 * Hide each code cell's output area.
1201 * Hide each code cell's output area.
1202 *
1202 *
1203 * @method collapse_all_output
1203 * @method collapse_all_output
1204 */
1204 */
1205 Notebook.prototype.collapse_all_output = function () {
1205 Notebook.prototype.collapse_all_output = function () {
1206 var ncells = this.ncells();
1206 var ncells = this.ncells();
1207 var cells = this.get_cells();
1207 var cells = this.get_cells();
1208 for (var i=0; i<ncells; i++) {
1208 for (var i=0; i<ncells; i++) {
1209 if (cells[i] instanceof IPython.CodeCell) {
1209 if (cells[i] instanceof IPython.CodeCell) {
1210 cells[i].output_area.collapse();
1210 cells[i].output_area.collapse();
1211 }
1211 }
1212 };
1212 };
1213 // this should not be set if the `collapse` key is removed from nbformat
1213 // this should not be set if the `collapse` key is removed from nbformat
1214 this.set_dirty(true);
1214 this.set_dirty(true);
1215 };
1215 };
1216
1216
1217 /**
1217 /**
1218 * Expand each code cell's output area, and add a scrollbar for long output.
1218 * Expand each code cell's output area, and add a scrollbar for long output.
1219 *
1219 *
1220 * @method scroll_all_output
1220 * @method scroll_all_output
1221 */
1221 */
1222 Notebook.prototype.scroll_all_output = function () {
1222 Notebook.prototype.scroll_all_output = function () {
1223 var ncells = this.ncells();
1223 var ncells = this.ncells();
1224 var cells = this.get_cells();
1224 var cells = this.get_cells();
1225 for (var i=0; i<ncells; i++) {
1225 for (var i=0; i<ncells; i++) {
1226 if (cells[i] instanceof IPython.CodeCell) {
1226 if (cells[i] instanceof IPython.CodeCell) {
1227 cells[i].output_area.expand();
1227 cells[i].output_area.expand();
1228 cells[i].output_area.scroll_if_long();
1228 cells[i].output_area.scroll_if_long();
1229 }
1229 }
1230 };
1230 };
1231 // this should not be set if the `collapse` key is removed from nbformat
1231 // this should not be set if the `collapse` key is removed from nbformat
1232 this.set_dirty(true);
1232 this.set_dirty(true);
1233 };
1233 };
1234
1234
1235 /**
1235 /**
1236 * Expand each code cell's output area, and remove scrollbars.
1236 * Expand each code cell's output area, and remove scrollbars.
1237 *
1237 *
1238 * @method expand_all_output
1238 * @method expand_all_output
1239 */
1239 */
1240 Notebook.prototype.expand_all_output = function () {
1240 Notebook.prototype.expand_all_output = function () {
1241 var ncells = this.ncells();
1241 var ncells = this.ncells();
1242 var cells = this.get_cells();
1242 var cells = this.get_cells();
1243 for (var i=0; i<ncells; i++) {
1243 for (var i=0; i<ncells; i++) {
1244 if (cells[i] instanceof IPython.CodeCell) {
1244 if (cells[i] instanceof IPython.CodeCell) {
1245 cells[i].output_area.expand();
1245 cells[i].output_area.expand();
1246 cells[i].output_area.unscroll_area();
1246 cells[i].output_area.unscroll_area();
1247 }
1247 }
1248 };
1248 };
1249 // this should not be set if the `collapse` key is removed from nbformat
1249 // this should not be set if the `collapse` key is removed from nbformat
1250 this.set_dirty(true);
1250 this.set_dirty(true);
1251 };
1251 };
1252
1252
1253 /**
1253 /**
1254 * Clear each code cell's output area.
1254 * Clear each code cell's output area.
1255 *
1255 *
1256 * @method clear_all_output
1256 * @method clear_all_output
1257 */
1257 */
1258 Notebook.prototype.clear_all_output = function () {
1258 Notebook.prototype.clear_all_output = function () {
1259 var ncells = this.ncells();
1259 var ncells = this.ncells();
1260 var cells = this.get_cells();
1260 var cells = this.get_cells();
1261 for (var i=0; i<ncells; i++) {
1261 for (var i=0; i<ncells; i++) {
1262 if (cells[i] instanceof IPython.CodeCell) {
1262 if (cells[i] instanceof IPython.CodeCell) {
1263 cells[i].clear_output();
1263 cells[i].clear_output();
1264 // Make all In[] prompts blank, as well
1264 // Make all In[] prompts blank, as well
1265 // TODO: make this configurable (via checkbox?)
1265 // TODO: make this configurable (via checkbox?)
1266 cells[i].set_input_prompt();
1266 cells[i].set_input_prompt();
1267 }
1267 }
1268 };
1268 };
1269 this.set_dirty(true);
1269 this.set_dirty(true);
1270 };
1270 };
1271
1271
1272
1272
1273 // Other cell functions: line numbers, ...
1273 // Other cell functions: line numbers, ...
1274
1274
1275 /**
1275 /**
1276 * Toggle line numbers in the selected cell's input area.
1276 * Toggle line numbers in the selected cell's input area.
1277 *
1277 *
1278 * @method cell_toggle_line_numbers
1278 * @method cell_toggle_line_numbers
1279 */
1279 */
1280 Notebook.prototype.cell_toggle_line_numbers = function() {
1280 Notebook.prototype.cell_toggle_line_numbers = function() {
1281 this.get_selected_cell().toggle_line_numbers();
1281 this.get_selected_cell().toggle_line_numbers();
1282 };
1282 };
1283
1283
1284 // Session related things
1284 // Session related things
1285
1285
1286 /**
1286 /**
1287 * Start a new session and set it on each code cell.
1287 * Start a new session and set it on each code cell.
1288 *
1288 *
1289 * @method start_session
1289 * @method start_session
1290 */
1290 */
1291 Notebook.prototype.start_session = function () {
1291 Notebook.prototype.start_session = function () {
1292 this.session = new IPython.Session(this.notebook_name, this.notebook_path, this);
1292 this.session = new IPython.Session(this.notebook_name, this.notebook_path, this);
1293 this.session.start($.proxy(this._session_started, this));
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 Notebook.prototype._session_started = function(){
1302 Notebook.prototype._session_started = function(){
1302 this.kernel = this.session.kernel;
1303 this.kernel = this.session.kernel;
1304 IPython.widget_manager.attach_comm_manager(this.kernel.comm_manager);
1303 var ncells = this.ncells();
1305 var ncells = this.ncells();
1304 for (var i=0; i<ncells; i++) {
1306 for (var i=0; i<ncells; i++) {
1305 var cell = this.get_cell(i);
1307 var cell = this.get_cell(i);
1306 if (cell instanceof IPython.CodeCell) {
1308 if (cell instanceof IPython.CodeCell) {
1307 cell.set_kernel(this.session.kernel);
1309 cell.set_kernel(this.session.kernel);
1308 };
1310 };
1309 };
1311 };
1310 };
1312 };
1311
1313
1312 /**
1314 /**
1313 * Prompt the user to restart the IPython kernel.
1315 * Prompt the user to restart the IPython kernel.
1314 *
1316 *
1315 * @method restart_kernel
1317 * @method restart_kernel
1316 */
1318 */
1317 Notebook.prototype.restart_kernel = function () {
1319 Notebook.prototype.restart_kernel = function () {
1318 var that = this;
1320 var that = this;
1319 IPython.dialog.modal({
1321 IPython.dialog.modal({
1320 title : "Restart kernel or continue running?",
1322 title : "Restart kernel or continue running?",
1321 body : $("<p/>").html(
1323 body : $("<p/>").html(
1322 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1324 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1323 ),
1325 ),
1324 buttons : {
1326 buttons : {
1325 "Continue running" : {},
1327 "Continue running" : {},
1326 "Restart" : {
1328 "Restart" : {
1327 "class" : "btn-danger",
1329 "class" : "btn-danger",
1328 "click" : function() {
1330 "click" : function() {
1329 that.session.restart_kernel();
1331 that.session.restart_kernel();
1330 }
1332 }
1331 }
1333 }
1332 }
1334 }
1333 });
1335 });
1334 };
1336 };
1335
1337
1336 /**
1338 /**
1337 * Execute or render cell outputs and go into command mode.
1339 * Execute or render cell outputs and go into command mode.
1338 *
1340 *
1339 * @method execute_cell
1341 * @method execute_cell
1340 */
1342 */
1341 Notebook.prototype.execute_cell = function () {
1343 Notebook.prototype.execute_cell = function () {
1342 // mode = shift, ctrl, alt
1344 // mode = shift, ctrl, alt
1343 var cell = this.get_selected_cell();
1345 var cell = this.get_selected_cell();
1344 var cell_index = this.find_cell_index(cell);
1346 var cell_index = this.find_cell_index(cell);
1345
1347
1346 cell.execute();
1348 cell.execute();
1347 this.command_mode();
1349 this.command_mode();
1348 cell.focus_cell();
1350 cell.focus_cell();
1349 this.set_dirty(true);
1351 this.set_dirty(true);
1350 }
1352 }
1351
1353
1352 /**
1354 /**
1353 * Execute or render cell outputs and insert a new cell below.
1355 * Execute or render cell outputs and insert a new cell below.
1354 *
1356 *
1355 * @method execute_cell_and_insert_below
1357 * @method execute_cell_and_insert_below
1356 */
1358 */
1357 Notebook.prototype.execute_cell_and_insert_below = function () {
1359 Notebook.prototype.execute_cell_and_insert_below = function () {
1358 var cell = this.get_selected_cell();
1360 var cell = this.get_selected_cell();
1359 var cell_index = this.find_cell_index(cell);
1361 var cell_index = this.find_cell_index(cell);
1360
1362
1361 cell.execute();
1363 cell.execute();
1362
1364
1363 // If we are at the end always insert a new cell and return
1365 // If we are at the end always insert a new cell and return
1364 if (cell_index === (this.ncells()-1)) {
1366 if (cell_index === (this.ncells()-1)) {
1365 this.insert_cell_below('code');
1367 this.insert_cell_below('code');
1366 this.select(cell_index+1);
1368 this.select(cell_index+1);
1367 this.edit_mode();
1369 this.edit_mode();
1368 this.scroll_to_bottom();
1370 this.scroll_to_bottom();
1369 this.set_dirty(true);
1371 this.set_dirty(true);
1370 return;
1372 return;
1371 }
1373 }
1372
1374
1373 // Only insert a new cell, if we ended up in an already populated cell
1375 // Only insert a new cell, if we ended up in an already populated cell
1374 var next_text = this.get_cell(cell_index+1).get_text();
1376 var next_text = this.get_cell(cell_index+1).get_text();
1375 if (/\S/.test(next_text) === true) {
1377 if (/\S/.test(next_text) === true) {
1376 this.insert_cell_below('code');
1378 this.insert_cell_below('code');
1377 }
1379 }
1378 this.select(cell_index+1);
1380 this.select(cell_index+1);
1379 this.edit_mode();
1381 this.edit_mode();
1380 this.set_dirty(true);
1382 this.set_dirty(true);
1381 };
1383 };
1382
1384
1383 /**
1385 /**
1384 * Execute or render cell outputs and select the next cell.
1386 * Execute or render cell outputs and select the next cell.
1385 *
1387 *
1386 * @method execute_cell_and_select_below
1388 * @method execute_cell_and_select_below
1387 */
1389 */
1388 Notebook.prototype.execute_cell_and_select_below = function () {
1390 Notebook.prototype.execute_cell_and_select_below = function () {
1389
1391
1390 var cell = this.get_selected_cell();
1392 var cell = this.get_selected_cell();
1391 var cell_index = this.find_cell_index(cell);
1393 var cell_index = this.find_cell_index(cell);
1392
1394
1393 cell.execute();
1395 cell.execute();
1394
1396
1395 // If we are at the end always insert a new cell and return
1397 // If we are at the end always insert a new cell and return
1396 if (cell_index === (this.ncells()-1)) {
1398 if (cell_index === (this.ncells()-1)) {
1397 this.insert_cell_below('code');
1399 this.insert_cell_below('code');
1398 this.select(cell_index+1);
1400 this.select(cell_index+1);
1399 this.edit_mode();
1401 this.edit_mode();
1400 this.scroll_to_bottom();
1402 this.scroll_to_bottom();
1401 this.set_dirty(true);
1403 this.set_dirty(true);
1402 return;
1404 return;
1403 }
1405 }
1404
1406
1405 this.select(cell_index+1);
1407 this.select(cell_index+1);
1406 this.get_cell(cell_index+1).focus_cell();
1408 this.get_cell(cell_index+1).focus_cell();
1407 this.set_dirty(true);
1409 this.set_dirty(true);
1408 };
1410 };
1409
1411
1410 /**
1412 /**
1411 * Execute all cells below the selected cell.
1413 * Execute all cells below the selected cell.
1412 *
1414 *
1413 * @method execute_cells_below
1415 * @method execute_cells_below
1414 */
1416 */
1415 Notebook.prototype.execute_cells_below = function () {
1417 Notebook.prototype.execute_cells_below = function () {
1416 this.execute_cell_range(this.get_selected_index(), this.ncells());
1418 this.execute_cell_range(this.get_selected_index(), this.ncells());
1417 this.scroll_to_bottom();
1419 this.scroll_to_bottom();
1418 };
1420 };
1419
1421
1420 /**
1422 /**
1421 * Execute all cells above the selected cell.
1423 * Execute all cells above the selected cell.
1422 *
1424 *
1423 * @method execute_cells_above
1425 * @method execute_cells_above
1424 */
1426 */
1425 Notebook.prototype.execute_cells_above = function () {
1427 Notebook.prototype.execute_cells_above = function () {
1426 this.execute_cell_range(0, this.get_selected_index());
1428 this.execute_cell_range(0, this.get_selected_index());
1427 };
1429 };
1428
1430
1429 /**
1431 /**
1430 * Execute all cells.
1432 * Execute all cells.
1431 *
1433 *
1432 * @method execute_all_cells
1434 * @method execute_all_cells
1433 */
1435 */
1434 Notebook.prototype.execute_all_cells = function () {
1436 Notebook.prototype.execute_all_cells = function () {
1435 this.execute_cell_range(0, this.ncells());
1437 this.execute_cell_range(0, this.ncells());
1436 this.scroll_to_bottom();
1438 this.scroll_to_bottom();
1437 };
1439 };
1438
1440
1439 /**
1441 /**
1440 * Execute a contiguous range of cells.
1442 * Execute a contiguous range of cells.
1441 *
1443 *
1442 * @method execute_cell_range
1444 * @method execute_cell_range
1443 * @param {Number} start Index of the first cell to execute (inclusive)
1445 * @param {Number} start Index of the first cell to execute (inclusive)
1444 * @param {Number} end Index of the last cell to execute (exclusive)
1446 * @param {Number} end Index of the last cell to execute (exclusive)
1445 */
1447 */
1446 Notebook.prototype.execute_cell_range = function (start, end) {
1448 Notebook.prototype.execute_cell_range = function (start, end) {
1447 for (var i=start; i<end; i++) {
1449 for (var i=start; i<end; i++) {
1448 this.select(i);
1450 this.select(i);
1449 this.execute_cell();
1451 this.execute_cell();
1450 };
1452 };
1451 };
1453 };
1452
1454
1453 // Persistance and loading
1455 // Persistance and loading
1454
1456
1455 /**
1457 /**
1456 * Getter method for this notebook's name.
1458 * Getter method for this notebook's name.
1457 *
1459 *
1458 * @method get_notebook_name
1460 * @method get_notebook_name
1459 * @return {String} This notebook's name
1461 * @return {String} This notebook's name
1460 */
1462 */
1461 Notebook.prototype.get_notebook_name = function () {
1463 Notebook.prototype.get_notebook_name = function () {
1462 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1464 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1463 return nbname;
1465 return nbname;
1464 };
1466 };
1465
1467
1466 /**
1468 /**
1467 * Setter method for this notebook's name.
1469 * Setter method for this notebook's name.
1468 *
1470 *
1469 * @method set_notebook_name
1471 * @method set_notebook_name
1470 * @param {String} name A new name for this notebook
1472 * @param {String} name A new name for this notebook
1471 */
1473 */
1472 Notebook.prototype.set_notebook_name = function (name) {
1474 Notebook.prototype.set_notebook_name = function (name) {
1473 this.notebook_name = name;
1475 this.notebook_name = name;
1474 };
1476 };
1475
1477
1476 /**
1478 /**
1477 * Check that a notebook's name is valid.
1479 * Check that a notebook's name is valid.
1478 *
1480 *
1479 * @method test_notebook_name
1481 * @method test_notebook_name
1480 * @param {String} nbname A name for this notebook
1482 * @param {String} nbname A name for this notebook
1481 * @return {Boolean} True if the name is valid, false if invalid
1483 * @return {Boolean} True if the name is valid, false if invalid
1482 */
1484 */
1483 Notebook.prototype.test_notebook_name = function (nbname) {
1485 Notebook.prototype.test_notebook_name = function (nbname) {
1484 nbname = nbname || '';
1486 nbname = nbname || '';
1485 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1487 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1486 return true;
1488 return true;
1487 } else {
1489 } else {
1488 return false;
1490 return false;
1489 };
1491 };
1490 };
1492 };
1491
1493
1492 /**
1494 /**
1493 * Load a notebook from JSON (.ipynb).
1495 * Load a notebook from JSON (.ipynb).
1494 *
1496 *
1495 * This currently handles one worksheet: others are deleted.
1497 * This currently handles one worksheet: others are deleted.
1496 *
1498 *
1497 * @method fromJSON
1499 * @method fromJSON
1498 * @param {Object} data JSON representation of a notebook
1500 * @param {Object} data JSON representation of a notebook
1499 */
1501 */
1500 Notebook.prototype.fromJSON = function (data) {
1502 Notebook.prototype.fromJSON = function (data) {
1501 var content = data.content;
1503 var content = data.content;
1502 var ncells = this.ncells();
1504 var ncells = this.ncells();
1503 var i;
1505 var i;
1504 for (i=0; i<ncells; i++) {
1506 for (i=0; i<ncells; i++) {
1505 // Always delete cell 0 as they get renumbered as they are deleted.
1507 // Always delete cell 0 as they get renumbered as they are deleted.
1506 this.delete_cell(0);
1508 this.delete_cell(0);
1507 };
1509 };
1508 // Save the metadata and name.
1510 // Save the metadata and name.
1509 this.metadata = content.metadata;
1511 this.metadata = content.metadata;
1510 this.notebook_name = data.name;
1512 this.notebook_name = data.name;
1511 // Only handle 1 worksheet for now.
1513 // Only handle 1 worksheet for now.
1512 var worksheet = content.worksheets[0];
1514 var worksheet = content.worksheets[0];
1513 if (worksheet !== undefined) {
1515 if (worksheet !== undefined) {
1514 if (worksheet.metadata) {
1516 if (worksheet.metadata) {
1515 this.worksheet_metadata = worksheet.metadata;
1517 this.worksheet_metadata = worksheet.metadata;
1516 }
1518 }
1517 var new_cells = worksheet.cells;
1519 var new_cells = worksheet.cells;
1518 ncells = new_cells.length;
1520 ncells = new_cells.length;
1519 var cell_data = null;
1521 var cell_data = null;
1520 var new_cell = null;
1522 var new_cell = null;
1521 for (i=0; i<ncells; i++) {
1523 for (i=0; i<ncells; i++) {
1522 cell_data = new_cells[i];
1524 cell_data = new_cells[i];
1523 // VERSIONHACK: plaintext -> raw
1525 // VERSIONHACK: plaintext -> raw
1524 // handle never-released plaintext name for raw cells
1526 // handle never-released plaintext name for raw cells
1525 if (cell_data.cell_type === 'plaintext'){
1527 if (cell_data.cell_type === 'plaintext'){
1526 cell_data.cell_type = 'raw';
1528 cell_data.cell_type = 'raw';
1527 }
1529 }
1528
1530
1529 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1531 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1530 new_cell.fromJSON(cell_data);
1532 new_cell.fromJSON(cell_data);
1531 };
1533 };
1532 };
1534 };
1533 if (content.worksheets.length > 1) {
1535 if (content.worksheets.length > 1) {
1534 IPython.dialog.modal({
1536 IPython.dialog.modal({
1535 title : "Multiple worksheets",
1537 title : "Multiple worksheets",
1536 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1538 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1537 "but this version of IPython can only handle the first. " +
1539 "but this version of IPython can only handle the first. " +
1538 "If you save this notebook, worksheets after the first will be lost.",
1540 "If you save this notebook, worksheets after the first will be lost.",
1539 buttons : {
1541 buttons : {
1540 OK : {
1542 OK : {
1541 class : "btn-danger"
1543 class : "btn-danger"
1542 }
1544 }
1543 }
1545 }
1544 });
1546 });
1545 }
1547 }
1546 };
1548 };
1547
1549
1548 /**
1550 /**
1549 * Dump this notebook into a JSON-friendly object.
1551 * Dump this notebook into a JSON-friendly object.
1550 *
1552 *
1551 * @method toJSON
1553 * @method toJSON
1552 * @return {Object} A JSON-friendly representation of this notebook.
1554 * @return {Object} A JSON-friendly representation of this notebook.
1553 */
1555 */
1554 Notebook.prototype.toJSON = function () {
1556 Notebook.prototype.toJSON = function () {
1555 var cells = this.get_cells();
1557 var cells = this.get_cells();
1556 var ncells = cells.length;
1558 var ncells = cells.length;
1557 var cell_array = new Array(ncells);
1559 var cell_array = new Array(ncells);
1558 for (var i=0; i<ncells; i++) {
1560 for (var i=0; i<ncells; i++) {
1559 cell_array[i] = cells[i].toJSON();
1561 cell_array[i] = cells[i].toJSON();
1560 };
1562 };
1561 var data = {
1563 var data = {
1562 // Only handle 1 worksheet for now.
1564 // Only handle 1 worksheet for now.
1563 worksheets : [{
1565 worksheets : [{
1564 cells: cell_array,
1566 cells: cell_array,
1565 metadata: this.worksheet_metadata
1567 metadata: this.worksheet_metadata
1566 }],
1568 }],
1567 metadata : this.metadata
1569 metadata : this.metadata
1568 };
1570 };
1569 return data;
1571 return data;
1570 };
1572 };
1571
1573
1572 /**
1574 /**
1573 * Start an autosave timer, for periodically saving the notebook.
1575 * Start an autosave timer, for periodically saving the notebook.
1574 *
1576 *
1575 * @method set_autosave_interval
1577 * @method set_autosave_interval
1576 * @param {Integer} interval the autosave interval in milliseconds
1578 * @param {Integer} interval the autosave interval in milliseconds
1577 */
1579 */
1578 Notebook.prototype.set_autosave_interval = function (interval) {
1580 Notebook.prototype.set_autosave_interval = function (interval) {
1579 var that = this;
1581 var that = this;
1580 // clear previous interval, so we don't get simultaneous timers
1582 // clear previous interval, so we don't get simultaneous timers
1581 if (this.autosave_timer) {
1583 if (this.autosave_timer) {
1582 clearInterval(this.autosave_timer);
1584 clearInterval(this.autosave_timer);
1583 }
1585 }
1584
1586
1585 this.autosave_interval = this.minimum_autosave_interval = interval;
1587 this.autosave_interval = this.minimum_autosave_interval = interval;
1586 if (interval) {
1588 if (interval) {
1587 this.autosave_timer = setInterval(function() {
1589 this.autosave_timer = setInterval(function() {
1588 if (that.dirty) {
1590 if (that.dirty) {
1589 that.save_notebook();
1591 that.save_notebook();
1590 }
1592 }
1591 }, interval);
1593 }, interval);
1592 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1594 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1593 } else {
1595 } else {
1594 this.autosave_timer = null;
1596 this.autosave_timer = null;
1595 $([IPython.events]).trigger("autosave_disabled.Notebook");
1597 $([IPython.events]).trigger("autosave_disabled.Notebook");
1596 };
1598 };
1597 };
1599 };
1598
1600
1599 /**
1601 /**
1600 * Save this notebook on the server.
1602 * Save this notebook on the server.
1601 *
1603 *
1602 * @method save_notebook
1604 * @method save_notebook
1603 */
1605 */
1604 Notebook.prototype.save_notebook = function (extra_settings) {
1606 Notebook.prototype.save_notebook = function (extra_settings) {
1605 // Create a JSON model to be sent to the server.
1607 // Create a JSON model to be sent to the server.
1606 var model = {};
1608 var model = {};
1607 model.name = this.notebook_name;
1609 model.name = this.notebook_name;
1608 model.path = this.notebook_path;
1610 model.path = this.notebook_path;
1609 model.content = this.toJSON();
1611 model.content = this.toJSON();
1610 model.content.nbformat = this.nbformat;
1612 model.content.nbformat = this.nbformat;
1611 model.content.nbformat_minor = this.nbformat_minor;
1613 model.content.nbformat_minor = this.nbformat_minor;
1612 // time the ajax call for autosave tuning purposes.
1614 // time the ajax call for autosave tuning purposes.
1613 var start = new Date().getTime();
1615 var start = new Date().getTime();
1614 // We do the call with settings so we can set cache to false.
1616 // We do the call with settings so we can set cache to false.
1615 var settings = {
1617 var settings = {
1616 processData : false,
1618 processData : false,
1617 cache : false,
1619 cache : false,
1618 type : "PUT",
1620 type : "PUT",
1619 data : JSON.stringify(model),
1621 data : JSON.stringify(model),
1620 headers : {'Content-Type': 'application/json'},
1622 headers : {'Content-Type': 'application/json'},
1621 success : $.proxy(this.save_notebook_success, this, start),
1623 success : $.proxy(this.save_notebook_success, this, start),
1622 error : $.proxy(this.save_notebook_error, this)
1624 error : $.proxy(this.save_notebook_error, this)
1623 };
1625 };
1624 if (extra_settings) {
1626 if (extra_settings) {
1625 for (var key in extra_settings) {
1627 for (var key in extra_settings) {
1626 settings[key] = extra_settings[key];
1628 settings[key] = extra_settings[key];
1627 }
1629 }
1628 }
1630 }
1629 $([IPython.events]).trigger('notebook_saving.Notebook');
1631 $([IPython.events]).trigger('notebook_saving.Notebook');
1630 var url = utils.url_join_encode(
1632 var url = utils.url_join_encode(
1631 this._baseProjectUrl,
1633 this._baseProjectUrl,
1632 'api/notebooks',
1634 'api/notebooks',
1633 this.notebook_path,
1635 this.notebook_path,
1634 this.notebook_name
1636 this.notebook_name
1635 );
1637 );
1636 $.ajax(url, settings);
1638 $.ajax(url, settings);
1637 };
1639 };
1638
1640
1639 /**
1641 /**
1640 * Success callback for saving a notebook.
1642 * Success callback for saving a notebook.
1641 *
1643 *
1642 * @method save_notebook_success
1644 * @method save_notebook_success
1643 * @param {Integer} start the time when the save request started
1645 * @param {Integer} start the time when the save request started
1644 * @param {Object} data JSON representation of a notebook
1646 * @param {Object} data JSON representation of a notebook
1645 * @param {String} status Description of response status
1647 * @param {String} status Description of response status
1646 * @param {jqXHR} xhr jQuery Ajax object
1648 * @param {jqXHR} xhr jQuery Ajax object
1647 */
1649 */
1648 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1650 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1649 this.set_dirty(false);
1651 this.set_dirty(false);
1650 $([IPython.events]).trigger('notebook_saved.Notebook');
1652 $([IPython.events]).trigger('notebook_saved.Notebook');
1651 this._update_autosave_interval(start);
1653 this._update_autosave_interval(start);
1652 if (this._checkpoint_after_save) {
1654 if (this._checkpoint_after_save) {
1653 this.create_checkpoint();
1655 this.create_checkpoint();
1654 this._checkpoint_after_save = false;
1656 this._checkpoint_after_save = false;
1655 };
1657 };
1656 };
1658 };
1657
1659
1658 /**
1660 /**
1659 * update the autosave interval based on how long the last save took
1661 * update the autosave interval based on how long the last save took
1660 *
1662 *
1661 * @method _update_autosave_interval
1663 * @method _update_autosave_interval
1662 * @param {Integer} timestamp when the save request started
1664 * @param {Integer} timestamp when the save request started
1663 */
1665 */
1664 Notebook.prototype._update_autosave_interval = function (start) {
1666 Notebook.prototype._update_autosave_interval = function (start) {
1665 var duration = (new Date().getTime() - start);
1667 var duration = (new Date().getTime() - start);
1666 if (this.autosave_interval) {
1668 if (this.autosave_interval) {
1667 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1669 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1668 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1670 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1669 // round to 10 seconds, otherwise we will be setting a new interval too often
1671 // round to 10 seconds, otherwise we will be setting a new interval too often
1670 interval = 10000 * Math.round(interval / 10000);
1672 interval = 10000 * Math.round(interval / 10000);
1671 // set new interval, if it's changed
1673 // set new interval, if it's changed
1672 if (interval != this.autosave_interval) {
1674 if (interval != this.autosave_interval) {
1673 this.set_autosave_interval(interval);
1675 this.set_autosave_interval(interval);
1674 }
1676 }
1675 }
1677 }
1676 };
1678 };
1677
1679
1678 /**
1680 /**
1679 * Failure callback for saving a notebook.
1681 * Failure callback for saving a notebook.
1680 *
1682 *
1681 * @method save_notebook_error
1683 * @method save_notebook_error
1682 * @param {jqXHR} xhr jQuery Ajax object
1684 * @param {jqXHR} xhr jQuery Ajax object
1683 * @param {String} status Description of response status
1685 * @param {String} status Description of response status
1684 * @param {String} error HTTP error message
1686 * @param {String} error HTTP error message
1685 */
1687 */
1686 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1688 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1687 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1689 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1688 };
1690 };
1689
1691
1690 Notebook.prototype.new_notebook = function(){
1692 Notebook.prototype.new_notebook = function(){
1691 var path = this.notebook_path;
1693 var path = this.notebook_path;
1692 var base_project_url = this._baseProjectUrl;
1694 var base_project_url = this._baseProjectUrl;
1693 var settings = {
1695 var settings = {
1694 processData : false,
1696 processData : false,
1695 cache : false,
1697 cache : false,
1696 type : "POST",
1698 type : "POST",
1697 dataType : "json",
1699 dataType : "json",
1698 async : false,
1700 async : false,
1699 success : function (data, status, xhr){
1701 success : function (data, status, xhr){
1700 var notebook_name = data.name;
1702 var notebook_name = data.name;
1701 window.open(
1703 window.open(
1702 utils.url_join_encode(
1704 utils.url_join_encode(
1703 base_project_url,
1705 base_project_url,
1704 'notebooks',
1706 'notebooks',
1705 path,
1707 path,
1706 notebook_name
1708 notebook_name
1707 ),
1709 ),
1708 '_blank'
1710 '_blank'
1709 );
1711 );
1710 }
1712 }
1711 };
1713 };
1712 var url = utils.url_join_encode(
1714 var url = utils.url_join_encode(
1713 base_project_url,
1715 base_project_url,
1714 'api/notebooks',
1716 'api/notebooks',
1715 path
1717 path
1716 );
1718 );
1717 $.ajax(url,settings);
1719 $.ajax(url,settings);
1718 };
1720 };
1719
1721
1720
1722
1721 Notebook.prototype.copy_notebook = function(){
1723 Notebook.prototype.copy_notebook = function(){
1722 var path = this.notebook_path;
1724 var path = this.notebook_path;
1723 var base_project_url = this._baseProjectUrl;
1725 var base_project_url = this._baseProjectUrl;
1724 var settings = {
1726 var settings = {
1725 processData : false,
1727 processData : false,
1726 cache : false,
1728 cache : false,
1727 type : "POST",
1729 type : "POST",
1728 dataType : "json",
1730 dataType : "json",
1729 data : JSON.stringify({copy_from : this.notebook_name}),
1731 data : JSON.stringify({copy_from : this.notebook_name}),
1730 async : false,
1732 async : false,
1731 success : function (data, status, xhr) {
1733 success : function (data, status, xhr) {
1732 window.open(utils.url_join_encode(
1734 window.open(utils.url_join_encode(
1733 base_project_url,
1735 base_project_url,
1734 'notebooks',
1736 'notebooks',
1735 data.path,
1737 data.path,
1736 data.name
1738 data.name
1737 ), '_blank');
1739 ), '_blank');
1738 }
1740 }
1739 };
1741 };
1740 var url = utils.url_join_encode(
1742 var url = utils.url_join_encode(
1741 base_project_url,
1743 base_project_url,
1742 'api/notebooks',
1744 'api/notebooks',
1743 path
1745 path
1744 );
1746 );
1745 $.ajax(url,settings);
1747 $.ajax(url,settings);
1746 };
1748 };
1747
1749
1748 Notebook.prototype.rename = function (nbname) {
1750 Notebook.prototype.rename = function (nbname) {
1749 var that = this;
1751 var that = this;
1750 var data = {name: nbname + '.ipynb'};
1752 var data = {name: nbname + '.ipynb'};
1751 var settings = {
1753 var settings = {
1752 processData : false,
1754 processData : false,
1753 cache : false,
1755 cache : false,
1754 type : "PATCH",
1756 type : "PATCH",
1755 data : JSON.stringify(data),
1757 data : JSON.stringify(data),
1756 dataType: "json",
1758 dataType: "json",
1757 headers : {'Content-Type': 'application/json'},
1759 headers : {'Content-Type': 'application/json'},
1758 success : $.proxy(that.rename_success, this),
1760 success : $.proxy(that.rename_success, this),
1759 error : $.proxy(that.rename_error, this)
1761 error : $.proxy(that.rename_error, this)
1760 };
1762 };
1761 $([IPython.events]).trigger('rename_notebook.Notebook', data);
1763 $([IPython.events]).trigger('rename_notebook.Notebook', data);
1762 var url = utils.url_join_encode(
1764 var url = utils.url_join_encode(
1763 this._baseProjectUrl,
1765 this._baseProjectUrl,
1764 'api/notebooks',
1766 'api/notebooks',
1765 this.notebook_path,
1767 this.notebook_path,
1766 this.notebook_name
1768 this.notebook_name
1767 );
1769 );
1768 $.ajax(url, settings);
1770 $.ajax(url, settings);
1769 };
1771 };
1770
1772
1771
1773
1772 Notebook.prototype.rename_success = function (json, status, xhr) {
1774 Notebook.prototype.rename_success = function (json, status, xhr) {
1773 this.notebook_name = json.name;
1775 this.notebook_name = json.name;
1774 var name = this.notebook_name;
1776 var name = this.notebook_name;
1775 var path = json.path;
1777 var path = json.path;
1776 this.session.rename_notebook(name, path);
1778 this.session.rename_notebook(name, path);
1777 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
1779 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
1778 }
1780 }
1779
1781
1780 Notebook.prototype.rename_error = function (xhr, status, error) {
1782 Notebook.prototype.rename_error = function (xhr, status, error) {
1781 var that = this;
1783 var that = this;
1782 var dialog = $('<div/>').append(
1784 var dialog = $('<div/>').append(
1783 $("<p/>").addClass("rename-message")
1785 $("<p/>").addClass("rename-message")
1784 .html('This notebook name already exists.')
1786 .html('This notebook name already exists.')
1785 )
1787 )
1786 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1788 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1787 IPython.dialog.modal({
1789 IPython.dialog.modal({
1788 title: "Notebook Rename Error!",
1790 title: "Notebook Rename Error!",
1789 body: dialog,
1791 body: dialog,
1790 buttons : {
1792 buttons : {
1791 "Cancel": {},
1793 "Cancel": {},
1792 "OK": {
1794 "OK": {
1793 class: "btn-primary",
1795 class: "btn-primary",
1794 click: function () {
1796 click: function () {
1795 IPython.save_widget.rename_notebook();
1797 IPython.save_widget.rename_notebook();
1796 }}
1798 }}
1797 },
1799 },
1798 open : function (event, ui) {
1800 open : function (event, ui) {
1799 var that = $(this);
1801 var that = $(this);
1800 // Upon ENTER, click the OK button.
1802 // Upon ENTER, click the OK button.
1801 that.find('input[type="text"]').keydown(function (event, ui) {
1803 that.find('input[type="text"]').keydown(function (event, ui) {
1802 if (event.which === utils.keycodes.ENTER) {
1804 if (event.which === utils.keycodes.ENTER) {
1803 that.find('.btn-primary').first().click();
1805 that.find('.btn-primary').first().click();
1804 }
1806 }
1805 });
1807 });
1806 that.find('input[type="text"]').focus();
1808 that.find('input[type="text"]').focus();
1807 }
1809 }
1808 });
1810 });
1809 }
1811 }
1810
1812
1811 /**
1813 /**
1812 * Request a notebook's data from the server.
1814 * Request a notebook's data from the server.
1813 *
1815 *
1814 * @method load_notebook
1816 * @method load_notebook
1815 * @param {String} notebook_name and path A notebook to load
1817 * @param {String} notebook_name and path A notebook to load
1816 */
1818 */
1817 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
1819 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
1818 var that = this;
1820 var that = this;
1819 this.notebook_name = notebook_name;
1821 this.notebook_name = notebook_name;
1820 this.notebook_path = notebook_path;
1822 this.notebook_path = notebook_path;
1821 // We do the call with settings so we can set cache to false.
1823 // We do the call with settings so we can set cache to false.
1822 var settings = {
1824 var settings = {
1823 processData : false,
1825 processData : false,
1824 cache : false,
1826 cache : false,
1825 type : "GET",
1827 type : "GET",
1826 dataType : "json",
1828 dataType : "json",
1827 success : $.proxy(this.load_notebook_success,this),
1829 success : $.proxy(this.load_notebook_success,this),
1828 error : $.proxy(this.load_notebook_error,this),
1830 error : $.proxy(this.load_notebook_error,this),
1829 };
1831 };
1830 $([IPython.events]).trigger('notebook_loading.Notebook');
1832 $([IPython.events]).trigger('notebook_loading.Notebook');
1831 var url = utils.url_join_encode(
1833 var url = utils.url_join_encode(
1832 this._baseProjectUrl,
1834 this._baseProjectUrl,
1833 'api/notebooks',
1835 'api/notebooks',
1834 this.notebook_path,
1836 this.notebook_path,
1835 this.notebook_name
1837 this.notebook_name
1836 );
1838 );
1837 $.ajax(url, settings);
1839 $.ajax(url, settings);
1838 };
1840 };
1839
1841
1840 /**
1842 /**
1841 * Success callback for loading a notebook from the server.
1843 * Success callback for loading a notebook from the server.
1842 *
1844 *
1843 * Load notebook data from the JSON response.
1845 * Load notebook data from the JSON response.
1844 *
1846 *
1845 * @method load_notebook_success
1847 * @method load_notebook_success
1846 * @param {Object} data JSON representation of a notebook
1848 * @param {Object} data JSON representation of a notebook
1847 * @param {String} status Description of response status
1849 * @param {String} status Description of response status
1848 * @param {jqXHR} xhr jQuery Ajax object
1850 * @param {jqXHR} xhr jQuery Ajax object
1849 */
1851 */
1850 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1852 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1851 this.fromJSON(data);
1853 this.fromJSON(data);
1852 if (this.ncells() === 0) {
1854 if (this.ncells() === 0) {
1853 this.insert_cell_below('code');
1855 this.insert_cell_below('code');
1854 this.select(0);
1856 this.select(0);
1855 this.edit_mode();
1857 this.edit_mode();
1856 } else {
1858 } else {
1857 this.select(0);
1859 this.select(0);
1858 this.command_mode();
1860 this.command_mode();
1859 };
1861 };
1860 this.set_dirty(false);
1862 this.set_dirty(false);
1861 this.scroll_to_top();
1863 this.scroll_to_top();
1862 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1864 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1863 var msg = "This notebook has been converted from an older " +
1865 var msg = "This notebook has been converted from an older " +
1864 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1866 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1865 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1867 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1866 "newer notebook format will be used and older versions of IPython " +
1868 "newer notebook format will be used and older versions of IPython " +
1867 "may not be able to read it. To keep the older version, close the " +
1869 "may not be able to read it. To keep the older version, close the " +
1868 "notebook without saving it.";
1870 "notebook without saving it.";
1869 IPython.dialog.modal({
1871 IPython.dialog.modal({
1870 title : "Notebook converted",
1872 title : "Notebook converted",
1871 body : msg,
1873 body : msg,
1872 buttons : {
1874 buttons : {
1873 OK : {
1875 OK : {
1874 class : "btn-primary"
1876 class : "btn-primary"
1875 }
1877 }
1876 }
1878 }
1877 });
1879 });
1878 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1880 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1879 var that = this;
1881 var that = this;
1880 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1882 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1881 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1883 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1882 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1884 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1883 this_vs + ". You can still work with this notebook, but some features " +
1885 this_vs + ". You can still work with this notebook, but some features " +
1884 "introduced in later notebook versions may not be available."
1886 "introduced in later notebook versions may not be available."
1885
1887
1886 IPython.dialog.modal({
1888 IPython.dialog.modal({
1887 title : "Newer Notebook",
1889 title : "Newer Notebook",
1888 body : msg,
1890 body : msg,
1889 buttons : {
1891 buttons : {
1890 OK : {
1892 OK : {
1891 class : "btn-danger"
1893 class : "btn-danger"
1892 }
1894 }
1893 }
1895 }
1894 });
1896 });
1895
1897
1896 }
1898 }
1897
1899
1898 // Create the session after the notebook is completely loaded to prevent
1900 // Create the session after the notebook is completely loaded to prevent
1899 // code execution upon loading, which is a security risk.
1901 // code execution upon loading, which is a security risk.
1900 if (this.session == null) {
1902 if (this.session == null) {
1901 this.start_session();
1903 this.start_session();
1902 }
1904 }
1903 // load our checkpoint list
1905 // load our checkpoint list
1904 this.list_checkpoints();
1906 this.list_checkpoints();
1905
1907
1906 // load toolbar state
1908 // load toolbar state
1907 if (this.metadata.celltoolbar) {
1909 if (this.metadata.celltoolbar) {
1908 IPython.CellToolbar.global_show();
1910 IPython.CellToolbar.global_show();
1909 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
1911 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
1910 }
1912 }
1911
1913
1912 $([IPython.events]).trigger('notebook_loaded.Notebook');
1914 $([IPython.events]).trigger('notebook_loaded.Notebook');
1913 };
1915 };
1914
1916
1915 /**
1917 /**
1916 * Failure callback for loading a notebook from the server.
1918 * Failure callback for loading a notebook from the server.
1917 *
1919 *
1918 * @method load_notebook_error
1920 * @method load_notebook_error
1919 * @param {jqXHR} xhr jQuery Ajax object
1921 * @param {jqXHR} xhr jQuery Ajax object
1920 * @param {String} status Description of response status
1922 * @param {String} status Description of response status
1921 * @param {String} error HTTP error message
1923 * @param {String} error HTTP error message
1922 */
1924 */
1923 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
1925 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
1924 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
1926 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
1925 if (xhr.status === 400) {
1927 if (xhr.status === 400) {
1926 var msg = error;
1928 var msg = error;
1927 } else if (xhr.status === 500) {
1929 } else if (xhr.status === 500) {
1928 var msg = "An unknown error occurred while loading this notebook. " +
1930 var msg = "An unknown error occurred while loading this notebook. " +
1929 "This version can load notebook formats " +
1931 "This version can load notebook formats " +
1930 "v" + this.nbformat + " or earlier.";
1932 "v" + this.nbformat + " or earlier.";
1931 }
1933 }
1932 IPython.dialog.modal({
1934 IPython.dialog.modal({
1933 title: "Error loading notebook",
1935 title: "Error loading notebook",
1934 body : msg,
1936 body : msg,
1935 buttons : {
1937 buttons : {
1936 "OK": {}
1938 "OK": {}
1937 }
1939 }
1938 });
1940 });
1939 }
1941 }
1940
1942
1941 /********************* checkpoint-related *********************/
1943 /********************* checkpoint-related *********************/
1942
1944
1943 /**
1945 /**
1944 * Save the notebook then immediately create a checkpoint.
1946 * Save the notebook then immediately create a checkpoint.
1945 *
1947 *
1946 * @method save_checkpoint
1948 * @method save_checkpoint
1947 */
1949 */
1948 Notebook.prototype.save_checkpoint = function () {
1950 Notebook.prototype.save_checkpoint = function () {
1949 this._checkpoint_after_save = true;
1951 this._checkpoint_after_save = true;
1950 this.save_notebook();
1952 this.save_notebook();
1951 };
1953 };
1952
1954
1953 /**
1955 /**
1954 * Add a checkpoint for this notebook.
1956 * Add a checkpoint for this notebook.
1955 * for use as a callback from checkpoint creation.
1957 * for use as a callback from checkpoint creation.
1956 *
1958 *
1957 * @method add_checkpoint
1959 * @method add_checkpoint
1958 */
1960 */
1959 Notebook.prototype.add_checkpoint = function (checkpoint) {
1961 Notebook.prototype.add_checkpoint = function (checkpoint) {
1960 var found = false;
1962 var found = false;
1961 for (var i = 0; i < this.checkpoints.length; i++) {
1963 for (var i = 0; i < this.checkpoints.length; i++) {
1962 var existing = this.checkpoints[i];
1964 var existing = this.checkpoints[i];
1963 if (existing.id == checkpoint.id) {
1965 if (existing.id == checkpoint.id) {
1964 found = true;
1966 found = true;
1965 this.checkpoints[i] = checkpoint;
1967 this.checkpoints[i] = checkpoint;
1966 break;
1968 break;
1967 }
1969 }
1968 }
1970 }
1969 if (!found) {
1971 if (!found) {
1970 this.checkpoints.push(checkpoint);
1972 this.checkpoints.push(checkpoint);
1971 }
1973 }
1972 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
1974 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
1973 };
1975 };
1974
1976
1975 /**
1977 /**
1976 * List checkpoints for this notebook.
1978 * List checkpoints for this notebook.
1977 *
1979 *
1978 * @method list_checkpoints
1980 * @method list_checkpoints
1979 */
1981 */
1980 Notebook.prototype.list_checkpoints = function () {
1982 Notebook.prototype.list_checkpoints = function () {
1981 var url = utils.url_join_encode(
1983 var url = utils.url_join_encode(
1982 this._baseProjectUrl,
1984 this._baseProjectUrl,
1983 'api/notebooks',
1985 'api/notebooks',
1984 this.notebook_path,
1986 this.notebook_path,
1985 this.notebook_name,
1987 this.notebook_name,
1986 'checkpoints'
1988 'checkpoints'
1987 );
1989 );
1988 $.get(url).done(
1990 $.get(url).done(
1989 $.proxy(this.list_checkpoints_success, this)
1991 $.proxy(this.list_checkpoints_success, this)
1990 ).fail(
1992 ).fail(
1991 $.proxy(this.list_checkpoints_error, this)
1993 $.proxy(this.list_checkpoints_error, this)
1992 );
1994 );
1993 };
1995 };
1994
1996
1995 /**
1997 /**
1996 * Success callback for listing checkpoints.
1998 * Success callback for listing checkpoints.
1997 *
1999 *
1998 * @method list_checkpoint_success
2000 * @method list_checkpoint_success
1999 * @param {Object} data JSON representation of a checkpoint
2001 * @param {Object} data JSON representation of a checkpoint
2000 * @param {String} status Description of response status
2002 * @param {String} status Description of response status
2001 * @param {jqXHR} xhr jQuery Ajax object
2003 * @param {jqXHR} xhr jQuery Ajax object
2002 */
2004 */
2003 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2005 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2004 var data = $.parseJSON(data);
2006 var data = $.parseJSON(data);
2005 this.checkpoints = data;
2007 this.checkpoints = data;
2006 if (data.length) {
2008 if (data.length) {
2007 this.last_checkpoint = data[data.length - 1];
2009 this.last_checkpoint = data[data.length - 1];
2008 } else {
2010 } else {
2009 this.last_checkpoint = null;
2011 this.last_checkpoint = null;
2010 }
2012 }
2011 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
2013 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
2012 };
2014 };
2013
2015
2014 /**
2016 /**
2015 * Failure callback for listing a checkpoint.
2017 * Failure callback for listing a checkpoint.
2016 *
2018 *
2017 * @method list_checkpoint_error
2019 * @method list_checkpoint_error
2018 * @param {jqXHR} xhr jQuery Ajax object
2020 * @param {jqXHR} xhr jQuery Ajax object
2019 * @param {String} status Description of response status
2021 * @param {String} status Description of response status
2020 * @param {String} error_msg HTTP error message
2022 * @param {String} error_msg HTTP error message
2021 */
2023 */
2022 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2024 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2023 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
2025 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
2024 };
2026 };
2025
2027
2026 /**
2028 /**
2027 * Create a checkpoint of this notebook on the server from the most recent save.
2029 * Create a checkpoint of this notebook on the server from the most recent save.
2028 *
2030 *
2029 * @method create_checkpoint
2031 * @method create_checkpoint
2030 */
2032 */
2031 Notebook.prototype.create_checkpoint = function () {
2033 Notebook.prototype.create_checkpoint = function () {
2032 var url = utils.url_join_encode(
2034 var url = utils.url_join_encode(
2033 this._baseProjectUrl,
2035 this._baseProjectUrl,
2034 'api/notebooks',
2036 'api/notebooks',
2035 this.notebookPath(),
2037 this.notebookPath(),
2036 this.notebook_name,
2038 this.notebook_name,
2037 'checkpoints'
2039 'checkpoints'
2038 );
2040 );
2039 $.post(url).done(
2041 $.post(url).done(
2040 $.proxy(this.create_checkpoint_success, this)
2042 $.proxy(this.create_checkpoint_success, this)
2041 ).fail(
2043 ).fail(
2042 $.proxy(this.create_checkpoint_error, this)
2044 $.proxy(this.create_checkpoint_error, this)
2043 );
2045 );
2044 };
2046 };
2045
2047
2046 /**
2048 /**
2047 * Success callback for creating a checkpoint.
2049 * Success callback for creating a checkpoint.
2048 *
2050 *
2049 * @method create_checkpoint_success
2051 * @method create_checkpoint_success
2050 * @param {Object} data JSON representation of a checkpoint
2052 * @param {Object} data JSON representation of a checkpoint
2051 * @param {String} status Description of response status
2053 * @param {String} status Description of response status
2052 * @param {jqXHR} xhr jQuery Ajax object
2054 * @param {jqXHR} xhr jQuery Ajax object
2053 */
2055 */
2054 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2056 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2055 var data = $.parseJSON(data);
2057 var data = $.parseJSON(data);
2056 this.add_checkpoint(data);
2058 this.add_checkpoint(data);
2057 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2059 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2058 };
2060 };
2059
2061
2060 /**
2062 /**
2061 * Failure callback for creating a checkpoint.
2063 * Failure callback for creating a checkpoint.
2062 *
2064 *
2063 * @method create_checkpoint_error
2065 * @method create_checkpoint_error
2064 * @param {jqXHR} xhr jQuery Ajax object
2066 * @param {jqXHR} xhr jQuery Ajax object
2065 * @param {String} status Description of response status
2067 * @param {String} status Description of response status
2066 * @param {String} error_msg HTTP error message
2068 * @param {String} error_msg HTTP error message
2067 */
2069 */
2068 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2070 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2069 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2071 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2070 };
2072 };
2071
2073
2072 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2074 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2073 var that = this;
2075 var that = this;
2074 var checkpoint = checkpoint || this.last_checkpoint;
2076 var checkpoint = checkpoint || this.last_checkpoint;
2075 if ( ! checkpoint ) {
2077 if ( ! checkpoint ) {
2076 console.log("restore dialog, but no checkpoint to restore to!");
2078 console.log("restore dialog, but no checkpoint to restore to!");
2077 return;
2079 return;
2078 }
2080 }
2079 var body = $('<div/>').append(
2081 var body = $('<div/>').append(
2080 $('<p/>').addClass("p-space").text(
2082 $('<p/>').addClass("p-space").text(
2081 "Are you sure you want to revert the notebook to " +
2083 "Are you sure you want to revert the notebook to " +
2082 "the latest checkpoint?"
2084 "the latest checkpoint?"
2083 ).append(
2085 ).append(
2084 $("<strong/>").text(
2086 $("<strong/>").text(
2085 " This cannot be undone."
2087 " This cannot be undone."
2086 )
2088 )
2087 )
2089 )
2088 ).append(
2090 ).append(
2089 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2091 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2090 ).append(
2092 ).append(
2091 $('<p/>').addClass("p-space").text(
2093 $('<p/>').addClass("p-space").text(
2092 Date(checkpoint.last_modified)
2094 Date(checkpoint.last_modified)
2093 ).css("text-align", "center")
2095 ).css("text-align", "center")
2094 );
2096 );
2095
2097
2096 IPython.dialog.modal({
2098 IPython.dialog.modal({
2097 title : "Revert notebook to checkpoint",
2099 title : "Revert notebook to checkpoint",
2098 body : body,
2100 body : body,
2099 buttons : {
2101 buttons : {
2100 Revert : {
2102 Revert : {
2101 class : "btn-danger",
2103 class : "btn-danger",
2102 click : function () {
2104 click : function () {
2103 that.restore_checkpoint(checkpoint.id);
2105 that.restore_checkpoint(checkpoint.id);
2104 }
2106 }
2105 },
2107 },
2106 Cancel : {}
2108 Cancel : {}
2107 }
2109 }
2108 });
2110 });
2109 }
2111 }
2110
2112
2111 /**
2113 /**
2112 * Restore the notebook to a checkpoint state.
2114 * Restore the notebook to a checkpoint state.
2113 *
2115 *
2114 * @method restore_checkpoint
2116 * @method restore_checkpoint
2115 * @param {String} checkpoint ID
2117 * @param {String} checkpoint ID
2116 */
2118 */
2117 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2119 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2118 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2120 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2119 var url = utils.url_join_encode(
2121 var url = utils.url_join_encode(
2120 this._baseProjectUrl,
2122 this._baseProjectUrl,
2121 'api/notebooks',
2123 'api/notebooks',
2122 this.notebookPath(),
2124 this.notebookPath(),
2123 this.notebook_name,
2125 this.notebook_name,
2124 'checkpoints',
2126 'checkpoints',
2125 checkpoint
2127 checkpoint
2126 );
2128 );
2127 $.post(url).done(
2129 $.post(url).done(
2128 $.proxy(this.restore_checkpoint_success, this)
2130 $.proxy(this.restore_checkpoint_success, this)
2129 ).fail(
2131 ).fail(
2130 $.proxy(this.restore_checkpoint_error, this)
2132 $.proxy(this.restore_checkpoint_error, this)
2131 );
2133 );
2132 };
2134 };
2133
2135
2134 /**
2136 /**
2135 * Success callback for restoring a notebook to a checkpoint.
2137 * Success callback for restoring a notebook to a checkpoint.
2136 *
2138 *
2137 * @method restore_checkpoint_success
2139 * @method restore_checkpoint_success
2138 * @param {Object} data (ignored, should be empty)
2140 * @param {Object} data (ignored, should be empty)
2139 * @param {String} status Description of response status
2141 * @param {String} status Description of response status
2140 * @param {jqXHR} xhr jQuery Ajax object
2142 * @param {jqXHR} xhr jQuery Ajax object
2141 */
2143 */
2142 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2144 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2143 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2145 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2144 this.load_notebook(this.notebook_name, this.notebook_path);
2146 this.load_notebook(this.notebook_name, this.notebook_path);
2145 };
2147 };
2146
2148
2147 /**
2149 /**
2148 * Failure callback for restoring a notebook to a checkpoint.
2150 * Failure callback for restoring a notebook to a checkpoint.
2149 *
2151 *
2150 * @method restore_checkpoint_error
2152 * @method restore_checkpoint_error
2151 * @param {jqXHR} xhr jQuery Ajax object
2153 * @param {jqXHR} xhr jQuery Ajax object
2152 * @param {String} status Description of response status
2154 * @param {String} status Description of response status
2153 * @param {String} error_msg HTTP error message
2155 * @param {String} error_msg HTTP error message
2154 */
2156 */
2155 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2157 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2156 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2158 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2157 };
2159 };
2158
2160
2159 /**
2161 /**
2160 * Delete a notebook checkpoint.
2162 * Delete a notebook checkpoint.
2161 *
2163 *
2162 * @method delete_checkpoint
2164 * @method delete_checkpoint
2163 * @param {String} checkpoint ID
2165 * @param {String} checkpoint ID
2164 */
2166 */
2165 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2167 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2166 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2168 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2167 var url = utils.url_join_encode(
2169 var url = utils.url_join_encode(
2168 this._baseProjectUrl,
2170 this._baseProjectUrl,
2169 'api/notebooks',
2171 'api/notebooks',
2170 this.notebookPath(),
2172 this.notebookPath(),
2171 this.notebook_name,
2173 this.notebook_name,
2172 'checkpoints',
2174 'checkpoints',
2173 checkpoint
2175 checkpoint
2174 );
2176 );
2175 $.ajax(url, {
2177 $.ajax(url, {
2176 type: 'DELETE',
2178 type: 'DELETE',
2177 success: $.proxy(this.delete_checkpoint_success, this),
2179 success: $.proxy(this.delete_checkpoint_success, this),
2178 error: $.proxy(this.delete_notebook_error,this)
2180 error: $.proxy(this.delete_notebook_error,this)
2179 });
2181 });
2180 };
2182 };
2181
2183
2182 /**
2184 /**
2183 * Success callback for deleting a notebook checkpoint
2185 * Success callback for deleting a notebook checkpoint
2184 *
2186 *
2185 * @method delete_checkpoint_success
2187 * @method delete_checkpoint_success
2186 * @param {Object} data (ignored, should be empty)
2188 * @param {Object} data (ignored, should be empty)
2187 * @param {String} status Description of response status
2189 * @param {String} status Description of response status
2188 * @param {jqXHR} xhr jQuery Ajax object
2190 * @param {jqXHR} xhr jQuery Ajax object
2189 */
2191 */
2190 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2192 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2191 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2193 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2192 this.load_notebook(this.notebook_name, this.notebook_path);
2194 this.load_notebook(this.notebook_name, this.notebook_path);
2193 };
2195 };
2194
2196
2195 /**
2197 /**
2196 * Failure callback for deleting a notebook checkpoint.
2198 * Failure callback for deleting a notebook checkpoint.
2197 *
2199 *
2198 * @method delete_checkpoint_error
2200 * @method delete_checkpoint_error
2199 * @param {jqXHR} xhr jQuery Ajax object
2201 * @param {jqXHR} xhr jQuery Ajax object
2200 * @param {String} status Description of response status
2202 * @param {String} status Description of response status
2201 * @param {String} error_msg HTTP error message
2203 * @param {String} error_msg HTTP error message
2202 */
2204 */
2203 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2205 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2204 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2206 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2205 };
2207 };
2206
2208
2207
2209
2208 IPython.Notebook = Notebook;
2210 IPython.Notebook = Notebook;
2209
2211
2210
2212
2211 return IPython;
2213 return IPython;
2212
2214
2213 }(IPython)); No newline at end of file
2215 }(IPython));
This diff has been collapsed as it changes many lines, (786 lines changed) Show them Hide them
@@ -1,468 +1,476 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
2 // Copyright (C) 2013 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // WidgetModel, WidgetView, and WidgetManager
9 // WidgetModel, WidgetView, and WidgetManager
10 //============================================================================
10 //============================================================================
11 /**
11 /**
12 * Base Widget classes
12 * Base Widget classes
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule widget
15 * @submodule widget
16 */
16 */
17
17
18 "use strict";
18 "use strict";
19
19
20 // Use require.js 'define' method so that require.js is intelligent enough to
20 // Use require.js 'define' method so that require.js is intelligent enough to
21 // syncronously load everything within this file when it is being 'required'
21 // syncronously load everything within this file when it is being 'required'
22 // elsewhere.
22 // elsewhere.
23 define(["components/underscore/underscore-min",
23 define(["components/underscore/underscore-min",
24 "components/backbone/backbone-min",
24 "components/backbone/backbone-min",
25 ], function(){
25 ], function(){
26
26
27 // Only run once on a notebook.
27
28 if (IPython.notebook.widget_manager == undefined) {
28 //--------------------------------------------------------------------
29
29 // WidgetModel class
30 //--------------------------------------------------------------------
30 //--------------------------------------------------------------------
31 // WidgetModel class
31 var WidgetModel = Backbone.Model.extend({
32 //--------------------------------------------------------------------
32 constructor: function(comm_manager, comm, widget_view_types) {
33 var WidgetModel = Backbone.Model.extend({
33 this.comm_manager = comm_manager;
34 constructor: function(comm_manager, comm, widget_view_types) {
34 this.widget_view_types = widget_view_types;
35 this.comm_manager = comm_manager;
35 this.pending_msgs = 0;
36 this.widget_view_types = widget_view_types;
36 this.msg_throttle = 3;
37 this.pending_msgs = 0;
37 this.msg_buffer = null;
38 this.msg_throttle = 3;
38 this.views = {};
39 this.msg_buffer = null;
39
40 this.views = {};
40 // Remember comm associated with the model.
41
41 this.comm = comm;
42 // Remember comm associated with the model.
42 comm.model = this;
43 this.comm = comm;
43
44 comm.model = this;
44 // Hook comm messages up to model.
45
45 comm.on_close($.proxy(this.handle_comm_closed, this));
46 // Hook comm messages up to model.
46 comm.on_msg($.proxy(this.handle_comm_msg, this));
47 comm.on_close($.proxy(this.handle_comm_closed, this));
47
48 comm.on_msg($.proxy(this.handle_comm_msg, this));
48 return Backbone.Model.apply(this);
49
49 },
50 return Backbone.Model.apply(this);
50
51 },
51
52
52 update_other_views: function(caller) {
53
53 this.last_modified_view = caller;
54 update_other_views: function(caller) {
54 this.save(this.changedAttributes(), {patch: true});
55 this.last_modified_view = caller;
55
56 this.save(this.changedAttributes(), {patch: true});
56 for (var output_area in this.views) {
57
57 var views = this.views[output_area];
58 for (var output_area in this.views) {
58 for (var view_index in views) {
59 var views = this.views[output_area];
59 var view = views[view_index];
60 for (var view_index in views) {
60 if (view !== caller) {
61 var view = views[view_index];
61 view.update();
62 if (view !== caller) {
63 view.update();
64 }
65 }
62 }
66 }
63 }
67 },
64 }
68
65 },
69
70 handle_status: function (output_area, msg) {
71 //execution_state : ('busy', 'idle', 'starting')
72 if (msg.content.execution_state=='idle') {
73
74 // Send buffer if this message caused another message to be
75 // throttled.
76 if (this.msg_buffer != null) {
77 if (this.msg_throttle == this.pending_msgs &&
78 this.msg_buffer.length > 0) {
79
80 var output_area = this._get_msg_output_area(msg);
81 var callbacks = this._make_callbacks(output_area);
82 var data = {sync_method: 'update', sync_data: this.msg_buffer};
83 comm.send(data, callbacks);
84 this.msg_buffer = null;
85 } else {
86
66
87 // Only decrease the pending message count if the buffer
67
88 // doesn't get flushed (sent).
68 handle_status: function (output_area, msg) {
89 --this.pending_msgs;
69 //execution_state : ('busy', 'idle', 'starting')
90 }
70 if (msg.content.execution_state=='idle') {
91 }
71
72 // Send buffer if this message caused another message to be
73 // throttled.
74 if (this.msg_buffer != null &&
75 this.msg_throttle == this.pending_msgs &&
76 this.msg_buffer.length > 0) {
77
78 var output_area = this._get_msg_output_area(msg);
79 var callbacks = this._make_callbacks(output_area);
80 var data = {sync_method: 'update', sync_data: this.msg_buffer};
81 comm.send(data, callbacks);
82 this.msg_buffer = null;
83 } else {
84
85 // Only decrease the pending message count if the buffer
86 // doesn't get flushed (sent).
87 --this.pending_msgs;
92 }
88 }
93 },
89 }
94
90 },
95
91
96 // Custom syncronization logic.
92
97 handle_sync: function (method, options) {
93 // Custom syncronization logic.
98 var model_json = this.toJSON();
94 handle_sync: function (method, options) {
99
95 var model_json = this.toJSON();
100 // Only send updated state if the state hasn't been changed
96
101 // during an update.
97 // Only send updated state if the state hasn't been changed
102 if (!this.updating) {
98 // during an update.
103 if (this.pending_msgs >= this.msg_throttle) {
99 if (!this.updating) {
104 // The throttle has been exceeded, buffer the current msg so
100 if (this.pending_msgs >= this.msg_throttle) {
105 // it can be sent once the kernel has finished processing
101 // The throttle has been exceeded, buffer the current msg so
106 // some of the existing messages.
102 // it can be sent once the kernel has finished processing
107 if (method=='patch') {
103 // some of the existing messages.
108 if (this.msg_buffer == null) {
104 if (method=='patch') {
109 this.msg_buffer = $.extend({}, model_json); // Copy
105 if (this.msg_buffer == null) {
110 }
111 for (var attr in options.attrs) {
112 this.msg_buffer[attr] = options.attrs[attr];
113 }
114 } else {
115 this.msg_buffer = $.extend({}, model_json); // Copy
106 this.msg_buffer = $.extend({}, model_json); // Copy
116 }
107 }
117
108 for (var attr in options.attrs) {
118 } else {
109 this.msg_buffer[attr] = options.attrs[attr];
119 // We haven't exceeded the throttle, send the message like
120 // normal. If this is a patch operation, just send the
121 // changes.
122 var send_json = model_json;
123 if (method=='patch') {
124 send_json = {};
125 for (var attr in options.attrs) {
126 send_json[attr] = options.attrs[attr];
127 }
128 }
110 }
111 } else {
112 this.msg_buffer = $.extend({}, model_json); // Copy
113 }
129
114
130 var data = {sync_method: method, sync_data: send_json};
115 } else {
131 var output_area = this.last_modified_view.output_area;
116 // We haven't exceeded the throttle, send the message like
132 var callbacks = this._make_callbacks(output_area);
117 // normal. If this is a patch operation, just send the
133 this.comm.send(data, callbacks);
118 // changes.
134 this.pending_msgs++;
119 var send_json = model_json;
120 if (method=='patch') {
121 send_json = {};
122 for (var attr in options.attrs) {
123 send_json[attr] = options.attrs[attr];
124 }
135 }
125 }
126
127 var data = {sync_method: method, sync_data: send_json};
128 var output_area = this.last_modified_view.output_area;
129 var callbacks = this._make_callbacks(output_area);
130 this.comm.send(data, callbacks);
131 this.pending_msgs++;
136 }
132 }
137
133 }
138 // Since the comm is a one-way communication, assume the message
134
139 // arrived.
135 // Since the comm is a one-way communication, assume the message
140 return model_json;
136 // arrived.
141 },
137 return model_json;
142
138 },
143
139
144 // Handle incomming comm msg.
140
145 handle_comm_msg: function (msg) {
141 // Handle incomming comm msg.
146 var method = msg.content.data.method;
142 handle_comm_msg: function (msg) {
147 switch (method){
143 var method = msg.content.data.method;
148 case 'display':
144 switch (method){
149
145 case 'display':
150 // Try to get the cell index.
146
151 var output_area = this._get_output_area(msg.parent_header.msg_id);
147 // Try to get the cell index.
152 if (output_area == null) {
148 var output_area = this._get_output_area(msg.parent_header.msg_id);
153 console.log("Could not determine where the display" +
149 if (output_area == null) {
154 " message was from. Widget will not be displayed")
150 console.log("Could not determine where the display" +
151 " message was from. Widget will not be displayed")
152 } else {
153 this.display_view(msg.content.data.view_name,
154 msg.content.data.parent,
155 output_area);
156 }
157 break;
158 case 'update':
159 this.handle_update(msg.content.data.state);
160 break;
161 }
162 },
163
164
165 // Handle when a widget is updated via the python side.
166 handle_update: function (state) {
167 this.updating = true;
168 try {
169 for (var key in state) {
170 if (state.hasOwnProperty(key)) {
171 if (key == "_css"){
172 this.css = state[key];
155 } else {
173 } else {
156 this.display_view(msg.content.data.view_name,
174 this.set(key, state[key]);
157 msg.content.data.parent,
158 output_area);
159 }
160 break;
161 case 'update':
162 this.handle_update(msg.content.data.state);
163 break;
164 }
165 },
166
167
168 // Handle when a widget is updated via the python side.
169 handle_update: function (state) {
170 this.updating = true;
171 try {
172 for (var key in state) {
173 if (state.hasOwnProperty(key)) {
174 if (key == "_css"){
175 this.css = state[key];
176 } else {
177 this.set(key, state[key]);
178 }
179 }
175 }
180 }
176 }
181 this.id = this.comm.comm_id;
182 this.save();
183 } finally {
184 this.updating = false;
185 }
177 }
186 },
178 this.id = this.comm.comm_id;
187
179 this.save();
188
180 } finally {
189 // Handle when a widget is closed.
181 this.updating = false;
190 handle_comm_closed: function (msg) {
182 }
191 for (var output_area in this.views) {
183 },
192 var views = this.views[output_area];
184
193 for (var view_index in views) {
185
194 var view = views[view_index];
186 // Handle when a widget is closed.
195 view.remove();
187 handle_comm_closed: function (msg) {
196 }
188 for (var output_area in this.views) {
189 var views = this.views[output_area];
190 for (var view_index in views) {
191 var view = views[view_index];
192 view.remove();
197 }
193 }
198 },
194 }
199
195 },
200
196
201 // Create view that represents the model.
197
202 display_view: function (view_name, parent_comm_id, output_area) {
198 // Create view that represents the model.
203 var new_views = [];
199 display_view: function (view_name, parent_comm_id, output_area) {
204
200 var new_views = [];
205 var displayed = false;
201
206 if (parent_comm_id != undefined) {
202 var displayed = false;
207 var parent_comm = this.comm_manager.comms[parent_comm_id];
203 if (parent_comm_id != undefined) {
208 var parent_model = parent_comm.model;
204 var parent_comm = this.comm_manager.comms[parent_comm_id];
209 var parent_views = parent_model.views[output_area];
205 var parent_model = parent_comm.model;
210 for (var parent_view_index in parent_views) {
206 var parent_views = parent_model.views[output_area];
211 var parent_view = parent_views[parent_view_index];
207 for (var parent_view_index in parent_views) {
212 if (parent_view.display_child != undefined) {
208 var parent_view = parent_views[parent_view_index];
213 var view = this._create_view(view_name, output_area);
209 if (parent_view.display_child != undefined) {
214 new_views.push(view);
210 var view = this._create_view(view_name, output_area);
215 parent_view.display_child(view);
211 new_views.push(view);
216 displayed = true;
212 parent_view.display_child(view);
217 }
213 displayed = true;
218 }
214 }
219 }
215 }
220
216 }
221 if (!displayed) {
217
222 // No parent view is defined or exists. Add the view's
218 if (!displayed) {
223 // element to cell's widget div.
219 // No parent view is defined or exists. Add the view's
224 var view = this._create_view(view_name, output_area);
220 // element to cell's widget div.
225 new_views.push(view);
221 var view = this._create_view(view_name, output_area);
226 this._get_widget_area_element(output_area, true)
222 new_views.push(view);
227 .append(view.$el);
223 this._get_widget_area_element(output_area, true)
228
224 .append(view.$el);
225
226 }
227
228 for (var view_index in new_views) {
229 var view = new_views[view_index];
230 view.update();
231 }
232 },
233
234
235 // Create a view
236 _create_view: function (view_name, output_area) {
237 var view = new this.widget_view_types[view_name]({model: this});
238 view.render();
239 if (this.views[output_area]==undefined) {
240 this.views[output_area] = []
241 }
242 this.views[output_area].push(view);
243 view.output_area = output_area;
244
245 // Handle when the view element is remove from the page.
246 var that = this;
247 view.$el.on("remove", function(){
248 var index = that.views[output_area].indexOf(view);
249 if (index > -1) {
250 that.views[output_area].splice(index, 1);
229 }
251 }
230
252 view.remove(); // Clean-up view
231 for (var view_index in new_views) {
253 if (that.views[output_area].length()==0) {
232 var view = new_views[view_index];
254 delete that.views[output_area];
233 view.update();
234 }
255 }
235 },
236
237
256
238 // Create a view
257 // Close the comm if there are no views left.
239 _create_view: function (view_name, output_area) {
258 if (that.views.length()==0) {
240 var view = new this.widget_view_types[view_name]({model: this});
259 that.comm.close();
241 view.render();
242 if (this.views[output_area]==undefined) {
243 this.views[output_area] = []
244 }
260 }
245 this.views[output_area].push(view);
261 });
246 view.output_area = output_area;
262 return view;
247
263 },
248 // Handle when the view element is remove from the page.
249 var that = this;
250 view.$el.on("remove", function(){
251 var index = that.views[output_area].indexOf(view);
252 if (index > -1) {
253 that.views[output_area].splice(index, 1);
254 }
255 view.remove(); // Clean-up view
256 if (that.views[output_area].length()==0) {
257 delete that.views[output_area];
258 }
259
260 // Close the comm if there are no views left.
261 if (that.views.length()==0) {
262 that.comm.close();
263 }
264 });
265 return view;
266 },
267
264
268
265
269 // Build a callback dict.
266 // Build a callback dict.
270 _make_callbacks: function (output_area) {
267 _make_callbacks: function (output_area) {
271 var callbacks = {};
268 var callbacks = {};
272 if (output_area != null) {
269 if (output_area != null) {
273 var that = this;
270 var that = this;
274 callbacks = {
271 callbacks = {
275 iopub : {
272 iopub : {
276 output : $.proxy(output_area.handle_output, output_area),
273 output : $.proxy(output_area.handle_output, output_area),
277 clear_output : $.proxy(output_area.handle_clear_output, output_area),
274 clear_output : $.proxy(output_area.handle_clear_output, output_area),
278 status : function(msg){
275 status : function(msg){
279 that.handle_status(output_area, msg);
276 that.handle_status(output_area, msg);
280 },
281 get_output_area : function() {
282 if (that.last_modified_view != undefined &&
283 that.last_modified_view.output_area != undefined) {
284 return that.last_modified_view.output_area;
285 } else {
286 return null
287 }
288 },
289 },
277 },
290 };
278 get_output_area : function() {
291 }
279 if (that.last_modified_view != undefined &&
292 return callbacks;
280 that.last_modified_view.output_area != undefined) {
293 },
281 return that.last_modified_view.output_area;
282 } else {
283 return null
284 }
285 },
286 },
287 };
288 }
289 return callbacks;
290 },
294
291
295
292
296 // Get the output area corresponding to the msg_id.
293 // Get the output area corresponding to the msg_id.
297 // output_area is an instance of Ipython.OutputArea
294 // output_area is an instance of Ipython.OutputArea
298 _get_output_area: function (msg_id) {
295 _get_output_area: function (msg_id) {
299
296
300 // First, guess cell.execute triggered
297 // First, guess cell.execute triggered
301 var cells = IPython.notebook.get_cells();
298 var cells = IPython.notebook.get_cells();
302 for (var cell_index in cells) {
299 for (var cell_index in cells) {
303 if (cells[cell_index].last_msg_id == msg_id) {
300 if (cells[cell_index].last_msg_id == msg_id) {
304 var cell = IPython.notebook.get_cell(cell_index)
301 var cell = IPython.notebook.get_cell(cell_index)
305 return cell.output_area;
302 return cell.output_area;
306 }
307 }
303 }
304 }
308
305
309 // Second, guess widget triggered
306 // Second, guess widget triggered
310 var callbacks = this.comm_manager.kernel.get_callbacks_for_msg(msg_id)
307 var callbacks = this.comm_manager.kernel.get_callbacks_for_msg(msg_id)
311 if (callbacks != undefined && callbacks.iopub != undefined && callbacks.iopub.get_output_area != undefined) {
308 if (callbacks != undefined && callbacks.iopub != undefined && callbacks.iopub.get_output_area != undefined) {
312 var output_area = callbacks.iopub.get_output_area();
309 var output_area = callbacks.iopub.get_output_area();
313 if (output_area != null) {
310 if (output_area != null) {
314 return output_area;
311 return output_area;
315 }
316 }
312 }
317
313 }
318 // Not triggered by a widget or a cell
319 return null;
320 },
321
322 // Gets widget output area (as a JQuery element) from the
323 // output_area (Ipython.OutputArea instance)
324 _get_widget_area_element: function (output_area, show) {
325 var widget_area = output_area.element
326 .parent() // output_wrapper
327 .parent() // cell
328 .find('.widget-area');
329 if (show) { widget_area.show(); }
330 return widget_area.find('.widget-subarea');
331 },
332
333 });
334
335
336 //--------------------------------------------------------------------
337 // WidgetView class
338 //--------------------------------------------------------------------
339 var WidgetView = Backbone.View.extend({
340
341 initialize: function() {
342 this.visible = true;
343 this.model.on('change',this.update,this);
344 this._add_class_calls = this.model.get('_add_class')[0];
345 this._remove_class_calls = this.model.get('_remove_class')[0];
346 },
347
314
348 update: function() {
315 // Not triggered by a widget or a cell
349 if (this.model.get('visible') != undefined) {
316 return null;
350 if (this.visible != this.model.get('visible')) {
317 },
351 this.visible = this.model.get('visible');
318
352 if (this.visible) {
319 // Gets widget output area (as a JQuery element) from the
353 this.$el.show();
320 // output_area (Ipython.OutputArea instance)
354 } else {
321 _get_widget_area_element: function (output_area, show) {
355 this.$el.hide();
322 var widget_area = output_area.element
356 }
323 .parent() // output_wrapper
324 .parent() // cell
325 .find('.widget-area');
326 if (show) { widget_area.show(); }
327 return widget_area.find('.widget-subarea');
328 },
329
330 });
331
332
333 //--------------------------------------------------------------------
334 // WidgetView class
335 //--------------------------------------------------------------------
336 var WidgetView = Backbone.View.extend({
337
338 initialize: function() {
339 this.visible = true;
340 this.model.on('change',this.update,this);
341 this._add_class_calls = this.model.get('_add_class')[0];
342 this._remove_class_calls = this.model.get('_remove_class')[0];
343 },
344
345 update: function() {
346 if (this.model.get('visible') != undefined) {
347 if (this.visible != this.model.get('visible')) {
348 this.visible = this.model.get('visible');
349 if (this.visible) {
350 this.$el.show();
351 } else {
352 this.$el.hide();
357 }
353 }
358 }
354 }
359
355 }
360 if (this.model.css != undefined) {
356
361 for (var selector in this.model.css) {
357 if (this.model.css != undefined) {
362 if (this.model.css.hasOwnProperty(selector)) {
358 for (var selector in this.model.css) {
363
359 if (this.model.css.hasOwnProperty(selector)) {
364 // Apply the css traits to all elements that match the selector.
360
365 var elements = this.get_selector_element(selector);
361 // Apply the css traits to all elements that match the selector.
366 if (elements.length > 0) {
362 var elements = this.get_selector_element(selector);
367 var css_traits = this.model.css[selector];
363 if (elements.length > 0) {
368 for (var css_key in css_traits) {
364 var css_traits = this.model.css[selector];
369 if (css_traits.hasOwnProperty(css_key)) {
365 for (var css_key in css_traits) {
370 elements.css(css_key, css_traits[css_key]);
366 if (css_traits.hasOwnProperty(css_key)) {
371 }
367 elements.css(css_key, css_traits[css_key]);
372 }
368 }
373 }
369 }
374 }
370 }
375 }
371 }
376 }
372 }
377
373 }
378 var add_class = this.model.get('_add_class');
374
379 if (add_class != undefined){
375 var add_class = this.model.get('_add_class');
380 var add_class_calls = add_class[0];
376 if (add_class != undefined){
381 if (add_class_calls > this._add_class_calls) {
377 var add_class_calls = add_class[0];
382 this._add_class_calls = add_class_calls;
378 if (add_class_calls > this._add_class_calls) {
383 var elements = this.get_selector_element(add_class[1]);
379 this._add_class_calls = add_class_calls;
384 if (elements.length > 0) {
380 var elements = this.get_selector_element(add_class[1]);
385 elements.addClass(add_class[2]);
381 if (elements.length > 0) {
386 }
382 elements.addClass(add_class[2]);
387 }
388 }
389
390 var remove_class = this.model.get('_remove_class');
391 if (remove_class != undefined){
392 var remove_class_calls = remove_class[0];
393 if (remove_class_calls > this._remove_class_calls) {
394 this._remove_class_calls = remove_class_calls;
395 var elements = this.get_selector_element(remove_class[1]);
396 if (elements.length > 0) {
397 elements.removeClass(remove_class[2]);
398 }
399 }
400 }
401 },
402
403 get_selector_element: function(selector) {
404 // Get the elements via the css selector. If the selector is
405 // blank, apply the style to the $el_to_style element. If
406 // the $el_to_style element is not defined, use apply the
407 // style to the view's element.
408 var elements = this.$el.find(selector);
409 if (selector=='') {
410 if (this.$el_to_style == undefined) {
411 elements = this.$el;
412 } else {
413 elements = this.$el_to_style;
414 }
383 }
384 }
385 }
386
387 var remove_class = this.model.get('_remove_class');
388 if (remove_class != undefined){
389 var remove_class_calls = remove_class[0];
390 if (remove_class_calls > this._remove_class_calls) {
391 this._remove_class_calls = remove_class_calls;
392 var elements = this.get_selector_element(remove_class[1]);
393 if (elements.length > 0) {
394 elements.removeClass(remove_class[2]);
395 }
396 }
397 }
398 },
399
400 get_selector_element: function(selector) {
401 // Get the elements via the css selector. If the selector is
402 // blank, apply the style to the $el_to_style element. If
403 // the $el_to_style element is not defined, use apply the
404 // style to the view's element.
405 var elements = this.$el.find(selector);
406 if (selector=='') {
407 if (this.$el_to_style == undefined) {
408 elements = this.$el;
409 } else {
410 elements = this.$el_to_style;
415 }
411 }
416 return elements;
412 }
417 },
413 return elements;
418 });
414 },
419
415 });
420
416
421 //--------------------------------------------------------------------
417
422 // WidgetManager class
418 //--------------------------------------------------------------------
423 //--------------------------------------------------------------------
419 // WidgetManager class
424 var WidgetManager = function(comm_manager){
420 //--------------------------------------------------------------------
425 this.comm_manager = comm_manager;
421 var WidgetManager = function(){
426 this.widget_model_types = {};
422 this.comm_manager = null;
427 this.widget_view_types = {};
423 this.widget_model_types = {};
428
424 this.widget_view_types = {};
429 var that = this;
425
430 Backbone.sync = function(method, model, options, error) {
426 var that = this;
431 var result = model.handle_sync(method, options);
427 Backbone.sync = function(method, model, options, error) {
432 if (options.success) {
428 var result = model.handle_sync(method, options);
433 options.success(result);
429 if (options.success) {
434 }
430 options.success(result);
435 };
431 }
432 };
433 }
434
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));
436 }
442 }
443 }
437
444
438
445
439 WidgetManager.prototype.register_widget_model = function (widget_model_name, widget_model_type) {
446 WidgetManager.prototype.register_widget_model = function (widget_model_name, widget_model_type) {
440 // Register the widget with the comm manager. Make sure to pass this object's context
447 // Register the widget with the comm manager. Make sure to pass this object's context
441 // in so `this` works in the call back.
448 // in so `this` works in the call back.
449 if (this.comm_manager!=null) {
442 this.comm_manager.register_target(widget_model_name, $.proxy(this.handle_com_open, this));
450 this.comm_manager.register_target(widget_model_name, $.proxy(this.handle_com_open, this));
443 this.widget_model_types[widget_model_name] = widget_model_type;
444 }
451 }
452 this.widget_model_types[widget_model_name] = widget_model_type;
453 }
445
454
446
455
447 WidgetManager.prototype.register_widget_view = function (widget_view_name, widget_view_type) {
456 WidgetManager.prototype.register_widget_view = function (widget_view_name, widget_view_type) {
448 this.widget_view_types[widget_view_name] = widget_view_type;
457 this.widget_view_types[widget_view_name] = widget_view_type;
449 }
458 }
450
459
451
460
452 WidgetManager.prototype.handle_com_open = function (comm, msg) {
461 WidgetManager.prototype.handle_com_open = function (comm, msg) {
453 var widget_type_name = msg.content.target_name;
462 var widget_type_name = msg.content.target_name;
454 var widget_model = new this.widget_model_types[widget_type_name](this.comm_manager, comm, this.widget_view_types);
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 // Init code
468 // Init code
460 //--------------------------------------------------------------------
469 //--------------------------------------------------------------------
461 IPython.WidgetManager = WidgetManager;
470 IPython.WidgetManager = WidgetManager;
462 IPython.WidgetModel = WidgetModel;
471 IPython.WidgetModel = WidgetModel;
463 IPython.WidgetView = WidgetView;
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 var BoolWidgetModel = IPython.WidgetModel.extend({});
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 var CheckboxView = IPython.WidgetView.extend({
7 var CheckboxView = IPython.WidgetView.extend({
8
8
9 // Called when view is rendered.
9 // Called when view is rendered.
10 render : function(){
10 render : function(){
11 this.$el = $('<div />')
11 this.$el = $('<div />')
12 .addClass('widget-hbox-single');
12 .addClass('widget-hbox-single');
13 this.$label = $('<div />')
13 this.$label = $('<div />')
14 .addClass('widget-hlabel')
14 .addClass('widget-hlabel')
15 .appendTo(this.$el)
15 .appendTo(this.$el)
16 .hide();
16 .hide();
17 var that = this;
17 var that = this;
18 this.$checkbox = $('<input />')
18 this.$checkbox = $('<input />')
19 .attr('type', 'checkbox')
19 .attr('type', 'checkbox')
20 .click(function(el) {
20 .click(function(el) {
21 that.user_invoked_update = true;
21 that.user_invoked_update = true;
22 that.model.set('value', that.$checkbox.prop('checked'));
22 that.model.set('value', that.$checkbox.prop('checked'));
23 that.model.update_other_views(that);
23 that.model.update_other_views(that);
24 that.user_invoked_update = false;
24 that.user_invoked_update = false;
25 })
25 })
26 .appendTo(this.$el);
26 .appendTo(this.$el);
27
27
28 this.$el_to_style = this.$checkbox; // Set default element to style
28 this.$el_to_style = this.$checkbox; // Set default element to style
29 this.update(); // Set defaults.
29 this.update(); // Set defaults.
30 },
30 },
31
31
32 // Handles: Backend -> Frontend Sync
32 // Handles: Backend -> Frontend Sync
33 // Frontent -> Frontend Sync
33 // Frontent -> Frontend Sync
34 update : function(){
34 update : function(){
35 if (!this.user_invoked_update) {
35 if (!this.user_invoked_update) {
36 this.$checkbox.prop('checked', this.model.get('value'));
36 this.$checkbox.prop('checked', this.model.get('value'));
37
37
38 var disabled = this.model.get('disabled');
38 var disabled = this.model.get('disabled');
39 this.$checkbox.prop('disabled', disabled);
39 this.$checkbox.prop('disabled', disabled);
40
40
41 var description = this.model.get('description');
41 var description = this.model.get('description');
42 if (description.length == 0) {
42 if (description.length == 0) {
43 this.$label.hide();
43 this.$label.hide();
44 } else {
44 } else {
45 this.$label.html(description);
45 this.$label.html(description);
46 this.$label.show();
46 this.$label.show();
47 }
47 }
48 }
48 }
49 return IPython.WidgetView.prototype.update.call(this);
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 var ToggleButtonView = IPython.WidgetView.extend({
56 var ToggleButtonView = IPython.WidgetView.extend({
57
57
58 // Called when view is rendered.
58 // Called when view is rendered.
59 render : function(){
59 render : function(){
60 this.$el
60 this.$el
61 .html('');
61 .html('');
62
62
63 this.$button = $('<button />')
63 this.$button = $('<button />')
64 .addClass('btn')
64 .addClass('btn')
65 .attr('type', 'button')
65 .attr('type', 'button')
66 .attr('data-toggle', 'button')
66 .attr('data-toggle', 'button')
67 .appendTo(this.$el);
67 .appendTo(this.$el);
68 this.$el_to_style = this.$button; // Set default element to style
68 this.$el_to_style = this.$button; // Set default element to style
69
69
70 this.update(); // Set defaults.
70 this.update(); // Set defaults.
71 },
71 },
72
72
73 // Handles: Backend -> Frontend Sync
73 // Handles: Backend -> Frontend Sync
74 // Frontent -> Frontend Sync
74 // Frontent -> Frontend Sync
75 update : function(){
75 update : function(){
76 if (!this.user_invoked_update) {
76 if (!this.user_invoked_update) {
77 if (this.model.get('value')) {
77 if (this.model.get('value')) {
78 this.$button.addClass('active');
78 this.$button.addClass('active');
79 } else {
79 } else {
80 this.$button.removeClass('active');
80 this.$button.removeClass('active');
81 }
81 }
82
82
83 var disabled = this.model.get('disabled');
83 var disabled = this.model.get('disabled');
84 this.$button.prop('disabled', disabled);
84 this.$button.prop('disabled', disabled);
85
85
86 var description = this.model.get('description');
86 var description = this.model.get('description');
87 if (description.length == 0) {
87 if (description.length == 0) {
88 this.$button.html(' '); // Preserve button height
88 this.$button.html(' '); // Preserve button height
89 } else {
89 } else {
90 this.$button.html(description);
90 this.$button.html(description);
91 }
91 }
92 }
92 }
93 return IPython.WidgetView.prototype.update.call(this);
93 return IPython.WidgetView.prototype.update.call(this);
94 },
94 },
95
95
96 events: {"click button" : "handleClick"},
96 events: {"click button" : "handleClick"},
97
97
98 // Handles and validates user input.
98 // Handles and validates user input.
99 handleClick: function(e) {
99 handleClick: function(e) {
100 this.user_invoked_update = true;
100 this.user_invoked_update = true;
101 this.model.set('value', ! $(e.target).hasClass('active'));
101 this.model.set('value', ! $(e.target).hasClass('active'));
102 this.model.update_other_views(this);
102 this.model.update_other_views(this);
103 this.user_invoked_update = false;
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 var ButtonWidgetModel = IPython.WidgetModel.extend({});
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 var ButtonView = IPython.WidgetView.extend({
7 var ButtonView = IPython.WidgetView.extend({
8
8
9 // Called when view is rendered.
9 // Called when view is rendered.
10 render : function(){
10 render : function(){
11 var that = this;
11 var that = this;
12 this.$el = $("<button />")
12 this.$el = $("<button />")
13 .addClass('btn')
13 .addClass('btn')
14 .click(function() {
14 .click(function() {
15 that.model.set('clicks', that.model.get('clicks') + 1);
15 that.model.set('clicks', that.model.get('clicks') + 1);
16 that.model.update_other_views(that);
16 that.model.update_other_views(that);
17 });
17 });
18
18
19 this.update(); // Set defaults.
19 this.update(); // Set defaults.
20 },
20 },
21
21
22 // Handles: Backend -> Frontend Sync
22 // Handles: Backend -> Frontend Sync
23 // Frontent -> Frontend Sync
23 // Frontent -> Frontend Sync
24 update : function(){
24 update : function(){
25 var description = this.model.get('description');
25 var description = this.model.get('description');
26 if (description.length==0) {
26 if (description.length==0) {
27 this.$el.html(' '); // Preserve button height
27 this.$el.html(' '); // Preserve button height
28 } else {
28 } else {
29 this.$el.html(description);
29 this.$el.html(description);
30 }
30 }
31
31
32 return IPython.WidgetView.prototype.update.call(this);
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 var ContainerModel = IPython.WidgetModel.extend({});
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 var ContainerView = IPython.WidgetView.extend({
5 var ContainerView = IPython.WidgetView.extend({
6
6
7 render: function(){
7 render: function(){
8 this.$el = $('<div />')
8 this.$el = $('<div />')
9 .addClass('widget-container');
9 .addClass('widget-container');
10 },
10 },
11
11
12 update: function(){
12 update: function(){
13
13
14 // Apply flexible box model properties by adding and removing
14 // Apply flexible box model properties by adding and removing
15 // corrosponding CSS classes.
15 // corrosponding CSS classes.
16 // Defined in IPython/html/static/base/less/flexbox.less
16 // Defined in IPython/html/static/base/less/flexbox.less
17 this.set_flex_property('vbox', this.model.get('_vbox'));
17 this.set_flex_property('vbox', this.model.get('_vbox'));
18 this.set_flex_property('hbox', this.model.get('_hbox'));
18 this.set_flex_property('hbox', this.model.get('_hbox'));
19 this.set_flex_property('start', this.model.get('_pack_start'));
19 this.set_flex_property('start', this.model.get('_pack_start'));
20 this.set_flex_property('center', this.model.get('_pack_center'));
20 this.set_flex_property('center', this.model.get('_pack_center'));
21 this.set_flex_property('end', this.model.get('_pack_end'));
21 this.set_flex_property('end', this.model.get('_pack_end'));
22 this.set_flex_property('align-start', this.model.get('_align_start'));
22 this.set_flex_property('align-start', this.model.get('_align_start'));
23 this.set_flex_property('align-center', this.model.get('_align_center'));
23 this.set_flex_property('align-center', this.model.get('_align_center'));
24 this.set_flex_property('align-end', this.model.get('_align_end'));
24 this.set_flex_property('align-end', this.model.get('_align_end'));
25 this.set_flex_property('box-flex0', this.model.get('_flex0'));
25 this.set_flex_property('box-flex0', this.model.get('_flex0'));
26 this.set_flex_property('box-flex1', this.model.get('_flex1'));
26 this.set_flex_property('box-flex1', this.model.get('_flex1'));
27 this.set_flex_property('box-flex2', this.model.get('_flex2'));
27 this.set_flex_property('box-flex2', this.model.get('_flex2'));
28
28
29 return IPython.WidgetView.prototype.update.call(this);
29 return IPython.WidgetView.prototype.update.call(this);
30 },
30 },
31
31
32 set_flex_property: function(property_name, enabled) {
32 set_flex_property: function(property_name, enabled) {
33 if (enabled) {
33 if (enabled) {
34 this.$el.addClass(property_name);
34 this.$el.addClass(property_name);
35 } else {
35 } else {
36 this.$el.removeClass(property_name);
36 this.$el.removeClass(property_name);
37 }
37 }
38 },
38 },
39
39
40 display_child: function(view) {
40 display_child: function(view) {
41 this.$el.append(view.$el);
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 }); No newline at end of file
46 });
@@ -1,4 +1,4 b''
1 require(["notebook/js/widget"], function(){
1 define(["notebook/js/widget"], function(){
2 var FloatWidgetModel = IPython.WidgetModel.extend({});
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 }); No newline at end of file
4 });
@@ -1,239 +1,239 b''
1 require(["notebook/js/widget"], function(){
1 define(["notebook/js/widget"], function(){
2 var FloatRangeWidgetModel = IPython.WidgetModel.extend({});
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 var FloatSliderView = IPython.WidgetView.extend({
5 var FloatSliderView = IPython.WidgetView.extend({
6
6
7 // Called when view is rendered.
7 // Called when view is rendered.
8 render : function(){
8 render : function(){
9 this.$el
9 this.$el
10 .addClass('widget-hbox-single')
10 .addClass('widget-hbox-single')
11 .html('');
11 .html('');
12 this.$label = $('<div />')
12 this.$label = $('<div />')
13 .appendTo(this.$el)
13 .appendTo(this.$el)
14 .addClass('widget-hlabel')
14 .addClass('widget-hlabel')
15 .hide();
15 .hide();
16 this.$slider = $('<div />')
16 this.$slider = $('<div />')
17 .slider({})
17 .slider({})
18 .addClass('slider');
18 .addClass('slider');
19
19
20 // Put the slider in a container
20 // Put the slider in a container
21 this.$slider_container = $('<div />')
21 this.$slider_container = $('<div />')
22 .addClass('widget-hslider')
22 .addClass('widget-hslider')
23 .append(this.$slider);
23 .append(this.$slider);
24 this.$el_to_style = this.$slider_container; // Set default element to style
24 this.$el_to_style = this.$slider_container; // Set default element to style
25 this.$el.append(this.$slider_container);
25 this.$el.append(this.$slider_container);
26
26
27 // Set defaults.
27 // Set defaults.
28 this.update();
28 this.update();
29 },
29 },
30
30
31 // Handles: Backend -> Frontend Sync
31 // Handles: Backend -> Frontend Sync
32 // Frontent -> Frontend Sync
32 // Frontent -> Frontend Sync
33 update : function(){
33 update : function(){
34 // Slider related keys.
34 // Slider related keys.
35 var _keys = ['step', 'max', 'min', 'disabled'];
35 var _keys = ['step', 'max', 'min', 'disabled'];
36 for (var index in _keys) {
36 for (var index in _keys) {
37 var key = _keys[index];
37 var key = _keys[index];
38 if (this.model.get(key) != undefined) {
38 if (this.model.get(key) != undefined) {
39 this.$slider.slider("option", key, this.model.get(key));
39 this.$slider.slider("option", key, this.model.get(key));
40 }
40 }
41 }
41 }
42
42
43 // WORKAROUND FOR JQUERY SLIDER BUG.
43 // WORKAROUND FOR JQUERY SLIDER BUG.
44 // The horizontal position of the slider handle
44 // The horizontal position of the slider handle
45 // depends on the value of the slider at the time
45 // depends on the value of the slider at the time
46 // of orientation change. Before applying the new
46 // of orientation change. Before applying the new
47 // workaround, we set the value to the minimum to
47 // workaround, we set the value to the minimum to
48 // make sure that the horizontal placement of the
48 // make sure that the horizontal placement of the
49 // handle in the vertical slider is always
49 // handle in the vertical slider is always
50 // consistent.
50 // consistent.
51 var orientation = this.model.get('orientation');
51 var orientation = this.model.get('orientation');
52 var value = this.model.get('min');
52 var value = this.model.get('min');
53 this.$slider.slider('option', 'value', value);
53 this.$slider.slider('option', 'value', value);
54 this.$slider.slider('option', 'orientation', orientation);
54 this.$slider.slider('option', 'orientation', orientation);
55 var value = this.model.get('value');
55 var value = this.model.get('value');
56 this.$slider.slider('option', 'value', value);
56 this.$slider.slider('option', 'value', value);
57
57
58 // Use the right CSS classes for vertical & horizontal sliders
58 // Use the right CSS classes for vertical & horizontal sliders
59 if (orientation=='vertical') {
59 if (orientation=='vertical') {
60 this.$slider_container
60 this.$slider_container
61 .removeClass('widget-hslider')
61 .removeClass('widget-hslider')
62 .addClass('widget-vslider');
62 .addClass('widget-vslider');
63 this.$el
63 this.$el
64 .removeClass('widget-hbox-single')
64 .removeClass('widget-hbox-single')
65 .addClass('widget-vbox-single');
65 .addClass('widget-vbox-single');
66 this.$label
66 this.$label
67 .removeClass('widget-hlabel')
67 .removeClass('widget-hlabel')
68 .addClass('widget-vlabel');
68 .addClass('widget-vlabel');
69
69
70 } else {
70 } else {
71 this.$slider_container
71 this.$slider_container
72 .removeClass('widget-vslider')
72 .removeClass('widget-vslider')
73 .addClass('widget-hslider');
73 .addClass('widget-hslider');
74 this.$el
74 this.$el
75 .removeClass('widget-vbox-single')
75 .removeClass('widget-vbox-single')
76 .addClass('widget-hbox-single');
76 .addClass('widget-hbox-single');
77 this.$label
77 this.$label
78 .removeClass('widget-vlabel')
78 .removeClass('widget-vlabel')
79 .addClass('widget-hlabel');
79 .addClass('widget-hlabel');
80 }
80 }
81
81
82 var description = this.model.get('description');
82 var description = this.model.get('description');
83 if (description.length == 0) {
83 if (description.length == 0) {
84 this.$label.hide();
84 this.$label.hide();
85 } else {
85 } else {
86 this.$label.html(description);
86 this.$label.html(description);
87 this.$label.show();
87 this.$label.show();
88 }
88 }
89 return IPython.WidgetView.prototype.update.call(this);
89 return IPython.WidgetView.prototype.update.call(this);
90 },
90 },
91
91
92 // Handles: User input
92 // Handles: User input
93 events: { "slide" : "handleSliderChange" },
93 events: { "slide" : "handleSliderChange" },
94 handleSliderChange: function(e, ui) {
94 handleSliderChange: function(e, ui) {
95 this.model.set('value', ui.value);
95 this.model.set('value', ui.value);
96 this.model.update_other_views(this);
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 var FloatTextView = IPython.WidgetView.extend({
103 var FloatTextView = IPython.WidgetView.extend({
104
104
105 // Called when view is rendered.
105 // Called when view is rendered.
106 render : function(){
106 render : function(){
107 this.$el
107 this.$el
108 .addClass('widget-hbox-single')
108 .addClass('widget-hbox-single')
109 .html('');
109 .html('');
110 this.$label = $('<div />')
110 this.$label = $('<div />')
111 .appendTo(this.$el)
111 .appendTo(this.$el)
112 .addClass('widget-hlabel')
112 .addClass('widget-hlabel')
113 .hide();
113 .hide();
114 this.$textbox = $('<input type="text" />')
114 this.$textbox = $('<input type="text" />')
115 .addClass('input')
115 .addClass('input')
116 .addClass('widget-numeric-text')
116 .addClass('widget-numeric-text')
117 .appendTo(this.$el);
117 .appendTo(this.$el);
118 this.$el_to_style = this.$textbox; // Set default element to style
118 this.$el_to_style = this.$textbox; // Set default element to style
119 this.update(); // Set defaults.
119 this.update(); // Set defaults.
120 },
120 },
121
121
122 // Handles: Backend -> Frontend Sync
122 // Handles: Backend -> Frontend Sync
123 // Frontent -> Frontend Sync
123 // Frontent -> Frontend Sync
124 update : function(){
124 update : function(){
125 var value = this.model.get('value');
125 var value = this.model.get('value');
126 if (!this.changing && parseFloat(this.$textbox.val()) != value) {
126 if (!this.changing && parseFloat(this.$textbox.val()) != value) {
127 this.$textbox.val(value);
127 this.$textbox.val(value);
128 }
128 }
129
129
130 if (this.model.get('disabled')) {
130 if (this.model.get('disabled')) {
131 this.$textbox.attr('disabled','disabled');
131 this.$textbox.attr('disabled','disabled');
132 } else {
132 } else {
133 this.$textbox.removeAttr('disabled');
133 this.$textbox.removeAttr('disabled');
134 }
134 }
135
135
136 var description = this.model.get('description');
136 var description = this.model.get('description');
137 if (description.length == 0) {
137 if (description.length == 0) {
138 this.$label.hide();
138 this.$label.hide();
139 } else {
139 } else {
140 this.$label.html(description);
140 this.$label.html(description);
141 this.$label.show();
141 this.$label.show();
142 }
142 }
143 return IPython.WidgetView.prototype.update.call(this);
143 return IPython.WidgetView.prototype.update.call(this);
144 },
144 },
145
145
146
146
147 events: {"keyup input" : "handleChanging",
147 events: {"keyup input" : "handleChanging",
148 "paste input" : "handleChanging",
148 "paste input" : "handleChanging",
149 "cut input" : "handleChanging",
149 "cut input" : "handleChanging",
150 "change input" : "handleChanged"}, // Fires only when control is validated or looses focus.
150 "change input" : "handleChanged"}, // Fires only when control is validated or looses focus.
151
151
152 // Handles and validates user input.
152 // Handles and validates user input.
153 handleChanging: function(e) {
153 handleChanging: function(e) {
154
154
155 // Try to parse value as a float.
155 // Try to parse value as a float.
156 var numericalValue = 0.0;
156 var numericalValue = 0.0;
157 if (e.target.value != '') {
157 if (e.target.value != '') {
158 numericalValue = parseFloat(e.target.value);
158 numericalValue = parseFloat(e.target.value);
159 }
159 }
160
160
161 // If parse failed, reset value to value stored in model.
161 // If parse failed, reset value to value stored in model.
162 if (isNaN(numericalValue)) {
162 if (isNaN(numericalValue)) {
163 e.target.value = this.model.get('value');
163 e.target.value = this.model.get('value');
164 } else if (!isNaN(numericalValue)) {
164 } else if (!isNaN(numericalValue)) {
165 if (this.model.get('max') != undefined) {
165 if (this.model.get('max') != undefined) {
166 numericalValue = Math.min(this.model.get('max'), numericalValue);
166 numericalValue = Math.min(this.model.get('max'), numericalValue);
167 }
167 }
168 if (this.model.get('min') != undefined) {
168 if (this.model.get('min') != undefined) {
169 numericalValue = Math.max(this.model.get('min'), numericalValue);
169 numericalValue = Math.max(this.model.get('min'), numericalValue);
170 }
170 }
171
171
172 // Apply the value if it has changed.
172 // Apply the value if it has changed.
173 if (numericalValue != this.model.get('value')) {
173 if (numericalValue != this.model.get('value')) {
174 this.changing = true;
174 this.changing = true;
175 this.model.set('value', numericalValue);
175 this.model.set('value', numericalValue);
176 this.model.update_other_views(this);
176 this.model.update_other_views(this);
177 this.changing = false;
177 this.changing = false;
178 }
178 }
179 }
179 }
180 },
180 },
181
181
182 // Applies validated input.
182 // Applies validated input.
183 handleChanged: function(e) {
183 handleChanged: function(e) {
184 // Update the textbox
184 // Update the textbox
185 if (this.model.get('value') != e.target.value) {
185 if (this.model.get('value') != e.target.value) {
186 e.target.value = this.model.get('value');
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 var ProgressView = IPython.WidgetView.extend({
194 var ProgressView = IPython.WidgetView.extend({
195
195
196 // Called when view is rendered.
196 // Called when view is rendered.
197 render : function(){
197 render : function(){
198 this.$el
198 this.$el
199 .addClass('widget-hbox-single')
199 .addClass('widget-hbox-single')
200 .html('');
200 .html('');
201 this.$label = $('<div />')
201 this.$label = $('<div />')
202 .appendTo(this.$el)
202 .appendTo(this.$el)
203 .addClass('widget-hlabel')
203 .addClass('widget-hlabel')
204 .hide();
204 .hide();
205 this.$progress = $('<div />')
205 this.$progress = $('<div />')
206 .addClass('progress')
206 .addClass('progress')
207 .addClass('widget-progress')
207 .addClass('widget-progress')
208 .appendTo(this.$el);
208 .appendTo(this.$el);
209 this.$el_to_style = this.$progress; // Set default element to style
209 this.$el_to_style = this.$progress; // Set default element to style
210 this.$bar = $('<div />')
210 this.$bar = $('<div />')
211 .addClass('bar')
211 .addClass('bar')
212 .css('width', '50%')
212 .css('width', '50%')
213 .appendTo(this.$progress);
213 .appendTo(this.$progress);
214 this.update(); // Set defaults.
214 this.update(); // Set defaults.
215 },
215 },
216
216
217 // Handles: Backend -> Frontend Sync
217 // Handles: Backend -> Frontend Sync
218 // Frontent -> Frontend Sync
218 // Frontent -> Frontend Sync
219 update : function(){
219 update : function(){
220 var value = this.model.get('value');
220 var value = this.model.get('value');
221 var max = this.model.get('max');
221 var max = this.model.get('max');
222 var min = this.model.get('min');
222 var min = this.model.get('min');
223 var percent = 100.0 * (value - min) / (max - min);
223 var percent = 100.0 * (value - min) / (max - min);
224 this.$bar.css('width', percent + '%');
224 this.$bar.css('width', percent + '%');
225
225
226 var description = this.model.get('description');
226 var description = this.model.get('description');
227 if (description.length == 0) {
227 if (description.length == 0) {
228 this.$label.hide();
228 this.$label.hide();
229 } else {
229 } else {
230 this.$label.html(description);
230 this.$label.html(description);
231 this.$label.show();
231 this.$label.show();
232 }
232 }
233 return IPython.WidgetView.prototype.update.call(this);
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 var IntWidgetModel = IPython.WidgetModel.extend({});
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 }); No newline at end of file
4 });
@@ -1,191 +1,191 b''
1 require(["notebook/js/widget"], function(){
1 define(["notebook/js/widget"], function(){
2 var IntRangeWidgetModel = IPython.WidgetModel.extend({});
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 var IntSliderView = IPython.WidgetView.extend({
5 var IntSliderView = IPython.WidgetView.extend({
6
6
7 // Called when view is rendered.
7 // Called when view is rendered.
8 render : function(){
8 render : function(){
9 this.$el
9 this.$el
10 .addClass('widget-hbox-single')
10 .addClass('widget-hbox-single')
11 .html('');
11 .html('');
12 this.$label = $('<div />')
12 this.$label = $('<div />')
13 .appendTo(this.$el)
13 .appendTo(this.$el)
14 .addClass('widget-hlabel')
14 .addClass('widget-hlabel')
15 .hide();
15 .hide();
16 this.$slider = $('<div />')
16 this.$slider = $('<div />')
17 .slider({})
17 .slider({})
18 .addClass('slider');
18 .addClass('slider');
19
19
20 // Put the slider in a container
20 // Put the slider in a container
21 this.$slider_container = $('<div />')
21 this.$slider_container = $('<div />')
22 .addClass('widget-hslider')
22 .addClass('widget-hslider')
23 .append(this.$slider);
23 .append(this.$slider);
24 this.$el_to_style = this.$slider_container; // Set default element to style
24 this.$el_to_style = this.$slider_container; // Set default element to style
25 this.$el.append(this.$slider_container);
25 this.$el.append(this.$slider_container);
26
26
27 // Set defaults.
27 // Set defaults.
28 this.update();
28 this.update();
29 },
29 },
30
30
31 // Handles: Backend -> Frontend Sync
31 // Handles: Backend -> Frontend Sync
32 // Frontent -> Frontend Sync
32 // Frontent -> Frontend Sync
33 update : function(){
33 update : function(){
34 // Slider related keys.
34 // Slider related keys.
35 var _keys = ['step', 'max', 'min', 'disabled'];
35 var _keys = ['step', 'max', 'min', 'disabled'];
36 for (var index in _keys) {
36 for (var index in _keys) {
37 var key = _keys[index];
37 var key = _keys[index];
38 if (this.model.get(key) != undefined) {
38 if (this.model.get(key) != undefined) {
39 this.$slider.slider("option", key, this.model.get(key));
39 this.$slider.slider("option", key, this.model.get(key));
40 }
40 }
41 }
41 }
42
42
43 // WORKAROUND FOR JQUERY SLIDER BUG.
43 // WORKAROUND FOR JQUERY SLIDER BUG.
44 // The horizontal position of the slider handle
44 // The horizontal position of the slider handle
45 // depends on the value of the slider at the time
45 // depends on the value of the slider at the time
46 // of orientation change. Before applying the new
46 // of orientation change. Before applying the new
47 // workaround, we set the value to the minimum to
47 // workaround, we set the value to the minimum to
48 // make sure that the horizontal placement of the
48 // make sure that the horizontal placement of the
49 // handle in the vertical slider is always
49 // handle in the vertical slider is always
50 // consistent.
50 // consistent.
51 var orientation = this.model.get('orientation');
51 var orientation = this.model.get('orientation');
52 var value = this.model.get('min');
52 var value = this.model.get('min');
53 this.$slider.slider('option', 'value', value);
53 this.$slider.slider('option', 'value', value);
54 this.$slider.slider('option', 'orientation', orientation);
54 this.$slider.slider('option', 'orientation', orientation);
55 var value = this.model.get('value');
55 var value = this.model.get('value');
56 this.$slider.slider('option', 'value', value);
56 this.$slider.slider('option', 'value', value);
57
57
58 // Use the right CSS classes for vertical & horizontal sliders
58 // Use the right CSS classes for vertical & horizontal sliders
59 if (orientation=='vertical') {
59 if (orientation=='vertical') {
60 this.$slider_container
60 this.$slider_container
61 .removeClass('widget-hslider')
61 .removeClass('widget-hslider')
62 .addClass('widget-vslider');
62 .addClass('widget-vslider');
63 this.$el
63 this.$el
64 .removeClass('widget-hbox-single')
64 .removeClass('widget-hbox-single')
65 .addClass('widget-vbox-single');
65 .addClass('widget-vbox-single');
66 this.$label
66 this.$label
67 .removeClass('widget-hlabel')
67 .removeClass('widget-hlabel')
68 .addClass('widget-vlabel');
68 .addClass('widget-vlabel');
69
69
70 } else {
70 } else {
71 this.$slider_container
71 this.$slider_container
72 .removeClass('widget-vslider')
72 .removeClass('widget-vslider')
73 .addClass('widget-hslider');
73 .addClass('widget-hslider');
74 this.$el
74 this.$el
75 .removeClass('widget-vbox-single')
75 .removeClass('widget-vbox-single')
76 .addClass('widget-hbox-single');
76 .addClass('widget-hbox-single');
77 this.$label
77 this.$label
78 .removeClass('widget-vlabel')
78 .removeClass('widget-vlabel')
79 .addClass('widget-hlabel');
79 .addClass('widget-hlabel');
80 }
80 }
81
81
82 var description = this.model.get('description');
82 var description = this.model.get('description');
83 if (description.length == 0) {
83 if (description.length == 0) {
84 this.$label.hide();
84 this.$label.hide();
85 } else {
85 } else {
86 this.$label.html(description);
86 this.$label.html(description);
87 this.$label.show();
87 this.$label.show();
88 }
88 }
89 return IPython.WidgetView.prototype.update.call(this);
89 return IPython.WidgetView.prototype.update.call(this);
90 },
90 },
91
91
92 // Handles: User input
92 // Handles: User input
93 events: { "slide" : "handleSliderChange" },
93 events: { "slide" : "handleSliderChange" },
94 handleSliderChange: function(e, ui) {
94 handleSliderChange: function(e, ui) {
95 this.model.set('value', ~~ui.value); // Double bit-wise not to truncate decimel
95 this.model.set('value', ~~ui.value); // Double bit-wise not to truncate decimel
96 this.model.update_other_views(this);
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 var IntTextView = IPython.WidgetView.extend({
102 var IntTextView = IPython.WidgetView.extend({
103
103
104 // Called when view is rendered.
104 // Called when view is rendered.
105 render : function(){
105 render : function(){
106 this.$el
106 this.$el
107 .addClass('widget-hbox-single')
107 .addClass('widget-hbox-single')
108 .html('');
108 .html('');
109 this.$label = $('<div />')
109 this.$label = $('<div />')
110 .appendTo(this.$el)
110 .appendTo(this.$el)
111 .addClass('widget-hlabel')
111 .addClass('widget-hlabel')
112 .hide();
112 .hide();
113 this.$textbox = $('<input type="text" />')
113 this.$textbox = $('<input type="text" />')
114 .addClass('input')
114 .addClass('input')
115 .addClass('widget-numeric-text')
115 .addClass('widget-numeric-text')
116 .appendTo(this.$el);
116 .appendTo(this.$el);
117 this.$el_to_style = this.$textbox; // Set default element to style
117 this.$el_to_style = this.$textbox; // Set default element to style
118 this.update(); // Set defaults.
118 this.update(); // Set defaults.
119 },
119 },
120
120
121 // Handles: Backend -> Frontend Sync
121 // Handles: Backend -> Frontend Sync
122 // Frontent -> Frontend Sync
122 // Frontent -> Frontend Sync
123 update : function(){
123 update : function(){
124 var value = this.model.get('value');
124 var value = this.model.get('value');
125 if (!this.changing && parseInt(this.$textbox.val()) != value) {
125 if (!this.changing && parseInt(this.$textbox.val()) != value) {
126 this.$textbox.val(value);
126 this.$textbox.val(value);
127 }
127 }
128
128
129 if (this.model.get('disabled')) {
129 if (this.model.get('disabled')) {
130 this.$textbox.attr('disabled','disabled');
130 this.$textbox.attr('disabled','disabled');
131 } else {
131 } else {
132 this.$textbox.removeAttr('disabled');
132 this.$textbox.removeAttr('disabled');
133 }
133 }
134
134
135 var description = this.model.get('description');
135 var description = this.model.get('description');
136 if (description.length == 0) {
136 if (description.length == 0) {
137 this.$label.hide();
137 this.$label.hide();
138 } else {
138 } else {
139 this.$label.html(description);
139 this.$label.html(description);
140 this.$label.show();
140 this.$label.show();
141 }
141 }
142 return IPython.WidgetView.prototype.update.call(this);
142 return IPython.WidgetView.prototype.update.call(this);
143 },
143 },
144
144
145
145
146 events: {"keyup input" : "handleChanging",
146 events: {"keyup input" : "handleChanging",
147 "paste input" : "handleChanging",
147 "paste input" : "handleChanging",
148 "cut input" : "handleChanging",
148 "cut input" : "handleChanging",
149 "change input" : "handleChanged"}, // Fires only when control is validated or looses focus.
149 "change input" : "handleChanged"}, // Fires only when control is validated or looses focus.
150
150
151 // Handles and validates user input.
151 // Handles and validates user input.
152 handleChanging: function(e) {
152 handleChanging: function(e) {
153
153
154 // Try to parse value as a float.
154 // Try to parse value as a float.
155 var numericalValue = 0;
155 var numericalValue = 0;
156 if (e.target.value != '') {
156 if (e.target.value != '') {
157 numericalValue = parseInt(e.target.value);
157 numericalValue = parseInt(e.target.value);
158 }
158 }
159
159
160 // If parse failed, reset value to value stored in model.
160 // If parse failed, reset value to value stored in model.
161 if (isNaN(numericalValue)) {
161 if (isNaN(numericalValue)) {
162 e.target.value = this.model.get('value');
162 e.target.value = this.model.get('value');
163 } else if (!isNaN(numericalValue)) {
163 } else if (!isNaN(numericalValue)) {
164 if (this.model.get('max') != undefined) {
164 if (this.model.get('max') != undefined) {
165 numericalValue = Math.min(this.model.get('max'), numericalValue);
165 numericalValue = Math.min(this.model.get('max'), numericalValue);
166 }
166 }
167 if (this.model.get('min') != undefined) {
167 if (this.model.get('min') != undefined) {
168 numericalValue = Math.max(this.model.get('min'), numericalValue);
168 numericalValue = Math.max(this.model.get('min'), numericalValue);
169 }
169 }
170
170
171 // Apply the value if it has changed.
171 // Apply the value if it has changed.
172 if (numericalValue != this.model.get('value')) {
172 if (numericalValue != this.model.get('value')) {
173 this.changing = true;
173 this.changing = true;
174 this.model.set('value', numericalValue);
174 this.model.set('value', numericalValue);
175 this.model.update_other_views(this);
175 this.model.update_other_views(this);
176 this.changing = false;
176 this.changing = false;
177 }
177 }
178 }
178 }
179 },
179 },
180
180
181 // Applies validated input.
181 // Applies validated input.
182 handleChanged: function(e) {
182 handleChanged: function(e) {
183 // Update the textbox
183 // Update the textbox
184 if (this.model.get('value') != e.target.value) {
184 if (this.model.get('value') != e.target.value) {
185 e.target.value = this.model.get('value');
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 var MulticontainerModel = IPython.WidgetModel.extend({});
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 var AccordionView = IPython.WidgetView.extend({
5 var AccordionView = IPython.WidgetView.extend({
6
6
7 render: function(){
7 render: function(){
8 this.$el = $('<div />', {id: IPython.utils.uuid()})
8 this.$el = $('<div />', {id: IPython.utils.uuid()})
9 .addClass('accordion');
9 .addClass('accordion');
10 this.containers = [];
10 this.containers = [];
11 },
11 },
12
12
13 update: function() {
13 update: function() {
14 // Set tab titles
14 // Set tab titles
15 var titles = this.model.get('_titles');
15 var titles = this.model.get('_titles');
16 for (var page_index in titles) {
16 for (var page_index in titles) {
17
17
18 var accordian = this.containers[page_index]
18 var accordian = this.containers[page_index]
19 if (accordian != undefined) {
19 if (accordian != undefined) {
20 accordian
20 accordian
21 .find('.accordion-heading')
21 .find('.accordion-heading')
22 .find('.accordion-toggle')
22 .find('.accordion-toggle')
23 .html(titles[page_index]);
23 .html(titles[page_index]);
24 }
24 }
25 }
25 }
26
26
27 return IPython.WidgetView.prototype.update.call(this);
27 return IPython.WidgetView.prototype.update.call(this);
28 },
28 },
29
29
30 display_child: function(view) {
30 display_child: function(view) {
31
31
32 var index = this.containers.length;
32 var index = this.containers.length;
33 var uuid = IPython.utils.uuid();
33 var uuid = IPython.utils.uuid();
34 var accordion_group = $('<div />')
34 var accordion_group = $('<div />')
35 .addClass('accordion-group')
35 .addClass('accordion-group')
36 .appendTo(this.$el);
36 .appendTo(this.$el);
37 var accordion_heading = $('<div />')
37 var accordion_heading = $('<div />')
38 .addClass('accordion-heading')
38 .addClass('accordion-heading')
39 .appendTo(accordion_group);
39 .appendTo(accordion_group);
40 var accordion_toggle = $('<a />')
40 var accordion_toggle = $('<a />')
41 .addClass('accordion-toggle')
41 .addClass('accordion-toggle')
42 .attr('data-toggle', 'collapse')
42 .attr('data-toggle', 'collapse')
43 .attr('data-parent', '#' + this.$el.attr('id'))
43 .attr('data-parent', '#' + this.$el.attr('id'))
44 .attr('href', '#' + uuid)
44 .attr('href', '#' + uuid)
45 .html('Page ' + index)
45 .html('Page ' + index)
46 .appendTo(accordion_heading);
46 .appendTo(accordion_heading);
47 var accordion_body = $('<div />', {id: uuid})
47 var accordion_body = $('<div />', {id: uuid})
48 .addClass('accordion-body collapse')
48 .addClass('accordion-body collapse')
49 .appendTo(accordion_group);
49 .appendTo(accordion_group);
50 var accordion_inner = $('<div />')
50 var accordion_inner = $('<div />')
51 .addClass('accordion-inner')
51 .addClass('accordion-inner')
52 .appendTo(accordion_body);
52 .appendTo(accordion_body);
53 this.containers.push(accordion_group);
53 this.containers.push(accordion_group);
54
54
55 accordion_inner.append(view.$el);
55 accordion_inner.append(view.$el);
56 this.update();
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 var TabView = IPython.WidgetView.extend({
62 var TabView = IPython.WidgetView.extend({
63
63
64 render: function(){
64 render: function(){
65 this.$el = $('<div />');
65 this.$el = $('<div />');
66 var uuid = IPython.utils.uuid();
66 var uuid = IPython.utils.uuid();
67 var that = this;
67 var that = this;
68 this.$tabs = $('<div />', {id: uuid})
68 this.$tabs = $('<div />', {id: uuid})
69 .addClass('nav')
69 .addClass('nav')
70 .addClass('nav-tabs')
70 .addClass('nav-tabs')
71 .appendTo(this.$el);
71 .appendTo(this.$el);
72 this.$tab_contents = $('<div />', {id: uuid + 'Content'})
72 this.$tab_contents = $('<div />', {id: uuid + 'Content'})
73 .addClass('tab-content')
73 .addClass('tab-content')
74 .appendTo(this.$el);
74 .appendTo(this.$el);
75
75
76 this.containers = [];
76 this.containers = [];
77 },
77 },
78
78
79 update: function() {
79 update: function() {
80 // Set tab titles
80 // Set tab titles
81 var titles = this.model.get('_titles');
81 var titles = this.model.get('_titles');
82 for (var page_index in titles) {
82 for (var page_index in titles) {
83 var tab_text = this.containers[page_index]
83 var tab_text = this.containers[page_index]
84 if (tab_text != undefined) {
84 if (tab_text != undefined) {
85 tab_text.html(titles[page_index]);
85 tab_text.html(titles[page_index]);
86 }
86 }
87 }
87 }
88
88
89 var selected_index = this.model.get('selected_index');
89 var selected_index = this.model.get('selected_index');
90 if (0 <= selected_index && selected_index < this.containers.length) {
90 if (0 <= selected_index && selected_index < this.containers.length) {
91 this.select_page(selected_index);
91 this.select_page(selected_index);
92 }
92 }
93
93
94 return IPython.WidgetView.prototype.update.call(this);
94 return IPython.WidgetView.prototype.update.call(this);
95 },
95 },
96
96
97 display_child: function(view) {
97 display_child: function(view) {
98
98
99 var index = this.containers.length;
99 var index = this.containers.length;
100 var uuid = IPython.utils.uuid();
100 var uuid = IPython.utils.uuid();
101
101
102 var that = this;
102 var that = this;
103 var tab = $('<li />')
103 var tab = $('<li />')
104 .css('list-style-type', 'none')
104 .css('list-style-type', 'none')
105 .appendTo(this.$tabs);
105 .appendTo(this.$tabs);
106 var tab_text = $('<a />')
106 var tab_text = $('<a />')
107 .attr('href', '#' + uuid)
107 .attr('href', '#' + uuid)
108 .attr('data-toggle', 'tab')
108 .attr('data-toggle', 'tab')
109 .html('Page ' + index)
109 .html('Page ' + index)
110 .appendTo(tab)
110 .appendTo(tab)
111 .click(function (e) {
111 .click(function (e) {
112 that.model.set("selected_index", index);
112 that.model.set("selected_index", index);
113 that.model.update_other_views(that);
113 that.model.update_other_views(that);
114 that.select_page(index);
114 that.select_page(index);
115 });
115 });
116 this.containers.push(tab_text);
116 this.containers.push(tab_text);
117
117
118 var contents_div = $('<div />', {id: uuid})
118 var contents_div = $('<div />', {id: uuid})
119 .addClass('tab-pane')
119 .addClass('tab-pane')
120 .addClass('fade')
120 .addClass('fade')
121 .append(view.$el)
121 .append(view.$el)
122 .appendTo(this.$tab_contents);
122 .appendTo(this.$tab_contents);
123
123
124 if (index==0) {
124 if (index==0) {
125 tab_text.tab('show');
125 tab_text.tab('show');
126 }
126 }
127 this.update();
127 this.update();
128 },
128 },
129
129
130 select_page: function(index) {
130 select_page: function(index) {
131 this.$tabs.find('li')
131 this.$tabs.find('li')
132 .removeClass('active');
132 .removeClass('active');
133 this.containers[index].tab('show');
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 var SelectionWidgetModel = IPython.WidgetModel.extend({});
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 var DropdownView = IPython.WidgetView.extend({
5 var DropdownView = IPython.WidgetView.extend({
6
6
7 // Called when view is rendered.
7 // Called when view is rendered.
8 render : function(){
8 render : function(){
9
9
10 this.$el
10 this.$el
11 .addClass('widget-hbox-single')
11 .addClass('widget-hbox-single')
12 .html('');
12 .html('');
13 this.$label = $('<div />')
13 this.$label = $('<div />')
14 .appendTo(this.$el)
14 .appendTo(this.$el)
15 .addClass('widget-hlabel')
15 .addClass('widget-hlabel')
16 .hide();
16 .hide();
17 this.$buttongroup = $('<div />')
17 this.$buttongroup = $('<div />')
18 .addClass('widget_item')
18 .addClass('widget_item')
19 .addClass('btn-group')
19 .addClass('btn-group')
20 .appendTo(this.$el);
20 .appendTo(this.$el);
21 this.$el_to_style = this.$buttongroup; // Set default element to style
21 this.$el_to_style = this.$buttongroup; // Set default element to style
22 this.$droplabel = $('<button />')
22 this.$droplabel = $('<button />')
23 .addClass('btn')
23 .addClass('btn')
24 .addClass('widget-combo-btn')
24 .addClass('widget-combo-btn')
25 .appendTo(this.$buttongroup);
25 .appendTo(this.$buttongroup);
26 this.$dropbutton = $('<button />')
26 this.$dropbutton = $('<button />')
27 .addClass('btn')
27 .addClass('btn')
28 .addClass('dropdown-toggle')
28 .addClass('dropdown-toggle')
29 .attr('data-toggle', 'dropdown')
29 .attr('data-toggle', 'dropdown')
30 .html('<span class="caret"></span>')
30 .html('<span class="caret"></span>')
31 .appendTo(this.$buttongroup);
31 .appendTo(this.$buttongroup);
32 this.$droplist = $('<ul />')
32 this.$droplist = $('<ul />')
33 .addClass('dropdown-menu')
33 .addClass('dropdown-menu')
34 .appendTo(this.$buttongroup);
34 .appendTo(this.$buttongroup);
35
35
36 // Set defaults.
36 // Set defaults.
37 this.update();
37 this.update();
38 },
38 },
39
39
40 // Handles: Backend -> Frontend Sync
40 // Handles: Backend -> Frontend Sync
41 // Frontent -> Frontend Sync
41 // Frontent -> Frontend Sync
42 update : function(){
42 update : function(){
43 this.$droplabel.html(this.model.get('value'));
43 this.$droplabel.html(this.model.get('value'));
44
44
45 var items = this.model.get('values');
45 var items = this.model.get('values');
46 this.$droplist.html('');
46 this.$droplist.html('');
47 for (var index in items) {
47 for (var index in items) {
48 var that = this;
48 var that = this;
49 var item_button = $('<a href="#"/>')
49 var item_button = $('<a href="#"/>')
50 .html(items[index])
50 .html(items[index])
51 .on('click', function(e){
51 .on('click', function(e){
52 that.model.set('value', $(e.target).html(), this);
52 that.model.set('value', $(e.target).html(), this);
53 that.model.update_other_views(that);
53 that.model.update_other_views(that);
54 })
54 })
55
55
56 this.$droplist.append($('<li />').append(item_button))
56 this.$droplist.append($('<li />').append(item_button))
57 }
57 }
58
58
59 if (this.model.get('disabled')) {
59 if (this.model.get('disabled')) {
60 this.$buttongroup.attr('disabled','disabled');
60 this.$buttongroup.attr('disabled','disabled');
61 this.$droplabel.attr('disabled','disabled');
61 this.$droplabel.attr('disabled','disabled');
62 this.$dropbutton.attr('disabled','disabled');
62 this.$dropbutton.attr('disabled','disabled');
63 this.$droplist.attr('disabled','disabled');
63 this.$droplist.attr('disabled','disabled');
64 } else {
64 } else {
65 this.$buttongroup.removeAttr('disabled');
65 this.$buttongroup.removeAttr('disabled');
66 this.$droplabel.removeAttr('disabled');
66 this.$droplabel.removeAttr('disabled');
67 this.$dropbutton.removeAttr('disabled');
67 this.$dropbutton.removeAttr('disabled');
68 this.$droplist.removeAttr('disabled');
68 this.$droplist.removeAttr('disabled');
69 }
69 }
70
70
71 var description = this.model.get('description');
71 var description = this.model.get('description');
72 if (description.length == 0) {
72 if (description.length == 0) {
73 this.$label.hide();
73 this.$label.hide();
74 } else {
74 } else {
75 this.$label.html(description);
75 this.$label.html(description);
76 this.$label.show();
76 this.$label.show();
77 }
77 }
78 return IPython.WidgetView.prototype.update.call(this);
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 var RadioButtonsView = IPython.WidgetView.extend({
85 var RadioButtonsView = IPython.WidgetView.extend({
86
86
87 // Called when view is rendered.
87 // Called when view is rendered.
88 render : function(){
88 render : function(){
89 this.$el
89 this.$el
90 .addClass('widget-hbox')
90 .addClass('widget-hbox')
91 .html('');
91 .html('');
92 this.$label = $('<div />')
92 this.$label = $('<div />')
93 .appendTo(this.$el)
93 .appendTo(this.$el)
94 .addClass('widget-hlabel')
94 .addClass('widget-hlabel')
95 .hide();
95 .hide();
96 this.$container = $('<div />')
96 this.$container = $('<div />')
97 .appendTo(this.$el)
97 .appendTo(this.$el)
98 .addClass('widget-container')
98 .addClass('widget-container')
99 .addClass('vbox');
99 .addClass('vbox');
100 this.$el_to_style = this.$container; // Set default element to style
100 this.$el_to_style = this.$container; // Set default element to style
101 this.update();
101 this.update();
102 },
102 },
103
103
104 // Handles: Backend -> Frontend Sync
104 // Handles: Backend -> Frontend Sync
105 // Frontent -> Frontend Sync
105 // Frontent -> Frontend Sync
106 update : function(){
106 update : function(){
107
107
108 // Add missing items to the DOM.
108 // Add missing items to the DOM.
109 var items = this.model.get('values');
109 var items = this.model.get('values');
110 var disabled = this.model.get('disabled');
110 var disabled = this.model.get('disabled');
111 for (var index in items) {
111 for (var index in items) {
112 var item_query = ' :input[value="' + items[index] + '"]';
112 var item_query = ' :input[value="' + items[index] + '"]';
113 if (this.$el.find(item_query).length == 0) {
113 if (this.$el.find(item_query).length == 0) {
114 var $label = $('<label />')
114 var $label = $('<label />')
115 .addClass('radio')
115 .addClass('radio')
116 .html(items[index])
116 .html(items[index])
117 .appendTo(this.$container);
117 .appendTo(this.$container);
118
118
119 var that = this;
119 var that = this;
120 $('<input />')
120 $('<input />')
121 .attr('type', 'radio')
121 .attr('type', 'radio')
122 .addClass(this.model)
122 .addClass(this.model)
123 .val(items[index])
123 .val(items[index])
124 .prependTo($label)
124 .prependTo($label)
125 .on('click', function(e){
125 .on('click', function(e){
126 that.model.set('value', $(e.target).val(), this);
126 that.model.set('value', $(e.target).val(), this);
127 that.model.update_other_views(that);
127 that.model.update_other_views(that);
128 });
128 });
129 }
129 }
130
130
131 var $item_element = this.$container.find(item_query);
131 var $item_element = this.$container.find(item_query);
132 if (this.model.get('value') == items[index]) {
132 if (this.model.get('value') == items[index]) {
133 $item_element.prop('checked', true);
133 $item_element.prop('checked', true);
134 } else {
134 } else {
135 $item_element.prop('checked', false);
135 $item_element.prop('checked', false);
136 }
136 }
137 $item_element.prop('disabled', disabled);
137 $item_element.prop('disabled', disabled);
138 }
138 }
139
139
140 // Remove items that no longer exist.
140 // Remove items that no longer exist.
141 this.$container.find('input').each(function(i, obj) {
141 this.$container.find('input').each(function(i, obj) {
142 var value = $(obj).val();
142 var value = $(obj).val();
143 var found = false;
143 var found = false;
144 for (var index in items) {
144 for (var index in items) {
145 if (items[index] == value) {
145 if (items[index] == value) {
146 found = true;
146 found = true;
147 break;
147 break;
148 }
148 }
149 }
149 }
150
150
151 if (!found) {
151 if (!found) {
152 $(obj).parent().remove();
152 $(obj).parent().remove();
153 }
153 }
154 });
154 });
155
155
156 var description = this.model.get('description');
156 var description = this.model.get('description');
157 if (description.length == 0) {
157 if (description.length == 0) {
158 this.$label.hide();
158 this.$label.hide();
159 } else {
159 } else {
160 this.$label.html(description);
160 this.$label.html(description);
161 this.$label.show();
161 this.$label.show();
162 }
162 }
163 return IPython.WidgetView.prototype.update.call(this);
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 var ToggleButtonsView = IPython.WidgetView.extend({
171 var ToggleButtonsView = IPython.WidgetView.extend({
172
172
173 // Called when view is rendered.
173 // Called when view is rendered.
174 render : function(){
174 render : function(){
175 this.$el
175 this.$el
176 .addClass('widget-hbox-single')
176 .addClass('widget-hbox-single')
177 .html('');
177 .html('');
178 this.$label = $('<div />')
178 this.$label = $('<div />')
179 .appendTo(this.$el)
179 .appendTo(this.$el)
180 .addClass('widget-hlabel')
180 .addClass('widget-hlabel')
181 .hide();
181 .hide();
182 this.$buttongroup = $('<div />')
182 this.$buttongroup = $('<div />')
183 .addClass('btn-group')
183 .addClass('btn-group')
184 .attr('data-toggle', 'buttons-radio')
184 .attr('data-toggle', 'buttons-radio')
185 .appendTo(this.$el);
185 .appendTo(this.$el);
186 this.$el_to_style = this.$buttongroup; // Set default element to style
186 this.$el_to_style = this.$buttongroup; // Set default element to style
187 this.update();
187 this.update();
188 },
188 },
189
189
190 // Handles: Backend -> Frontend Sync
190 // Handles: Backend -> Frontend Sync
191 // Frontent -> Frontend Sync
191 // Frontent -> Frontend Sync
192 update : function(){
192 update : function(){
193
193
194 // Add missing items to the DOM.
194 // Add missing items to the DOM.
195 var items = this.model.get('values');
195 var items = this.model.get('values');
196 var disabled = this.model.get('disabled');
196 var disabled = this.model.get('disabled');
197 for (var index in items) {
197 for (var index in items) {
198 var item_query = ' :contains("' + items[index] + '")';
198 var item_query = ' :contains("' + items[index] + '")';
199 if (this.$buttongroup.find(item_query).length == 0) {
199 if (this.$buttongroup.find(item_query).length == 0) {
200
200
201 var that = this;
201 var that = this;
202 $('<button />')
202 $('<button />')
203 .attr('type', 'button')
203 .attr('type', 'button')
204 .addClass('btn')
204 .addClass('btn')
205 .html(items[index])
205 .html(items[index])
206 .appendTo(this.$buttongroup)
206 .appendTo(this.$buttongroup)
207 .on('click', function(e){
207 .on('click', function(e){
208 that.model.set('value', $(e.target).html(), this);
208 that.model.set('value', $(e.target).html(), this);
209 that.model.update_other_views(that);
209 that.model.update_other_views(that);
210 });
210 });
211 }
211 }
212
212
213 var $item_element = this.$buttongroup.find(item_query);
213 var $item_element = this.$buttongroup.find(item_query);
214 if (this.model.get('value') == items[index]) {
214 if (this.model.get('value') == items[index]) {
215 $item_element.addClass('active');
215 $item_element.addClass('active');
216 } else {
216 } else {
217 $item_element.removeClass('active');
217 $item_element.removeClass('active');
218 }
218 }
219 $item_element.prop('disabled', disabled);
219 $item_element.prop('disabled', disabled);
220 }
220 }
221
221
222 // Remove items that no longer exist.
222 // Remove items that no longer exist.
223 this.$buttongroup.find('button').each(function(i, obj) {
223 this.$buttongroup.find('button').each(function(i, obj) {
224 var value = $(obj).html();
224 var value = $(obj).html();
225 var found = false;
225 var found = false;
226 for (var index in items) {
226 for (var index in items) {
227 if (items[index] == value) {
227 if (items[index] == value) {
228 found = true;
228 found = true;
229 break;
229 break;
230 }
230 }
231 }
231 }
232
232
233 if (!found) {
233 if (!found) {
234 $(obj).remove();
234 $(obj).remove();
235 }
235 }
236 });
236 });
237
237
238 var description = this.model.get('description');
238 var description = this.model.get('description');
239 if (description.length == 0) {
239 if (description.length == 0) {
240 this.$label.hide();
240 this.$label.hide();
241 } else {
241 } else {
242 this.$label.html(description);
242 this.$label.html(description);
243 this.$label.show();
243 this.$label.show();
244 }
244 }
245 return IPython.WidgetView.prototype.update.call(this);
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 var StringWidgetModel = IPython.WidgetModel.extend({});
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 var LabelView = IPython.WidgetView.extend({
5 var LabelView = IPython.WidgetView.extend({
6
6
7 // Called when view is rendered.
7 // Called when view is rendered.
8 render : function(){
8 render : function(){
9 this.$el = $('<div />');
9 this.$el = $('<div />');
10 this.update(); // Set defaults.
10 this.update(); // Set defaults.
11 },
11 },
12
12
13 // Handles: Backend -> Frontend Sync
13 // Handles: Backend -> Frontend Sync
14 // Frontent -> Frontend Sync
14 // Frontent -> Frontend Sync
15 update : function(){
15 update : function(){
16 this.$el.html(this.model.get('value'));
16 this.$el.html(this.model.get('value'));
17 return IPython.WidgetView.prototype.update.call(this);
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 var TextAreaView = IPython.WidgetView.extend({
24 var TextAreaView = IPython.WidgetView.extend({
25
25
26 // Called when view is rendered.
26 // Called when view is rendered.
27 render : function(){
27 render : function(){
28 this.$el
28 this.$el
29 .addClass('widget-hbox')
29 .addClass('widget-hbox')
30 .html('');
30 .html('');
31 this.$label = $('<div />')
31 this.$label = $('<div />')
32 .appendTo(this.$el)
32 .appendTo(this.$el)
33 .addClass('widget-hlabel')
33 .addClass('widget-hlabel')
34 .hide();
34 .hide();
35 this.$textbox = $('<textarea />')
35 this.$textbox = $('<textarea />')
36 .attr('rows', 5)
36 .attr('rows', 5)
37 .addClass('widget-text')
37 .addClass('widget-text')
38 .appendTo(this.$el);
38 .appendTo(this.$el);
39 this.$el_to_style = this.$textbox; // Set default element to style
39 this.$el_to_style = this.$textbox; // Set default element to style
40 this.update(); // Set defaults.
40 this.update(); // Set defaults.
41 },
41 },
42
42
43 // Handles: Backend -> Frontend Sync
43 // Handles: Backend -> Frontend Sync
44 // Frontent -> Frontend Sync
44 // Frontent -> Frontend Sync
45 update : function(){
45 update : function(){
46 if (!this.user_invoked_update) {
46 if (!this.user_invoked_update) {
47 this.$textbox.val(this.model.get('value'));
47 this.$textbox.val(this.model.get('value'));
48 }
48 }
49
49
50 var disabled = this.model.get('disabled');
50 var disabled = this.model.get('disabled');
51 this.$textbox.prop('disabled', disabled);
51 this.$textbox.prop('disabled', disabled);
52
52
53 var description = this.model.get('description');
53 var description = this.model.get('description');
54 if (description.length == 0) {
54 if (description.length == 0) {
55 this.$label.hide();
55 this.$label.hide();
56 } else {
56 } else {
57 this.$label.html(description);
57 this.$label.html(description);
58 this.$label.show();
58 this.$label.show();
59 }
59 }
60 return IPython.WidgetView.prototype.update.call(this);
60 return IPython.WidgetView.prototype.update.call(this);
61 },
61 },
62
62
63 events: {"keyup textarea" : "handleChanging",
63 events: {"keyup textarea" : "handleChanging",
64 "paste textarea" : "handleChanging",
64 "paste textarea" : "handleChanging",
65 "cut textarea" : "handleChanging"},
65 "cut textarea" : "handleChanging"},
66
66
67 // Handles and validates user input.
67 // Handles and validates user input.
68 handleChanging: function(e) {
68 handleChanging: function(e) {
69 this.user_invoked_update = true;
69 this.user_invoked_update = true;
70 this.model.set('value', e.target.value);
70 this.model.set('value', e.target.value);
71 this.model.update_other_views(this);
71 this.model.update_other_views(this);
72 this.user_invoked_update = false;
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 var TextBoxView = IPython.WidgetView.extend({
78 var TextBoxView = IPython.WidgetView.extend({
79
79
80 // Called when view is rendered.
80 // Called when view is rendered.
81 render : function(){
81 render : function(){
82 this.$el
82 this.$el
83 .addClass('widget-hbox-single')
83 .addClass('widget-hbox-single')
84 .html('');
84 .html('');
85 this.$label = $('<div />')
85 this.$label = $('<div />')
86 .addClass('widget-hlabel')
86 .addClass('widget-hlabel')
87 .appendTo(this.$el)
87 .appendTo(this.$el)
88 .hide();
88 .hide();
89 this.$textbox = $('<input type="text" />')
89 this.$textbox = $('<input type="text" />')
90 .addClass('input')
90 .addClass('input')
91 .addClass('widget-text')
91 .addClass('widget-text')
92 .appendTo(this.$el);
92 .appendTo(this.$el);
93 this.$el_to_style = this.$textbox; // Set default element to style
93 this.$el_to_style = this.$textbox; // Set default element to style
94 this.update(); // Set defaults.
94 this.update(); // Set defaults.
95 },
95 },
96
96
97 // Handles: Backend -> Frontend Sync
97 // Handles: Backend -> Frontend Sync
98 // Frontent -> Frontend Sync
98 // Frontent -> Frontend Sync
99 update : function(){
99 update : function(){
100 if (!this.user_invoked_update) {
100 if (!this.user_invoked_update) {
101 this.$textbox.val(this.model.get('value'));
101 this.$textbox.val(this.model.get('value'));
102 }
102 }
103
103
104 var disabled = this.model.get('disabled');
104 var disabled = this.model.get('disabled');
105 this.$textbox.prop('disabled', disabled);
105 this.$textbox.prop('disabled', disabled);
106
106
107 var description = this.model.get('description');
107 var description = this.model.get('description');
108 if (description.length == 0) {
108 if (description.length == 0) {
109 this.$label.hide();
109 this.$label.hide();
110 } else {
110 } else {
111 this.$label.html(description);
111 this.$label.html(description);
112 this.$label.show();
112 this.$label.show();
113 }
113 }
114 return IPython.WidgetView.prototype.update.call(this);
114 return IPython.WidgetView.prototype.update.call(this);
115 },
115 },
116
116
117 events: {"keyup input" : "handleChanging",
117 events: {"keyup input" : "handleChanging",
118 "paste input" : "handleChanging",
118 "paste input" : "handleChanging",
119 "cut input" : "handleChanging"},
119 "cut input" : "handleChanging"},
120
120
121 // Handles and validates user input.
121 // Handles and validates user input.
122 handleChanging: function(e) {
122 handleChanging: function(e) {
123 this.user_invoked_update = true;
123 this.user_invoked_update = true;
124 this.model.set('value', e.target.value);
124 this.model.set('value', e.target.value);
125 this.model.update_other_views(this);
125 this.model.update_other_views(this);
126 this.user_invoked_update = false;
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 from .widget_bool import BoolWidget
3 from .widget_bool import BoolWidget
4 from .widget_button import ButtonWidget
4 from .widget_button import ButtonWidget
5 from .widget_container import ContainerWidget
5 from .widget_container import ContainerWidget
6 from .widget_float import FloatWidget
6 from .widget_float import FloatWidget
7 from .widget_float_range import FloatRangeWidget
7 from .widget_float_range import FloatRangeWidget
8 from .widget_int import IntWidget
8 from .widget_int import IntWidget
9 from .widget_int_range import IntRangeWidget
9 from .widget_int_range import IntRangeWidget
10 from .widget_multicontainer import MulticontainerWidget
10 from .widget_multicontainer import MulticontainerWidget
11 from .widget_selection import SelectionWidget
11 from .widget_selection import SelectionWidget
12 from .widget_string import StringWidget
12 from .widget_string import StringWidget
@@ -1,365 +1,352 b''
1 """Base Widget class. Allows user to create widgets in the backend that render
1 """Base Widget class. Allows user to create widgets in the backend that render
2 in the IPython notebook frontend.
2 in the IPython notebook frontend.
3 """
3 """
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (c) 2013, the IPython Development Team.
5 # Copyright (c) 2013, the IPython Development Team.
6 #
6 #
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8 #
8 #
9 # The full license is in the file COPYING.txt, distributed with this software.
9 # The full license is in the file COPYING.txt, distributed with this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 from copy import copy
15 from copy import copy
16 from glob import glob
16 from glob import glob
17 import uuid
17 import uuid
18 import sys
18 import sys
19 import os
19 import os
20 import inspect
20 import inspect
21
21
22 import IPython
22 import IPython
23 from IPython.kernel.comm import Comm
23 from IPython.kernel.comm import Comm
24 from IPython.config import LoggingConfigurable
24 from IPython.config import LoggingConfigurable
25 from IPython.utils.traitlets import Unicode, Dict, List, Instance, Bool
25 from IPython.utils.traitlets import Unicode, Dict, List, Instance, Bool
26 from IPython.display import Javascript, display
26 from IPython.display import Javascript, display
27 from IPython.utils.py3compat import string_types
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 # Classes
31 # Classes
45 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
46 class Widget(LoggingConfigurable):
33 class Widget(LoggingConfigurable):
47
34
48 # Shared declarations
35 # Shared declarations
49 _keys = []
36 _keys = []
50
37
51 # Public declarations
38 # Public declarations
52 target_name = Unicode('widget', help="""Name of the backbone model
39 target_name = Unicode('widget', help="""Name of the backbone model
53 registered in the frontend to create and sync this widget with.""")
40 registered in the frontend to create and sync this widget with.""")
54 default_view_name = Unicode(help="""Default view registered in the frontend
41 default_view_name = Unicode(help="""Default view registered in the frontend
55 to use to represent the widget.""")
42 to use to represent the widget.""")
56 parent = Instance('IPython.html.widgets.widget.Widget')
43 parent = Instance('IPython.html.widgets.widget.Widget')
57 visible = Bool(True, help="Whether or not the widget is visible.")
44 visible = Bool(True, help="Whether or not the widget is visible.")
58
45
59 def _parent_changed(self, name, old, new):
46 def _parent_changed(self, name, old, new):
60 if self._displayed:
47 if self._displayed:
61 raise Exception('Parent cannot be set because widget has been displayed.')
48 raise Exception('Parent cannot be set because widget has been displayed.')
62 elif new == self:
49 elif new == self:
63 raise Exception('Parent cannot be set to self.')
50 raise Exception('Parent cannot be set to self.')
64 else:
51 else:
65
52
66 # Parent/child association
53 # Parent/child association
67 if new is not None and not self in new._children:
54 if new is not None and not self in new._children:
68 new._children.append(self)
55 new._children.append(self)
69 if old is not None and self in old._children:
56 if old is not None and self in old._children:
70 old._children.remove(self)
57 old._children.remove(self)
71
58
72 # Private/protected declarations
59 # Private/protected declarations
73 _property_lock = False
60 _property_lock = False
74 _css = Dict() # Internal CSS property dict
61 _css = Dict() # Internal CSS property dict
75 _add_class = List() # Used to add a js class to a DOM element (call#, selector, class_name)
62 _add_class = List() # Used to add a js class to a DOM element (call#, selector, class_name)
76 _remove_class = List() # Used to remove a js class from a DOM element (call#, selector, class_name)
63 _remove_class = List() # Used to remove a js class from a DOM element (call#, selector, class_name)
77 _displayed = False
64 _displayed = False
78 _comm = None
65 _comm = None
79
66
80
67
81 def __init__(self, **kwargs):
68 def __init__(self, **kwargs):
82 """Public constructor
69 """Public constructor
83
70
84 Parameters
71 Parameters
85 ----------
72 ----------
86 parent : Widget instance (optional)
73 parent : Widget instance (optional)
87 Widget that this widget instance is child of. When the widget is
74 Widget that this widget instance is child of. When the widget is
88 displayed in the frontend, it's corresponding view will be made
75 displayed in the frontend, it's corresponding view will be made
89 child of the parent's view if the parent's view exists already. If
76 child of the parent's view if the parent's view exists already. If
90 the parent's view is displayed, it will automatically display this
77 the parent's view is displayed, it will automatically display this
91 widget's default view as it's child. The default view can be set
78 widget's default view as it's child. The default view can be set
92 via the default_view_name property.
79 via the default_view_name property.
93 """
80 """
94 self._children = []
81 self._children = []
95 self._add_class = [0]
82 self._add_class = [0]
96 self._remove_class = [0]
83 self._remove_class = [0]
97 self._display_callbacks = []
84 self._display_callbacks = []
98 super(Widget, self).__init__(**kwargs)
85 super(Widget, self).__init__(**kwargs)
99
86
100 # Register after init to allow default values to be specified
87 # Register after init to allow default values to be specified
101 self.on_trait_change(self._handle_property_changed, self.keys)
88 self.on_trait_change(self._handle_property_changed, self.keys)
102
89
103
90
104 def __del__(self):
91 def __del__(self):
105 """Object disposal"""
92 """Object disposal"""
106 self.close()
93 self.close()
107
94
108
95
109 def close(self):
96 def close(self):
110 """Close method. Closes the widget which closes the underlying comm.
97 """Close method. Closes the widget which closes the underlying comm.
111 When the comm is closed, all of the widget views are automatically
98 When the comm is closed, all of the widget views are automatically
112 removed from the frontend."""
99 removed from the frontend."""
113 self._comm.close()
100 self._comm.close()
114 del self._comm
101 del self._comm
115
102
116
103
117 # Properties
104 # Properties
118 def _get_keys(self):
105 def _get_keys(self):
119 keys = ['visible', '_css', '_add_class', '_remove_class']
106 keys = ['visible', '_css', '_add_class', '_remove_class']
120 keys.extend(self._keys)
107 keys.extend(self._keys)
121 return keys
108 return keys
122 keys = property(_get_keys)
109 keys = property(_get_keys)
123
110
124
111
125 # Event handlers
112 # Event handlers
126 def _handle_msg(self, msg):
113 def _handle_msg(self, msg):
127 """Called when a msg is recieved from the frontend"""
114 """Called when a msg is recieved from the frontend"""
128 # Handle backbone sync methods CREATE, PATCH, and UPDATE
115 # Handle backbone sync methods CREATE, PATCH, and UPDATE
129 sync_method = msg['content']['data']['sync_method']
116 sync_method = msg['content']['data']['sync_method']
130 sync_data = msg['content']['data']['sync_data']
117 sync_data = msg['content']['data']['sync_data']
131 self._handle_recieve_state(sync_data) # handles all methods
118 self._handle_recieve_state(sync_data) # handles all methods
132
119
133
120
134 def _handle_recieve_state(self, sync_data):
121 def _handle_recieve_state(self, sync_data):
135 """Called when a state is recieved from the frontend."""
122 """Called when a state is recieved from the frontend."""
136 self._property_lock = True
123 self._property_lock = True
137 try:
124 try:
138
125
139 # Use _keys instead of keys - Don't get retrieve the css from the client side.
126 # Use _keys instead of keys - Don't get retrieve the css from the client side.
140 for name in self._keys:
127 for name in self._keys:
141 if name in sync_data:
128 if name in sync_data:
142 setattr(self, name, sync_data[name])
129 setattr(self, name, sync_data[name])
143 finally:
130 finally:
144 self._property_lock = False
131 self._property_lock = False
145
132
146
133
147 def _handle_property_changed(self, name, old, new):
134 def _handle_property_changed(self, name, old, new):
148 """Called when a proeprty has been changed."""
135 """Called when a proeprty has been changed."""
149 if not self._property_lock and self._comm is not None:
136 if not self._property_lock and self._comm is not None:
150 # TODO: Validate properties.
137 # TODO: Validate properties.
151 # Send new state to frontend
138 # Send new state to frontend
152 self.send_state(key=name)
139 self.send_state(key=name)
153
140
154
141
155 def _handle_close(self):
142 def _handle_close(self):
156 """Called when the comm is closed by the frontend."""
143 """Called when the comm is closed by the frontend."""
157 self._comm = None
144 self._comm = None
158
145
159
146
160 # Public methods
147 # Public methods
161 def send_state(self, key=None):
148 def send_state(self, key=None):
162 """Sends the widget state, or a piece of it, to the frontend.
149 """Sends the widget state, or a piece of it, to the frontend.
163
150
164 Parameters
151 Parameters
165 ----------
152 ----------
166 key : unicode (optional)
153 key : unicode (optional)
167 A single property's name to sync with the frontend.
154 A single property's name to sync with the frontend.
168 """
155 """
169 if self._comm is not None:
156 if self._comm is not None:
170 state = {}
157 state = {}
171
158
172 # If a key is provided, just send the state of that key.
159 # If a key is provided, just send the state of that key.
173 keys = []
160 keys = []
174 if key is None:
161 if key is None:
175 keys.extend(self.keys)
162 keys.extend(self.keys)
176 else:
163 else:
177 keys.append(key)
164 keys.append(key)
178 for key in self.keys:
165 for key in self.keys:
179 try:
166 try:
180 state[key] = getattr(self, key)
167 state[key] = getattr(self, key)
181 except Exception as e:
168 except Exception as e:
182 pass # Eat errors, nom nom nom
169 pass # Eat errors, nom nom nom
183 self._comm.send({"method": "update",
170 self._comm.send({"method": "update",
184 "state": state})
171 "state": state})
185
172
186
173
187 def get_css(self, key, selector=""):
174 def get_css(self, key, selector=""):
188 """Get a CSS property of the widget. Note, this function does not
175 """Get a CSS property of the widget. Note, this function does not
189 actually request the CSS from the front-end; Only properties that have
176 actually request the CSS from the front-end; Only properties that have
190 been set with set_css can be read.
177 been set with set_css can be read.
191
178
192 Parameters
179 Parameters
193 ----------
180 ----------
194 key: unicode
181 key: unicode
195 CSS key
182 CSS key
196 selector: unicode (optional)
183 selector: unicode (optional)
197 JQuery selector used when the CSS key/value was set.
184 JQuery selector used when the CSS key/value was set.
198 """
185 """
199 if selector in self._css and key in self._css[selector]:
186 if selector in self._css and key in self._css[selector]:
200 return self._css[selector][key]
187 return self._css[selector][key]
201 else:
188 else:
202 return None
189 return None
203
190
204
191
205 def set_css(self, *args, **kwargs):
192 def set_css(self, *args, **kwargs):
206 """Set one or more CSS properties of the widget (shared among all of the
193 """Set one or more CSS properties of the widget (shared among all of the
207 views). This function has two signatures:
194 views). This function has two signatures:
208 - set_css(css_dict, [selector=''])
195 - set_css(css_dict, [selector=''])
209 - set_css(key, value, [selector=''])
196 - set_css(key, value, [selector=''])
210
197
211 Parameters
198 Parameters
212 ----------
199 ----------
213 css_dict : dict
200 css_dict : dict
214 CSS key/value pairs to apply
201 CSS key/value pairs to apply
215 key: unicode
202 key: unicode
216 CSS key
203 CSS key
217 value
204 value
218 CSS value
205 CSS value
219 selector: unicode (optional)
206 selector: unicode (optional)
220 JQuery selector to use to apply the CSS key/value.
207 JQuery selector to use to apply the CSS key/value.
221 """
208 """
222 selector = kwargs.get('selector', '')
209 selector = kwargs.get('selector', '')
223
210
224 # Signature 1: set_css(css_dict, [selector=''])
211 # Signature 1: set_css(css_dict, [selector=''])
225 if len(args) == 1:
212 if len(args) == 1:
226 if isinstance(args[0], dict):
213 if isinstance(args[0], dict):
227 for (key, value) in args[0].items():
214 for (key, value) in args[0].items():
228 self.set_css(key, value, selector=selector)
215 self.set_css(key, value, selector=selector)
229 else:
216 else:
230 raise Exception('css_dict must be a dict.')
217 raise Exception('css_dict must be a dict.')
231
218
232 # Signature 2: set_css(key, value, [selector=''])
219 # Signature 2: set_css(key, value, [selector=''])
233 elif len(args) == 2 or len(args) == 3:
220 elif len(args) == 2 or len(args) == 3:
234
221
235 # Selector can be a positional arg if it's the 3rd value
222 # Selector can be a positional arg if it's the 3rd value
236 if len(args) == 3:
223 if len(args) == 3:
237 selector = args[2]
224 selector = args[2]
238 if selector not in self._css:
225 if selector not in self._css:
239 self._css[selector] = {}
226 self._css[selector] = {}
240
227
241 # Only update the property if it has changed.
228 # Only update the property if it has changed.
242 key = args[0]
229 key = args[0]
243 value = args[1]
230 value = args[1]
244 if not (key in self._css[selector] and value in self._css[selector][key]):
231 if not (key in self._css[selector] and value in self._css[selector][key]):
245 self._css[selector][key] = value
232 self._css[selector][key] = value
246 self.send_state('_css') # Send new state to client.
233 self.send_state('_css') # Send new state to client.
247 else:
234 else:
248 raise Exception('set_css only accepts 1-3 arguments')
235 raise Exception('set_css only accepts 1-3 arguments')
249
236
250
237
251 def add_class(self, class_name, selector=""):
238 def add_class(self, class_name, selector=""):
252 """Add class[es] to a DOM element
239 """Add class[es] to a DOM element
253
240
254 Parameters
241 Parameters
255 ----------
242 ----------
256 class_name: unicode
243 class_name: unicode
257 Class name(s) to add to the DOM element(s). Multiple class names
244 Class name(s) to add to the DOM element(s). Multiple class names
258 must be space separated.
245 must be space separated.
259 selector: unicode (optional)
246 selector: unicode (optional)
260 JQuery selector to select the DOM element(s) that the class(es) will
247 JQuery selector to select the DOM element(s) that the class(es) will
261 be added to.
248 be added to.
262 """
249 """
263 self._add_class = [self._add_class[0] + 1, selector, class_name]
250 self._add_class = [self._add_class[0] + 1, selector, class_name]
264 self.send_state(key='_add_class')
251 self.send_state(key='_add_class')
265
252
266
253
267 def remove_class(self, class_name, selector=""):
254 def remove_class(self, class_name, selector=""):
268 """Remove class[es] from a DOM element
255 """Remove class[es] from a DOM element
269
256
270 Parameters
257 Parameters
271 ----------
258 ----------
272 class_name: unicode
259 class_name: unicode
273 Class name(s) to remove from the DOM element(s). Multiple class
260 Class name(s) to remove from the DOM element(s). Multiple class
274 names must be space separated.
261 names must be space separated.
275 selector: unicode (optional)
262 selector: unicode (optional)
276 JQuery selector to select the DOM element(s) that the class(es) will
263 JQuery selector to select the DOM element(s) that the class(es) will
277 be removed from.
264 be removed from.
278 """
265 """
279 self._remove_class = [self._remove_class[0] + 1, selector, class_name]
266 self._remove_class = [self._remove_class[0] + 1, selector, class_name]
280 self.send_state(key='_remove_class')
267 self.send_state(key='_remove_class')
281
268
282
269
283 def on_displayed(self, callback, remove=False):
270 def on_displayed(self, callback, remove=False):
284 """Register a callback to be called when the widget has been displayed
271 """Register a callback to be called when the widget has been displayed
285
272
286 Parameters
273 Parameters
287 ----------
274 ----------
288 callback: method handler
275 callback: method handler
289 Can have a signature of:
276 Can have a signature of:
290 - callback()
277 - callback()
291 - callback(sender)
278 - callback(sender)
292 - callback(sender, view_name)
279 - callback(sender, view_name)
293 remove: bool
280 remove: bool
294 True if the callback should be unregistered."""
281 True if the callback should be unregistered."""
295 if remove:
282 if remove:
296 self._display_callbacks.remove(callback)
283 self._display_callbacks.remove(callback)
297 elif not callback in self._display_callbacks:
284 elif not callback in self._display_callbacks:
298 self._display_callbacks.append(callback)
285 self._display_callbacks.append(callback)
299
286
300
287
301 def handle_displayed(self, view_name):
288 def handle_displayed(self, view_name):
302 """Called when a view has been displayed for this widget instance
289 """Called when a view has been displayed for this widget instance
303
290
304 Parameters
291 Parameters
305 ----------
292 ----------
306 view_name: unicode
293 view_name: unicode
307 Name of the view that was displayed."""
294 Name of the view that was displayed."""
308 for handler in self._display_callbacks:
295 for handler in self._display_callbacks:
309 if callable(handler):
296 if callable(handler):
310 argspec = inspect.getargspec(handler)
297 argspec = inspect.getargspec(handler)
311 nargs = len(argspec[0])
298 nargs = len(argspec[0])
312
299
313 # Bound methods have an additional 'self' argument
300 # Bound methods have an additional 'self' argument
314 if isinstance(handler, types.MethodType):
301 if isinstance(handler, types.MethodType):
315 nargs -= 1
302 nargs -= 1
316
303
317 # Call the callback
304 # Call the callback
318 if nargs == 0:
305 if nargs == 0:
319 handler()
306 handler()
320 elif nargs == 1:
307 elif nargs == 1:
321 handler(self)
308 handler(self)
322 elif nargs == 2:
309 elif nargs == 2:
323 handler(self, view_name)
310 handler(self, view_name)
324 else:
311 else:
325 raise TypeError('Widget display callback must ' \
312 raise TypeError('Widget display callback must ' \
326 'accept 0-2 arguments, not %d.' % nargs)
313 'accept 0-2 arguments, not %d.' % nargs)
327
314
328
315
329 # Support methods
316 # Support methods
330 def _repr_widget_(self, view_name=None):
317 def _repr_widget_(self, view_name=None):
331 """Function that is called when `IPython.display.display` is called on
318 """Function that is called when `IPython.display.display` is called on
332 the widget.
319 the widget.
333
320
334 Parameters
321 Parameters
335 ----------
322 ----------
336 view_name: unicode (optional)
323 view_name: unicode (optional)
337 View to display in the frontend. Overrides default_view_name."""
324 View to display in the frontend. Overrides default_view_name."""
338
325
339 if not view_name:
326 if not view_name:
340 view_name = self.default_view_name
327 view_name = self.default_view_name
341
328
342 # Create a comm.
329 # Create a comm.
343 if self._comm is None:
330 if self._comm is None:
344 self._comm = Comm(target_name=self.target_name)
331 self._comm = Comm(target_name=self.target_name)
345 self._comm.on_msg(self._handle_msg)
332 self._comm.on_msg(self._handle_msg)
346 self._comm.on_close(self._handle_close)
333 self._comm.on_close(self._handle_close)
347
334
348 # Make sure model is syncronized
335 # Make sure model is syncronized
349 self.send_state()
336 self.send_state()
350
337
351 # Show view.
338 # Show view.
352 if self.parent is None or self.parent._comm is None:
339 if self.parent is None or self.parent._comm is None:
353 self._comm.send({"method": "display", "view_name": view_name})
340 self._comm.send({"method": "display", "view_name": view_name})
354 else:
341 else:
355 self._comm.send({"method": "display",
342 self._comm.send({"method": "display",
356 "view_name": view_name,
343 "view_name": view_name,
357 "parent": self.parent._comm.comm_id})
344 "parent": self.parent._comm.comm_id})
358 self._displayed = True
345 self._displayed = True
359 self.handle_displayed(view_name)
346 self.handle_displayed(view_name)
360
347
361 # Now display children if any.
348 # Now display children if any.
362 for child in self._children:
349 for child in self._children:
363 if child != self:
350 if child != self:
364 child._repr_widget_()
351 child._repr_widget_()
365 return None
352 return None
@@ -1,295 +1,220 b''
1 {
1 {
2 "metadata": {
2 "metadata": {
3 "cell_tags": [
3 "cell_tags": [
4 [
4 [
5 "<None>",
5 "<None>",
6 null
6 null
7 ]
7 ]
8 ],
8 ],
9 "name": ""
9 "name": ""
10 },
10 },
11 "nbformat": 3,
11 "nbformat": 3,
12 "nbformat_minor": 0,
12 "nbformat_minor": 0,
13 "worksheets": [
13 "worksheets": [
14 {
14 {
15 "cells": [
15 "cells": [
16 {
16 {
17 "cell_type": "code",
17 "cell_type": "code",
18 "collapsed": false,
18 "collapsed": false,
19 "input": [
19 "input": [
20 "from IPython.html import widgets # Widget definitions\n",
20 "from IPython.html import widgets # Widget definitions\n",
21 "from IPython.display import display # Used to display widgets in the notebook\n",
21 "from IPython.display import display # Used to display widgets in the notebook"
22 "\n",
23 "# Enable widgets in this notebook\n",
24 "widgets.init_widget_js()"
25 ],
22 ],
26 "language": "python",
23 "language": "python",
27 "metadata": {},
24 "metadata": {},
28 "outputs": [
25 "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 ],
100 "prompt_number": 1
26 "prompt_number": 1
101 },
27 },
102 {
28 {
103 "cell_type": "heading",
29 "cell_type": "heading",
104 "level": 1,
30 "level": 1,
105 "metadata": {},
31 "metadata": {},
106 "source": [
32 "source": [
107 "Custom Widget"
33 "Custom Widget"
108 ]
34 ]
109 },
35 },
110 {
36 {
111 "cell_type": "code",
37 "cell_type": "code",
112 "collapsed": false,
38 "collapsed": false,
113 "input": [
39 "input": [
114 "# Import the base Widget class and the traitlets Unicode class.\n",
40 "# Import the base Widget class and the traitlets Unicode class.\n",
115 "from IPython.html.widgets import Widget\n",
41 "from IPython.html.widgets import Widget\n",
116 "from IPython.utils.traitlets import Unicode, Int\n",
42 "from IPython.utils.traitlets import Unicode, Int\n",
117 "\n",
43 "\n",
118 "# Define our FileWidget and its target model and default view.\n",
44 "# Define our FileWidget and its target model and default view.\n",
119 "class FileWidget(Widget):\n",
45 "class FileWidget(Widget):\n",
120 " target_name = Unicode('FileWidgetModel')\n",
46 " target_name = Unicode('FileWidgetModel')\n",
121 " default_view_name = Unicode('FilePickerView')\n",
47 " default_view_name = Unicode('FilePickerView')\n",
122 " \n",
48 " \n",
123 " # Define the custom state properties to sync with the front-end\n",
49 " # Define the custom state properties to sync with the front-end\n",
124 " _keys = ['value', 'filename']\n",
50 " _keys = ['value', 'filename']\n",
125 " value = Unicode('')\n",
51 " value = Unicode('')\n",
126 " filename = Unicode('')\n",
52 " filename = Unicode('')\n",
127 " on_failed = Int(0)"
53 " on_failed = Int(0)"
128 ],
54 ],
129 "language": "python",
55 "language": "python",
130 "metadata": {},
56 "metadata": {},
131 "outputs": [],
57 "outputs": [],
132 "prompt_number": 2
58 "prompt_number": 2
133 },
59 },
134 {
60 {
135 "cell_type": "code",
61 "cell_type": "code",
136 "collapsed": false,
62 "collapsed": false,
137 "input": [
63 "input": [
138 "%%javascript\n",
64 "%%javascript\n",
139 "\n",
65 "\n",
140 "require([\"notebook/js/widget\"], function(){\n",
66 "require([\"notebook/js/widget\"], function(){\n",
141 " \n",
67 " \n",
142 " // Define the FileModel and register it with the widget manager.\n",
68 " // Define the FileModel and register it with the widget manager.\n",
143 " var FileModel = IPython.WidgetModel.extend({});\n",
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 " \n",
71 " \n",
146 " // Define the FilePickerView\n",
72 " // Define the FilePickerView\n",
147 " var FilePickerView = IPython.WidgetView.extend({\n",
73 " var FilePickerView = IPython.WidgetView.extend({\n",
148 " \n",
74 " \n",
149 " render: function(){\n",
75 " render: function(){\n",
150 " var that = this;\n",
76 " var that = this;\n",
151 " this.$el = $('<input />')\n",
77 " this.$el = $('<input />')\n",
152 " .attr('type', 'file')\n",
78 " .attr('type', 'file')\n",
153 " .change(function(evt){ that.handleFileChange(evt) });\n",
79 " .change(function(evt){ that.handleFileChange(evt) });\n",
154 " },\n",
80 " },\n",
155 " \n",
81 " \n",
156 " // Handles: User input\n",
82 " // Handles: User input\n",
157 " handleFileChange: function(evt) { \n",
83 " handleFileChange: function(evt) { \n",
158 " \n",
84 " \n",
159 " //Retrieve the first (and only!) File from the FileList object\n",
85 " //Retrieve the first (and only!) File from the FileList object\n",
160 " var that = this;\n",
86 " var that = this;\n",
161 " var f = evt.target.files[0];\n",
87 " var f = evt.target.files[0];\n",
162 " if (f) {\n",
88 " if (f) {\n",
163 " var r = new FileReader();\n",
89 " var r = new FileReader();\n",
164 " r.onload = function(e) {\n",
90 " r.onload = function(e) {\n",
165 " that.model.set('value', e.target.result);\n",
91 " that.model.set('value', e.target.result);\n",
166 " that.model.update_other_views(that);\n",
92 " that.model.update_other_views(that);\n",
167 " }\n",
93 " }\n",
168 " r.readAsText(f);\n",
94 " r.readAsText(f);\n",
169 " } else {\n",
95 " } else {\n",
170 " this.model.set('on_failed', this.model.get('on_failed') + 1);\n",
96 " this.model.set('on_failed', this.model.get('on_failed') + 1);\n",
171 " this.model.update_other_views(this);\n",
97 " this.model.update_other_views(this);\n",
172 " }\n",
98 " }\n",
173 " this.model.set('filename', f.name);\n",
99 " this.model.set('filename', f.name);\n",
174 " this.model.update_other_views(this);\n",
100 " this.model.update_other_views(this);\n",
175 " },\n",
101 " },\n",
176 " });\n",
102 " });\n",
177 " \n",
103 " \n",
178 " // Register the DatePickerView with the widget manager.\n",
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 "language": "python",
108 "language": "python",
183 "metadata": {},
109 "metadata": {},
184 "outputs": [
110 "outputs": [
185 {
111 {
186 "javascript": [
112 "javascript": [
187 "\n",
113 "\n",
188 "require([\"notebook/js/widget\"], function(){\n",
114 "require([\"notebook/js/widget\"], function(){\n",
189 " \n",
115 " \n",
190 " // Define the FileModel and register it with the widget manager.\n",
116 " // Define the FileModel and register it with the widget manager.\n",
191 " var FileModel = IPython.WidgetModel.extend({});\n",
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 " \n",
119 " \n",
194 " // Define the FilePickerView\n",
120 " // Define the FilePickerView\n",
195 " var FilePickerView = IPython.WidgetView.extend({\n",
121 " var FilePickerView = IPython.WidgetView.extend({\n",
196 " \n",
122 " \n",
197 " render: function(){\n",
123 " render: function(){\n",
198 " var that = this;\n",
124 " var that = this;\n",
199 " this.$el = $('<input />')\n",
125 " this.$el = $('<input />')\n",
200 " .attr('type', 'file')\n",
126 " .attr('type', 'file')\n",
201 " .change(function(evt){ that.handleFileChange(evt) });\n",
127 " .change(function(evt){ that.handleFileChange(evt) });\n",
202 " },\n",
128 " },\n",
203 " \n",
129 " \n",
204 " // Handles: User input\n",
130 " // Handles: User input\n",
205 " events: { \"change\" : \"handleFileChange\" }, \n",
206 " handleFileChange: function(evt) { \n",
131 " handleFileChange: function(evt) { \n",
207 " \n",
132 " \n",
208 " //Retrieve the first (and only!) File from the FileList object\n",
133 " //Retrieve the first (and only!) File from the FileList object\n",
209 " var that = this;\n",
134 " var that = this;\n",
210 " var f = evt.target.files[0];\n",
135 " var f = evt.target.files[0];\n",
211 " if (f) {\n",
136 " if (f) {\n",
212 " var r = new FileReader();\n",
137 " var r = new FileReader();\n",
213 " r.onload = function(e) {\n",
138 " r.onload = function(e) {\n",
214 " that.model.set('value', e.target.result);\n",
139 " that.model.set('value', e.target.result);\n",
215 " that.model.update_other_views(that);\n",
140 " that.model.update_other_views(that);\n",
216 " }\n",
141 " }\n",
217 " r.readAsText(f);\n",
142 " r.readAsText(f);\n",
218 " } else {\n",
143 " } else {\n",
219 " this.model.set('on_failed', this.model.get('on_failed') + 1);\n",
144 " this.model.set('on_failed', this.model.get('on_failed') + 1);\n",
220 " this.model.update_other_views(this);\n",
145 " this.model.update_other_views(this);\n",
221 " }\n",
146 " }\n",
222 " this.model.set('filename', f.name);\n",
147 " this.model.set('filename', f.name);\n",
223 " this.model.update_other_views(this);\n",
148 " this.model.update_other_views(this);\n",
224 " },\n",
149 " },\n",
225 " });\n",
150 " });\n",
226 " \n",
151 " \n",
227 " // Register the DatePickerView with the widget manager.\n",
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 "metadata": {},
156 "metadata": {},
232 "output_type": "display_data",
157 "output_type": "display_data",
233 "text": [
158 "text": [
234 "<IPython.core.display.Javascript at 0x2fa80d0>"
159 "<IPython.core.display.Javascript at 0x319fe90>"
235 ]
160 ]
236 }
161 }
237 ],
162 ],
238 "prompt_number": 3
163 "prompt_number": 3
239 },
164 },
240 {
165 {
241 "cell_type": "heading",
166 "cell_type": "heading",
242 "level": 1,
167 "level": 1,
243 "metadata": {},
168 "metadata": {},
244 "source": [
169 "source": [
245 "Usage"
170 "Usage"
246 ]
171 ]
247 },
172 },
248 {
173 {
249 "cell_type": "code",
174 "cell_type": "code",
250 "collapsed": false,
175 "collapsed": false,
251 "input": [
176 "input": [
252 "file_widget = FileWidget()\n",
177 "file_widget = FileWidget()\n",
253 "display(file_widget)\n",
178 "display(file_widget)\n",
254 "\n",
179 "\n",
255 "def file_loading():\n",
180 "def file_loading():\n",
256 " print \"Loading %s\" % file_widget.filename\n",
181 " print \"Loading %s\" % file_widget.filename\n",
257 "\n",
182 "\n",
258 "def file_loaded():\n",
183 "def file_loaded():\n",
259 " print \"Loaded, file contents: %s\" % file_widget.value\n",
184 " print \"Loaded, file contents: %s\" % file_widget.value\n",
260 "\n",
185 "\n",
261 "def file_failed(name, old_value, new_value):\n",
186 "def file_failed(name, old_value, new_value):\n",
262 " if new_value > old_value:\n",
187 " if new_value > old_value:\n",
263 " print \"Could not load file contents of %s\" % file_widget.filename\n",
188 " print \"Could not load file contents of %s\" % file_widget.filename\n",
264 "\n",
189 "\n",
265 "\n",
190 "\n",
266 "file_widget.on_trait_change(file_loading, 'filename')\n",
191 "file_widget.on_trait_change(file_loading, 'filename')\n",
267 "file_widget.on_trait_change(file_loaded, 'value')\n",
192 "file_widget.on_trait_change(file_loaded, 'value')\n",
268 "file_widget.on_trait_change(file_failed, 'on_failed')"
193 "file_widget.on_trait_change(file_failed, 'on_failed')"
269 ],
194 ],
270 "language": "python",
195 "language": "python",
271 "metadata": {},
196 "metadata": {},
272 "outputs": [
197 "outputs": [
273 {
198 {
274 "output_type": "stream",
199 "output_type": "stream",
275 "stream": "stdout",
200 "stream": "stdout",
276 "text": [
201 "text": [
277 "Loading test.txt\n"
202 "Loading test.txt\n"
278 ]
203 ]
279 },
204 },
280 {
205 {
281 "output_type": "stream",
206 "output_type": "stream",
282 "stream": "stdout",
207 "stream": "stdout",
283 "text": [
208 "text": [
284 "Loaded, file contents: \n",
209 "Loaded, file contents: \n",
285 "hello world!\n"
210 "hello world!\n"
286 ]
211 ]
287 }
212 }
288 ],
213 ],
289 "prompt_number": 4
214 "prompt_number": 4
290 }
215 }
291 ],
216 ],
292 "metadata": {}
217 "metadata": {}
293 }
218 }
294 ]
219 ]
295 } No newline at end of file
220 }
@@ -1,391 +1,323 b''
1 {
1 {
2 "metadata": {
2 "metadata": {
3 "cell_tags": [
4 [
5 "<None>",
6 null
7 ]
8 ],
3 "name": ""
9 "name": ""
4 },
10 },
5 "nbformat": 3,
11 "nbformat": 3,
6 "nbformat_minor": 0,
12 "nbformat_minor": 0,
7 "worksheets": [
13 "worksheets": [
8 {
14 {
9 "cells": [
15 "cells": [
10 {
16 {
11 "cell_type": "markdown",
17 "cell_type": "markdown",
12 "metadata": {},
18 "metadata": {},
13 "source": [
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 "cell_type": "code",
24 "cell_type": "code",
19 "collapsed": false,
25 "collapsed": false,
20 "input": [
26 "input": [
21 "from IPython.html import widgets # Widget definitions\n",
27 "from IPython.html import widgets # Widget definitions\n",
22 "from IPython.display import display # Used to display widgets in the notebook\n",
28 "from IPython.display import display # Used to display widgets in the notebook"
23 "\n",
24 "# Enable widgets in this notebook\n",
25 "widgets.init_widget_js()"
26 ],
29 ],
27 "language": "python",
30 "language": "python",
28 "metadata": {},
31 "metadata": {},
29 "outputs": [
32 "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 ],
101 "prompt_number": 1
33 "prompt_number": 1
102 },
34 },
103 {
35 {
104 "cell_type": "heading",
36 "cell_type": "heading",
105 "level": 1,
37 "level": 1,
106 "metadata": {},
38 "metadata": {},
107 "source": [
39 "source": [
108 "Basic Widgets"
40 "Basic Widgets"
109 ]
41 ]
110 },
42 },
111 {
43 {
112 "cell_type": "markdown",
44 "cell_type": "markdown",
113 "metadata": {},
45 "metadata": {},
114 "source": [
46 "source": [
115 "The IPython notebook comes preloaded with basic widgets that represent common data types. These widgets are\n",
47 "The IPython notebook comes preloaded with basic widgets that represent common data types. These widgets are\n",
116 "\n",
48 "\n",
117 "- BoolWidget : boolean \n",
49 "- BoolWidget : boolean \n",
118 "- FloatRangeWidget : bounded float \n",
50 "- FloatRangeWidget : bounded float \n",
119 "- FloatWidget : unbounded float \n",
51 "- FloatWidget : unbounded float \n",
120 "- IntRangeWidget : bounded integer \n",
52 "- IntRangeWidget : bounded integer \n",
121 "- IntWidget : unbounded integer \n",
53 "- IntWidget : unbounded integer \n",
122 "- SelectionWidget : enumeration \n",
54 "- SelectionWidget : enumeration \n",
123 "- StringWidget : string \n",
55 "- StringWidget : string \n",
124 "\n",
56 "\n",
125 "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",
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 "\n",
58 "\n",
127 "- ButtonWidget \n",
59 "- ButtonWidget \n",
128 "- ContainerWidget \n",
60 "- ContainerWidget \n",
129 "- MulticontainerWidget \n",
61 "- MulticontainerWidget \n",
130 "\n",
62 "\n",
131 "To see the complete list of widgets, one can execute the following"
63 "To see the complete list of widgets, one can execute the following"
132 ]
64 ]
133 },
65 },
134 {
66 {
135 "cell_type": "code",
67 "cell_type": "code",
136 "collapsed": false,
68 "collapsed": false,
137 "input": [
69 "input": [
138 "[widget for widget in dir(widgets) if widget.endswith('Widget')]"
70 "[widget for widget in dir(widgets) if widget.endswith('Widget')]"
139 ],
71 ],
140 "language": "python",
72 "language": "python",
141 "metadata": {},
73 "metadata": {},
142 "outputs": [
74 "outputs": [
143 {
75 {
144 "metadata": {},
76 "metadata": {},
145 "output_type": "pyout",
77 "output_type": "pyout",
146 "prompt_number": 2,
78 "prompt_number": 2,
147 "text": [
79 "text": [
148 "['BoolWidget',\n",
80 "['BoolWidget',\n",
149 " 'ButtonWidget',\n",
81 " 'ButtonWidget',\n",
150 " 'ContainerWidget',\n",
82 " 'ContainerWidget',\n",
151 " 'FloatRangeWidget',\n",
83 " 'FloatRangeWidget',\n",
152 " 'FloatWidget',\n",
84 " 'FloatWidget',\n",
153 " 'IntRangeWidget',\n",
85 " 'IntRangeWidget',\n",
154 " 'IntWidget',\n",
86 " 'IntWidget',\n",
155 " 'MulticontainerWidget',\n",
87 " 'MulticontainerWidget',\n",
156 " 'SelectionWidget',\n",
88 " 'SelectionWidget',\n",
157 " 'StringWidget',\n",
89 " 'StringWidget',\n",
158 " 'Widget']"
90 " 'Widget']"
159 ]
91 ]
160 }
92 }
161 ],
93 ],
162 "prompt_number": 2
94 "prompt_number": 2
163 },
95 },
164 {
96 {
165 "cell_type": "markdown",
97 "cell_type": "markdown",
166 "metadata": {},
98 "metadata": {},
167 "source": [
99 "source": [
168 "The basic widgets can all be constructed without arguments. The following creates a FloatRangeWidget without displaying it"
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 "cell_type": "code",
104 "cell_type": "code",
173 "collapsed": false,
105 "collapsed": false,
174 "input": [
106 "input": [
175 "mywidget = widgets.FloatRangeWidget()"
107 "mywidget = widgets.FloatRangeWidget()"
176 ],
108 ],
177 "language": "python",
109 "language": "python",
178 "metadata": {},
110 "metadata": {},
179 "outputs": [],
111 "outputs": [],
180 "prompt_number": 3
112 "prompt_number": 3
181 },
113 },
182 {
114 {
183 "cell_type": "markdown",
115 "cell_type": "markdown",
184 "metadata": {},
116 "metadata": {},
185 "source": [
117 "source": [
186 "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"
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 "cell_type": "code",
122 "cell_type": "code",
191 "collapsed": false,
123 "collapsed": false,
192 "input": [
124 "input": [
193 "display(mywidget)"
125 "display(mywidget)"
194 ],
126 ],
195 "language": "python",
127 "language": "python",
196 "metadata": {},
128 "metadata": {},
197 "outputs": [],
129 "outputs": [],
198 "prompt_number": 4
130 "prompt_number": 4
199 },
131 },
200 {
132 {
201 "cell_type": "markdown",
133 "cell_type": "markdown",
202 "metadata": {},
134 "metadata": {},
203 "source": [
135 "source": [
204 "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",
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 "\n",
137 "\n",
206 "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."
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 "cell_type": "code",
142 "cell_type": "code",
211 "collapsed": false,
143 "collapsed": false,
212 "input": [
144 "input": [
213 "mywidget.keys"
145 "mywidget.keys"
214 ],
146 ],
215 "language": "python",
147 "language": "python",
216 "metadata": {},
148 "metadata": {},
217 "outputs": [
149 "outputs": [
218 {
150 {
219 "metadata": {},
151 "metadata": {},
220 "output_type": "pyout",
152 "output_type": "pyout",
221 "prompt_number": 5,
153 "prompt_number": 5,
222 "text": [
154 "text": [
223 "['visible',\n",
155 "['visible',\n",
224 " '_css',\n",
156 " '_css',\n",
225 " '_add_class',\n",
157 " '_add_class',\n",
226 " '_remove_class',\n",
158 " '_remove_class',\n",
227 " 'value',\n",
159 " 'value',\n",
228 " 'step',\n",
160 " 'step',\n",
229 " 'max',\n",
161 " 'max',\n",
230 " 'min',\n",
162 " 'min',\n",
231 " 'disabled',\n",
163 " 'disabled',\n",
232 " 'orientation',\n",
164 " 'orientation',\n",
233 " 'description']"
165 " 'description']"
234 ]
166 ]
235 }
167 }
236 ],
168 ],
237 "prompt_number": 5
169 "prompt_number": 5
238 },
170 },
239 {
171 {
240 "cell_type": "markdown",
172 "cell_type": "markdown",
241 "metadata": {},
173 "metadata": {},
242 "source": [
174 "source": [
243 "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."
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 "cell_type": "code",
179 "cell_type": "code",
248 "collapsed": false,
180 "collapsed": false,
249 "input": [
181 "input": [
250 "mywidget.value = 25.0"
182 "mywidget.value = 25.0"
251 ],
183 ],
252 "language": "python",
184 "language": "python",
253 "metadata": {},
185 "metadata": {},
254 "outputs": [],
186 "outputs": [],
255 "prompt_number": 6
187 "prompt_number": 6
256 },
188 },
257 {
189 {
258 "cell_type": "markdown",
190 "cell_type": "markdown",
259 "metadata": {},
191 "metadata": {},
260 "source": [
192 "source": [
261 "After changing the widget's value in the notebook by hand to 0.0 (sliding the bar to the far left)."
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 "cell_type": "code",
197 "cell_type": "code",
266 "collapsed": false,
198 "collapsed": false,
267 "input": [
199 "input": [
268 "mywidget.value"
200 "mywidget.value"
269 ],
201 ],
270 "language": "python",
202 "language": "python",
271 "metadata": {},
203 "metadata": {},
272 "outputs": [
204 "outputs": [
273 {
205 {
274 "metadata": {},
206 "metadata": {},
275 "output_type": "pyout",
207 "output_type": "pyout",
276 "prompt_number": 7,
208 "prompt_number": 7,
277 "text": [
209 "text": [
278 "0.0"
210 "0.0"
279 ]
211 ]
280 }
212 }
281 ],
213 ],
282 "prompt_number": 7
214 "prompt_number": 7
283 },
215 },
284 {
216 {
285 "cell_type": "markdown",
217 "cell_type": "markdown",
286 "metadata": {},
218 "metadata": {},
287 "source": [
219 "source": [
288 "Widget property values can also be set with kwargs during the construction of the widget (as seen below)."
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 "cell_type": "code",
224 "cell_type": "code",
293 "collapsed": false,
225 "collapsed": false,
294 "input": [
226 "input": [
295 "mysecondwidget = widgets.SelectionWidget(values=[\"Item A\", \"Item B\", \"Item C\"], value=\"Nothing Selected\")\n",
227 "mysecondwidget = widgets.SelectionWidget(values=[\"Item A\", \"Item B\", \"Item C\"], value=\"Nothing Selected\")\n",
296 "display(mysecondwidget)"
228 "display(mysecondwidget)"
297 ],
229 ],
298 "language": "python",
230 "language": "python",
299 "metadata": {},
231 "metadata": {},
300 "outputs": [],
232 "outputs": [],
301 "prompt_number": 8
233 "prompt_number": 8
302 },
234 },
303 {
235 {
304 "cell_type": "heading",
236 "cell_type": "heading",
305 "level": 1,
237 "level": 1,
306 "metadata": {},
238 "metadata": {},
307 "source": [
239 "source": [
308 "Views"
240 "Views"
309 ]
241 ]
310 },
242 },
311 {
243 {
312 "cell_type": "markdown",
244 "cell_type": "markdown",
313 "metadata": {},
245 "metadata": {},
314 "source": [
246 "source": [
315 "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)."
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 "cell_type": "code",
251 "cell_type": "code",
320 "collapsed": false,
252 "collapsed": false,
321 "input": [
253 "input": [
322 "mywidget.default_view_name"
254 "mywidget.default_view_name"
323 ],
255 ],
324 "language": "python",
256 "language": "python",
325 "metadata": {},
257 "metadata": {},
326 "outputs": [
258 "outputs": [
327 {
259 {
328 "metadata": {},
260 "metadata": {},
329 "output_type": "pyout",
261 "output_type": "pyout",
330 "prompt_number": 9,
262 "prompt_number": 9,
331 "text": [
263 "text": [
332 "u'FloatSliderView'"
264 "u'FloatSliderView'"
333 ]
265 ]
334 }
266 }
335 ],
267 ],
336 "prompt_number": 9
268 "prompt_number": 9
337 },
269 },
338 {
270 {
339 "cell_type": "markdown",
271 "cell_type": "markdown",
340 "metadata": {},
272 "metadata": {},
341 "source": [
273 "source": [
342 "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)."
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 "cell_type": "code",
278 "cell_type": "code",
347 "collapsed": false,
279 "collapsed": false,
348 "input": [
280 "input": [
349 "display(mywidget)\n",
281 "display(mywidget)\n",
350 "display(mywidget, view_name=\"FloatTextView\")"
282 "display(mywidget, view_name=\"FloatTextView\")"
351 ],
283 ],
352 "language": "python",
284 "language": "python",
353 "metadata": {},
285 "metadata": {},
354 "outputs": [],
286 "outputs": [],
355 "prompt_number": 10
287 "prompt_number": 10
356 },
288 },
357 {
289 {
358 "cell_type": "markdown",
290 "cell_type": "markdown",
359 "metadata": {},
291 "metadata": {},
360 "source": [
292 "source": [
361 "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",
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 "\n",
294 "\n",
363 "| Widget Name | View Names |\n",
295 "| Widget Name | View Names |\n",
364 "|:-----------------------|:--------------------|\n",
296 "|:-----------------------|:--------------------|\n",
365 "| BoolWidget | *CheckboxView* |\n",
297 "| BoolWidget | *CheckboxView* |\n",
366 "| | ToggleButtonView |\n",
298 "| | ToggleButtonView |\n",
367 "| ButtonWidget | *ButtonView* |\n",
299 "| ButtonWidget | *ButtonView* |\n",
368 "| ContainerWidget | *ContainerView* |\n",
300 "| ContainerWidget | *ContainerView* |\n",
369 "| FloatRangeWidget | *FloatSliderView* |\n",
301 "| FloatRangeWidget | *FloatSliderView* |\n",
370 "| | FloatTextView |\n",
302 "| | FloatTextView |\n",
371 "| | ProgressView |\n",
303 "| | ProgressView |\n",
372 "| FloatWidget | *FloatTextView* |\n",
304 "| FloatWidget | *FloatTextView* |\n",
373 "| IntRangeWidget | *IntSliderView* |\n",
305 "| IntRangeWidget | *IntSliderView* |\n",
374 "| | IntTextView |\n",
306 "| | IntTextView |\n",
375 "| | ProgressView |\n",
307 "| | ProgressView |\n",
376 "| IntWidget | *IntTextView* |\n",
308 "| IntWidget | *IntTextView* |\n",
377 "| MulticontainerWidget | AccordionView |\n",
309 "| MulticontainerWidget | AccordionView |\n",
378 "| | *TabView* |\n",
310 "| | *TabView* |\n",
379 "| SelectionWidget | ToggleButtonsView |\n",
311 "| SelectionWidget | ToggleButtonsView |\n",
380 "| | RadioButtonsView |\n",
312 "| | RadioButtonsView |\n",
381 "| | *DropdownView* |\n",
313 "| | *DropdownView* |\n",
382 "| StringWidget | LabelView |\n",
314 "| StringWidget | LabelView |\n",
383 "| | TextAreaView |\n",
315 "| | TextAreaView |\n",
384 "| | *TextBoxView* |\n"
316 "| | *TextBoxView* |\n"
385 ]
317 ]
386 }
318 }
387 ],
319 ],
388 "metadata": {}
320 "metadata": {}
389 }
321 }
390 ]
322 ]
391 } No newline at end of file
323 }
@@ -1,310 +1,242 b''
1 {
1 {
2 "metadata": {
2 "metadata": {
3 "cell_tags": [
4 [
5 "<None>",
6 null
7 ]
8 ],
3 "name": ""
9 "name": ""
4 },
10 },
5 "nbformat": 3,
11 "nbformat": 3,
6 "nbformat_minor": 0,
12 "nbformat_minor": 0,
7 "worksheets": [
13 "worksheets": [
8 {
14 {
9 "cells": [
15 "cells": [
10 {
16 {
11 "cell_type": "code",
17 "cell_type": "code",
12 "collapsed": false,
18 "collapsed": false,
13 "input": [
19 "input": [
14 "from IPython.html import widgets # Widget definitions\n",
20 "from IPython.html import widgets # Widget definitions\n",
15 "from IPython.display import display # Used to display widgets in the notebook\n",
21 "from IPython.display import display # Used to display widgets in the notebook"
16 "\n",
17 "# Enable widgets in this notebook\n",
18 "widgets.init_widget_js()"
19 ],
22 ],
20 "language": "python",
23 "language": "python",
21 "metadata": {},
24 "metadata": {},
22 "outputs": [
25 "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 ],
94 "prompt_number": 1
26 "prompt_number": 1
95 },
27 },
96 {
28 {
97 "cell_type": "heading",
29 "cell_type": "heading",
98 "level": 1,
30 "level": 1,
99 "metadata": {},
31 "metadata": {},
100 "source": [
32 "source": [
101 "Traitlet Events"
33 "Traitlet Events"
102 ]
34 ]
103 },
35 },
104 {
36 {
105 "cell_type": "markdown",
37 "cell_type": "markdown",
106 "metadata": {},
38 "metadata": {},
107 "source": [
39 "source": [
108 "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."
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 "cell_type": "code",
44 "cell_type": "code",
113 "collapsed": false,
45 "collapsed": false,
114 "input": [
46 "input": [
115 "print(widgets.Widget.on_trait_change.__doc__)"
47 "print(widgets.Widget.on_trait_change.__doc__)"
116 ],
48 ],
117 "language": "python",
49 "language": "python",
118 "metadata": {},
50 "metadata": {},
119 "outputs": [
51 "outputs": [
120 {
52 {
121 "output_type": "stream",
53 "output_type": "stream",
122 "stream": "stdout",
54 "stream": "stdout",
123 "text": [
55 "text": [
124 "Setup a handler to be called when a trait changes.\n",
56 "Setup a handler to be called when a trait changes.\n",
125 "\n",
57 "\n",
126 " This is used to setup dynamic notifications of trait changes.\n",
58 " This is used to setup dynamic notifications of trait changes.\n",
127 "\n",
59 "\n",
128 " Static handlers can be created by creating methods on a HasTraits\n",
60 " Static handlers can be created by creating methods on a HasTraits\n",
129 " subclass with the naming convention '_[traitname]_changed'. Thus,\n",
61 " subclass with the naming convention '_[traitname]_changed'. Thus,\n",
130 " to create static handler for the trait 'a', create the method\n",
62 " to create static handler for the trait 'a', create the method\n",
131 " _a_changed(self, name, old, new) (fewer arguments can be used, see\n",
63 " _a_changed(self, name, old, new) (fewer arguments can be used, see\n",
132 " below).\n",
64 " below).\n",
133 "\n",
65 "\n",
134 " Parameters\n",
66 " Parameters\n",
135 " ----------\n",
67 " ----------\n",
136 " handler : callable\n",
68 " handler : callable\n",
137 " A callable that is called when a trait changes. Its\n",
69 " A callable that is called when a trait changes. Its\n",
138 " signature can be handler(), handler(name), handler(name, new)\n",
70 " signature can be handler(), handler(name), handler(name, new)\n",
139 " or handler(name, old, new).\n",
71 " or handler(name, old, new).\n",
140 " name : list, str, None\n",
72 " name : list, str, None\n",
141 " If None, the handler will apply to all traits. If a list\n",
73 " If None, the handler will apply to all traits. If a list\n",
142 " of str, handler will apply to all names in the list. If a\n",
74 " of str, handler will apply to all names in the list. If a\n",
143 " str, the handler will apply just to that name.\n",
75 " str, the handler will apply just to that name.\n",
144 " remove : bool\n",
76 " remove : bool\n",
145 " If False (the default), then install the handler. If True\n",
77 " If False (the default), then install the handler. If True\n",
146 " then unintall it.\n",
78 " then unintall it.\n",
147 " \n"
79 " \n"
148 ]
80 ]
149 }
81 }
150 ],
82 ],
151 "prompt_number": 2
83 "prompt_number": 2
152 },
84 },
153 {
85 {
154 "cell_type": "markdown",
86 "cell_type": "markdown",
155 "metadata": {},
87 "metadata": {},
156 "source": [
88 "source": [
157 "Mentioned in the doc string, the callback registered can have 4 possible signatures:\n",
89 "Mentioned in the doc string, the callback registered can have 4 possible signatures:\n",
158 "\n",
90 "\n",
159 "- callback()\n",
91 "- callback()\n",
160 "- callback(trait_name)\n",
92 "- callback(trait_name)\n",
161 "- callback(trait_name, new_value)\n",
93 "- callback(trait_name, new_value)\n",
162 "- callback(trait_name, old_value, new_value)\n",
94 "- callback(trait_name, old_value, new_value)\n",
163 "\n",
95 "\n",
164 "An example of how to output an IntRangeWiget's value as it is changed can be seen below."
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 "cell_type": "code",
100 "cell_type": "code",
169 "collapsed": false,
101 "collapsed": false,
170 "input": [
102 "input": [
171 "intrange = widgets.IntRangeWidget()\n",
103 "intrange = widgets.IntRangeWidget()\n",
172 "display(intrange)\n",
104 "display(intrange)\n",
173 "\n",
105 "\n",
174 "def on_value_change(name, value):\n",
106 "def on_value_change(name, value):\n",
175 " print value\n",
107 " print value\n",
176 "\n",
108 "\n",
177 "intrange.on_trait_change(on_value_change, 'value')"
109 "intrange.on_trait_change(on_value_change, 'value')"
178 ],
110 ],
179 "language": "python",
111 "language": "python",
180 "metadata": {},
112 "metadata": {},
181 "outputs": [],
113 "outputs": [],
182 "prompt_number": 3
114 "prompt_number": 3
183 },
115 },
184 {
116 {
185 "cell_type": "heading",
117 "cell_type": "heading",
186 "level": 1,
118 "level": 1,
187 "metadata": {},
119 "metadata": {},
188 "source": [
120 "source": [
189 "Specialized Events"
121 "Specialized Events"
190 ]
122 ]
191 },
123 },
192 {
124 {
193 "cell_type": "heading",
125 "cell_type": "heading",
194 "level": 2,
126 "level": 2,
195 "metadata": {},
127 "metadata": {},
196 "source": [
128 "source": [
197 "Button On Click Event"
129 "Button On Click Event"
198 ]
130 ]
199 },
131 },
200 {
132 {
201 "cell_type": "markdown",
133 "cell_type": "markdown",
202 "metadata": {},
134 "metadata": {},
203 "source": [
135 "source": [
204 "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."
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 "cell_type": "code",
140 "cell_type": "code",
209 "collapsed": false,
141 "collapsed": false,
210 "input": [
142 "input": [
211 "print(widgets.ButtonWidget.on_click.__doc__)"
143 "print(widgets.ButtonWidget.on_click.__doc__)"
212 ],
144 ],
213 "language": "python",
145 "language": "python",
214 "metadata": {},
146 "metadata": {},
215 "outputs": [
147 "outputs": [
216 {
148 {
217 "output_type": "stream",
149 "output_type": "stream",
218 "stream": "stdout",
150 "stream": "stdout",
219 "text": [
151 "text": [
220 "Register a callback to execute when the button is clicked. The\n",
152 "Register a callback to execute when the button is clicked. The\n",
221 " callback can either accept no parameters or one sender parameter:\n",
153 " callback can either accept no parameters or one sender parameter:\n",
222 " - callback()\n",
154 " - callback()\n",
223 " - callback(sender)\n",
155 " - callback(sender)\n",
224 " If the callback has a sender parameter, the ButtonWidget instance that\n",
156 " If the callback has a sender parameter, the ButtonWidget instance that\n",
225 " called the callback will be passed into the method as the sender.\n",
157 " called the callback will be passed into the method as the sender.\n",
226 "\n",
158 "\n",
227 " Parameters\n",
159 " Parameters\n",
228 " ----------\n",
160 " ----------\n",
229 " remove : bool (optional)\n",
161 " remove : bool (optional)\n",
230 " Set to true to remove the callback from the list of callbacks.\n"
162 " Set to true to remove the callback from the list of callbacks.\n"
231 ]
163 ]
232 }
164 }
233 ],
165 ],
234 "prompt_number": 4
166 "prompt_number": 4
235 },
167 },
236 {
168 {
237 "cell_type": "markdown",
169 "cell_type": "markdown",
238 "metadata": {},
170 "metadata": {},
239 "source": [
171 "source": [
240 "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."
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 "cell_type": "code",
176 "cell_type": "code",
245 "collapsed": false,
177 "collapsed": false,
246 "input": [
178 "input": [
247 "button = widgets.ButtonWidget(description=\"Click Me!\")\n",
179 "button = widgets.ButtonWidget(description=\"Click Me!\")\n",
248 "display(button)\n",
180 "display(button)\n",
249 "\n",
181 "\n",
250 "def on_button_clicked(sender):\n",
182 "def on_button_clicked(sender):\n",
251 " print(\"Button clicked %d times.\" % sender.clicks)\n",
183 " print(\"Button clicked %d times.\" % sender.clicks)\n",
252 "\n",
184 "\n",
253 "button.on_click(on_button_clicked)"
185 "button.on_click(on_button_clicked)"
254 ],
186 ],
255 "language": "python",
187 "language": "python",
256 "metadata": {},
188 "metadata": {},
257 "outputs": [
189 "outputs": [
258 {
190 {
259 "output_type": "stream",
191 "output_type": "stream",
260 "stream": "stdout",
192 "stream": "stdout",
261 "text": [
193 "text": [
262 "Button clicked 1 times.\n"
194 "Button clicked 1 times.\n"
263 ]
195 ]
264 },
196 },
265 {
197 {
266 "output_type": "stream",
198 "output_type": "stream",
267 "stream": "stdout",
199 "stream": "stdout",
268 "text": [
200 "text": [
269 "Button clicked 2 times.\n"
201 "Button clicked 2 times.\n"
270 ]
202 ]
271 },
203 },
272 {
204 {
273 "output_type": "stream",
205 "output_type": "stream",
274 "stream": "stdout",
206 "stream": "stdout",
275 "text": [
207 "text": [
276 "Button clicked 3 times.\n"
208 "Button clicked 3 times.\n"
277 ]
209 ]
278 }
210 }
279 ],
211 ],
280 "prompt_number": 5
212 "prompt_number": 5
281 },
213 },
282 {
214 {
283 "cell_type": "markdown",
215 "cell_type": "markdown",
284 "metadata": {},
216 "metadata": {},
285 "source": [
217 "source": [
286 "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."
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 "cell_type": "code",
222 "cell_type": "code",
291 "collapsed": false,
223 "collapsed": false,
292 "input": [
224 "input": [
293 "def show_button(sender=None):\n",
225 "def show_button(sender=None):\n",
294 " button = widgets.ButtonWidget()\n",
226 " button = widgets.ButtonWidget()\n",
295 " button.description = \"%d\" % (sender.clicks if sender is not None else 0)\n",
227 " button.description = \"%d\" % (sender.clicks if sender is not None else 0)\n",
296 " display(button)\n",
228 " display(button)\n",
297 " button.on_click(show_button)\n",
229 " button.on_click(show_button)\n",
298 "show_button()\n",
230 "show_button()\n",
299 " "
231 " "
300 ],
232 ],
301 "language": "python",
233 "language": "python",
302 "metadata": {},
234 "metadata": {},
303 "outputs": [],
235 "outputs": [],
304 "prompt_number": 6
236 "prompt_number": 6
305 }
237 }
306 ],
238 ],
307 "metadata": {}
239 "metadata": {}
308 }
240 }
309 ]
241 ]
310 } No newline at end of file
242 }
@@ -1,326 +1,258 b''
1 {
1 {
2 "metadata": {
2 "metadata": {
3 "cell_tags": [
4 [
5 "<None>",
6 null
7 ]
8 ],
3 "name": ""
9 "name": ""
4 },
10 },
5 "nbformat": 3,
11 "nbformat": 3,
6 "nbformat_minor": 0,
12 "nbformat_minor": 0,
7 "worksheets": [
13 "worksheets": [
8 {
14 {
9 "cells": [
15 "cells": [
10 {
16 {
11 "cell_type": "code",
17 "cell_type": "code",
12 "collapsed": false,
18 "collapsed": false,
13 "input": [
19 "input": [
14 "from IPython.html import widgets # Widget definitions\n",
20 "from IPython.html import widgets # Widget definitions\n",
15 "from IPython.display import display # Used to display widgets in the notebook\n",
21 "from IPython.display import display # Used to display widgets in the notebook"
16 "\n",
17 "# Enable widgets in this notebook\n",
18 "widgets.init_widget_js()"
19 ],
22 ],
20 "language": "python",
23 "language": "python",
21 "metadata": {},
24 "metadata": {},
22 "outputs": [
25 "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 ],
94 "prompt_number": 1
26 "prompt_number": 1
95 },
27 },
96 {
28 {
97 "cell_type": "heading",
29 "cell_type": "heading",
98 "level": 1,
30 "level": 1,
99 "metadata": {},
31 "metadata": {},
100 "source": [
32 "source": [
101 "Parent/Child Relationships"
33 "Parent/Child Relationships"
102 ]
34 ]
103 },
35 },
104 {
36 {
105 "cell_type": "markdown",
37 "cell_type": "markdown",
106 "metadata": {},
38 "metadata": {},
107 "source": [
39 "source": [
108 "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",
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 "\n",
41 "\n",
110 "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)."
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 "cell_type": "code",
46 "cell_type": "code",
115 "collapsed": false,
47 "collapsed": false,
116 "input": [
48 "input": [
117 "container = widgets.MulticontainerWidget()\n",
49 "container = widgets.MulticontainerWidget()\n",
118 "\n",
50 "\n",
119 "floatrange = widgets.FloatRangeWidget(parent=container) # You can set the parent in the constructor,\n",
51 "floatrange = widgets.FloatRangeWidget(parent=container) # You can set the parent in the constructor,\n",
120 "\n",
52 "\n",
121 "string = widgets.StringWidget()\n",
53 "string = widgets.StringWidget()\n",
122 "string.parent = container # or after the widget has been created.\n",
54 "string.parent = container # or after the widget has been created.\n",
123 "\n",
55 "\n",
124 "display(container) # Displays the `container` and all of it's children."
56 "display(container) # Displays the `container` and all of it's children."
125 ],
57 ],
126 "language": "python",
58 "language": "python",
127 "metadata": {},
59 "metadata": {},
128 "outputs": [],
60 "outputs": [],
129 "prompt_number": 2
61 "prompt_number": 2
130 },
62 },
131 {
63 {
132 "cell_type": "markdown",
64 "cell_type": "markdown",
133 "metadata": {},
65 "metadata": {},
134 "source": [
66 "source": [
135 "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",
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 "\n",
68 "\n",
137 "In the example below, the IntRangeWidget is never rendered since display was called on the parent before the parent/child relationship was established."
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 "cell_type": "code",
73 "cell_type": "code",
142 "collapsed": false,
74 "collapsed": false,
143 "input": [
75 "input": [
144 "container = widgets.MulticontainerWidget()\n",
76 "container = widgets.MulticontainerWidget()\n",
145 "display(container)\n",
77 "display(container)\n",
146 "\n",
78 "\n",
147 "intrange = widgets.IntRangeWidget(parent=container) # Never gets displayed."
79 "intrange = widgets.IntRangeWidget(parent=container) # Never gets displayed."
148 ],
80 ],
149 "language": "python",
81 "language": "python",
150 "metadata": {},
82 "metadata": {},
151 "outputs": [],
83 "outputs": [],
152 "prompt_number": 3
84 "prompt_number": 3
153 },
85 },
154 {
86 {
155 "cell_type": "markdown",
87 "cell_type": "markdown",
156 "metadata": {},
88 "metadata": {},
157 "source": [
89 "source": [
158 "Calling display on the child fixes the problem."
90 "Calling display on the child fixes the problem."
159 ]
91 ]
160 },
92 },
161 {
93 {
162 "cell_type": "code",
94 "cell_type": "code",
163 "collapsed": false,
95 "collapsed": false,
164 "input": [
96 "input": [
165 "container = widgets.MulticontainerWidget()\n",
97 "container = widgets.MulticontainerWidget()\n",
166 "display(container)\n",
98 "display(container)\n",
167 "\n",
99 "\n",
168 "intrange = widgets.IntRangeWidget(parent=container)\n",
100 "intrange = widgets.IntRangeWidget(parent=container)\n",
169 "display(intrange) # This line is needed since the `container` has already been displayed."
101 "display(intrange) # This line is needed since the `container` has already been displayed."
170 ],
102 ],
171 "language": "python",
103 "language": "python",
172 "metadata": {},
104 "metadata": {},
173 "outputs": [],
105 "outputs": [],
174 "prompt_number": 4
106 "prompt_number": 4
175 },
107 },
176 {
108 {
177 "cell_type": "heading",
109 "cell_type": "heading",
178 "level": 1,
110 "level": 1,
179 "metadata": {},
111 "metadata": {},
180 "source": [
112 "source": [
181 "Changing Child Views"
113 "Changing Child Views"
182 ]
114 ]
183 },
115 },
184 {
116 {
185 "cell_type": "markdown",
117 "cell_type": "markdown",
186 "metadata": {},
118 "metadata": {},
187 "source": [
119 "source": [
188 "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)."
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 "cell_type": "code",
124 "cell_type": "code",
193 "collapsed": false,
125 "collapsed": false,
194 "input": [
126 "input": [
195 "container = widgets.MulticontainerWidget()\n",
127 "container = widgets.MulticontainerWidget()\n",
196 "\n",
128 "\n",
197 "floatrange = widgets.FloatRangeWidget(parent=container)\n",
129 "floatrange = widgets.FloatRangeWidget(parent=container)\n",
198 "floatrange.default_view_name = \"FloatTextView\" # It can be set as a property.\n",
130 "floatrange.default_view_name = \"FloatTextView\" # It can be set as a property.\n",
199 "\n",
131 "\n",
200 "string = widgets.StringWidget(default_view_name = \"TextAreaView\") # It can also be set in the constructor.\n",
132 "string = widgets.StringWidget(default_view_name = \"TextAreaView\") # It can also be set in the constructor.\n",
201 "string.parent = container\n",
133 "string.parent = container\n",
202 "\n",
134 "\n",
203 "display(container)"
135 "display(container)"
204 ],
136 ],
205 "language": "python",
137 "language": "python",
206 "metadata": {},
138 "metadata": {},
207 "outputs": [],
139 "outputs": [],
208 "prompt_number": 5
140 "prompt_number": 5
209 },
141 },
210 {
142 {
211 "cell_type": "markdown",
143 "cell_type": "markdown",
212 "metadata": {},
144 "metadata": {},
213 "source": [
145 "source": [
214 "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."
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 "cell_type": "code",
150 "cell_type": "code",
219 "collapsed": false,
151 "collapsed": false,
220 "input": [
152 "input": [
221 "container = widgets.MulticontainerWidget()\n",
153 "container = widgets.MulticontainerWidget()\n",
222 "display(container)\n",
154 "display(container)\n",
223 "\n",
155 "\n",
224 "floatrange = widgets.FloatRangeWidget()\n",
156 "floatrange = widgets.FloatRangeWidget()\n",
225 "floatrange.parent=container\n",
157 "floatrange.parent=container\n",
226 "display(floatrange, view_name = \"FloatTextView\") # view_name can be set during display.\n",
158 "display(floatrange, view_name = \"FloatTextView\") # view_name can be set during display.\n",
227 "\n",
159 "\n",
228 "string = widgets.StringWidget()\n",
160 "string = widgets.StringWidget()\n",
229 "string.parent = container\n",
161 "string.parent = container\n",
230 "string.default_view_name = \"TextAreaView\" # Setting default_view_name still works.\n",
162 "string.default_view_name = \"TextAreaView\" # Setting default_view_name still works.\n",
231 "display(string)\n"
163 "display(string)\n"
232 ],
164 ],
233 "language": "python",
165 "language": "python",
234 "metadata": {},
166 "metadata": {},
235 "outputs": [],
167 "outputs": [],
236 "prompt_number": 6
168 "prompt_number": 6
237 },
169 },
238 {
170 {
239 "cell_type": "heading",
171 "cell_type": "heading",
240 "level": 1,
172 "level": 1,
241 "metadata": {},
173 "metadata": {},
242 "source": [
174 "source": [
243 "Visibility"
175 "Visibility"
244 ]
176 ]
245 },
177 },
246 {
178 {
247 "cell_type": "markdown",
179 "cell_type": "markdown",
248 "metadata": {},
180 "metadata": {},
249 "source": [
181 "source": [
250 "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)."
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 "cell_type": "code",
186 "cell_type": "code",
255 "collapsed": false,
187 "collapsed": false,
256 "input": [
188 "input": [
257 "string = widgets.StringWidget(value=\"Hello World!\")\n",
189 "string = widgets.StringWidget(value=\"Hello World!\")\n",
258 "display(string, view_name=\"LabelView\") "
190 "display(string, view_name=\"LabelView\") "
259 ],
191 ],
260 "language": "python",
192 "language": "python",
261 "metadata": {},
193 "metadata": {},
262 "outputs": [],
194 "outputs": [],
263 "prompt_number": 7
195 "prompt_number": 7
264 },
196 },
265 {
197 {
266 "cell_type": "code",
198 "cell_type": "code",
267 "collapsed": false,
199 "collapsed": false,
268 "input": [
200 "input": [
269 "string.visible=False"
201 "string.visible=False"
270 ],
202 ],
271 "language": "python",
203 "language": "python",
272 "metadata": {},
204 "metadata": {},
273 "outputs": [],
205 "outputs": [],
274 "prompt_number": 8
206 "prompt_number": 8
275 },
207 },
276 {
208 {
277 "cell_type": "code",
209 "cell_type": "code",
278 "collapsed": false,
210 "collapsed": false,
279 "input": [
211 "input": [
280 "string.visible=True"
212 "string.visible=True"
281 ],
213 ],
282 "language": "python",
214 "language": "python",
283 "metadata": {},
215 "metadata": {},
284 "outputs": [],
216 "outputs": [],
285 "prompt_number": 9
217 "prompt_number": 9
286 },
218 },
287 {
219 {
288 "cell_type": "markdown",
220 "cell_type": "markdown",
289 "metadata": {},
221 "metadata": {},
290 "source": [
222 "source": [
291 "In the example below, a form is rendered which conditionally displays widgets depending on the state of other widgets. Try toggling the student checkbox."
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 "cell_type": "code",
227 "cell_type": "code",
296 "collapsed": false,
228 "collapsed": false,
297 "input": [
229 "input": [
298 "form = widgets.ContainerWidget()\n",
230 "form = widgets.ContainerWidget()\n",
299 "first = widgets.StringWidget(description=\"First Name:\", parent=form)\n",
231 "first = widgets.StringWidget(description=\"First Name:\", parent=form)\n",
300 "last = widgets.StringWidget(description=\"Last Name:\", parent=form)\n",
232 "last = widgets.StringWidget(description=\"Last Name:\", parent=form)\n",
301 "\n",
233 "\n",
302 "student = widgets.BoolWidget(description=\"Student:\", value=False, parent=form)\n",
234 "student = widgets.BoolWidget(description=\"Student:\", value=False, parent=form)\n",
303 "school_info = widgets.ContainerWidget(visible=False, parent=form)\n",
235 "school_info = widgets.ContainerWidget(visible=False, parent=form)\n",
304 "school = widgets.StringWidget(description=\"School:\", parent=school_info)\n",
236 "school = widgets.StringWidget(description=\"School:\", parent=school_info)\n",
305 "grade = widgets.IntRangeWidget(description=\"Grade:\", min=0, max=12, default_view_name='IntTextView', parent=school_info)\n",
237 "grade = widgets.IntRangeWidget(description=\"Grade:\", min=0, max=12, default_view_name='IntTextView', parent=school_info)\n",
306 "\n",
238 "\n",
307 "pet = widgets.StringWidget(description=\"Pet's Name:\", parent=form)\n",
239 "pet = widgets.StringWidget(description=\"Pet's Name:\", parent=form)\n",
308 "display(form)\n",
240 "display(form)\n",
309 "\n",
241 "\n",
310 "def on_student_toggle(name, value):\n",
242 "def on_student_toggle(name, value):\n",
311 " if value:\n",
243 " if value:\n",
312 " school_info.visible = True\n",
244 " school_info.visible = True\n",
313 " else:\n",
245 " else:\n",
314 " school_info.visible = False\n",
246 " school_info.visible = False\n",
315 "student.on_trait_change(on_student_toggle, 'value')\n"
247 "student.on_trait_change(on_student_toggle, 'value')\n"
316 ],
248 ],
317 "language": "python",
249 "language": "python",
318 "metadata": {},
250 "metadata": {},
319 "outputs": [],
251 "outputs": [],
320 "prompt_number": 10
252 "prompt_number": 10
321 }
253 }
322 ],
254 ],
323 "metadata": {}
255 "metadata": {}
324 }
256 }
325 ]
257 ]
326 } No newline at end of file
258 }
@@ -1,405 +1,337 b''
1 {
1 {
2 "metadata": {
2 "metadata": {
3 "cell_tags": [
4 [
5 "<None>",
6 null
7 ]
8 ],
3 "name": ""
9 "name": ""
4 },
10 },
5 "nbformat": 3,
11 "nbformat": 3,
6 "nbformat_minor": 0,
12 "nbformat_minor": 0,
7 "worksheets": [
13 "worksheets": [
8 {
14 {
9 "cells": [
15 "cells": [
10 {
16 {
11 "cell_type": "code",
17 "cell_type": "code",
12 "collapsed": false,
18 "collapsed": false,
13 "input": [
19 "input": [
14 "from IPython.html import widgets # Widget definitions\n",
20 "from IPython.html import widgets # Widget definitions\n",
15 "from IPython.display import display # Used to display widgets in the notebook\n",
21 "from IPython.display import display # Used to display widgets in the notebook"
16 "\n",
17 "# Enable widgets in this notebook\n",
18 "widgets.init_widget_js()"
19 ],
22 ],
20 "language": "python",
23 "language": "python",
21 "metadata": {},
24 "metadata": {},
22 "outputs": [
25 "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 ],
94 "prompt_number": 1
26 "prompt_number": 1
95 },
27 },
96 {
28 {
97 "cell_type": "heading",
29 "cell_type": "heading",
98 "level": 1,
30 "level": 1,
99 "metadata": {},
31 "metadata": {},
100 "source": [
32 "source": [
101 "CSS"
33 "CSS"
102 ]
34 ]
103 },
35 },
104 {
36 {
105 "cell_type": "markdown",
37 "cell_type": "markdown",
106 "metadata": {},
38 "metadata": {},
107 "source": [
39 "source": [
108 "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. "
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 "cell_type": "code",
44 "cell_type": "code",
113 "collapsed": false,
45 "collapsed": false,
114 "input": [
46 "input": [
115 "print(widgets.Widget.set_css.__doc__)"
47 "print(widgets.Widget.set_css.__doc__)"
116 ],
48 ],
117 "language": "python",
49 "language": "python",
118 "metadata": {},
50 "metadata": {},
119 "outputs": [
51 "outputs": [
120 {
52 {
121 "output_type": "stream",
53 "output_type": "stream",
122 "stream": "stdout",
54 "stream": "stdout",
123 "text": [
55 "text": [
124 "Set one or more CSS properties of the widget (shared among all of the \n",
56 "Set one or more CSS properties of the widget (shared among all of the \n",
125 " views). This function has two signatures:\n",
57 " views). This function has two signatures:\n",
126 " - set_css(css_dict, [selector=''])\n",
58 " - set_css(css_dict, [selector=''])\n",
127 " - set_css(key, value, [selector=''])\n",
59 " - set_css(key, value, [selector=''])\n",
128 "\n",
60 "\n",
129 " Parameters\n",
61 " Parameters\n",
130 " ----------\n",
62 " ----------\n",
131 " css_dict : dict\n",
63 " css_dict : dict\n",
132 " CSS key/value pairs to apply\n",
64 " CSS key/value pairs to apply\n",
133 " key: unicode\n",
65 " key: unicode\n",
134 " CSS key\n",
66 " CSS key\n",
135 " value\n",
67 " value\n",
136 " CSS value\n",
68 " CSS value\n",
137 " selector: unicode (optional)\n",
69 " selector: unicode (optional)\n",
138 " JQuery selector to use to apply the CSS key/value.\n",
70 " JQuery selector to use to apply the CSS key/value.\n",
139 " \n"
71 " \n"
140 ]
72 ]
141 }
73 }
142 ],
74 ],
143 "prompt_number": 2
75 "prompt_number": 2
144 },
76 },
145 {
77 {
146 "cell_type": "markdown",
78 "cell_type": "markdown",
147 "metadata": {},
79 "metadata": {},
148 "source": [
80 "source": [
149 "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."
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 "cell_type": "code",
85 "cell_type": "code",
154 "collapsed": false,
86 "collapsed": false,
155 "input": [
87 "input": [
156 "print(widgets.Widget.get_css.__doc__)"
88 "print(widgets.Widget.get_css.__doc__)"
157 ],
89 ],
158 "language": "python",
90 "language": "python",
159 "metadata": {},
91 "metadata": {},
160 "outputs": [
92 "outputs": [
161 {
93 {
162 "output_type": "stream",
94 "output_type": "stream",
163 "stream": "stdout",
95 "stream": "stdout",
164 "text": [
96 "text": [
165 "Get a CSS property of the widget. Note, this function does not \n",
97 "Get a CSS property of the widget. Note, this function does not \n",
166 " actually request the CSS from the front-end; Only properties that have \n",
98 " actually request the CSS from the front-end; Only properties that have \n",
167 " been set with set_css can be read.\n",
99 " been set with set_css can be read.\n",
168 "\n",
100 "\n",
169 " Parameters\n",
101 " Parameters\n",
170 " ----------\n",
102 " ----------\n",
171 " key: unicode\n",
103 " key: unicode\n",
172 " CSS key\n",
104 " CSS key\n",
173 " selector: unicode (optional)\n",
105 " selector: unicode (optional)\n",
174 " JQuery selector used when the CSS key/value was set.\n",
106 " JQuery selector used when the CSS key/value was set.\n",
175 " \n"
107 " \n"
176 ]
108 ]
177 }
109 }
178 ],
110 ],
179 "prompt_number": 3
111 "prompt_number": 3
180 },
112 },
181 {
113 {
182 "cell_type": "markdown",
114 "cell_type": "markdown",
183 "metadata": {},
115 "metadata": {},
184 "source": [
116 "source": [
185 "Below is an example that applies CSS attributes to a container to emphasize text."
117 "Below is an example that applies CSS attributes to a container to emphasize text."
186 ]
118 ]
187 },
119 },
188 {
120 {
189 "cell_type": "code",
121 "cell_type": "code",
190 "collapsed": false,
122 "collapsed": false,
191 "input": [
123 "input": [
192 "container = widgets.ContainerWidget()\n",
124 "container = widgets.ContainerWidget()\n",
193 "\n",
125 "\n",
194 "# set_css used to set a single CSS attribute.\n",
126 "# set_css used to set a single CSS attribute.\n",
195 "container.set_css('border', '3px solid black') # Border the container\n",
127 "container.set_css('border', '3px solid black') # Border the container\n",
196 "\n",
128 "\n",
197 "# set_css used to set multiple CSS attributes.\n",
129 "# set_css used to set multiple CSS attributes.\n",
198 "container.set_css({'padding': '6px', # Add padding to the container\n",
130 "container.set_css({'padding': '6px', # Add padding to the container\n",
199 " 'background': 'yellow'}) # Fill the container yellow\n",
131 " 'background': 'yellow'}) # Fill the container yellow\n",
200 "\n",
132 "\n",
201 "label = widgets.StringWidget(default_view_name=\"LabelView\", parent=container)\n",
133 "label = widgets.StringWidget(default_view_name=\"LabelView\", parent=container)\n",
202 "label.value = \"<strong>ALERT: </strong> Hello World!\"\n",
134 "label.value = \"<strong>ALERT: </strong> Hello World!\"\n",
203 "\n",
135 "\n",
204 "display(container)"
136 "display(container)"
205 ],
137 ],
206 "language": "python",
138 "language": "python",
207 "metadata": {},
139 "metadata": {},
208 "outputs": [],
140 "outputs": [],
209 "prompt_number": 4
141 "prompt_number": 4
210 },
142 },
211 {
143 {
212 "cell_type": "heading",
144 "cell_type": "heading",
213 "level": 1,
145 "level": 1,
214 "metadata": {},
146 "metadata": {},
215 "source": [
147 "source": [
216 "DOM Classes"
148 "DOM Classes"
217 ]
149 ]
218 },
150 },
219 {
151 {
220 "cell_type": "markdown",
152 "cell_type": "markdown",
221 "metadata": {},
153 "metadata": {},
222 "source": [
154 "source": [
223 "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."
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 "cell_type": "code",
159 "cell_type": "code",
228 "collapsed": false,
160 "collapsed": false,
229 "input": [
161 "input": [
230 "print(widgets.Widget.add_class.__doc__)"
162 "print(widgets.Widget.add_class.__doc__)"
231 ],
163 ],
232 "language": "python",
164 "language": "python",
233 "metadata": {},
165 "metadata": {},
234 "outputs": [
166 "outputs": [
235 {
167 {
236 "output_type": "stream",
168 "output_type": "stream",
237 "stream": "stdout",
169 "stream": "stdout",
238 "text": [
170 "text": [
239 "Add class[es] to a DOM element\n",
171 "Add class[es] to a DOM element\n",
240 "\n",
172 "\n",
241 " Parameters\n",
173 " Parameters\n",
242 " ----------\n",
174 " ----------\n",
243 " class_name: unicode\n",
175 " class_name: unicode\n",
244 " Class name(s) to add to the DOM element(s). Multiple class names \n",
176 " Class name(s) to add to the DOM element(s). Multiple class names \n",
245 " must be space separated.\n",
177 " must be space separated.\n",
246 " selector: unicode (optional)\n",
178 " selector: unicode (optional)\n",
247 " JQuery selector to select the DOM element(s) that the class(es) will \n",
179 " JQuery selector to select the DOM element(s) that the class(es) will \n",
248 " be added to.\n",
180 " be added to.\n",
249 " \n"
181 " \n"
250 ]
182 ]
251 }
183 }
252 ],
184 ],
253 "prompt_number": 5
185 "prompt_number": 5
254 },
186 },
255 {
187 {
256 "cell_type": "markdown",
188 "cell_type": "markdown",
257 "metadata": {},
189 "metadata": {},
258 "source": [
190 "source": [
259 "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). "
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 "cell_type": "code",
195 "cell_type": "code",
264 "collapsed": false,
196 "collapsed": false,
265 "input": [
197 "input": [
266 "container = widgets.ContainerWidget()\n",
198 "container = widgets.ContainerWidget()\n",
267 "container.set_css({'border': '3px solid black',\n",
199 "container.set_css({'border': '3px solid black',\n",
268 " 'padding': '6px',\n",
200 " 'padding': '6px',\n",
269 " 'background': 'yellow'}) \n",
201 " 'background': 'yellow'}) \n",
270 "\n",
202 "\n",
271 "label = widgets.StringWidget(default_view_name=\"LabelView\", parent=container) \n",
203 "label = widgets.StringWidget(default_view_name=\"LabelView\", parent=container) \n",
272 "label.value = \"<strong>ALERT: </strong> Hello World!\"\n",
204 "label.value = \"<strong>ALERT: </strong> Hello World!\"\n",
273 "\n",
205 "\n",
274 "display(container)\n",
206 "display(container)\n",
275 "container.add_class('corner-all') # Must be called AFTER display"
207 "container.add_class('corner-all') # Must be called AFTER display"
276 ],
208 ],
277 "language": "python",
209 "language": "python",
278 "metadata": {},
210 "metadata": {},
279 "outputs": [],
211 "outputs": [],
280 "prompt_number": 6
212 "prompt_number": 6
281 },
213 },
282 {
214 {
283 "cell_type": "markdown",
215 "cell_type": "markdown",
284 "metadata": {},
216 "metadata": {},
285 "source": [
217 "source": [
286 "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 ."
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 "cell_type": "code",
222 "cell_type": "code",
291 "collapsed": false,
223 "collapsed": false,
292 "input": [
224 "input": [
293 "label = widgets.StringWidget(value = \"<strong>ALERT: </strong> Hello World!\")\n",
225 "label = widgets.StringWidget(value = \"<strong>ALERT: </strong> Hello World!\")\n",
294 "display(label, view_name=\"LabelView\")\n",
226 "display(label, view_name=\"LabelView\")\n",
295 "\n",
227 "\n",
296 "# Apply twitter bootstrap alert class to the label.\n",
228 "# Apply twitter bootstrap alert class to the label.\n",
297 "label.add_class(\"alert\")"
229 "label.add_class(\"alert\")"
298 ],
230 ],
299 "language": "python",
231 "language": "python",
300 "metadata": {},
232 "metadata": {},
301 "outputs": [],
233 "outputs": [],
302 "prompt_number": 7
234 "prompt_number": 7
303 },
235 },
304 {
236 {
305 "cell_type": "markdown",
237 "cell_type": "markdown",
306 "metadata": {},
238 "metadata": {},
307 "source": [
239 "source": [
308 "The example below shows how bootstrap classes can be used to change button apearance."
240 "The example below shows how bootstrap classes can be used to change button apearance."
309 ]
241 ]
310 },
242 },
311 {
243 {
312 "cell_type": "code",
244 "cell_type": "code",
313 "collapsed": false,
245 "collapsed": false,
314 "input": [
246 "input": [
315 "# List of the bootstrap button styles\n",
247 "# List of the bootstrap button styles\n",
316 "button_classes = ['Default', 'btn-primary', 'btn-info', 'btn-success', \n",
248 "button_classes = ['Default', 'btn-primary', 'btn-info', 'btn-success', \n",
317 " 'btn-warning', 'btn-danger', 'btn-inverse', 'btn-link']\n",
249 " 'btn-warning', 'btn-danger', 'btn-inverse', 'btn-link']\n",
318 "\n",
250 "\n",
319 "# Create each button and apply the style. Also add margin to the buttons so they space\n",
251 "# Create each button and apply the style. Also add margin to the buttons so they space\n",
320 "# themselves nicely.\n",
252 "# themselves nicely.\n",
321 "for i in range(8):\n",
253 "for i in range(8):\n",
322 " button = widgets.ButtonWidget(description=button_classes[i])\n",
254 " button = widgets.ButtonWidget(description=button_classes[i])\n",
323 " button.set_css(\"margin\", \"5px\")\n",
255 " button.set_css(\"margin\", \"5px\")\n",
324 " display(button)\n",
256 " display(button)\n",
325 " if i > 0: # Don't add a class the first button.\n",
257 " if i > 0: # Don't add a class the first button.\n",
326 " button.add_class(button_classes[i])\n",
258 " button.add_class(button_classes[i])\n",
327 " "
259 " "
328 ],
260 ],
329 "language": "python",
261 "language": "python",
330 "metadata": {},
262 "metadata": {},
331 "outputs": [],
263 "outputs": [],
332 "prompt_number": 8
264 "prompt_number": 8
333 },
265 },
334 {
266 {
335 "cell_type": "markdown",
267 "cell_type": "markdown",
336 "metadata": {},
268 "metadata": {},
337 "source": [
269 "source": [
338 "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."
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 "cell_type": "code",
274 "cell_type": "code",
343 "collapsed": false,
275 "collapsed": false,
344 "input": [
276 "input": [
345 "print(widgets.Widget.remove_class.__doc__)"
277 "print(widgets.Widget.remove_class.__doc__)"
346 ],
278 ],
347 "language": "python",
279 "language": "python",
348 "metadata": {},
280 "metadata": {},
349 "outputs": [
281 "outputs": [
350 {
282 {
351 "output_type": "stream",
283 "output_type": "stream",
352 "stream": "stdout",
284 "stream": "stdout",
353 "text": [
285 "text": [
354 "Remove class[es] from a DOM element\n",
286 "Remove class[es] from a DOM element\n",
355 "\n",
287 "\n",
356 " Parameters\n",
288 " Parameters\n",
357 " ----------\n",
289 " ----------\n",
358 " class_name: unicode\n",
290 " class_name: unicode\n",
359 " Class name(s) to remove from the DOM element(s). Multiple class \n",
291 " Class name(s) to remove from the DOM element(s). Multiple class \n",
360 " names must be space separated.\n",
292 " names must be space separated.\n",
361 " selector: unicode (optional)\n",
293 " selector: unicode (optional)\n",
362 " JQuery selector to select the DOM element(s) that the class(es) will \n",
294 " JQuery selector to select the DOM element(s) that the class(es) will \n",
363 " be removed from.\n",
295 " be removed from.\n",
364 " \n"
296 " \n"
365 ]
297 ]
366 }
298 }
367 ],
299 ],
368 "prompt_number": 9
300 "prompt_number": 9
369 },
301 },
370 {
302 {
371 "cell_type": "markdown",
303 "cell_type": "markdown",
372 "metadata": {},
304 "metadata": {},
373 "source": [
305 "source": [
374 "The example below animates an alert using different bootstrap styles."
306 "The example below animates an alert using different bootstrap styles."
375 ]
307 ]
376 },
308 },
377 {
309 {
378 "cell_type": "code",
310 "cell_type": "code",
379 "collapsed": false,
311 "collapsed": false,
380 "input": [
312 "input": [
381 "import time\n",
313 "import time\n",
382 "label = widgets.StringWidget(value = \"<strong>ALERT: </strong> Hello World!\")\n",
314 "label = widgets.StringWidget(value = \"<strong>ALERT: </strong> Hello World!\")\n",
383 "display(label, view_name=\"LabelView\")\n",
315 "display(label, view_name=\"LabelView\")\n",
384 "\n",
316 "\n",
385 "# Apply twitter bootstrap alert class to the label.\n",
317 "# Apply twitter bootstrap alert class to the label.\n",
386 "label.add_class(\"alert\")\n",
318 "label.add_class(\"alert\")\n",
387 "\n",
319 "\n",
388 "# Animate through additional bootstrap label styles 3 times\n",
320 "# Animate through additional bootstrap label styles 3 times\n",
389 "additional_alert_styles = ['alert-error', 'alert-info', 'alert-success']\n",
321 "additional_alert_styles = ['alert-error', 'alert-info', 'alert-success']\n",
390 "for i in range(3 * len(additional_alert_styles)):\n",
322 "for i in range(3 * len(additional_alert_styles)):\n",
391 " label.add_class(additional_alert_styles[i % 3])\n",
323 " label.add_class(additional_alert_styles[i % 3])\n",
392 " label.remove_class(additional_alert_styles[(i-1) % 3])\n",
324 " label.remove_class(additional_alert_styles[(i-1) % 3])\n",
393 " time.sleep(1)\n",
325 " time.sleep(1)\n",
394 " "
326 " "
395 ],
327 ],
396 "language": "python",
328 "language": "python",
397 "metadata": {},
329 "metadata": {},
398 "outputs": [],
330 "outputs": [],
399 "prompt_number": 10
331 "prompt_number": 10
400 }
332 }
401 ],
333 ],
402 "metadata": {}
334 "metadata": {}
403 }
335 }
404 ]
336 ]
405 } No newline at end of file
337 }
@@ -1,378 +1,304 b''
1 {
1 {
2 "metadata": {
2 "metadata": {
3 "cell_tags": [
3 "cell_tags": [
4 [
4 [
5 "<None>",
5 "<None>",
6 null
6 null
7 ]
7 ]
8 ],
8 ],
9 "name": ""
9 "name": ""
10 },
10 },
11 "nbformat": 3,
11 "nbformat": 3,
12 "nbformat_minor": 0,
12 "nbformat_minor": 0,
13 "worksheets": [
13 "worksheets": [
14 {
14 {
15 "cells": [
15 "cells": [
16 {
16 {
17 "cell_type": "code",
17 "cell_type": "code",
18 "collapsed": false,
18 "collapsed": false,
19 "input": [
19 "input": [
20 "from IPython.html import widgets # Widget definitions\n",
20 "from IPython.html import widgets # Widget definitions\n",
21 "from IPython.display import display # Used to display widgets in the notebook\n",
21 "from IPython.display import display # Used to display widgets in the notebook"
22 "\n",
23 "# Enable widgets in this notebook\n",
24 "widgets.init_widget_js()"
25 ],
22 ],
26 "language": "python",
23 "language": "python",
27 "metadata": {},
24 "metadata": {},
28 "outputs": [
25 "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 ],
100 "prompt_number": 1
26 "prompt_number": 1
101 },
27 },
102 {
28 {
103 "cell_type": "heading",
29 "cell_type": "heading",
104 "level": 1,
30 "level": 1,
105 "metadata": {},
31 "metadata": {},
106 "source": [
32 "source": [
107 "Alignment"
33 "Alignment"
108 ]
34 ]
109 },
35 },
110 {
36 {
111 "cell_type": "markdown",
37 "cell_type": "markdown",
112 "metadata": {},
38 "metadata": {},
113 "source": [
39 "source": [
114 "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) "
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 "cell_type": "code",
44 "cell_type": "code",
119 "collapsed": false,
45 "collapsed": false,
120 "input": [
46 "input": [
121 "display(widgets.StringWidget(description=\"a:\"))\n",
47 "display(widgets.StringWidget(description=\"a:\"))\n",
122 "display(widgets.StringWidget(description=\"aa:\"))\n",
48 "display(widgets.StringWidget(description=\"aa:\"))\n",
123 "display(widgets.StringWidget(description=\"aaa:\"))"
49 "display(widgets.StringWidget(description=\"aaa:\"))"
124 ],
50 ],
125 "language": "python",
51 "language": "python",
126 "metadata": {},
52 "metadata": {},
127 "outputs": [],
53 "outputs": [],
128 "prompt_number": 2
54 "prompt_number": 2
129 },
55 },
130 {
56 {
131 "cell_type": "markdown",
57 "cell_type": "markdown",
132 "metadata": {},
58 "metadata": {},
133 "source": [
59 "source": [
134 "If a label is longer than the minimum width, the widget is shifted to the right (as seen below)."
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 "cell_type": "code",
64 "cell_type": "code",
139 "collapsed": false,
65 "collapsed": false,
140 "input": [
66 "input": [
141 "display(widgets.StringWidget(description=\"a:\"))\n",
67 "display(widgets.StringWidget(description=\"a:\"))\n",
142 "display(widgets.StringWidget(description=\"aa:\"))\n",
68 "display(widgets.StringWidget(description=\"aa:\"))\n",
143 "display(widgets.StringWidget(description=\"aaa:\"))\n",
69 "display(widgets.StringWidget(description=\"aaa:\"))\n",
144 "display(widgets.StringWidget(description=\"aaaaaaaaaaaaaaaaaa:\"))"
70 "display(widgets.StringWidget(description=\"aaaaaaaaaaaaaaaaaa:\"))"
145 ],
71 ],
146 "language": "python",
72 "language": "python",
147 "metadata": {},
73 "metadata": {},
148 "outputs": [],
74 "outputs": [],
149 "prompt_number": 3
75 "prompt_number": 3
150 },
76 },
151 {
77 {
152 "cell_type": "markdown",
78 "cell_type": "markdown",
153 "metadata": {},
79 "metadata": {},
154 "source": [
80 "source": [
155 "If a `description` is not set for the widget, the label is not displayed (as seen below)."
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 "cell_type": "code",
85 "cell_type": "code",
160 "collapsed": false,
86 "collapsed": false,
161 "input": [
87 "input": [
162 "display(widgets.StringWidget(description=\"a:\"))\n",
88 "display(widgets.StringWidget(description=\"a:\"))\n",
163 "display(widgets.StringWidget(description=\"aa:\"))\n",
89 "display(widgets.StringWidget(description=\"aa:\"))\n",
164 "display(widgets.StringWidget(description=\"aaa:\"))\n",
90 "display(widgets.StringWidget(description=\"aaa:\"))\n",
165 "display(widgets.StringWidget())"
91 "display(widgets.StringWidget())"
166 ],
92 ],
167 "language": "python",
93 "language": "python",
168 "metadata": {},
94 "metadata": {},
169 "outputs": [],
95 "outputs": [],
170 "prompt_number": 4
96 "prompt_number": 4
171 },
97 },
172 {
98 {
173 "cell_type": "heading",
99 "cell_type": "heading",
174 "level": 1,
100 "level": 1,
175 "metadata": {},
101 "metadata": {},
176 "source": [
102 "source": [
177 "Custom Alignment"
103 "Custom Alignment"
178 ]
104 ]
179 },
105 },
180 {
106 {
181 "cell_type": "markdown",
107 "cell_type": "markdown",
182 "metadata": {},
108 "metadata": {},
183 "source": [
109 "source": [
184 "`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`."
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 "cell_type": "code",
114 "cell_type": "code",
189 "collapsed": false,
115 "collapsed": false,
190 "input": [
116 "input": [
191 "child_style = {\n",
117 "child_style = {\n",
192 " 'background': '#77CC77',\n",
118 " 'background': '#77CC77',\n",
193 " 'padding': '25px',\n",
119 " 'padding': '25px',\n",
194 " 'margin': '5px',\n",
120 " 'margin': '5px',\n",
195 " 'font-size': 'xx-large',\n",
121 " 'font-size': 'xx-large',\n",
196 " 'color': 'white',\n",
122 " 'color': 'white',\n",
197 "}\n",
123 "}\n",
198 "\n",
124 "\n",
199 "def make_container(title):\n",
125 "def make_container(title):\n",
200 " display(widgets.StringWidget(default_view_name='LabelView', value='<h2><br>' + title + '</h2>'))\n",
126 " display(widgets.StringWidget(default_view_name='LabelView', value='<h2><br>' + title + '</h2>'))\n",
201 " container = widgets.ContainerWidget()\n",
127 " container = widgets.ContainerWidget()\n",
202 " container.set_css('background', '#999999')\n",
128 " container.set_css('background', '#999999')\n",
203 " display(container)\n",
129 " display(container)\n",
204 " return container\n",
130 " return container\n",
205 "\n",
131 "\n",
206 "def fill_container(container):\n",
132 "def fill_container(container):\n",
207 " components = []\n",
133 " components = []\n",
208 " for i in range(3):\n",
134 " for i in range(3):\n",
209 " components.append(widgets.StringWidget(parent=container, default_view_name='LabelView', value=\"ABC\"[i]))\n",
135 " components.append(widgets.StringWidget(parent=container, default_view_name='LabelView', value=\"ABC\"[i]))\n",
210 " components[i].set_css(child_style)\n",
136 " components[i].set_css(child_style)\n",
211 " display(components[i])\n",
137 " display(components[i])\n",
212 " \n",
138 " \n",
213 "container = make_container('VBox')\n",
139 "container = make_container('VBox')\n",
214 "container.vbox()\n",
140 "container.vbox()\n",
215 "fill_container(container)\n",
141 "fill_container(container)\n",
216 "\n",
142 "\n",
217 "container = make_container('HBox')\n",
143 "container = make_container('HBox')\n",
218 "container.hbox()\n",
144 "container.hbox()\n",
219 "fill_container(container)\n"
145 "fill_container(container)\n"
220 ],
146 ],
221 "language": "python",
147 "language": "python",
222 "metadata": {},
148 "metadata": {},
223 "outputs": [],
149 "outputs": [],
224 "prompt_number": 5
150 "prompt_number": 5
225 },
151 },
226 {
152 {
227 "cell_type": "markdown",
153 "cell_type": "markdown",
228 "metadata": {},
154 "metadata": {},
229 "source": [
155 "source": [
230 "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."
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 "cell_type": "code",
160 "cell_type": "code",
235 "collapsed": false,
161 "collapsed": false,
236 "input": [
162 "input": [
237 "container = make_container('HBox Pack Start')\n",
163 "container = make_container('HBox Pack Start')\n",
238 "container.hbox()\n",
164 "container.hbox()\n",
239 "container.pack_start()\n",
165 "container.pack_start()\n",
240 "fill_container(container)\n",
166 "fill_container(container)\n",
241 " \n",
167 " \n",
242 "container = make_container('HBox Pack Center')\n",
168 "container = make_container('HBox Pack Center')\n",
243 "container.hbox()\n",
169 "container.hbox()\n",
244 "container.pack_center()\n",
170 "container.pack_center()\n",
245 "fill_container(container)\n",
171 "fill_container(container)\n",
246 " \n",
172 " \n",
247 "container = make_container('HBox Pack End')\n",
173 "container = make_container('HBox Pack End')\n",
248 "container.hbox()\n",
174 "container.hbox()\n",
249 "container.pack_end()\n",
175 "container.pack_end()\n",
250 "fill_container(container)"
176 "fill_container(container)"
251 ],
177 ],
252 "language": "python",
178 "language": "python",
253 "metadata": {},
179 "metadata": {},
254 "outputs": [],
180 "outputs": [],
255 "prompt_number": 6
181 "prompt_number": 6
256 },
182 },
257 {
183 {
258 "cell_type": "markdown",
184 "cell_type": "markdown",
259 "metadata": {},
185 "metadata": {},
260 "source": [
186 "source": [
261 "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."
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 "cell_type": "code",
191 "cell_type": "code",
266 "collapsed": false,
192 "collapsed": false,
267 "input": [
193 "input": [
268 "def fill_container(container, flexes):\n",
194 "def fill_container(container, flexes):\n",
269 " components = []\n",
195 " components = []\n",
270 " for i in range(len(flexes)):\n",
196 " for i in range(len(flexes)):\n",
271 " components.append(widgets.ContainerWidget(parent=container))\n",
197 " components.append(widgets.ContainerWidget(parent=container))\n",
272 " components[i].set_css(child_style)\n",
198 " components[i].set_css(child_style)\n",
273 " \n",
199 " \n",
274 " label = widgets.StringWidget(parent=components[i], default_view_name='LabelView', value=str(flexes[i]))\n",
200 " label = widgets.StringWidget(parent=components[i], default_view_name='LabelView', value=str(flexes[i]))\n",
275 " \n",
201 " \n",
276 " if flexes[i] == 0:\n",
202 " if flexes[i] == 0:\n",
277 " components[i].flex0()\n",
203 " components[i].flex0()\n",
278 " elif flexes[i] == 1:\n",
204 " elif flexes[i] == 1:\n",
279 " components[i].flex1()\n",
205 " components[i].flex1()\n",
280 " elif flexes[i] == 2:\n",
206 " elif flexes[i] == 2:\n",
281 " components[i].flex2()\n",
207 " components[i].flex2()\n",
282 " display(components[i])\n",
208 " display(components[i])\n",
283 " \n",
209 " \n",
284 "container = make_container('Different Flex Configurations')\n",
210 "container = make_container('Different Flex Configurations')\n",
285 "container.hbox()\n",
211 "container.hbox()\n",
286 "fill_container(container, [0, 0, 0])\n",
212 "fill_container(container, [0, 0, 0])\n",
287 " \n",
213 " \n",
288 "container = make_container('')\n",
214 "container = make_container('')\n",
289 "container.hbox()\n",
215 "container.hbox()\n",
290 "fill_container(container, [0, 0, 1])\n",
216 "fill_container(container, [0, 0, 1])\n",
291 " \n",
217 " \n",
292 "container = make_container('')\n",
218 "container = make_container('')\n",
293 "container.hbox()\n",
219 "container.hbox()\n",
294 "fill_container(container, [0, 1, 1])\n",
220 "fill_container(container, [0, 1, 1])\n",
295 " \n",
221 " \n",
296 "container = make_container('')\n",
222 "container = make_container('')\n",
297 "container.hbox()\n",
223 "container.hbox()\n",
298 "fill_container(container, [0, 2, 2])\n",
224 "fill_container(container, [0, 2, 2])\n",
299 " \n",
225 " \n",
300 "container = make_container('')\n",
226 "container = make_container('')\n",
301 "container.hbox()\n",
227 "container.hbox()\n",
302 "fill_container(container, [0, 1, 2])\n",
228 "fill_container(container, [0, 1, 2])\n",
303 " \n",
229 " \n",
304 "container = make_container('')\n",
230 "container = make_container('')\n",
305 "container.hbox()\n",
231 "container.hbox()\n",
306 "fill_container(container, [1, 1, 2])"
232 "fill_container(container, [1, 1, 2])"
307 ],
233 ],
308 "language": "python",
234 "language": "python",
309 "metadata": {},
235 "metadata": {},
310 "outputs": [],
236 "outputs": [],
311 "prompt_number": 7
237 "prompt_number": 7
312 },
238 },
313 {
239 {
314 "cell_type": "markdown",
240 "cell_type": "markdown",
315 "metadata": {},
241 "metadata": {},
316 "source": [
242 "source": [
317 "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."
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 "cell_type": "code",
247 "cell_type": "code",
322 "collapsed": false,
248 "collapsed": false,
323 "input": [
249 "input": [
324 "def fill_container(container):\n",
250 "def fill_container(container):\n",
325 " components = []\n",
251 " components = []\n",
326 " for i in range(3):\n",
252 " for i in range(3):\n",
327 " components.append(widgets.StringWidget(parent=container, default_view_name='LabelView', value=\"ABC\"[i]))\n",
253 " components.append(widgets.StringWidget(parent=container, default_view_name='LabelView', value=\"ABC\"[i]))\n",
328 " components[i].set_css(child_style)\n",
254 " components[i].set_css(child_style)\n",
329 " components[i].set_css('height', str((i+1) * 50) + 'px')\n",
255 " components[i].set_css('height', str((i+1) * 50) + 'px')\n",
330 " display(components[i])\n",
256 " display(components[i])\n",
331 "\n",
257 "\n",
332 "container = make_container('HBox Align Start')\n",
258 "container = make_container('HBox Align Start')\n",
333 "container.hbox()\n",
259 "container.hbox()\n",
334 "container.align_start()\n",
260 "container.align_start()\n",
335 "fill_container(container)\n",
261 "fill_container(container)\n",
336 " \n",
262 " \n",
337 "container = make_container('HBox Align Center')\n",
263 "container = make_container('HBox Align Center')\n",
338 "container.hbox()\n",
264 "container.hbox()\n",
339 "container.align_center()\n",
265 "container.align_center()\n",
340 "fill_container(container)\n",
266 "fill_container(container)\n",
341 " \n",
267 " \n",
342 "container = make_container('HBox Align End')\n",
268 "container = make_container('HBox Align End')\n",
343 "container.hbox()\n",
269 "container.hbox()\n",
344 "container.align_end()\n",
270 "container.align_end()\n",
345 "fill_container(container)"
271 "fill_container(container)"
346 ],
272 ],
347 "language": "python",
273 "language": "python",
348 "metadata": {},
274 "metadata": {},
349 "outputs": [],
275 "outputs": [],
350 "prompt_number": 8
276 "prompt_number": 8
351 },
277 },
352 {
278 {
353 "cell_type": "markdown",
279 "cell_type": "markdown",
354 "metadata": {},
280 "metadata": {},
355 "source": [
281 "source": [
356 "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."
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 "cell_type": "code",
286 "cell_type": "code",
361 "collapsed": false,
287 "collapsed": false,
362 "input": [
288 "input": [
363 "container = widgets.ContainerWidget()\n",
289 "container = widgets.ContainerWidget()\n",
364 "container.hbox()\n",
290 "container.hbox()\n",
365 "for i in range(15):\n",
291 "for i in range(15):\n",
366 " widgets.FloatRangeWidget(orientation='vertical', parent=container, description=str(i+1), value=50.0)\n",
292 " widgets.FloatRangeWidget(orientation='vertical', parent=container, description=str(i+1), value=50.0)\n",
367 "display(container)"
293 "display(container)"
368 ],
294 ],
369 "language": "python",
295 "language": "python",
370 "metadata": {},
296 "metadata": {},
371 "outputs": [],
297 "outputs": [],
372 "prompt_number": 9
298 "prompt_number": 9
373 }
299 }
374 ],
300 ],
375 "metadata": {}
301 "metadata": {}
376 }
302 }
377 ]
303 ]
378 } No newline at end of file
304 }
@@ -1,1278 +1,1203 b''
1 {
1 {
2 "metadata": {
2 "metadata": {
3 "cell_tags": [
3 "cell_tags": [
4 [
4 [
5 "<None>",
5 "<None>",
6 null
6 null
7 ]
7 ]
8 ],
8 ],
9 "name": ""
9 "name": ""
10 },
10 },
11 "nbformat": 3,
11 "nbformat": 3,
12 "nbformat_minor": 0,
12 "nbformat_minor": 0,
13 "worksheets": [
13 "worksheets": [
14 {
14 {
15 "cells": [
15 "cells": [
16 {
16 {
17 "cell_type": "markdown",
17 "cell_type": "markdown",
18 "metadata": {},
18 "metadata": {},
19 "source": [
19 "source": [
20 "Before reading, the author recommends the reader to review\n",
20 "Before reading, the author recommends the reader to review\n",
21 "\n",
21 "\n",
22 "- [MVC prgramming](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller)\n",
22 "- [MVC prgramming](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller)\n",
23 "- [Backbone.js](https://www.codeschool.com/courses/anatomy-of-backbonejs)\n",
23 "- [Backbone.js](https://www.codeschool.com/courses/anatomy-of-backbonejs)\n",
24 "- [The widget IPEP](https://github.com/ipython/ipython/wiki/IPEP-23%3A-Backbone.js-Widgets)\n",
24 "- [The widget IPEP](https://github.com/ipython/ipython/wiki/IPEP-23%3A-Backbone.js-Widgets)\n",
25 "- [The original widget PR discussion](https://github.com/ipython/ipython/pull/4374)"
25 "- [The original widget PR discussion](https://github.com/ipython/ipython/pull/4374)"
26 ]
26 ]
27 },
27 },
28 {
28 {
29 "cell_type": "code",
29 "cell_type": "code",
30 "collapsed": false,
30 "collapsed": false,
31 "input": [
31 "input": [
32 "from IPython.html import widgets # Widget definitions\n",
32 "from IPython.html import widgets # Widget definitions\n",
33 "from IPython.display import display # Used to display widgets in the notebook\n",
33 "from IPython.display import display # Used to display widgets in the notebook"
34 "\n",
35 "# Enable widgets in this notebook\n",
36 "widgets.init_widget_js()"
37 ],
34 ],
38 "language": "python",
35 "language": "python",
39 "metadata": {},
36 "metadata": {},
40 "outputs": [
37 "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 ],
112 "prompt_number": 1
38 "prompt_number": 1
113 },
39 },
114 {
40 {
115 "cell_type": "heading",
41 "cell_type": "heading",
116 "level": 1,
42 "level": 1,
117 "metadata": {},
43 "metadata": {},
118 "source": [
44 "source": [
119 "Abstract"
45 "Abstract"
120 ]
46 ]
121 },
47 },
122 {
48 {
123 "cell_type": "markdown",
49 "cell_type": "markdown",
124 "metadata": {},
50 "metadata": {},
125 "source": [
51 "source": [
126 "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."
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 "cell_type": "heading",
56 "cell_type": "heading",
131 "level": 1,
57 "level": 1,
132 "metadata": {},
58 "metadata": {},
133 "source": [
59 "source": [
134 "Section 1 - Basics"
60 "Section 1 - Basics"
135 ]
61 ]
136 },
62 },
137 {
63 {
138 "cell_type": "heading",
64 "cell_type": "heading",
139 "level": 2,
65 "level": 2,
140 "metadata": {},
66 "metadata": {},
141 "source": [
67 "source": [
142 "Python"
68 "Python"
143 ]
69 ]
144 },
70 },
145 {
71 {
146 "cell_type": "markdown",
72 "cell_type": "markdown",
147 "metadata": {},
73 "metadata": {},
148 "source": [
74 "source": [
149 "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."
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 "cell_type": "code",
79 "cell_type": "code",
154 "collapsed": false,
80 "collapsed": false,
155 "input": [
81 "input": [
156 "# Import the base Widget class and the traitlets Unicode class.\n",
82 "# Import the base Widget class and the traitlets Unicode class.\n",
157 "from IPython.html.widgets import Widget\n",
83 "from IPython.html.widgets import Widget\n",
158 "from IPython.utils.traitlets import Unicode\n",
84 "from IPython.utils.traitlets import Unicode\n",
159 "\n",
85 "\n",
160 "# Define our DateWidget and its target model and default view.\n",
86 "# Define our DateWidget and its target model and default view.\n",
161 "class DateWidget(Widget):\n",
87 "class DateWidget(Widget):\n",
162 " target_name = Unicode('DateWidgetModel')\n",
88 " target_name = Unicode('DateWidgetModel')\n",
163 " default_view_name = Unicode('DatePickerView')"
89 " default_view_name = Unicode('DatePickerView')"
164 ],
90 ],
165 "language": "python",
91 "language": "python",
166 "metadata": {},
92 "metadata": {},
167 "outputs": [],
93 "outputs": [],
168 "prompt_number": 2
94 "prompt_number": 2
169 },
95 },
170 {
96 {
171 "cell_type": "markdown",
97 "cell_type": "markdown",
172 "metadata": {},
98 "metadata": {},
173 "source": [
99 "source": [
174 "- **target_name** is a special `Widget` property that tells the widget framework which Backbone model in the front-end corresponds to this widget.\n",
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 "- **default_view_name** is the default Backbone view to display when the user calls `display` to display an instance of this widget.\n"
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 "cell_type": "heading",
105 "cell_type": "heading",
180 "level": 2,
106 "level": 2,
181 "metadata": {},
107 "metadata": {},
182 "source": [
108 "source": [
183 "JavaScript"
109 "JavaScript"
184 ]
110 ]
185 },
111 },
186 {
112 {
187 "cell_type": "markdown",
113 "cell_type": "markdown",
188 "metadata": {},
114 "metadata": {},
189 "source": [
115 "source": [
190 "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:"
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 "cell_type": "code",
120 "cell_type": "code",
195 "collapsed": false,
121 "collapsed": false,
196 "input": [
122 "input": [
197 "%%javascript\n",
123 "%%javascript\n",
198 "\n",
124 "\n",
199 "require([\"notebook/js/widget\"], function(){\n",
125 "require([\"notebook/js/widget\"], function(){\n",
200 "\n",
126 "\n",
201 "});"
127 "});"
202 ],
128 ],
203 "language": "python",
129 "language": "python",
204 "metadata": {},
130 "metadata": {},
205 "outputs": [
131 "outputs": [
206 {
132 {
207 "javascript": [
133 "javascript": [
208 "\n",
134 "\n",
209 "require([\"notebook/js/widget\"], function(){\n",
135 "require([\"notebook/js/widget\"], function(){\n",
210 "\n",
136 "\n",
211 "});"
137 "});"
212 ],
138 ],
213 "metadata": {},
139 "metadata": {},
214 "output_type": "display_data",
140 "output_type": "display_data",
215 "text": [
141 "text": [
216 "<IPython.core.display.Javascript at 0x168b0d0>"
142 "<IPython.core.display.Javascript at 0x21f8f10>"
217 ]
143 ]
218 }
144 }
219 ],
145 ],
220 "prompt_number": 3
146 "prompt_number": 3
221 },
147 },
222 {
148 {
223 "cell_type": "markdown",
149 "cell_type": "markdown",
224 "metadata": {},
150 "metadata": {},
225 "source": [
151 "source": [
226 "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."
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 "cell_type": "code",
156 "cell_type": "code",
231 "collapsed": false,
157 "collapsed": false,
232 "input": [
158 "input": [
233 "%%javascript\n",
159 "%%javascript\n",
234 "\n",
160 "\n",
235 "require([\"notebook/js/widget\"], function(){\n",
161 "require([\"notebook/js/widget\"], function(){\n",
236 " \n",
162 " \n",
237 " // Define the DateModel and register it with the widget manager.\n",
163 " // Define the DateModel and register it with the widget manager.\n",
238 " var DateModel = IPython.WidgetModel.extend({});\n",
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 "language": "python",
168 "language": "python",
243 "metadata": {},
169 "metadata": {},
244 "outputs": [
170 "outputs": [
245 {
171 {
246 "javascript": [
172 "javascript": [
247 "\n",
173 "\n",
248 "require([\"notebook/js/widget\"], function(){\n",
174 "require([\"notebook/js/widget\"], function(){\n",
249 " \n",
175 " \n",
250 " // Define the DateModel and register it with the widget manager.\n",
176 " // Define the DateModel and register it with the widget manager.\n",
251 " var DateModel = IPython.WidgetModel.extend({});\n",
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 "metadata": {},
181 "metadata": {},
256 "output_type": "display_data",
182 "output_type": "display_data",
257 "text": [
183 "text": [
258 "<IPython.core.display.Javascript at 0x168df50>"
184 "<IPython.core.display.Javascript at 0x21f8ed0>"
259 ]
185 ]
260 }
186 }
261 ],
187 ],
262 "prompt_number": 4
188 "prompt_number": 4
263 },
189 },
264 {
190 {
265 "cell_type": "markdown",
191 "cell_type": "markdown",
266 "metadata": {},
192 "metadata": {},
267 "source": [
193 "source": [
268 "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",
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 "\n",
195 "\n",
270 "**Final JavaScript code below:**"
196 "**Final JavaScript code below:**"
271 ]
197 ]
272 },
198 },
273 {
199 {
274 "cell_type": "code",
200 "cell_type": "code",
275 "collapsed": false,
201 "collapsed": false,
276 "input": [
202 "input": [
277 "%%javascript\n",
203 "%%javascript\n",
278 "\n",
204 "\n",
279 "require([\"notebook/js/widget\"], function(){\n",
205 "require([\"notebook/js/widget\"], function(){\n",
280 " \n",
206 " \n",
281 " // Define the DateModel and register it with the widget manager.\n",
207 " // Define the DateModel and register it with the widget manager.\n",
282 " var DateModel = IPython.WidgetModel.extend({});\n",
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 " \n",
210 " \n",
285 " // Define the DatePickerView\n",
211 " // Define the DatePickerView\n",
286 " var DatePickerView = IPython.WidgetView.extend({\n",
212 " var DatePickerView = IPython.WidgetView.extend({\n",
287 " \n",
213 " \n",
288 " render: function(){\n",
214 " render: function(){\n",
289 " this.$el = $('<div />')\n",
215 " this.$el = $('<div />')\n",
290 " .html('Hello World!');\n",
216 " .html('Hello World!');\n",
291 " },\n",
217 " },\n",
292 " });\n",
218 " });\n",
293 " \n",
219 " \n",
294 " // Register the DatePickerView with the widget manager.\n",
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 "language": "python",
224 "language": "python",
299 "metadata": {},
225 "metadata": {},
300 "outputs": [
226 "outputs": [
301 {
227 {
302 "javascript": [
228 "javascript": [
303 "\n",
229 "\n",
304 "require([\"notebook/js/widget\"], function(){\n",
230 "require([\"notebook/js/widget\"], function(){\n",
305 " \n",
231 " \n",
306 " // Define the DateModel and register it with the widget manager.\n",
232 " // Define the DateModel and register it with the widget manager.\n",
307 " var DateModel = IPython.WidgetModel.extend({});\n",
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 " \n",
235 " \n",
310 " // Define the DatePickerView\n",
236 " // Define the DatePickerView\n",
311 " var DatePickerView = IPython.WidgetView.extend({\n",
237 " var DatePickerView = IPython.WidgetView.extend({\n",
312 " \n",
238 " \n",
313 " render: function(){\n",
239 " render: function(){\n",
314 " this.$el = $('<div />')\n",
240 " this.$el = $('<div />')\n",
315 " .html('Hello World!');\n",
241 " .html('Hello World!');\n",
316 " },\n",
242 " },\n",
317 " });\n",
243 " });\n",
318 " \n",
244 " \n",
319 " // Register the DatePickerView with the widget manager.\n",
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 "metadata": {},
249 "metadata": {},
324 "output_type": "display_data",
250 "output_type": "display_data",
325 "text": [
251 "text": [
326 "<IPython.core.display.Javascript at 0x168f050>"
252 "<IPython.core.display.Javascript at 0x21f8cd0>"
327 ]
253 ]
328 }
254 }
329 ],
255 ],
330 "prompt_number": 5
256 "prompt_number": 5
331 },
257 },
332 {
258 {
333 "cell_type": "heading",
259 "cell_type": "heading",
334 "level": 2,
260 "level": 2,
335 "metadata": {},
261 "metadata": {},
336 "source": [
262 "source": [
337 "Test"
263 "Test"
338 ]
264 ]
339 },
265 },
340 {
266 {
341 "cell_type": "markdown",
267 "cell_type": "markdown",
342 "metadata": {},
268 "metadata": {},
343 "source": [
269 "source": [
344 "To test, create the widget the same way that the other widgets are created."
270 "To test, create the widget the same way that the other widgets are created."
345 ]
271 ]
346 },
272 },
347 {
273 {
348 "cell_type": "code",
274 "cell_type": "code",
349 "collapsed": false,
275 "collapsed": false,
350 "input": [
276 "input": [
351 "my_widget = DateWidget()\n",
277 "my_widget = DateWidget()\n",
352 "display(my_widget)"
278 "display(my_widget)"
353 ],
279 ],
354 "language": "python",
280 "language": "python",
355 "metadata": {},
281 "metadata": {},
356 "outputs": [],
282 "outputs": [],
357 "prompt_number": 6
283 "prompt_number": 6
358 },
284 },
359 {
285 {
360 "cell_type": "heading",
286 "cell_type": "heading",
361 "level": 1,
287 "level": 1,
362 "metadata": {},
288 "metadata": {},
363 "source": [
289 "source": [
364 "Section 2 - Something useful"
290 "Section 2 - Something useful"
365 ]
291 ]
366 },
292 },
367 {
293 {
368 "cell_type": "heading",
294 "cell_type": "heading",
369 "level": 2,
295 "level": 2,
370 "metadata": {},
296 "metadata": {},
371 "source": [
297 "source": [
372 "Python"
298 "Python"
373 ]
299 ]
374 },
300 },
375 {
301 {
376 "cell_type": "markdown",
302 "cell_type": "markdown",
377 "metadata": {},
303 "metadata": {},
378 "source": [
304 "source": [
379 "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:"
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 "cell_type": "code",
309 "cell_type": "code",
384 "collapsed": false,
310 "collapsed": false,
385 "input": [
311 "input": [
386 "# Import the base Widget class and the traitlets Unicode class.\n",
312 "# Import the base Widget class and the traitlets Unicode class.\n",
387 "from IPython.html.widgets import Widget\n",
313 "from IPython.html.widgets import Widget\n",
388 "from IPython.utils.traitlets import Unicode\n",
314 "from IPython.utils.traitlets import Unicode\n",
389 "\n",
315 "\n",
390 "# Define our DateWidget and its target model and default view.\n",
316 "# Define our DateWidget and its target model and default view.\n",
391 "class DateWidget(Widget):\n",
317 "class DateWidget(Widget):\n",
392 " target_name = Unicode('DateWidgetModel')\n",
318 " target_name = Unicode('DateWidgetModel')\n",
393 " default_view_name = Unicode('DatePickerView')\n",
319 " default_view_name = Unicode('DatePickerView')\n",
394 " \n",
320 " \n",
395 " # Define the custom state properties to sync with the front-end\n",
321 " # Define the custom state properties to sync with the front-end\n",
396 " _keys = ['value']\n",
322 " _keys = ['value']\n",
397 " value = Unicode()"
323 " value = Unicode()"
398 ],
324 ],
399 "language": "python",
325 "language": "python",
400 "metadata": {},
326 "metadata": {},
401 "outputs": [],
327 "outputs": [],
402 "prompt_number": 7
328 "prompt_number": 7
403 },
329 },
404 {
330 {
405 "cell_type": "heading",
331 "cell_type": "heading",
406 "level": 2,
332 "level": 2,
407 "metadata": {},
333 "metadata": {},
408 "source": [
334 "source": [
409 "JavaScript"
335 "JavaScript"
410 ]
336 ]
411 },
337 },
412 {
338 {
413 "cell_type": "markdown",
339 "cell_type": "markdown",
414 "metadata": {},
340 "metadata": {},
415 "source": [
341 "source": [
416 "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."
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 "cell_type": "code",
346 "cell_type": "code",
421 "collapsed": false,
347 "collapsed": false,
422 "input": [
348 "input": [
423 "%%javascript\n",
349 "%%javascript\n",
424 "\n",
350 "\n",
425 "require([\"notebook/js/widget\"], function(){\n",
351 "require([\"notebook/js/widget\"], function(){\n",
426 " \n",
352 " \n",
427 " // Define the DateModel and register it with the widget manager.\n",
353 " // Define the DateModel and register it with the widget manager.\n",
428 " var DateModel = IPython.WidgetModel.extend({});\n",
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 " \n",
356 " \n",
431 " // Define the DatePickerView\n",
357 " // Define the DatePickerView\n",
432 " var DatePickerView = IPython.WidgetView.extend({\n",
358 " var DatePickerView = IPython.WidgetView.extend({\n",
433 " \n",
359 " \n",
434 " render: function(){\n",
360 " render: function(){\n",
435 " \n",
361 " \n",
436 " // Create a div to hold our widget.\n",
362 " // Create a div to hold our widget.\n",
437 " this.$el = $('<div />');\n",
363 " this.$el = $('<div />');\n",
438 " \n",
364 " \n",
439 " // Create the date picker control.\n",
365 " // Create the date picker control.\n",
440 " this.$date = $('<input />')\n",
366 " this.$date = $('<input />')\n",
441 " .attr('type', 'date');\n",
367 " .attr('type', 'date');\n",
442 " },\n",
368 " },\n",
443 " });\n",
369 " });\n",
444 " \n",
370 " \n",
445 " // Register the DatePickerView with the widget manager.\n",
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 "language": "python",
375 "language": "python",
450 "metadata": {},
376 "metadata": {},
451 "outputs": [
377 "outputs": [
452 {
378 {
453 "javascript": [
379 "javascript": [
454 "\n",
380 "\n",
455 "require([\"notebook/js/widget\"], function(){\n",
381 "require([\"notebook/js/widget\"], function(){\n",
456 " \n",
382 " \n",
457 " // Define the DateModel and register it with the widget manager.\n",
383 " // Define the DateModel and register it with the widget manager.\n",
458 " var DateModel = IPython.WidgetModel.extend({});\n",
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 " \n",
386 " \n",
461 " // Define the DatePickerView\n",
387 " // Define the DatePickerView\n",
462 " var DatePickerView = IPython.WidgetView.extend({\n",
388 " var DatePickerView = IPython.WidgetView.extend({\n",
463 " \n",
389 " \n",
464 " render: function(){\n",
390 " render: function(){\n",
465 " \n",
391 " \n",
466 " // Create a div to hold our widget.\n",
392 " // Create a div to hold our widget.\n",
467 " this.$el = $('<div />');\n",
393 " this.$el = $('<div />');\n",
468 " \n",
394 " \n",
469 " // Create the date picker control.\n",
395 " // Create the date picker control.\n",
470 " this.$date = $('<input />')\n",
396 " this.$date = $('<input />')\n",
471 " .attr('type', 'date');\n",
397 " .attr('type', 'date');\n",
472 " },\n",
398 " },\n",
473 " });\n",
399 " });\n",
474 " \n",
400 " \n",
475 " // Register the DatePickerView with the widget manager.\n",
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 "metadata": {},
405 "metadata": {},
480 "output_type": "display_data",
406 "output_type": "display_data",
481 "text": [
407 "text": [
482 "<IPython.core.display.Javascript at 0x17822d0>"
408 "<IPython.core.display.Javascript at 0x21fc310>"
483 ]
409 ]
484 }
410 }
485 ],
411 ],
486 "prompt_number": 8
412 "prompt_number": 8
487 },
413 },
488 {
414 {
489 "cell_type": "markdown",
415 "cell_type": "markdown",
490 "metadata": {},
416 "metadata": {},
491 "source": [
417 "source": [
492 "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."
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 "cell_type": "code",
422 "cell_type": "code",
497 "collapsed": false,
423 "collapsed": false,
498 "input": [
424 "input": [
499 "%%javascript\n",
425 "%%javascript\n",
500 "\n",
426 "\n",
501 "require([\"notebook/js/widget\"], function(){\n",
427 "require([\"notebook/js/widget\"], function(){\n",
502 " \n",
428 " \n",
503 " // Define the DateModel and register it with the widget manager.\n",
429 " // Define the DateModel and register it with the widget manager.\n",
504 " var DateModel = IPython.WidgetModel.extend({});\n",
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 " \n",
432 " \n",
507 " // Define the DatePickerView\n",
433 " // Define the DatePickerView\n",
508 " var DatePickerView = IPython.WidgetView.extend({\n",
434 " var DatePickerView = IPython.WidgetView.extend({\n",
509 " \n",
435 " \n",
510 " render: function(){\n",
436 " render: function(){\n",
511 " \n",
437 " \n",
512 " // Create a div to hold our widget.\n",
438 " // Create a div to hold our widget.\n",
513 " this.$el = $('<div />');\n",
439 " this.$el = $('<div />');\n",
514 " \n",
440 " \n",
515 " // Create the date picker control.\n",
441 " // Create the date picker control.\n",
516 " this.$date = $('<input />')\n",
442 " this.$date = $('<input />')\n",
517 " .attr('type', 'date');\n",
443 " .attr('type', 'date');\n",
518 " },\n",
444 " },\n",
519 " \n",
445 " \n",
520 " update: function() {\n",
446 " update: function() {\n",
521 " \n",
447 " \n",
522 " // Set the value of the date control and then call base.\n",
448 " // Set the value of the date control and then call base.\n",
523 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
449 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
524 " return IPython.WidgetView.prototype.update.call(this);\n",
450 " return IPython.WidgetView.prototype.update.call(this);\n",
525 " },\n",
451 " },\n",
526 " });\n",
452 " });\n",
527 " \n",
453 " \n",
528 " // Register the DatePickerView with the widget manager.\n",
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 "language": "python",
458 "language": "python",
533 "metadata": {},
459 "metadata": {},
534 "outputs": [
460 "outputs": [
535 {
461 {
536 "javascript": [
462 "javascript": [
537 "\n",
463 "\n",
538 "require([\"notebook/js/widget\"], function(){\n",
464 "require([\"notebook/js/widget\"], function(){\n",
539 " \n",
465 " \n",
540 " // Define the DateModel and register it with the widget manager.\n",
466 " // Define the DateModel and register it with the widget manager.\n",
541 " var DateModel = IPython.WidgetModel.extend({});\n",
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 " \n",
469 " \n",
544 " // Define the DatePickerView\n",
470 " // Define the DatePickerView\n",
545 " var DatePickerView = IPython.WidgetView.extend({\n",
471 " var DatePickerView = IPython.WidgetView.extend({\n",
546 " \n",
472 " \n",
547 " render: function(){\n",
473 " render: function(){\n",
548 " \n",
474 " \n",
549 " // Create a div to hold our widget.\n",
475 " // Create a div to hold our widget.\n",
550 " this.$el = $('<div />');\n",
476 " this.$el = $('<div />');\n",
551 " \n",
477 " \n",
552 " // Create the date picker control.\n",
478 " // Create the date picker control.\n",
553 " this.$date = $('<input />')\n",
479 " this.$date = $('<input />')\n",
554 " .attr('type', 'date');\n",
480 " .attr('type', 'date');\n",
555 " },\n",
481 " },\n",
556 " \n",
482 " \n",
557 " update: function() {\n",
483 " update: function() {\n",
558 " \n",
484 " \n",
559 " // Set the value of the date control and then call base.\n",
485 " // Set the value of the date control and then call base.\n",
560 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
486 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
561 " return IPython.WidgetView.prototype.update.call(this);\n",
487 " return IPython.WidgetView.prototype.update.call(this);\n",
562 " },\n",
488 " },\n",
563 " });\n",
489 " });\n",
564 " \n",
490 " \n",
565 " // Register the DatePickerView with the widget manager.\n",
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 "metadata": {},
495 "metadata": {},
570 "output_type": "display_data",
496 "output_type": "display_data",
571 "text": [
497 "text": [
572 "<IPython.core.display.Javascript at 0x1782390>"
498 "<IPython.core.display.Javascript at 0x21fc290>"
573 ]
499 ]
574 }
500 }
575 ],
501 ],
576 "prompt_number": 9
502 "prompt_number": 9
577 },
503 },
578 {
504 {
579 "cell_type": "markdown",
505 "cell_type": "markdown",
580 "metadata": {},
506 "metadata": {},
581 "source": [
507 "source": [
582 "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",
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 "\n",
509 "\n",
584 "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",
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 "\n",
511 "\n",
586 "**Final JavaScript code below:**"
512 "**Final JavaScript code below:**"
587 ]
513 ]
588 },
514 },
589 {
515 {
590 "cell_type": "code",
516 "cell_type": "code",
591 "collapsed": false,
517 "collapsed": false,
592 "input": [
518 "input": [
593 "%%javascript\n",
519 "%%javascript\n",
594 "\n",
520 "\n",
595 "require([\"notebook/js/widget\"], function(){\n",
521 "require([\"notebook/js/widget\"], function(){\n",
596 " \n",
522 " \n",
597 " // Define the DateModel and register it with the widget manager.\n",
523 " // Define the DateModel and register it with the widget manager.\n",
598 " var DateModel = IPython.WidgetModel.extend({});\n",
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 " \n",
526 " \n",
601 " // Define the DatePickerView\n",
527 " // Define the DatePickerView\n",
602 " var DatePickerView = IPython.WidgetView.extend({\n",
528 " var DatePickerView = IPython.WidgetView.extend({\n",
603 " \n",
529 " \n",
604 " render: function(){\n",
530 " render: function(){\n",
605 " \n",
531 " \n",
606 " // Create a div to hold our widget.\n",
532 " // Create a div to hold our widget.\n",
607 " this.$el = $('<div />');\n",
533 " this.$el = $('<div />');\n",
608 " this.delegateEvents();\n",
534 " this.delegateEvents();\n",
609 " \n",
535 " \n",
610 " // Create the date picker control.\n",
536 " // Create the date picker control.\n",
611 " this.$date = $('<input />')\n",
537 " this.$date = $('<input />')\n",
612 " .attr('type', 'date')\n",
538 " .attr('type', 'date')\n",
613 " .appendTo(this.$el);\n",
539 " .appendTo(this.$el);\n",
614 " },\n",
540 " },\n",
615 " \n",
541 " \n",
616 " update: function() {\n",
542 " update: function() {\n",
617 " \n",
543 " \n",
618 " // Set the value of the date control and then call base.\n",
544 " // Set the value of the date control and then call base.\n",
619 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
545 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
620 " return IPython.WidgetView.prototype.update.call(this);\n",
546 " return IPython.WidgetView.prototype.update.call(this);\n",
621 " },\n",
547 " },\n",
622 " \n",
548 " \n",
623 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
549 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
624 " events: {\"change\": \"handle_date_change\"},\n",
550 " events: {\"change\": \"handle_date_change\"},\n",
625 " \n",
551 " \n",
626 " // Callback for when the date is changed.\n",
552 " // Callback for when the date is changed.\n",
627 " handle_date_change: function(event) {\n",
553 " handle_date_change: function(event) {\n",
628 " this.model.set('value', this.$date.val());\n",
554 " this.model.set('value', this.$date.val());\n",
629 " this.model.update_other_views(this);\n",
555 " this.model.update_other_views(this);\n",
630 " },\n",
556 " },\n",
631 " \n",
557 " \n",
632 " });\n",
558 " });\n",
633 " \n",
559 " \n",
634 " // Register the DatePickerView with the widget manager.\n",
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 "language": "python",
564 "language": "python",
639 "metadata": {},
565 "metadata": {},
640 "outputs": [
566 "outputs": [
641 {
567 {
642 "javascript": [
568 "javascript": [
643 "\n",
569 "\n",
644 "require([\"notebook/js/widget\"], function(){\n",
570 "require([\"notebook/js/widget\"], function(){\n",
645 " \n",
571 " \n",
646 " // Define the DateModel and register it with the widget manager.\n",
572 " // Define the DateModel and register it with the widget manager.\n",
647 " var DateModel = IPython.WidgetModel.extend({});\n",
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 " \n",
575 " \n",
650 " // Define the DatePickerView\n",
576 " // Define the DatePickerView\n",
651 " var DatePickerView = IPython.WidgetView.extend({\n",
577 " var DatePickerView = IPython.WidgetView.extend({\n",
652 " \n",
578 " \n",
653 " render: function(){\n",
579 " render: function(){\n",
654 " \n",
580 " \n",
655 " // Create a div to hold our widget.\n",
581 " // Create a div to hold our widget.\n",
656 " this.$el = $('<div />');\n",
582 " this.$el = $('<div />');\n",
657 " this.delegateEvents();\n",
583 " this.delegateEvents();\n",
658 " \n",
584 " \n",
659 " // Create the date picker control.\n",
585 " // Create the date picker control.\n",
660 " this.$date = $('<input />')\n",
586 " this.$date = $('<input />')\n",
661 " .attr('type', 'date')\n",
587 " .attr('type', 'date')\n",
662 " .appendTo(this.$el);\n",
588 " .appendTo(this.$el);\n",
663 " },\n",
589 " },\n",
664 " \n",
590 " \n",
665 " update: function() {\n",
591 " update: function() {\n",
666 " \n",
592 " \n",
667 " // Set the value of the date control and then call base.\n",
593 " // Set the value of the date control and then call base.\n",
668 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
594 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
669 " return IPython.WidgetView.prototype.update.call(this);\n",
595 " return IPython.WidgetView.prototype.update.call(this);\n",
670 " },\n",
596 " },\n",
671 " \n",
597 " \n",
672 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
598 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
673 " events: {\"change\": \"handle_date_change\"},\n",
599 " events: {\"change\": \"handle_date_change\"},\n",
674 " \n",
600 " \n",
675 " // Callback for when the date is changed.\n",
601 " // Callback for when the date is changed.\n",
676 " handle_date_change: function(event) {\n",
602 " handle_date_change: function(event) {\n",
677 " this.model.set('value', this.$date.val());\n",
603 " this.model.set('value', this.$date.val());\n",
678 " this.model.update_other_views(this);\n",
604 " this.model.update_other_views(this);\n",
679 " },\n",
605 " },\n",
680 " \n",
606 " \n",
681 " });\n",
607 " });\n",
682 " \n",
608 " \n",
683 " // Register the DatePickerView with the widget manager.\n",
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 "metadata": {},
613 "metadata": {},
688 "output_type": "display_data",
614 "output_type": "display_data",
689 "text": [
615 "text": [
690 "<IPython.core.display.Javascript at 0x17821d0>"
616 "<IPython.core.display.Javascript at 0x21fc3d0>"
691 ]
617 ]
692 }
618 }
693 ],
619 ],
694 "prompt_number": 10
620 "prompt_number": 10
695 },
621 },
696 {
622 {
697 "cell_type": "heading",
623 "cell_type": "heading",
698 "level": 2,
624 "level": 2,
699 "metadata": {},
625 "metadata": {},
700 "source": [
626 "source": [
701 "Test"
627 "Test"
702 ]
628 ]
703 },
629 },
704 {
630 {
705 "cell_type": "markdown",
631 "cell_type": "markdown",
706 "metadata": {},
632 "metadata": {},
707 "source": [
633 "source": [
708 "To test, create the widget the same way that the other widgets are created."
634 "To test, create the widget the same way that the other widgets are created."
709 ]
635 ]
710 },
636 },
711 {
637 {
712 "cell_type": "code",
638 "cell_type": "code",
713 "collapsed": false,
639 "collapsed": false,
714 "input": [
640 "input": [
715 "my_widget = DateWidget()\n",
641 "my_widget = DateWidget()\n",
716 "display(my_widget)"
642 "display(my_widget)"
717 ],
643 ],
718 "language": "python",
644 "language": "python",
719 "metadata": {},
645 "metadata": {},
720 "outputs": [],
646 "outputs": [],
721 "prompt_number": 11
647 "prompt_number": 11
722 },
648 },
723 {
649 {
724 "cell_type": "markdown",
650 "cell_type": "markdown",
725 "metadata": {},
651 "metadata": {},
726 "source": [
652 "source": [
727 "Display the widget again to make sure that both views remain in sync."
653 "Display the widget again to make sure that both views remain in sync."
728 ]
654 ]
729 },
655 },
730 {
656 {
731 "cell_type": "code",
657 "cell_type": "code",
732 "collapsed": false,
658 "collapsed": false,
733 "input": [
659 "input": [
734 "display(my_widget)"
660 "display(my_widget)"
735 ],
661 ],
736 "language": "python",
662 "language": "python",
737 "metadata": {},
663 "metadata": {},
738 "outputs": [],
664 "outputs": [],
739 "prompt_number": 12
665 "prompt_number": 12
740 },
666 },
741 {
667 {
742 "cell_type": "markdown",
668 "cell_type": "markdown",
743 "metadata": {},
669 "metadata": {},
744 "source": [
670 "source": [
745 "Read the date from Python"
671 "Read the date from Python"
746 ]
672 ]
747 },
673 },
748 {
674 {
749 "cell_type": "code",
675 "cell_type": "code",
750 "collapsed": false,
676 "collapsed": false,
751 "input": [
677 "input": [
752 "my_widget.value"
678 "my_widget.value"
753 ],
679 ],
754 "language": "python",
680 "language": "python",
755 "metadata": {},
681 "metadata": {},
756 "outputs": [
682 "outputs": [
757 {
683 {
758 "metadata": {},
684 "metadata": {},
759 "output_type": "pyout",
685 "output_type": "pyout",
760 "prompt_number": 13,
686 "prompt_number": 13,
761 "text": [
687 "text": [
762 "u''"
688 "u'2013-11-14'"
763 ]
689 ]
764 }
690 }
765 ],
691 ],
766 "prompt_number": 13
692 "prompt_number": 13
767 },
693 },
768 {
694 {
769 "cell_type": "markdown",
695 "cell_type": "markdown",
770 "metadata": {},
696 "metadata": {},
771 "source": [
697 "source": [
772 "Set the date from Python"
698 "Set the date from Python"
773 ]
699 ]
774 },
700 },
775 {
701 {
776 "cell_type": "code",
702 "cell_type": "code",
777 "collapsed": false,
703 "collapsed": false,
778 "input": [
704 "input": [
779 "my_widget.value = \"1999-12-01\" # December 1st, 1999"
705 "my_widget.value = \"1999-12-01\" # December 1st, 1999"
780 ],
706 ],
781 "language": "python",
707 "language": "python",
782 "metadata": {},
708 "metadata": {},
783 "outputs": [],
709 "outputs": [],
784 "prompt_number": 14
710 "prompt_number": 14
785 },
711 },
786 {
712 {
787 "cell_type": "heading",
713 "cell_type": "heading",
788 "level": 1,
714 "level": 1,
789 "metadata": {},
715 "metadata": {},
790 "source": [
716 "source": [
791 "Section 3 - Extra credit"
717 "Section 3 - Extra credit"
792 ]
718 ]
793 },
719 },
794 {
720 {
795 "cell_type": "markdown",
721 "cell_type": "markdown",
796 "metadata": {},
722 "metadata": {},
797 "source": [
723 "source": [
798 "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."
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 "cell_type": "heading",
728 "cell_type": "heading",
803 "level": 2,
729 "level": 2,
804 "metadata": {},
730 "metadata": {},
805 "source": [
731 "source": [
806 "Python"
732 "Python"
807 ]
733 ]
808 },
734 },
809 {
735 {
810 "cell_type": "markdown",
736 "cell_type": "markdown",
811 "metadata": {},
737 "metadata": {},
812 "source": [
738 "source": [
813 "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."
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 "cell_type": "code",
743 "cell_type": "code",
818 "collapsed": false,
744 "collapsed": false,
819 "input": [
745 "input": [
820 "# Import the base Widget class and the traitlets Unicode class.\n",
746 "# Import the base Widget class and the traitlets Unicode class.\n",
821 "from IPython.html.widgets import Widget\n",
747 "from IPython.html.widgets import Widget\n",
822 "from IPython.utils.traitlets import Unicode\n",
748 "from IPython.utils.traitlets import Unicode\n",
823 "\n",
749 "\n",
824 "# Define our DateWidget and its target model and default view.\n",
750 "# Define our DateWidget and its target model and default view.\n",
825 "class DateWidget(Widget):\n",
751 "class DateWidget(Widget):\n",
826 " target_name = Unicode('DateWidgetModel')\n",
752 " target_name = Unicode('DateWidgetModel')\n",
827 " default_view_name = Unicode('DatePickerView')\n",
753 " default_view_name = Unicode('DatePickerView')\n",
828 " \n",
754 " \n",
829 " # Define the custom state properties to sync with the front-end\n",
755 " # Define the custom state properties to sync with the front-end\n",
830 " _keys = ['value']\n",
756 " _keys = ['value']\n",
831 " value = Unicode()\n",
757 " value = Unicode()\n",
832 " \n",
758 " \n",
833 " # This function automatically gets called by the traitlet machinery when\n",
759 " # This function automatically gets called by the traitlet machinery when\n",
834 " # value is modified because of this function's name.\n",
760 " # value is modified because of this function's name.\n",
835 " def _value_changed(self, name, old_value, new_value):\n",
761 " def _value_changed(self, name, old_value, new_value):\n",
836 " pass\n",
762 " pass\n",
837 " "
763 " "
838 ],
764 ],
839 "language": "python",
765 "language": "python",
840 "metadata": {},
766 "metadata": {},
841 "outputs": [],
767 "outputs": [],
842 "prompt_number": 15
768 "prompt_number": 15
843 },
769 },
844 {
770 {
845 "cell_type": "markdown",
771 "cell_type": "markdown",
846 "metadata": {},
772 "metadata": {},
847 "source": [
773 "source": [
848 "Now the function that parses the date string and only sets it in the correct format can be added."
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 "cell_type": "code",
778 "cell_type": "code",
853 "collapsed": false,
779 "collapsed": false,
854 "input": [
780 "input": [
855 "# Import the dateutil library to parse date strings.\n",
781 "# Import the dateutil library to parse date strings.\n",
856 "from dateutil import parser\n",
782 "from dateutil import parser\n",
857 "\n",
783 "\n",
858 "# Import the base Widget class and the traitlets Unicode class.\n",
784 "# Import the base Widget class and the traitlets Unicode class.\n",
859 "from IPython.html.widgets import Widget\n",
785 "from IPython.html.widgets import Widget\n",
860 "from IPython.utils.traitlets import Unicode\n",
786 "from IPython.utils.traitlets import Unicode\n",
861 "\n",
787 "\n",
862 "# Define our DateWidget and its target model and default view.\n",
788 "# Define our DateWidget and its target model and default view.\n",
863 "class DateWidget(Widget):\n",
789 "class DateWidget(Widget):\n",
864 " target_name = Unicode('DateWidgetModel')\n",
790 " target_name = Unicode('DateWidgetModel')\n",
865 " default_view_name = Unicode('DatePickerView')\n",
791 " default_view_name = Unicode('DatePickerView')\n",
866 " \n",
792 " \n",
867 " # Define the custom state properties to sync with the front-end\n",
793 " # Define the custom state properties to sync with the front-end\n",
868 " _keys = ['value']\n",
794 " _keys = ['value']\n",
869 " value = Unicode()\n",
795 " value = Unicode()\n",
870 " \n",
796 " \n",
871 " # This function automatically gets called by the traitlet machinery when\n",
797 " # This function automatically gets called by the traitlet machinery when\n",
872 " # value is modified because of this function's name.\n",
798 " # value is modified because of this function's name.\n",
873 " def _value_changed(self, name, old_value, new_value):\n",
799 " def _value_changed(self, name, old_value, new_value):\n",
874 " \n",
800 " \n",
875 " # Parse the date time value.\n",
801 " # Parse the date time value.\n",
876 " try:\n",
802 " try:\n",
877 " parsed_date = parser.parse(new_value)\n",
803 " parsed_date = parser.parse(new_value)\n",
878 " parsed_date_string = parsed_date.strftime(\"%Y-%m-%d\")\n",
804 " parsed_date_string = parsed_date.strftime(\"%Y-%m-%d\")\n",
879 " except:\n",
805 " except:\n",
880 " parsed_date_string = ''\n",
806 " parsed_date_string = ''\n",
881 " \n",
807 " \n",
882 " # Set the parsed date string if the current date string is different.\n",
808 " # Set the parsed date string if the current date string is different.\n",
883 " if self.value != parsed_date_string:\n",
809 " if self.value != parsed_date_string:\n",
884 " self.value = parsed_date_string"
810 " self.value = parsed_date_string"
885 ],
811 ],
886 "language": "python",
812 "language": "python",
887 "metadata": {},
813 "metadata": {},
888 "outputs": [],
814 "outputs": [],
889 "prompt_number": 16
815 "prompt_number": 16
890 },
816 },
891 {
817 {
892 "cell_type": "markdown",
818 "cell_type": "markdown",
893 "metadata": {},
819 "metadata": {},
894 "source": [
820 "source": [
895 "The standard property name used for widget labels is `description`. In the code block below, `description` has been added to the Python widget."
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 "cell_type": "code",
825 "cell_type": "code",
900 "collapsed": false,
826 "collapsed": false,
901 "input": [
827 "input": [
902 "# Import the dateutil library to parse date strings.\n",
828 "# Import the dateutil library to parse date strings.\n",
903 "from dateutil import parser\n",
829 "from dateutil import parser\n",
904 "\n",
830 "\n",
905 "# Import the base Widget class and the traitlets Unicode class.\n",
831 "# Import the base Widget class and the traitlets Unicode class.\n",
906 "from IPython.html.widgets import Widget\n",
832 "from IPython.html.widgets import Widget\n",
907 "from IPython.utils.traitlets import Unicode\n",
833 "from IPython.utils.traitlets import Unicode\n",
908 "\n",
834 "\n",
909 "# Define our DateWidget and its target model and default view.\n",
835 "# Define our DateWidget and its target model and default view.\n",
910 "class DateWidget(Widget):\n",
836 "class DateWidget(Widget):\n",
911 " target_name = Unicode('DateWidgetModel')\n",
837 " target_name = Unicode('DateWidgetModel')\n",
912 " default_view_name = Unicode('DatePickerView')\n",
838 " default_view_name = Unicode('DatePickerView')\n",
913 " \n",
839 " \n",
914 " # Define the custom state properties to sync with the front-end\n",
840 " # Define the custom state properties to sync with the front-end\n",
915 " _keys = ['value', 'description']\n",
841 " _keys = ['value', 'description']\n",
916 " value = Unicode()\n",
842 " value = Unicode()\n",
917 " description = Unicode()\n",
843 " description = Unicode()\n",
918 " \n",
844 " \n",
919 " # This function automatically gets called by the traitlet machinery when\n",
845 " # This function automatically gets called by the traitlet machinery when\n",
920 " # value is modified because of this function's name.\n",
846 " # value is modified because of this function's name.\n",
921 " def _value_changed(self, name, old_value, new_value):\n",
847 " def _value_changed(self, name, old_value, new_value):\n",
922 " \n",
848 " \n",
923 " # Parse the date time value.\n",
849 " # Parse the date time value.\n",
924 " try:\n",
850 " try:\n",
925 " parsed_date = parser.parse(new_value)\n",
851 " parsed_date = parser.parse(new_value)\n",
926 " parsed_date_string = parsed_date.strftime(\"%Y-%m-%d\")\n",
852 " parsed_date_string = parsed_date.strftime(\"%Y-%m-%d\")\n",
927 " except:\n",
853 " except:\n",
928 " parsed_date_string = ''\n",
854 " parsed_date_string = ''\n",
929 " \n",
855 " \n",
930 " # Set the parsed date string if the current date string is different.\n",
856 " # Set the parsed date string if the current date string is different.\n",
931 " if self.value != parsed_date_string:\n",
857 " if self.value != parsed_date_string:\n",
932 " self.value = parsed_date_string"
858 " self.value = parsed_date_string"
933 ],
859 ],
934 "language": "python",
860 "language": "python",
935 "metadata": {},
861 "metadata": {},
936 "outputs": [],
862 "outputs": [],
937 "prompt_number": 17
863 "prompt_number": 17
938 },
864 },
939 {
865 {
940 "cell_type": "markdown",
866 "cell_type": "markdown",
941 "metadata": {},
867 "metadata": {},
942 "source": [
868 "source": [
943 "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",
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 "\n",
870 "\n",
945 "**Final Python code below:**"
871 "**Final Python code below:**"
946 ]
872 ]
947 },
873 },
948 {
874 {
949 "cell_type": "code",
875 "cell_type": "code",
950 "collapsed": false,
876 "collapsed": false,
951 "input": [
877 "input": [
952 "# Import the dateutil library to parse date strings.\n",
878 "# Import the dateutil library to parse date strings.\n",
953 "from dateutil import parser\n",
879 "from dateutil import parser\n",
954 "\n",
880 "\n",
955 "# Import the base Widget class and the traitlets Unicode class.\n",
881 "# Import the base Widget class and the traitlets Unicode class.\n",
956 "from IPython.html.widgets import Widget\n",
882 "from IPython.html.widgets import Widget\n",
957 "from IPython.utils.traitlets import Unicode\n",
883 "from IPython.utils.traitlets import Unicode\n",
958 "\n",
884 "\n",
959 "# Define our DateWidget and its target model and default view.\n",
885 "# Define our DateWidget and its target model and default view.\n",
960 "class DateWidget(Widget):\n",
886 "class DateWidget(Widget):\n",
961 " target_name = Unicode('DateWidgetModel')\n",
887 " target_name = Unicode('DateWidgetModel')\n",
962 " default_view_name = Unicode('DatePickerView')\n",
888 " default_view_name = Unicode('DatePickerView')\n",
963 " \n",
889 " \n",
964 " # Define the custom state properties to sync with the front-end\n",
890 " # Define the custom state properties to sync with the front-end\n",
965 " _keys = ['value', 'description']\n",
891 " _keys = ['value', 'description']\n",
966 " value = Unicode()\n",
892 " value = Unicode()\n",
967 " description = Unicode()\n",
893 " description = Unicode()\n",
968 " \n",
894 " \n",
969 " def __init__(self, **kwargs):\n",
895 " def __init__(self, **kwargs):\n",
970 " super(DateWidget, self).__init__(**kwargs)\n",
896 " super(DateWidget, self).__init__(**kwargs)\n",
971 " self._validation_callbacks = []\n",
897 " self._validation_callbacks = []\n",
972 " \n",
898 " \n",
973 " # This function automatically gets called by the traitlet machinery when\n",
899 " # This function automatically gets called by the traitlet machinery when\n",
974 " # value is modified because of this function's name.\n",
900 " # value is modified because of this function's name.\n",
975 " def _value_changed(self, name, old_value, new_value):\n",
901 " def _value_changed(self, name, old_value, new_value):\n",
976 " \n",
902 " \n",
977 " # Parse the date time value.\n",
903 " # Parse the date time value.\n",
978 " try:\n",
904 " try:\n",
979 " parsed_date = parser.parse(new_value)\n",
905 " parsed_date = parser.parse(new_value)\n",
980 " parsed_date_string = parsed_date.strftime(\"%Y-%m-%d\")\n",
906 " parsed_date_string = parsed_date.strftime(\"%Y-%m-%d\")\n",
981 " except:\n",
907 " except:\n",
982 " parsed_date = None\n",
908 " parsed_date = None\n",
983 " parsed_date_string = ''\n",
909 " parsed_date_string = ''\n",
984 " \n",
910 " \n",
985 " # Set the parsed date string if the current date string is different.\n",
911 " # Set the parsed date string if the current date string is different.\n",
986 " if old_value != new_value:\n",
912 " if old_value != new_value:\n",
987 " if self.handle_validate(parsed_date):\n",
913 " if self.handle_validate(parsed_date):\n",
988 " self.value = parsed_date_string\n",
914 " self.value = parsed_date_string\n",
989 " else:\n",
915 " else:\n",
990 " self.value = old_value\n",
916 " self.value = old_value\n",
991 " self.send_state() # The traitlet event won't fire since the value isn't changing.\n",
917 " self.send_state() # The traitlet event won't fire since the value isn't changing.\n",
992 " # We need to force the back-end to send the front-end the state\n",
918 " # We need to force the back-end to send the front-end the state\n",
993 " # to make sure that the date control date doesn't change.\n",
919 " # to make sure that the date control date doesn't change.\n",
994 " \n",
920 " \n",
995 " \n",
921 " \n",
996 " # Allow the user to register custom validation callbacks.\n",
922 " # Allow the user to register custom validation callbacks.\n",
997 " # callback(new value as a datetime object)\n",
923 " # callback(new value as a datetime object)\n",
998 " def on_validate(self, callback, remove=False):\n",
924 " def on_validate(self, callback, remove=False):\n",
999 " if remove and callback in self._validation_callbacks:\n",
925 " if remove and callback in self._validation_callbacks:\n",
1000 " self._validation_callbacks.remove(callback)\n",
926 " self._validation_callbacks.remove(callback)\n",
1001 " elif (not remove) and (not callback in self._validation_callbacks):\n",
927 " elif (not remove) and (not callback in self._validation_callbacks):\n",
1002 " self._validation_callbacks.append(callback)\n",
928 " self._validation_callbacks.append(callback)\n",
1003 " \n",
929 " \n",
1004 " # Call user validation callbacks. Return True if valid.\n",
930 " # Call user validation callbacks. Return True if valid.\n",
1005 " def handle_validate(self, new_value):\n",
931 " def handle_validate(self, new_value):\n",
1006 " for callback in self._validation_callbacks:\n",
932 " for callback in self._validation_callbacks:\n",
1007 " if not callback(new_value):\n",
933 " if not callback(new_value):\n",
1008 " return False\n",
934 " return False\n",
1009 " return True\n",
935 " return True\n",
1010 " "
936 " "
1011 ],
937 ],
1012 "language": "python",
938 "language": "python",
1013 "metadata": {},
939 "metadata": {},
1014 "outputs": [],
940 "outputs": [],
1015 "prompt_number": 18
941 "prompt_number": 18
1016 },
942 },
1017 {
943 {
1018 "cell_type": "heading",
944 "cell_type": "heading",
1019 "level": 2,
945 "level": 2,
1020 "metadata": {},
946 "metadata": {},
1021 "source": [
947 "source": [
1022 "JavaScript"
948 "JavaScript"
1023 ]
949 ]
1024 },
950 },
1025 {
951 {
1026 "cell_type": "markdown",
952 "cell_type": "markdown",
1027 "metadata": {},
953 "metadata": {},
1028 "source": [
954 "source": [
1029 "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",
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 "\n",
956 "\n",
1031 "We hide the label if the description value is blank."
957 "We hide the label if the description value is blank."
1032 ]
958 ]
1033 },
959 },
1034 {
960 {
1035 "cell_type": "code",
961 "cell_type": "code",
1036 "collapsed": false,
962 "collapsed": false,
1037 "input": [
963 "input": [
1038 "%%javascript\n",
964 "%%javascript\n",
1039 "\n",
965 "\n",
1040 "require([\"notebook/js/widget\"], function(){\n",
966 "require([\"notebook/js/widget\"], function(){\n",
1041 " \n",
967 " \n",
1042 " // Define the DateModel and register it with the widget manager.\n",
968 " // Define the DateModel and register it with the widget manager.\n",
1043 " var DateModel = IPython.WidgetModel.extend({});\n",
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 " \n",
971 " \n",
1046 " // Define the DatePickerView\n",
972 " // Define the DatePickerView\n",
1047 " var DatePickerView = IPython.WidgetView.extend({\n",
973 " var DatePickerView = IPython.WidgetView.extend({\n",
1048 " \n",
974 " \n",
1049 " render: function(){\n",
975 " render: function(){\n",
1050 " \n",
976 " \n",
1051 " // Create a div to hold our widget.\n",
977 " // Create a div to hold our widget.\n",
1052 " this.$el = $('<div />')\n",
978 " this.$el = $('<div />')\n",
1053 " .addClass('widget-hbox-single'); // Apply this class to the widget container to make\n",
979 " .addClass('widget-hbox-single'); // Apply this class to the widget container to make\n",
1054 " // it fit with the other built in widgets.\n",
980 " // it fit with the other built in widgets.\n",
1055 " this.delegateEvents();\n",
981 " this.delegateEvents();\n",
1056 " \n",
982 " \n",
1057 " // Create a label.\n",
983 " // Create a label.\n",
1058 " this.$label = $('<div />')\n",
984 " this.$label = $('<div />')\n",
1059 " .addClass('widget-hlabel')\n",
985 " .addClass('widget-hlabel')\n",
1060 " .appendTo(this.$el)\n",
986 " .appendTo(this.$el)\n",
1061 " .hide(); // Hide the label by default.\n",
987 " .hide(); // Hide the label by default.\n",
1062 " \n",
988 " \n",
1063 " // Create the date picker control.\n",
989 " // Create the date picker control.\n",
1064 " this.$date = $('<input />')\n",
990 " this.$date = $('<input />')\n",
1065 " .attr('type', 'date')\n",
991 " .attr('type', 'date')\n",
1066 " .appendTo(this.$el);\n",
992 " .appendTo(this.$el);\n",
1067 " },\n",
993 " },\n",
1068 " \n",
994 " \n",
1069 " update: function() {\n",
995 " update: function() {\n",
1070 " \n",
996 " \n",
1071 " // Set the value of the date control and then call base.\n",
997 " // Set the value of the date control and then call base.\n",
1072 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
998 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
1073 " \n",
999 " \n",
1074 " // Hide or show the label depending on the existance of a description.\n",
1000 " // Hide or show the label depending on the existance of a description.\n",
1075 " var description = this.model.get('description');\n",
1001 " var description = this.model.get('description');\n",
1076 " if (description == undefined || description == '') {\n",
1002 " if (description == undefined || description == '') {\n",
1077 " this.$label.hide();\n",
1003 " this.$label.hide();\n",
1078 " } else {\n",
1004 " } else {\n",
1079 " this.$label.show();\n",
1005 " this.$label.show();\n",
1080 " this.$label.html(description);\n",
1006 " this.$label.html(description);\n",
1081 " }\n",
1007 " }\n",
1082 " \n",
1008 " \n",
1083 " return IPython.WidgetView.prototype.update.call(this);\n",
1009 " return IPython.WidgetView.prototype.update.call(this);\n",
1084 " },\n",
1010 " },\n",
1085 " \n",
1011 " \n",
1086 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
1012 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
1087 " events: {\"change\": \"handle_date_change\"},\n",
1013 " events: {\"change\": \"handle_date_change\"},\n",
1088 " \n",
1014 " \n",
1089 " // Callback for when the date is changed.\n",
1015 " // Callback for when the date is changed.\n",
1090 " handle_date_change: function(event) {\n",
1016 " handle_date_change: function(event) {\n",
1091 " this.model.set('value', this.$date.val());\n",
1017 " this.model.set('value', this.$date.val());\n",
1092 " this.model.update_other_views(this);\n",
1018 " this.model.update_other_views(this);\n",
1093 " },\n",
1019 " },\n",
1094 " \n",
1020 " \n",
1095 " });\n",
1021 " });\n",
1096 " \n",
1022 " \n",
1097 " // Register the DatePickerView with the widget manager.\n",
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 "language": "python",
1027 "language": "python",
1102 "metadata": {},
1028 "metadata": {},
1103 "outputs": [
1029 "outputs": [
1104 {
1030 {
1105 "javascript": [
1031 "javascript": [
1106 "\n",
1032 "\n",
1107 "require([\"notebook/js/widget\"], function(){\n",
1033 "require([\"notebook/js/widget\"], function(){\n",
1108 " \n",
1034 " \n",
1109 " // Define the DateModel and register it with the widget manager.\n",
1035 " // Define the DateModel and register it with the widget manager.\n",
1110 " var DateModel = IPython.WidgetModel.extend({});\n",
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 " \n",
1038 " \n",
1113 " // Define the DatePickerView\n",
1039 " // Define the DatePickerView\n",
1114 " var DatePickerView = IPython.WidgetView.extend({\n",
1040 " var DatePickerView = IPython.WidgetView.extend({\n",
1115 " \n",
1041 " \n",
1116 " render: function(){\n",
1042 " render: function(){\n",
1117 " \n",
1043 " \n",
1118 " // Create a div to hold our widget.\n",
1044 " // Create a div to hold our widget.\n",
1119 " this.$el = $('<div />')\n",
1045 " this.$el = $('<div />')\n",
1120 " .addClass('widget-hbox-single'); // Apply this class to the widget container to make\n",
1046 " .addClass('widget-hbox-single'); // Apply this class to the widget container to make\n",
1121 " // it fit with the other built in widgets.\n",
1047 " // it fit with the other built in widgets.\n",
1122 " this.delegateEvents();\n",
1048 " this.delegateEvents();\n",
1123 " \n",
1049 " \n",
1124 " // Create a label.\n",
1050 " // Create a label.\n",
1125 " this.$label = $('<div />')\n",
1051 " this.$label = $('<div />')\n",
1126 " .addClass('widget-hlabel')\n",
1052 " .addClass('widget-hlabel')\n",
1127 " .appendTo(this.$el)\n",
1053 " .appendTo(this.$el)\n",
1128 " .hide(); // Hide the label by default.\n",
1054 " .hide(); // Hide the label by default.\n",
1129 " \n",
1055 " \n",
1130 " // Create the date picker control.\n",
1056 " // Create the date picker control.\n",
1131 " this.$date = $('<input />')\n",
1057 " this.$date = $('<input />')\n",
1132 " .attr('type', 'date')\n",
1058 " .attr('type', 'date')\n",
1133 " .appendTo(this.$el);\n",
1059 " .appendTo(this.$el);\n",
1134 " },\n",
1060 " },\n",
1135 " \n",
1061 " \n",
1136 " update: function() {\n",
1062 " update: function() {\n",
1137 " \n",
1063 " \n",
1138 " // Set the value of the date control and then call base.\n",
1064 " // Set the value of the date control and then call base.\n",
1139 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
1065 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
1140 " \n",
1066 " \n",
1141 " // Hide or show the label depending on the existance of a description.\n",
1067 " // Hide or show the label depending on the existance of a description.\n",
1142 " var description = this.model.get('description');\n",
1068 " var description = this.model.get('description');\n",
1143 " if (description == undefined || description == '') {\n",
1069 " if (description == undefined || description == '') {\n",
1144 " this.$label.hide();\n",
1070 " this.$label.hide();\n",
1145 " } else {\n",
1071 " } else {\n",
1146 " this.$label.show();\n",
1072 " this.$label.show();\n",
1147 " this.$label.html(description);\n",
1073 " this.$label.html(description);\n",
1148 " }\n",
1074 " }\n",
1149 " \n",
1075 " \n",
1150 " return IPython.WidgetView.prototype.update.call(this);\n",
1076 " return IPython.WidgetView.prototype.update.call(this);\n",
1151 " },\n",
1077 " },\n",
1152 " \n",
1078 " \n",
1153 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
1079 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
1154 " events: {\"change\": \"handle_date_change\"},\n",
1080 " events: {\"change\": \"handle_date_change\"},\n",
1155 " \n",
1081 " \n",
1156 " // Callback for when the date is changed.\n",
1082 " // Callback for when the date is changed.\n",
1157 " handle_date_change: function(event) {\n",
1083 " handle_date_change: function(event) {\n",
1158 " this.model.set('value', this.$date.val());\n",
1084 " this.model.set('value', this.$date.val());\n",
1159 " this.model.update_other_views(this);\n",
1085 " this.model.update_other_views(this);\n",
1160 " },\n",
1086 " },\n",
1161 " \n",
1087 " \n",
1162 " });\n",
1088 " });\n",
1163 " \n",
1089 " \n",
1164 " // Register the DatePickerView with the widget manager.\n",
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 "metadata": {},
1094 "metadata": {},
1169 "output_type": "display_data",
1095 "output_type": "display_data",
1170 "text": [
1096 "text": [
1171 "<IPython.core.display.Javascript at 0x179f790>"
1097 "<IPython.core.display.Javascript at 0x221a850>"
1172 ]
1098 ]
1173 }
1099 }
1174 ],
1100 ],
1175 "prompt_number": 19
1101 "prompt_number": 19
1176 },
1102 },
1177 {
1103 {
1178 "cell_type": "heading",
1104 "cell_type": "heading",
1179 "level": 2,
1105 "level": 2,
1180 "metadata": {},
1106 "metadata": {},
1181 "source": [
1107 "source": [
1182 "Test"
1108 "Test"
1183 ]
1109 ]
1184 },
1110 },
1185 {
1111 {
1186 "cell_type": "markdown",
1112 "cell_type": "markdown",
1187 "metadata": {},
1113 "metadata": {},
1188 "source": [
1114 "source": [
1189 "To test the drawing of the label we create the widget like normal but supply the additional description property a value."
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 "cell_type": "code",
1119 "cell_type": "code",
1194 "collapsed": false,
1120 "collapsed": false,
1195 "input": [
1121 "input": [
1196 "# Add some additional widgets for aesthetic purpose\n",
1122 "# Add some additional widgets for aesthetic purpose\n",
1197 "display(widgets.StringWidget(description=\"First:\"))\n",
1123 "display(widgets.StringWidget(description=\"First:\"))\n",
1198 "display(widgets.StringWidget(description=\"Last:\"))\n",
1124 "display(widgets.StringWidget(description=\"Last:\"))\n",
1199 "\n",
1125 "\n",
1200 "my_widget = DateWidget(description=\"DOB:\")\n",
1126 "my_widget = DateWidget(description=\"DOB:\")\n",
1201 "display(my_widget)"
1127 "display(my_widget)"
1202 ],
1128 ],
1203 "language": "python",
1129 "language": "python",
1204 "metadata": {},
1130 "metadata": {},
1205 "outputs": [],
1131 "outputs": [],
1206 "prompt_number": 20
1132 "prompt_number": 20
1207 },
1133 },
1208 {
1134 {
1209 "cell_type": "markdown",
1135 "cell_type": "markdown",
1210 "metadata": {},
1136 "metadata": {},
1211 "source": [
1137 "source": [
1212 "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."
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 "cell_type": "code",
1142 "cell_type": "code",
1217 "collapsed": false,
1143 "collapsed": false,
1218 "input": [
1144 "input": [
1219 "\n",
1220 "display(my_widget, view_name=\"TextBoxView\")"
1145 "display(my_widget, view_name=\"TextBoxView\")"
1221 ],
1146 ],
1222 "language": "python",
1147 "language": "python",
1223 "metadata": {},
1148 "metadata": {},
1224 "outputs": [],
1149 "outputs": [],
1225 "prompt_number": 21
1150 "prompt_number": 21
1226 },
1151 },
1227 {
1152 {
1228 "cell_type": "markdown",
1153 "cell_type": "markdown",
1229 "metadata": {},
1154 "metadata": {},
1230 "source": [
1155 "source": [
1231 "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."
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 "cell_type": "code",
1160 "cell_type": "code",
1236 "collapsed": false,
1161 "collapsed": false,
1237 "input": [
1162 "input": [
1238 "my_widget = DateWidget()\n",
1163 "my_widget = DateWidget()\n",
1239 "display(my_widget)\n",
1164 "display(my_widget)\n",
1240 "\n",
1165 "\n",
1241 "def validate_date(date):\n",
1166 "def validate_date(date):\n",
1242 " return not date is None and date.year == 2013\n",
1167 " return not date is None and date.year == 2013\n",
1243 "my_widget.on_validate(validate_date)"
1168 "my_widget.on_validate(validate_date)"
1244 ],
1169 ],
1245 "language": "python",
1170 "language": "python",
1246 "metadata": {},
1171 "metadata": {},
1247 "outputs": [],
1172 "outputs": [],
1248 "prompt_number": 22
1173 "prompt_number": 22
1249 },
1174 },
1250 {
1175 {
1251 "cell_type": "code",
1176 "cell_type": "code",
1252 "collapsed": false,
1177 "collapsed": false,
1253 "input": [
1178 "input": [
1254 "# Try setting a valid date\n",
1179 "# Try setting a valid date\n",
1255 "my_widget.value = \"December 2, 2013\""
1180 "my_widget.value = \"December 2, 2013\""
1256 ],
1181 ],
1257 "language": "python",
1182 "language": "python",
1258 "metadata": {},
1183 "metadata": {},
1259 "outputs": [],
1184 "outputs": [],
1260 "prompt_number": 23
1185 "prompt_number": 23
1261 },
1186 },
1262 {
1187 {
1263 "cell_type": "code",
1188 "cell_type": "code",
1264 "collapsed": false,
1189 "collapsed": false,
1265 "input": [
1190 "input": [
1266 "# Try setting an invalid date\n",
1191 "# Try setting an invalid date\n",
1267 "my_widget.value = \"June 12, 1999\""
1192 "my_widget.value = \"June 12, 1999\""
1268 ],
1193 ],
1269 "language": "python",
1194 "language": "python",
1270 "metadata": {},
1195 "metadata": {},
1271 "outputs": [],
1196 "outputs": [],
1272 "prompt_number": 24
1197 "prompt_number": 24
1273 }
1198 }
1274 ],
1199 ],
1275 "metadata": {}
1200 "metadata": {}
1276 }
1201 }
1277 ]
1202 ]
1278 } No newline at end of file
1203 }
General Comments 0
You need to be logged in to leave comments. Login now