Show More
@@ -1,127 +1,128 b'' | |||
|
1 | 1 | //---------------------------------------------------------------------------- |
|
2 | 2 | // Copyright (C) 2011 The IPython Development Team |
|
3 | 3 | // |
|
4 | 4 | // Distributed under the terms of the BSD License. The full license is in |
|
5 | 5 | // the file COPYING, distributed as part of this software. |
|
6 | 6 | //---------------------------------------------------------------------------- |
|
7 | 7 | |
|
8 | 8 | //============================================================================ |
|
9 | 9 | // On document ready |
|
10 | 10 | //============================================================================ |
|
11 | 11 | "use strict"; |
|
12 | 12 | |
|
13 | 13 | // for the time beeing, we have to pass marked as a parameter here, |
|
14 | 14 | // as injecting require.js make marked not to put itself in the globals, |
|
15 | 15 | // which make both this file fail at setting marked configuration, and textcell.js |
|
16 | 16 | // which search marked into global. |
|
17 |
require(['components/marked/lib/marked' |
|
|
17 | require(['components/marked/lib/marked', | |
|
18 | 'notebook/js/widgets/basic_widgets'], | |
|
18 | 19 | |
|
19 | 20 | function (marked) { |
|
20 | 21 | |
|
21 | 22 | window.marked = marked |
|
22 | 23 | |
|
23 | 24 | // monkey patch CM to be able to syntax highlight cell magics |
|
24 | 25 | // bug reported upstream, |
|
25 | 26 | // see https://github.com/marijnh/CodeMirror2/issues/670 |
|
26 | 27 | if(CodeMirror.getMode(1,'text/plain').indent == undefined ){ |
|
27 | 28 | console.log('patching CM for undefined indent'); |
|
28 | 29 | CodeMirror.modes.null = function() { |
|
29 | 30 | return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0}} |
|
30 | 31 | } |
|
31 | 32 | } |
|
32 | 33 | |
|
33 | 34 | CodeMirror.patchedGetMode = function(config, mode){ |
|
34 | 35 | var cmmode = CodeMirror.getMode(config, mode); |
|
35 | 36 | if(cmmode.indent == null) |
|
36 | 37 | { |
|
37 | 38 | console.log('patch mode "' , mode, '" on the fly'); |
|
38 | 39 | cmmode.indent = function(){return 0}; |
|
39 | 40 | } |
|
40 | 41 | return cmmode; |
|
41 | 42 | } |
|
42 | 43 | // end monkey patching CodeMirror |
|
43 | 44 | |
|
44 | 45 | IPython.mathjaxutils.init(); |
|
45 | 46 | |
|
46 | 47 | $('#ipython-main-app').addClass('border-box-sizing'); |
|
47 | 48 | $('div#notebook_panel').addClass('border-box-sizing'); |
|
48 | 49 | |
|
49 | 50 | var baseProjectUrl = $('body').data('baseProjectUrl'); |
|
50 | 51 | var notebookPath = $('body').data('notebookPath'); |
|
51 | 52 | var notebookName = $('body').data('notebookName'); |
|
52 | 53 | notebookName = decodeURIComponent(notebookName); |
|
53 | 54 | notebookPath = decodeURIComponent(notebookPath); |
|
54 | 55 | console.log(notebookName); |
|
55 | 56 | if (notebookPath == 'None'){ |
|
56 | 57 | notebookPath = ""; |
|
57 | 58 | } |
|
58 | 59 | |
|
59 | 60 | IPython.page = new IPython.Page(); |
|
60 | 61 | IPython.layout_manager = new IPython.LayoutManager(); |
|
61 | 62 | IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter'); |
|
62 | 63 | IPython.quick_help = new IPython.QuickHelp(); |
|
63 | 64 | IPython.login_widget = new IPython.LoginWidget('span#login_widget',{baseProjectUrl:baseProjectUrl}); |
|
64 | 65 | IPython.notebook = new IPython.Notebook('div#notebook',{baseProjectUrl:baseProjectUrl, notebookPath:notebookPath, notebookName:notebookName}); |
|
65 | 66 | IPython.keyboard_manager = new IPython.KeyboardManager(); |
|
66 | 67 | IPython.save_widget = new IPython.SaveWidget('span#save_widget'); |
|
67 | 68 | IPython.menubar = new IPython.MenuBar('#menubar',{baseProjectUrl:baseProjectUrl, notebookPath: notebookPath}) |
|
68 | 69 | IPython.toolbar = new IPython.MainToolBar('#maintoolbar-container') |
|
69 | 70 | IPython.tooltip = new IPython.Tooltip() |
|
70 | 71 | IPython.notification_area = new IPython.NotificationArea('#notification_area') |
|
71 | 72 | IPython.notification_area.init_notification_widgets(); |
|
72 | 73 | |
|
73 | 74 | IPython.layout_manager.do_resize(); |
|
74 | 75 | |
|
75 | 76 | $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+ |
|
76 | 77 | '<span id="test2" style="font-weight: bold;">x</span>'+ |
|
77 | 78 | '<span id="test3" style="font-style: italic;">x</span></pre></div>') |
|
78 | 79 | var nh = $('#test1').innerHeight(); |
|
79 | 80 | var bh = $('#test2').innerHeight(); |
|
80 | 81 | var ih = $('#test3').innerHeight(); |
|
81 | 82 | if(nh != bh || nh != ih) { |
|
82 | 83 | $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>'); |
|
83 | 84 | } |
|
84 | 85 | $('#fonttest').remove(); |
|
85 | 86 | |
|
86 | 87 | IPython.page.show(); |
|
87 | 88 | |
|
88 | 89 | IPython.layout_manager.do_resize(); |
|
89 | 90 | var first_load = function () { |
|
90 | 91 | IPython.layout_manager.do_resize(); |
|
91 | 92 | var hash = document.location.hash; |
|
92 | 93 | if (hash) { |
|
93 | 94 | document.location.hash = ''; |
|
94 | 95 | document.location.hash = hash; |
|
95 | 96 | } |
|
96 | 97 | IPython.notebook.set_autosave_interval(IPython.notebook.minimum_autosave_interval); |
|
97 | 98 | // only do this once |
|
98 | 99 | $([IPython.events]).off('notebook_loaded.Notebook', first_load); |
|
99 | 100 | }; |
|
100 | 101 | |
|
101 | 102 | $([IPython.events]).on('notebook_loaded.Notebook', first_load); |
|
102 | 103 | $([IPython.events]).trigger('app_initialized.NotebookApp'); |
|
103 | 104 | IPython.notebook.load_notebook(notebookName, notebookPath); |
|
104 | 105 | |
|
105 | 106 | if (marked) { |
|
106 | 107 | marked.setOptions({ |
|
107 | 108 | gfm : true, |
|
108 | 109 | tables: true, |
|
109 | 110 | langPrefix: "language-", |
|
110 | 111 | highlight: function(code, lang) { |
|
111 | 112 | if (!lang) { |
|
112 | 113 | // no language, no highlight |
|
113 | 114 | return code; |
|
114 | 115 | } |
|
115 | 116 | var highlighted; |
|
116 | 117 | try { |
|
117 | 118 | highlighted = hljs.highlight(lang, code, false); |
|
118 | 119 | } catch(err) { |
|
119 | 120 | highlighted = hljs.highlightAuto(code); |
|
120 | 121 | } |
|
121 | 122 | return highlighted.value; |
|
122 | 123 | } |
|
123 | 124 | }) |
|
124 | 125 | } |
|
125 | 126 | } |
|
126 | 127 | |
|
127 | 128 | ); |
@@ -1,2213 +1,2215 b'' | |||
|
1 | 1 | //---------------------------------------------------------------------------- |
|
2 | 2 | // Copyright (C) 2011 The IPython Development Team |
|
3 | 3 | // |
|
4 | 4 | // Distributed under the terms of the BSD License. The full license is in |
|
5 | 5 | // the file COPYING, distributed as part of this software. |
|
6 | 6 | //---------------------------------------------------------------------------- |
|
7 | 7 | |
|
8 | 8 | //============================================================================ |
|
9 | 9 | // Notebook |
|
10 | 10 | //============================================================================ |
|
11 | 11 | |
|
12 | 12 | var IPython = (function (IPython) { |
|
13 | 13 | "use strict"; |
|
14 | 14 | |
|
15 | 15 | var utils = IPython.utils; |
|
16 | 16 | |
|
17 | 17 | /** |
|
18 | 18 | * A notebook contains and manages cells. |
|
19 | 19 | * |
|
20 | 20 | * @class Notebook |
|
21 | 21 | * @constructor |
|
22 | 22 | * @param {String} selector A jQuery selector for the notebook's DOM element |
|
23 | 23 | * @param {Object} [options] A config object |
|
24 | 24 | */ |
|
25 | 25 | var Notebook = function (selector, options) { |
|
26 | 26 | var options = options || {}; |
|
27 | 27 | this._baseProjectUrl = options.baseProjectUrl; |
|
28 | 28 | this.notebook_path = options.notebookPath; |
|
29 | 29 | this.notebook_name = options.notebookName; |
|
30 | 30 | this.element = $(selector); |
|
31 | 31 | this.element.scroll(); |
|
32 | 32 | this.element.data("notebook", this); |
|
33 | 33 | this.next_prompt_number = 1; |
|
34 | 34 | this.session = null; |
|
35 | 35 | this.kernel = null; |
|
36 | 36 | this.clipboard = null; |
|
37 | 37 | this.undelete_backup = null; |
|
38 | 38 | this.undelete_index = null; |
|
39 | 39 | this.undelete_below = false; |
|
40 | 40 | this.paste_enabled = false; |
|
41 | 41 | // It is important to start out in command mode to match the intial mode |
|
42 | 42 | // of the KeyboardManager. |
|
43 | 43 | this.mode = 'command'; |
|
44 | 44 | this.set_dirty(false); |
|
45 | 45 | this.metadata = {}; |
|
46 | 46 | this._checkpoint_after_save = false; |
|
47 | 47 | this.last_checkpoint = null; |
|
48 | 48 | this.checkpoints = []; |
|
49 | 49 | this.autosave_interval = 0; |
|
50 | 50 | this.autosave_timer = null; |
|
51 | 51 | // autosave *at most* every two minutes |
|
52 | 52 | this.minimum_autosave_interval = 120000; |
|
53 | 53 | // single worksheet for now |
|
54 | 54 | this.worksheet_metadata = {}; |
|
55 | 55 | this.notebook_name_blacklist_re = /[\/\\:]/; |
|
56 | 56 | this.nbformat = 3 // Increment this when changing the nbformat |
|
57 | 57 | this.nbformat_minor = 0 // Increment this when changing the nbformat |
|
58 | 58 | this.style(); |
|
59 | 59 | this.create_elements(); |
|
60 | 60 | this.bind_events(); |
|
61 | 61 | }; |
|
62 | 62 | |
|
63 | 63 | /** |
|
64 | 64 | * Tweak the notebook's CSS style. |
|
65 | 65 | * |
|
66 | 66 | * @method style |
|
67 | 67 | */ |
|
68 | 68 | Notebook.prototype.style = function () { |
|
69 | 69 | $('div#notebook').addClass('border-box-sizing'); |
|
70 | 70 | }; |
|
71 | 71 | |
|
72 | 72 | /** |
|
73 | 73 | * Get the root URL of the notebook server. |
|
74 | 74 | * |
|
75 | 75 | * @method baseProjectUrl |
|
76 | 76 | * @return {String} The base project URL |
|
77 | 77 | */ |
|
78 | 78 | Notebook.prototype.baseProjectUrl = function() { |
|
79 | 79 | return this._baseProjectUrl || $('body').data('baseProjectUrl'); |
|
80 | 80 | }; |
|
81 | 81 | |
|
82 | 82 | Notebook.prototype.notebookName = function() { |
|
83 | 83 | return $('body').data('notebookName'); |
|
84 | 84 | }; |
|
85 | 85 | |
|
86 | 86 | Notebook.prototype.notebookPath = function() { |
|
87 | 87 | return $('body').data('notebookPath'); |
|
88 | 88 | }; |
|
89 | 89 | |
|
90 | 90 | /** |
|
91 | 91 | * Create an HTML and CSS representation of the notebook. |
|
92 | 92 | * |
|
93 | 93 | * @method create_elements |
|
94 | 94 | */ |
|
95 | 95 | Notebook.prototype.create_elements = function () { |
|
96 | 96 | var that = this; |
|
97 | 97 | this.element.attr('tabindex','-1'); |
|
98 | 98 | this.container = $("<div/>").addClass("container").attr("id", "notebook-container"); |
|
99 | 99 | // We add this end_space div to the end of the notebook div to: |
|
100 | 100 | // i) provide a margin between the last cell and the end of the notebook |
|
101 | 101 | // ii) to prevent the div from scrolling up when the last cell is being |
|
102 | 102 | // edited, but is too low on the page, which browsers will do automatically. |
|
103 | 103 | var end_space = $('<div/>').addClass('end_space'); |
|
104 | 104 | end_space.dblclick(function (e) { |
|
105 | 105 | var ncells = that.ncells(); |
|
106 | 106 | that.insert_cell_below('code',ncells-1); |
|
107 | 107 | }); |
|
108 | 108 | this.element.append(this.container); |
|
109 | 109 | this.container.append(end_space); |
|
110 | 110 | }; |
|
111 | 111 | |
|
112 | 112 | /** |
|
113 | 113 | * Bind JavaScript events: key presses and custom IPython events. |
|
114 | 114 | * |
|
115 | 115 | * @method bind_events |
|
116 | 116 | */ |
|
117 | 117 | Notebook.prototype.bind_events = function () { |
|
118 | 118 | var that = this; |
|
119 | 119 | |
|
120 | 120 | $([IPython.events]).on('set_next_input.Notebook', function (event, data) { |
|
121 | 121 | var index = that.find_cell_index(data.cell); |
|
122 | 122 | var new_cell = that.insert_cell_below('code',index); |
|
123 | 123 | new_cell.set_text(data.text); |
|
124 | 124 | that.dirty = true; |
|
125 | 125 | }); |
|
126 | 126 | |
|
127 | 127 | $([IPython.events]).on('set_dirty.Notebook', function (event, data) { |
|
128 | 128 | that.dirty = data.value; |
|
129 | 129 | }); |
|
130 | 130 | |
|
131 | 131 | $([IPython.events]).on('select.Cell', function (event, data) { |
|
132 | 132 | var index = that.find_cell_index(data.cell); |
|
133 | 133 | that.select(index); |
|
134 | 134 | }); |
|
135 | 135 | |
|
136 | 136 | $([IPython.events]).on('edit_mode.Cell', function (event, data) { |
|
137 | 137 | var index = that.find_cell_index(data.cell); |
|
138 | 138 | that.select(index); |
|
139 | 139 | that.edit_mode(); |
|
140 | 140 | }); |
|
141 | 141 | |
|
142 | 142 | $([IPython.events]).on('command_mode.Cell', function (event, data) { |
|
143 | 143 | that.command_mode(); |
|
144 | 144 | }); |
|
145 | 145 | |
|
146 | 146 | $([IPython.events]).on('status_autorestarting.Kernel', function () { |
|
147 | 147 | IPython.dialog.modal({ |
|
148 | 148 | title: "Kernel Restarting", |
|
149 | 149 | body: "The kernel appears to have died. It will restart automatically.", |
|
150 | 150 | buttons: { |
|
151 | 151 | OK : { |
|
152 | 152 | class : "btn-primary" |
|
153 | 153 | } |
|
154 | 154 | } |
|
155 | 155 | }); |
|
156 | 156 | }); |
|
157 | 157 | |
|
158 | 158 | var collapse_time = function (time) { |
|
159 | 159 | var app_height = $('#ipython-main-app').height(); // content height |
|
160 | 160 | var splitter_height = $('div#pager_splitter').outerHeight(true); |
|
161 | 161 | var new_height = app_height - splitter_height; |
|
162 | 162 | that.element.animate({height : new_height + 'px'}, time); |
|
163 | 163 | }; |
|
164 | 164 | |
|
165 | 165 | this.element.bind('collapse_pager', function (event, extrap) { |
|
166 | 166 | var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast'; |
|
167 | 167 | collapse_time(time); |
|
168 | 168 | }); |
|
169 | 169 | |
|
170 | 170 | var expand_time = function (time) { |
|
171 | 171 | var app_height = $('#ipython-main-app').height(); // content height |
|
172 | 172 | var splitter_height = $('div#pager_splitter').outerHeight(true); |
|
173 | 173 | var pager_height = $('div#pager').outerHeight(true); |
|
174 | 174 | var new_height = app_height - pager_height - splitter_height; |
|
175 | 175 | that.element.animate({height : new_height + 'px'}, time); |
|
176 | 176 | }; |
|
177 | 177 | |
|
178 | 178 | this.element.bind('expand_pager', function (event, extrap) { |
|
179 | 179 | var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast'; |
|
180 | 180 | expand_time(time); |
|
181 | 181 | }); |
|
182 | 182 | |
|
183 | 183 | // Firefox 22 broke $(window).on("beforeunload") |
|
184 | 184 | // I'm not sure why or how. |
|
185 | 185 | window.onbeforeunload = function (e) { |
|
186 | 186 | // TODO: Make killing the kernel configurable. |
|
187 | 187 | var kill_kernel = false; |
|
188 | 188 | if (kill_kernel) { |
|
189 | 189 | that.session.kill_kernel(); |
|
190 | 190 | } |
|
191 | 191 | // if we are autosaving, trigger an autosave on nav-away. |
|
192 | 192 | // still warn, because if we don't the autosave may fail. |
|
193 | 193 | if (that.dirty) { |
|
194 | 194 | if ( that.autosave_interval ) { |
|
195 | 195 | // schedule autosave in a timeout |
|
196 | 196 | // this gives you a chance to forcefully discard changes |
|
197 | 197 | // by reloading the page if you *really* want to. |
|
198 | 198 | // the timer doesn't start until you *dismiss* the dialog. |
|
199 | 199 | setTimeout(function () { |
|
200 | 200 | if (that.dirty) { |
|
201 | 201 | that.save_notebook(); |
|
202 | 202 | } |
|
203 | 203 | }, 1000); |
|
204 | 204 | return "Autosave in progress, latest changes may be lost."; |
|
205 | 205 | } else { |
|
206 | 206 | return "Unsaved changes will be lost."; |
|
207 | 207 | } |
|
208 | 208 | }; |
|
209 | 209 | // Null is the *only* return value that will make the browser not |
|
210 | 210 | // pop up the "don't leave" dialog. |
|
211 | 211 | return null; |
|
212 | 212 | }; |
|
213 | 213 | }; |
|
214 | 214 | |
|
215 | 215 | /** |
|
216 | 216 | * Set the dirty flag, and trigger the set_dirty.Notebook event |
|
217 | 217 | * |
|
218 | 218 | * @method set_dirty |
|
219 | 219 | */ |
|
220 | 220 | Notebook.prototype.set_dirty = function (value) { |
|
221 | 221 | if (value === undefined) { |
|
222 | 222 | value = true; |
|
223 | 223 | } |
|
224 | 224 | if (this.dirty == value) { |
|
225 | 225 | return; |
|
226 | 226 | } |
|
227 | 227 | $([IPython.events]).trigger('set_dirty.Notebook', {value: value}); |
|
228 | 228 | }; |
|
229 | 229 | |
|
230 | 230 | /** |
|
231 | 231 | * Scroll the top of the page to a given cell. |
|
232 | 232 | * |
|
233 | 233 | * @method scroll_to_cell |
|
234 | 234 | * @param {Number} cell_number An index of the cell to view |
|
235 | 235 | * @param {Number} time Animation time in milliseconds |
|
236 | 236 | * @return {Number} Pixel offset from the top of the container |
|
237 | 237 | */ |
|
238 | 238 | Notebook.prototype.scroll_to_cell = function (cell_number, time) { |
|
239 | 239 | var cells = this.get_cells(); |
|
240 | 240 | var time = time || 0; |
|
241 | 241 | cell_number = Math.min(cells.length-1,cell_number); |
|
242 | 242 | cell_number = Math.max(0 ,cell_number); |
|
243 | 243 | var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ; |
|
244 | 244 | this.element.animate({scrollTop:scroll_value}, time); |
|
245 | 245 | return scroll_value; |
|
246 | 246 | }; |
|
247 | 247 | |
|
248 | 248 | /** |
|
249 | 249 | * Scroll to the bottom of the page. |
|
250 | 250 | * |
|
251 | 251 | * @method scroll_to_bottom |
|
252 | 252 | */ |
|
253 | 253 | Notebook.prototype.scroll_to_bottom = function () { |
|
254 | 254 | this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0); |
|
255 | 255 | }; |
|
256 | 256 | |
|
257 | 257 | /** |
|
258 | 258 | * Scroll to the top of the page. |
|
259 | 259 | * |
|
260 | 260 | * @method scroll_to_top |
|
261 | 261 | */ |
|
262 | 262 | Notebook.prototype.scroll_to_top = function () { |
|
263 | 263 | this.element.animate({scrollTop:0}, 0); |
|
264 | 264 | }; |
|
265 | 265 | |
|
266 | 266 | // Edit Notebook metadata |
|
267 | 267 | |
|
268 | 268 | Notebook.prototype.edit_metadata = function () { |
|
269 | 269 | var that = this; |
|
270 | 270 | IPython.dialog.edit_metadata(this.metadata, function (md) { |
|
271 | 271 | that.metadata = md; |
|
272 | 272 | }, 'Notebook'); |
|
273 | 273 | }; |
|
274 | 274 | |
|
275 | 275 | // Cell indexing, retrieval, etc. |
|
276 | 276 | |
|
277 | 277 | /** |
|
278 | 278 | * Get all cell elements in the notebook. |
|
279 | 279 | * |
|
280 | 280 | * @method get_cell_elements |
|
281 | 281 | * @return {jQuery} A selector of all cell elements |
|
282 | 282 | */ |
|
283 | 283 | Notebook.prototype.get_cell_elements = function () { |
|
284 | 284 | return this.container.children("div.cell"); |
|
285 | 285 | }; |
|
286 | 286 | |
|
287 | 287 | /** |
|
288 | 288 | * Get a particular cell element. |
|
289 | 289 | * |
|
290 | 290 | * @method get_cell_element |
|
291 | 291 | * @param {Number} index An index of a cell to select |
|
292 | 292 | * @return {jQuery} A selector of the given cell. |
|
293 | 293 | */ |
|
294 | 294 | Notebook.prototype.get_cell_element = function (index) { |
|
295 | 295 | var result = null; |
|
296 | 296 | var e = this.get_cell_elements().eq(index); |
|
297 | 297 | if (e.length !== 0) { |
|
298 | 298 | result = e; |
|
299 | 299 | } |
|
300 | 300 | return result; |
|
301 | 301 | }; |
|
302 | 302 | |
|
303 | 303 | /** |
|
304 | 304 | * Count the cells in this notebook. |
|
305 | 305 | * |
|
306 | 306 | * @method ncells |
|
307 | 307 | * @return {Number} The number of cells in this notebook |
|
308 | 308 | */ |
|
309 | 309 | Notebook.prototype.ncells = function () { |
|
310 | 310 | return this.get_cell_elements().length; |
|
311 | 311 | }; |
|
312 | 312 | |
|
313 | 313 | /** |
|
314 | 314 | * Get all Cell objects in this notebook. |
|
315 | 315 | * |
|
316 | 316 | * @method get_cells |
|
317 | 317 | * @return {Array} This notebook's Cell objects |
|
318 | 318 | */ |
|
319 | 319 | // TODO: we are often calling cells as cells()[i], which we should optimize |
|
320 | 320 | // to cells(i) or a new method. |
|
321 | 321 | Notebook.prototype.get_cells = function () { |
|
322 | 322 | return this.get_cell_elements().toArray().map(function (e) { |
|
323 | 323 | return $(e).data("cell"); |
|
324 | 324 | }); |
|
325 | 325 | }; |
|
326 | 326 | |
|
327 | 327 | /** |
|
328 | 328 | * Get a Cell object from this notebook. |
|
329 | 329 | * |
|
330 | 330 | * @method get_cell |
|
331 | 331 | * @param {Number} index An index of a cell to retrieve |
|
332 | 332 | * @return {Cell} A particular cell |
|
333 | 333 | */ |
|
334 | 334 | Notebook.prototype.get_cell = function (index) { |
|
335 | 335 | var result = null; |
|
336 | 336 | var ce = this.get_cell_element(index); |
|
337 | 337 | if (ce !== null) { |
|
338 | 338 | result = ce.data('cell'); |
|
339 | 339 | } |
|
340 | 340 | return result; |
|
341 | 341 | } |
|
342 | 342 | |
|
343 | 343 | /** |
|
344 | 344 | * Get the cell below a given cell. |
|
345 | 345 | * |
|
346 | 346 | * @method get_next_cell |
|
347 | 347 | * @param {Cell} cell The provided cell |
|
348 | 348 | * @return {Cell} The next cell |
|
349 | 349 | */ |
|
350 | 350 | Notebook.prototype.get_next_cell = function (cell) { |
|
351 | 351 | var result = null; |
|
352 | 352 | var index = this.find_cell_index(cell); |
|
353 | 353 | if (this.is_valid_cell_index(index+1)) { |
|
354 | 354 | result = this.get_cell(index+1); |
|
355 | 355 | } |
|
356 | 356 | return result; |
|
357 | 357 | } |
|
358 | 358 | |
|
359 | 359 | /** |
|
360 | 360 | * Get the cell above a given cell. |
|
361 | 361 | * |
|
362 | 362 | * @method get_prev_cell |
|
363 | 363 | * @param {Cell} cell The provided cell |
|
364 | 364 | * @return {Cell} The previous cell |
|
365 | 365 | */ |
|
366 | 366 | Notebook.prototype.get_prev_cell = function (cell) { |
|
367 | 367 | // TODO: off-by-one |
|
368 | 368 | // nb.get_prev_cell(nb.get_cell(1)) is null |
|
369 | 369 | var result = null; |
|
370 | 370 | var index = this.find_cell_index(cell); |
|
371 | 371 | if (index !== null && index > 1) { |
|
372 | 372 | result = this.get_cell(index-1); |
|
373 | 373 | } |
|
374 | 374 | return result; |
|
375 | 375 | } |
|
376 | 376 | |
|
377 | 377 | /** |
|
378 | 378 | * Get the numeric index of a given cell. |
|
379 | 379 | * |
|
380 | 380 | * @method find_cell_index |
|
381 | 381 | * @param {Cell} cell The provided cell |
|
382 | 382 | * @return {Number} The cell's numeric index |
|
383 | 383 | */ |
|
384 | 384 | Notebook.prototype.find_cell_index = function (cell) { |
|
385 | 385 | var result = null; |
|
386 | 386 | this.get_cell_elements().filter(function (index) { |
|
387 | 387 | if ($(this).data("cell") === cell) { |
|
388 | 388 | result = index; |
|
389 | 389 | }; |
|
390 | 390 | }); |
|
391 | 391 | return result; |
|
392 | 392 | }; |
|
393 | 393 | |
|
394 | 394 | /** |
|
395 | 395 | * Get a given index , or the selected index if none is provided. |
|
396 | 396 | * |
|
397 | 397 | * @method index_or_selected |
|
398 | 398 | * @param {Number} index A cell's index |
|
399 | 399 | * @return {Number} The given index, or selected index if none is provided. |
|
400 | 400 | */ |
|
401 | 401 | Notebook.prototype.index_or_selected = function (index) { |
|
402 | 402 | var i; |
|
403 | 403 | if (index === undefined || index === null) { |
|
404 | 404 | i = this.get_selected_index(); |
|
405 | 405 | if (i === null) { |
|
406 | 406 | i = 0; |
|
407 | 407 | } |
|
408 | 408 | } else { |
|
409 | 409 | i = index; |
|
410 | 410 | } |
|
411 | 411 | return i; |
|
412 | 412 | }; |
|
413 | 413 | |
|
414 | 414 | /** |
|
415 | 415 | * Get the currently selected cell. |
|
416 | 416 | * @method get_selected_cell |
|
417 | 417 | * @return {Cell} The selected cell |
|
418 | 418 | */ |
|
419 | 419 | Notebook.prototype.get_selected_cell = function () { |
|
420 | 420 | var index = this.get_selected_index(); |
|
421 | 421 | return this.get_cell(index); |
|
422 | 422 | }; |
|
423 | 423 | |
|
424 | 424 | /** |
|
425 | 425 | * Check whether a cell index is valid. |
|
426 | 426 | * |
|
427 | 427 | * @method is_valid_cell_index |
|
428 | 428 | * @param {Number} index A cell index |
|
429 | 429 | * @return True if the index is valid, false otherwise |
|
430 | 430 | */ |
|
431 | 431 | Notebook.prototype.is_valid_cell_index = function (index) { |
|
432 | 432 | if (index !== null && index >= 0 && index < this.ncells()) { |
|
433 | 433 | return true; |
|
434 | 434 | } else { |
|
435 | 435 | return false; |
|
436 | 436 | }; |
|
437 | 437 | } |
|
438 | 438 | |
|
439 | 439 | /** |
|
440 | 440 | * Get the index of the currently selected cell. |
|
441 | 441 | |
|
442 | 442 | * @method get_selected_index |
|
443 | 443 | * @return {Number} The selected cell's numeric index |
|
444 | 444 | */ |
|
445 | 445 | Notebook.prototype.get_selected_index = function () { |
|
446 | 446 | var result = null; |
|
447 | 447 | this.get_cell_elements().filter(function (index) { |
|
448 | 448 | if ($(this).data("cell").selected === true) { |
|
449 | 449 | result = index; |
|
450 | 450 | }; |
|
451 | 451 | }); |
|
452 | 452 | return result; |
|
453 | 453 | }; |
|
454 | 454 | |
|
455 | 455 | |
|
456 | 456 | // Cell selection. |
|
457 | 457 | |
|
458 | 458 | /** |
|
459 | 459 | * Programmatically select a cell. |
|
460 | 460 | * |
|
461 | 461 | * @method select |
|
462 | 462 | * @param {Number} index A cell's index |
|
463 | 463 | * @return {Notebook} This notebook |
|
464 | 464 | */ |
|
465 | 465 | Notebook.prototype.select = function (index) { |
|
466 | 466 | if (this.is_valid_cell_index(index)) { |
|
467 | 467 | var sindex = this.get_selected_index() |
|
468 | 468 | if (sindex !== null && index !== sindex) { |
|
469 | 469 | this.command_mode(); |
|
470 | 470 | this.get_cell(sindex).unselect(); |
|
471 | 471 | }; |
|
472 | 472 | var cell = this.get_cell(index); |
|
473 | 473 | cell.select(); |
|
474 | 474 | if (cell.cell_type === 'heading') { |
|
475 | 475 | $([IPython.events]).trigger('selected_cell_type_changed.Notebook', |
|
476 | 476 | {'cell_type':cell.cell_type,level:cell.level} |
|
477 | 477 | ); |
|
478 | 478 | } else { |
|
479 | 479 | $([IPython.events]).trigger('selected_cell_type_changed.Notebook', |
|
480 | 480 | {'cell_type':cell.cell_type} |
|
481 | 481 | ); |
|
482 | 482 | }; |
|
483 | 483 | }; |
|
484 | 484 | return this; |
|
485 | 485 | }; |
|
486 | 486 | |
|
487 | 487 | /** |
|
488 | 488 | * Programmatically select the next cell. |
|
489 | 489 | * |
|
490 | 490 | * @method select_next |
|
491 | 491 | * @return {Notebook} This notebook |
|
492 | 492 | */ |
|
493 | 493 | Notebook.prototype.select_next = function () { |
|
494 | 494 | var index = this.get_selected_index(); |
|
495 | 495 | this.select(index+1); |
|
496 | 496 | return this; |
|
497 | 497 | }; |
|
498 | 498 | |
|
499 | 499 | /** |
|
500 | 500 | * Programmatically select the previous cell. |
|
501 | 501 | * |
|
502 | 502 | * @method select_prev |
|
503 | 503 | * @return {Notebook} This notebook |
|
504 | 504 | */ |
|
505 | 505 | Notebook.prototype.select_prev = function () { |
|
506 | 506 | var index = this.get_selected_index(); |
|
507 | 507 | this.select(index-1); |
|
508 | 508 | return this; |
|
509 | 509 | }; |
|
510 | 510 | |
|
511 | 511 | |
|
512 | 512 | // Edit/Command mode |
|
513 | 513 | |
|
514 | 514 | Notebook.prototype.get_edit_index = function () { |
|
515 | 515 | var result = null; |
|
516 | 516 | this.get_cell_elements().filter(function (index) { |
|
517 | 517 | if ($(this).data("cell").mode === 'edit') { |
|
518 | 518 | result = index; |
|
519 | 519 | }; |
|
520 | 520 | }); |
|
521 | 521 | return result; |
|
522 | 522 | }; |
|
523 | 523 | |
|
524 | 524 | Notebook.prototype.command_mode = function () { |
|
525 | 525 | if (this.mode !== 'command') { |
|
526 | 526 | var index = this.get_edit_index(); |
|
527 | 527 | var cell = this.get_cell(index); |
|
528 | 528 | if (cell) { |
|
529 | 529 | cell.command_mode(); |
|
530 | 530 | }; |
|
531 | 531 | this.mode = 'command'; |
|
532 | 532 | IPython.keyboard_manager.command_mode(); |
|
533 | 533 | }; |
|
534 | 534 | }; |
|
535 | 535 | |
|
536 | 536 | Notebook.prototype.edit_mode = function () { |
|
537 | 537 | if (this.mode !== 'edit') { |
|
538 | 538 | var cell = this.get_selected_cell(); |
|
539 | 539 | if (cell === null) {return;} // No cell is selected |
|
540 | 540 | // We need to set the mode to edit to prevent reentering this method |
|
541 | 541 | // when cell.edit_mode() is called below. |
|
542 | 542 | this.mode = 'edit'; |
|
543 | 543 | IPython.keyboard_manager.edit_mode(); |
|
544 | 544 | cell.edit_mode(); |
|
545 | 545 | }; |
|
546 | 546 | }; |
|
547 | 547 | |
|
548 | 548 | Notebook.prototype.focus_cell = function () { |
|
549 | 549 | var cell = this.get_selected_cell(); |
|
550 | 550 | if (cell === null) {return;} // No cell is selected |
|
551 | 551 | cell.focus_cell(); |
|
552 | 552 | }; |
|
553 | 553 | |
|
554 | 554 | // Cell movement |
|
555 | 555 | |
|
556 | 556 | /** |
|
557 | 557 | * Move given (or selected) cell up and select it. |
|
558 | 558 | * |
|
559 | 559 | * @method move_cell_up |
|
560 | 560 | * @param [index] {integer} cell index |
|
561 | 561 | * @return {Notebook} This notebook |
|
562 | 562 | **/ |
|
563 | 563 | Notebook.prototype.move_cell_up = function (index) { |
|
564 | 564 | var i = this.index_or_selected(index); |
|
565 | 565 | if (this.is_valid_cell_index(i) && i > 0) { |
|
566 | 566 | var pivot = this.get_cell_element(i-1); |
|
567 | 567 | var tomove = this.get_cell_element(i); |
|
568 | 568 | if (pivot !== null && tomove !== null) { |
|
569 | 569 | tomove.detach(); |
|
570 | 570 | pivot.before(tomove); |
|
571 | 571 | this.select(i-1); |
|
572 | 572 | var cell = this.get_selected_cell(); |
|
573 | 573 | cell.focus_cell(); |
|
574 | 574 | }; |
|
575 | 575 | this.set_dirty(true); |
|
576 | 576 | }; |
|
577 | 577 | return this; |
|
578 | 578 | }; |
|
579 | 579 | |
|
580 | 580 | |
|
581 | 581 | /** |
|
582 | 582 | * Move given (or selected) cell down and select it |
|
583 | 583 | * |
|
584 | 584 | * @method move_cell_down |
|
585 | 585 | * @param [index] {integer} cell index |
|
586 | 586 | * @return {Notebook} This notebook |
|
587 | 587 | **/ |
|
588 | 588 | Notebook.prototype.move_cell_down = function (index) { |
|
589 | 589 | var i = this.index_or_selected(index); |
|
590 | 590 | if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) { |
|
591 | 591 | var pivot = this.get_cell_element(i+1); |
|
592 | 592 | var tomove = this.get_cell_element(i); |
|
593 | 593 | if (pivot !== null && tomove !== null) { |
|
594 | 594 | tomove.detach(); |
|
595 | 595 | pivot.after(tomove); |
|
596 | 596 | this.select(i+1); |
|
597 | 597 | var cell = this.get_selected_cell(); |
|
598 | 598 | cell.focus_cell(); |
|
599 | 599 | }; |
|
600 | 600 | }; |
|
601 | 601 | this.set_dirty(); |
|
602 | 602 | return this; |
|
603 | 603 | }; |
|
604 | 604 | |
|
605 | 605 | |
|
606 | 606 | // Insertion, deletion. |
|
607 | 607 | |
|
608 | 608 | /** |
|
609 | 609 | * Delete a cell from the notebook. |
|
610 | 610 | * |
|
611 | 611 | * @method delete_cell |
|
612 | 612 | * @param [index] A cell's numeric index |
|
613 | 613 | * @return {Notebook} This notebook |
|
614 | 614 | */ |
|
615 | 615 | Notebook.prototype.delete_cell = function (index) { |
|
616 | 616 | var i = this.index_or_selected(index); |
|
617 | 617 | var cell = this.get_selected_cell(); |
|
618 | 618 | this.undelete_backup = cell.toJSON(); |
|
619 | 619 | $('#undelete_cell').removeClass('disabled'); |
|
620 | 620 | if (this.is_valid_cell_index(i)) { |
|
621 | 621 | var old_ncells = this.ncells(); |
|
622 | 622 | var ce = this.get_cell_element(i); |
|
623 | 623 | ce.remove(); |
|
624 | 624 | if (i === 0) { |
|
625 | 625 | // Always make sure we have at least one cell. |
|
626 | 626 | if (old_ncells === 1) { |
|
627 | 627 | this.insert_cell_below('code'); |
|
628 | 628 | } |
|
629 | 629 | this.select(0); |
|
630 | 630 | this.undelete_index = 0; |
|
631 | 631 | this.undelete_below = false; |
|
632 | 632 | } else if (i === old_ncells-1 && i !== 0) { |
|
633 | 633 | this.select(i-1); |
|
634 | 634 | this.undelete_index = i - 1; |
|
635 | 635 | this.undelete_below = true; |
|
636 | 636 | } else { |
|
637 | 637 | this.select(i); |
|
638 | 638 | this.undelete_index = i; |
|
639 | 639 | this.undelete_below = false; |
|
640 | 640 | }; |
|
641 | 641 | $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i}); |
|
642 | 642 | this.set_dirty(true); |
|
643 | 643 | }; |
|
644 | 644 | return this; |
|
645 | 645 | }; |
|
646 | 646 | |
|
647 | 647 | /** |
|
648 | 648 | * Restore the most recently deleted cell. |
|
649 | 649 | * |
|
650 | 650 | * @method undelete |
|
651 | 651 | */ |
|
652 | 652 | Notebook.prototype.undelete_cell = function() { |
|
653 | 653 | if (this.undelete_backup !== null && this.undelete_index !== null) { |
|
654 | 654 | var current_index = this.get_selected_index(); |
|
655 | 655 | if (this.undelete_index < current_index) { |
|
656 | 656 | current_index = current_index + 1; |
|
657 | 657 | } |
|
658 | 658 | if (this.undelete_index >= this.ncells()) { |
|
659 | 659 | this.select(this.ncells() - 1); |
|
660 | 660 | } |
|
661 | 661 | else { |
|
662 | 662 | this.select(this.undelete_index); |
|
663 | 663 | } |
|
664 | 664 | var cell_data = this.undelete_backup; |
|
665 | 665 | var new_cell = null; |
|
666 | 666 | if (this.undelete_below) { |
|
667 | 667 | new_cell = this.insert_cell_below(cell_data.cell_type); |
|
668 | 668 | } else { |
|
669 | 669 | new_cell = this.insert_cell_above(cell_data.cell_type); |
|
670 | 670 | } |
|
671 | 671 | new_cell.fromJSON(cell_data); |
|
672 | 672 | if (this.undelete_below) { |
|
673 | 673 | this.select(current_index+1); |
|
674 | 674 | } else { |
|
675 | 675 | this.select(current_index); |
|
676 | 676 | } |
|
677 | 677 | this.undelete_backup = null; |
|
678 | 678 | this.undelete_index = null; |
|
679 | 679 | } |
|
680 | 680 | $('#undelete_cell').addClass('disabled'); |
|
681 | 681 | } |
|
682 | 682 | |
|
683 | 683 | /** |
|
684 | 684 | * Insert a cell so that after insertion the cell is at given index. |
|
685 | 685 | * |
|
686 | 686 | * Similar to insert_above, but index parameter is mandatory |
|
687 | 687 | * |
|
688 | 688 | * Index will be brought back into the accissible range [0,n] |
|
689 | 689 | * |
|
690 | 690 | * @method insert_cell_at_index |
|
691 | 691 | * @param type {string} in ['code','markdown','heading'] |
|
692 | 692 | * @param [index] {int} a valid index where to inser cell |
|
693 | 693 | * |
|
694 | 694 | * @return cell {cell|null} created cell or null |
|
695 | 695 | **/ |
|
696 | 696 | Notebook.prototype.insert_cell_at_index = function(type, index){ |
|
697 | 697 | |
|
698 | 698 | var ncells = this.ncells(); |
|
699 | 699 | var index = Math.min(index,ncells); |
|
700 | 700 | index = Math.max(index,0); |
|
701 | 701 | var cell = null; |
|
702 | 702 | |
|
703 | 703 | if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) { |
|
704 | 704 | if (type === 'code') { |
|
705 | 705 | cell = new IPython.CodeCell(this.kernel); |
|
706 | 706 | cell.set_input_prompt(); |
|
707 | 707 | } else if (type === 'markdown') { |
|
708 | 708 | cell = new IPython.MarkdownCell(); |
|
709 | 709 | } else if (type === 'raw') { |
|
710 | 710 | cell = new IPython.RawCell(); |
|
711 | 711 | } else if (type === 'heading') { |
|
712 | 712 | cell = new IPython.HeadingCell(); |
|
713 | 713 | } |
|
714 | 714 | |
|
715 | 715 | if(this._insert_element_at_index(cell.element,index)) { |
|
716 | 716 | cell.render(); |
|
717 | 717 | $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index}); |
|
718 | 718 | cell.refresh(); |
|
719 | 719 | // We used to select the cell after we refresh it, but there |
|
720 | 720 | // are now cases were this method is called where select is |
|
721 | 721 | // not appropriate. The selection logic should be handled by the |
|
722 | 722 | // caller of the the top level insert_cell methods. |
|
723 | 723 | this.set_dirty(true); |
|
724 | 724 | } |
|
725 | 725 | } |
|
726 | 726 | return cell; |
|
727 | 727 | |
|
728 | 728 | }; |
|
729 | 729 | |
|
730 | 730 | /** |
|
731 | 731 | * Insert an element at given cell index. |
|
732 | 732 | * |
|
733 | 733 | * @method _insert_element_at_index |
|
734 | 734 | * @param element {dom element} a cell element |
|
735 | 735 | * @param [index] {int} a valid index where to inser cell |
|
736 | 736 | * @private |
|
737 | 737 | * |
|
738 | 738 | * return true if everything whent fine. |
|
739 | 739 | **/ |
|
740 | 740 | Notebook.prototype._insert_element_at_index = function(element, index){ |
|
741 | 741 | if (element === undefined){ |
|
742 | 742 | return false; |
|
743 | 743 | } |
|
744 | 744 | |
|
745 | 745 | var ncells = this.ncells(); |
|
746 | 746 | |
|
747 | 747 | if (ncells === 0) { |
|
748 | 748 | // special case append if empty |
|
749 | 749 | this.element.find('div.end_space').before(element); |
|
750 | 750 | } else if ( ncells === index ) { |
|
751 | 751 | // special case append it the end, but not empty |
|
752 | 752 | this.get_cell_element(index-1).after(element); |
|
753 | 753 | } else if (this.is_valid_cell_index(index)) { |
|
754 | 754 | // otherwise always somewhere to append to |
|
755 | 755 | this.get_cell_element(index).before(element); |
|
756 | 756 | } else { |
|
757 | 757 | return false; |
|
758 | 758 | } |
|
759 | 759 | |
|
760 | 760 | if (this.undelete_index !== null && index <= this.undelete_index) { |
|
761 | 761 | this.undelete_index = this.undelete_index + 1; |
|
762 | 762 | this.set_dirty(true); |
|
763 | 763 | } |
|
764 | 764 | return true; |
|
765 | 765 | }; |
|
766 | 766 | |
|
767 | 767 | /** |
|
768 | 768 | * Insert a cell of given type above given index, or at top |
|
769 | 769 | * of notebook if index smaller than 0. |
|
770 | 770 | * |
|
771 | 771 | * default index value is the one of currently selected cell |
|
772 | 772 | * |
|
773 | 773 | * @method insert_cell_above |
|
774 | 774 | * @param type {string} cell type |
|
775 | 775 | * @param [index] {integer} |
|
776 | 776 | * |
|
777 | 777 | * @return handle to created cell or null |
|
778 | 778 | **/ |
|
779 | 779 | Notebook.prototype.insert_cell_above = function (type, index) { |
|
780 | 780 | index = this.index_or_selected(index); |
|
781 | 781 | return this.insert_cell_at_index(type, index); |
|
782 | 782 | }; |
|
783 | 783 | |
|
784 | 784 | /** |
|
785 | 785 | * Insert a cell of given type below given index, or at bottom |
|
786 | 786 | * of notebook if index greater thatn number of cell |
|
787 | 787 | * |
|
788 | 788 | * default index value is the one of currently selected cell |
|
789 | 789 | * |
|
790 | 790 | * @method insert_cell_below |
|
791 | 791 | * @param type {string} cell type |
|
792 | 792 | * @param [index] {integer} |
|
793 | 793 | * |
|
794 | 794 | * @return handle to created cell or null |
|
795 | 795 | * |
|
796 | 796 | **/ |
|
797 | 797 | Notebook.prototype.insert_cell_below = function (type, index) { |
|
798 | 798 | index = this.index_or_selected(index); |
|
799 | 799 | return this.insert_cell_at_index(type, index+1); |
|
800 | 800 | }; |
|
801 | 801 | |
|
802 | 802 | |
|
803 | 803 | /** |
|
804 | 804 | * Insert cell at end of notebook |
|
805 | 805 | * |
|
806 | 806 | * @method insert_cell_at_bottom |
|
807 | 807 | * @param {String} type cell type |
|
808 | 808 | * |
|
809 | 809 | * @return the added cell; or null |
|
810 | 810 | **/ |
|
811 | 811 | Notebook.prototype.insert_cell_at_bottom = function (type){ |
|
812 | 812 | var len = this.ncells(); |
|
813 | 813 | return this.insert_cell_below(type,len-1); |
|
814 | 814 | }; |
|
815 | 815 | |
|
816 | 816 | /** |
|
817 | 817 | * Turn a cell into a code cell. |
|
818 | 818 | * |
|
819 | 819 | * @method to_code |
|
820 | 820 | * @param {Number} [index] A cell's index |
|
821 | 821 | */ |
|
822 | 822 | Notebook.prototype.to_code = function (index) { |
|
823 | 823 | var i = this.index_or_selected(index); |
|
824 | 824 | if (this.is_valid_cell_index(i)) { |
|
825 | 825 | var source_element = this.get_cell_element(i); |
|
826 | 826 | var source_cell = source_element.data("cell"); |
|
827 | 827 | if (!(source_cell instanceof IPython.CodeCell)) { |
|
828 | 828 | var target_cell = this.insert_cell_below('code',i); |
|
829 | 829 | var text = source_cell.get_text(); |
|
830 | 830 | if (text === source_cell.placeholder) { |
|
831 | 831 | text = ''; |
|
832 | 832 | } |
|
833 | 833 | target_cell.set_text(text); |
|
834 | 834 | // make this value the starting point, so that we can only undo |
|
835 | 835 | // to this state, instead of a blank cell |
|
836 | 836 | target_cell.code_mirror.clearHistory(); |
|
837 | 837 | source_element.remove(); |
|
838 | 838 | this.select(i); |
|
839 | 839 | this.edit_mode(); |
|
840 | 840 | this.set_dirty(true); |
|
841 | 841 | }; |
|
842 | 842 | }; |
|
843 | 843 | }; |
|
844 | 844 | |
|
845 | 845 | /** |
|
846 | 846 | * Turn a cell into a Markdown cell. |
|
847 | 847 | * |
|
848 | 848 | * @method to_markdown |
|
849 | 849 | * @param {Number} [index] A cell's index |
|
850 | 850 | */ |
|
851 | 851 | Notebook.prototype.to_markdown = function (index) { |
|
852 | 852 | var i = this.index_or_selected(index); |
|
853 | 853 | if (this.is_valid_cell_index(i)) { |
|
854 | 854 | var source_element = this.get_cell_element(i); |
|
855 | 855 | var source_cell = source_element.data("cell"); |
|
856 | 856 | if (!(source_cell instanceof IPython.MarkdownCell)) { |
|
857 | 857 | var target_cell = this.insert_cell_below('markdown',i); |
|
858 | 858 | var text = source_cell.get_text(); |
|
859 | 859 | if (text === source_cell.placeholder) { |
|
860 | 860 | text = ''; |
|
861 | 861 | }; |
|
862 | 862 | // We must show the editor before setting its contents |
|
863 | 863 | target_cell.unrender(); |
|
864 | 864 | target_cell.set_text(text); |
|
865 | 865 | // make this value the starting point, so that we can only undo |
|
866 | 866 | // to this state, instead of a blank cell |
|
867 | 867 | target_cell.code_mirror.clearHistory(); |
|
868 | 868 | source_element.remove(); |
|
869 | 869 | this.select(i); |
|
870 | 870 | this.edit_mode(); |
|
871 | 871 | this.set_dirty(true); |
|
872 | 872 | }; |
|
873 | 873 | }; |
|
874 | 874 | }; |
|
875 | 875 | |
|
876 | 876 | /** |
|
877 | 877 | * Turn a cell into a raw text cell. |
|
878 | 878 | * |
|
879 | 879 | * @method to_raw |
|
880 | 880 | * @param {Number} [index] A cell's index |
|
881 | 881 | */ |
|
882 | 882 | Notebook.prototype.to_raw = function (index) { |
|
883 | 883 | var i = this.index_or_selected(index); |
|
884 | 884 | if (this.is_valid_cell_index(i)) { |
|
885 | 885 | var source_element = this.get_cell_element(i); |
|
886 | 886 | var source_cell = source_element.data("cell"); |
|
887 | 887 | var target_cell = null; |
|
888 | 888 | if (!(source_cell instanceof IPython.RawCell)) { |
|
889 | 889 | target_cell = this.insert_cell_below('raw',i); |
|
890 | 890 | var text = source_cell.get_text(); |
|
891 | 891 | if (text === source_cell.placeholder) { |
|
892 | 892 | text = ''; |
|
893 | 893 | }; |
|
894 | 894 | // We must show the editor before setting its contents |
|
895 | 895 | target_cell.unrender(); |
|
896 | 896 | target_cell.set_text(text); |
|
897 | 897 | // make this value the starting point, so that we can only undo |
|
898 | 898 | // to this state, instead of a blank cell |
|
899 | 899 | target_cell.code_mirror.clearHistory(); |
|
900 | 900 | source_element.remove(); |
|
901 | 901 | this.select(i); |
|
902 | 902 | this.edit_mode(); |
|
903 | 903 | this.set_dirty(true); |
|
904 | 904 | }; |
|
905 | 905 | }; |
|
906 | 906 | }; |
|
907 | 907 | |
|
908 | 908 | /** |
|
909 | 909 | * Turn a cell into a heading cell. |
|
910 | 910 | * |
|
911 | 911 | * @method to_heading |
|
912 | 912 | * @param {Number} [index] A cell's index |
|
913 | 913 | * @param {Number} [level] A heading level (e.g., 1 becomes <h1>) |
|
914 | 914 | */ |
|
915 | 915 | Notebook.prototype.to_heading = function (index, level) { |
|
916 | 916 | level = level || 1; |
|
917 | 917 | var i = this.index_or_selected(index); |
|
918 | 918 | if (this.is_valid_cell_index(i)) { |
|
919 | 919 | var source_element = this.get_cell_element(i); |
|
920 | 920 | var source_cell = source_element.data("cell"); |
|
921 | 921 | var target_cell = null; |
|
922 | 922 | if (source_cell instanceof IPython.HeadingCell) { |
|
923 | 923 | source_cell.set_level(level); |
|
924 | 924 | } else { |
|
925 | 925 | target_cell = this.insert_cell_below('heading',i); |
|
926 | 926 | var text = source_cell.get_text(); |
|
927 | 927 | if (text === source_cell.placeholder) { |
|
928 | 928 | text = ''; |
|
929 | 929 | }; |
|
930 | 930 | // We must show the editor before setting its contents |
|
931 | 931 | target_cell.set_level(level); |
|
932 | 932 | target_cell.unrender(); |
|
933 | 933 | target_cell.set_text(text); |
|
934 | 934 | // make this value the starting point, so that we can only undo |
|
935 | 935 | // to this state, instead of a blank cell |
|
936 | 936 | target_cell.code_mirror.clearHistory(); |
|
937 | 937 | source_element.remove(); |
|
938 | 938 | this.select(i); |
|
939 | 939 | }; |
|
940 | 940 | this.edit_mode(); |
|
941 | 941 | this.set_dirty(true); |
|
942 | 942 | $([IPython.events]).trigger('selected_cell_type_changed.Notebook', |
|
943 | 943 | {'cell_type':'heading',level:level} |
|
944 | 944 | ); |
|
945 | 945 | }; |
|
946 | 946 | }; |
|
947 | 947 | |
|
948 | 948 | |
|
949 | 949 | // Cut/Copy/Paste |
|
950 | 950 | |
|
951 | 951 | /** |
|
952 | 952 | * Enable UI elements for pasting cells. |
|
953 | 953 | * |
|
954 | 954 | * @method enable_paste |
|
955 | 955 | */ |
|
956 | 956 | Notebook.prototype.enable_paste = function () { |
|
957 | 957 | var that = this; |
|
958 | 958 | if (!this.paste_enabled) { |
|
959 | 959 | $('#paste_cell_replace').removeClass('disabled') |
|
960 | 960 | .on('click', function () {that.paste_cell_replace();}); |
|
961 | 961 | $('#paste_cell_above').removeClass('disabled') |
|
962 | 962 | .on('click', function () {that.paste_cell_above();}); |
|
963 | 963 | $('#paste_cell_below').removeClass('disabled') |
|
964 | 964 | .on('click', function () {that.paste_cell_below();}); |
|
965 | 965 | this.paste_enabled = true; |
|
966 | 966 | }; |
|
967 | 967 | }; |
|
968 | 968 | |
|
969 | 969 | /** |
|
970 | 970 | * Disable UI elements for pasting cells. |
|
971 | 971 | * |
|
972 | 972 | * @method disable_paste |
|
973 | 973 | */ |
|
974 | 974 | Notebook.prototype.disable_paste = function () { |
|
975 | 975 | if (this.paste_enabled) { |
|
976 | 976 | $('#paste_cell_replace').addClass('disabled').off('click'); |
|
977 | 977 | $('#paste_cell_above').addClass('disabled').off('click'); |
|
978 | 978 | $('#paste_cell_below').addClass('disabled').off('click'); |
|
979 | 979 | this.paste_enabled = false; |
|
980 | 980 | }; |
|
981 | 981 | }; |
|
982 | 982 | |
|
983 | 983 | /** |
|
984 | 984 | * Cut a cell. |
|
985 | 985 | * |
|
986 | 986 | * @method cut_cell |
|
987 | 987 | */ |
|
988 | 988 | Notebook.prototype.cut_cell = function () { |
|
989 | 989 | this.copy_cell(); |
|
990 | 990 | this.delete_cell(); |
|
991 | 991 | } |
|
992 | 992 | |
|
993 | 993 | /** |
|
994 | 994 | * Copy a cell. |
|
995 | 995 | * |
|
996 | 996 | * @method copy_cell |
|
997 | 997 | */ |
|
998 | 998 | Notebook.prototype.copy_cell = function () { |
|
999 | 999 | var cell = this.get_selected_cell(); |
|
1000 | 1000 | this.clipboard = cell.toJSON(); |
|
1001 | 1001 | this.enable_paste(); |
|
1002 | 1002 | }; |
|
1003 | 1003 | |
|
1004 | 1004 | /** |
|
1005 | 1005 | * Replace the selected cell with a cell in the clipboard. |
|
1006 | 1006 | * |
|
1007 | 1007 | * @method paste_cell_replace |
|
1008 | 1008 | */ |
|
1009 | 1009 | Notebook.prototype.paste_cell_replace = function () { |
|
1010 | 1010 | if (this.clipboard !== null && this.paste_enabled) { |
|
1011 | 1011 | var cell_data = this.clipboard; |
|
1012 | 1012 | var new_cell = this.insert_cell_above(cell_data.cell_type); |
|
1013 | 1013 | new_cell.fromJSON(cell_data); |
|
1014 | 1014 | var old_cell = this.get_next_cell(new_cell); |
|
1015 | 1015 | this.delete_cell(this.find_cell_index(old_cell)); |
|
1016 | 1016 | this.select(this.find_cell_index(new_cell)); |
|
1017 | 1017 | }; |
|
1018 | 1018 | }; |
|
1019 | 1019 | |
|
1020 | 1020 | /** |
|
1021 | 1021 | * Paste a cell from the clipboard above the selected cell. |
|
1022 | 1022 | * |
|
1023 | 1023 | * @method paste_cell_above |
|
1024 | 1024 | */ |
|
1025 | 1025 | Notebook.prototype.paste_cell_above = function () { |
|
1026 | 1026 | if (this.clipboard !== null && this.paste_enabled) { |
|
1027 | 1027 | var cell_data = this.clipboard; |
|
1028 | 1028 | var new_cell = this.insert_cell_above(cell_data.cell_type); |
|
1029 | 1029 | new_cell.fromJSON(cell_data); |
|
1030 | 1030 | }; |
|
1031 | 1031 | }; |
|
1032 | 1032 | |
|
1033 | 1033 | /** |
|
1034 | 1034 | * Paste a cell from the clipboard below the selected cell. |
|
1035 | 1035 | * |
|
1036 | 1036 | * @method paste_cell_below |
|
1037 | 1037 | */ |
|
1038 | 1038 | Notebook.prototype.paste_cell_below = function () { |
|
1039 | 1039 | if (this.clipboard !== null && this.paste_enabled) { |
|
1040 | 1040 | var cell_data = this.clipboard; |
|
1041 | 1041 | var new_cell = this.insert_cell_below(cell_data.cell_type); |
|
1042 | 1042 | new_cell.fromJSON(cell_data); |
|
1043 | 1043 | }; |
|
1044 | 1044 | }; |
|
1045 | 1045 | |
|
1046 | 1046 | // Split/merge |
|
1047 | 1047 | |
|
1048 | 1048 | /** |
|
1049 | 1049 | * Split the selected cell into two, at the cursor. |
|
1050 | 1050 | * |
|
1051 | 1051 | * @method split_cell |
|
1052 | 1052 | */ |
|
1053 | 1053 | Notebook.prototype.split_cell = function () { |
|
1054 | 1054 | var mdc = IPython.MarkdownCell; |
|
1055 | 1055 | var rc = IPython.RawCell; |
|
1056 | 1056 | var cell = this.get_selected_cell(); |
|
1057 | 1057 | if (cell.is_splittable()) { |
|
1058 | 1058 | var texta = cell.get_pre_cursor(); |
|
1059 | 1059 | var textb = cell.get_post_cursor(); |
|
1060 | 1060 | if (cell instanceof IPython.CodeCell) { |
|
1061 | 1061 | // In this case the operations keep the notebook in its existing mode |
|
1062 | 1062 | // so we don't need to do any post-op mode changes. |
|
1063 | 1063 | cell.set_text(textb); |
|
1064 | 1064 | var new_cell = this.insert_cell_above('code'); |
|
1065 | 1065 | new_cell.set_text(texta); |
|
1066 | 1066 | } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) { |
|
1067 | 1067 | // We know cell is !rendered so we can use set_text. |
|
1068 | 1068 | cell.set_text(textb); |
|
1069 | 1069 | var new_cell = this.insert_cell_above(cell.cell_type); |
|
1070 | 1070 | // Unrender the new cell so we can call set_text. |
|
1071 | 1071 | new_cell.unrender(); |
|
1072 | 1072 | new_cell.set_text(texta); |
|
1073 | 1073 | } |
|
1074 | 1074 | }; |
|
1075 | 1075 | }; |
|
1076 | 1076 | |
|
1077 | 1077 | /** |
|
1078 | 1078 | * Combine the selected cell into the cell above it. |
|
1079 | 1079 | * |
|
1080 | 1080 | * @method merge_cell_above |
|
1081 | 1081 | */ |
|
1082 | 1082 | Notebook.prototype.merge_cell_above = function () { |
|
1083 | 1083 | var mdc = IPython.MarkdownCell; |
|
1084 | 1084 | var rc = IPython.RawCell; |
|
1085 | 1085 | var index = this.get_selected_index(); |
|
1086 | 1086 | var cell = this.get_cell(index); |
|
1087 | 1087 | var render = cell.rendered; |
|
1088 | 1088 | if (!cell.is_mergeable()) { |
|
1089 | 1089 | return; |
|
1090 | 1090 | } |
|
1091 | 1091 | if (index > 0) { |
|
1092 | 1092 | var upper_cell = this.get_cell(index-1); |
|
1093 | 1093 | if (!upper_cell.is_mergeable()) { |
|
1094 | 1094 | return; |
|
1095 | 1095 | } |
|
1096 | 1096 | var upper_text = upper_cell.get_text(); |
|
1097 | 1097 | var text = cell.get_text(); |
|
1098 | 1098 | if (cell instanceof IPython.CodeCell) { |
|
1099 | 1099 | cell.set_text(upper_text+'\n'+text); |
|
1100 | 1100 | } else if ((cell instanceof mdc) || (cell instanceof rc)) { |
|
1101 | 1101 | cell.unrender(); // Must unrender before we set_text. |
|
1102 | 1102 | cell.set_text(upper_text+'\n\n'+text); |
|
1103 | 1103 | if (render) { |
|
1104 | 1104 | // The rendered state of the final cell should match |
|
1105 | 1105 | // that of the original selected cell; |
|
1106 | 1106 | cell.render(); |
|
1107 | 1107 | } |
|
1108 | 1108 | }; |
|
1109 | 1109 | this.delete_cell(index-1); |
|
1110 | 1110 | this.select(this.find_cell_index(cell)); |
|
1111 | 1111 | }; |
|
1112 | 1112 | }; |
|
1113 | 1113 | |
|
1114 | 1114 | /** |
|
1115 | 1115 | * Combine the selected cell into the cell below it. |
|
1116 | 1116 | * |
|
1117 | 1117 | * @method merge_cell_below |
|
1118 | 1118 | */ |
|
1119 | 1119 | Notebook.prototype.merge_cell_below = function () { |
|
1120 | 1120 | var mdc = IPython.MarkdownCell; |
|
1121 | 1121 | var rc = IPython.RawCell; |
|
1122 | 1122 | var index = this.get_selected_index(); |
|
1123 | 1123 | var cell = this.get_cell(index); |
|
1124 | 1124 | var render = cell.rendered; |
|
1125 | 1125 | if (!cell.is_mergeable()) { |
|
1126 | 1126 | return; |
|
1127 | 1127 | } |
|
1128 | 1128 | if (index < this.ncells()-1) { |
|
1129 | 1129 | var lower_cell = this.get_cell(index+1); |
|
1130 | 1130 | if (!lower_cell.is_mergeable()) { |
|
1131 | 1131 | return; |
|
1132 | 1132 | } |
|
1133 | 1133 | var lower_text = lower_cell.get_text(); |
|
1134 | 1134 | var text = cell.get_text(); |
|
1135 | 1135 | if (cell instanceof IPython.CodeCell) { |
|
1136 | 1136 | cell.set_text(text+'\n'+lower_text); |
|
1137 | 1137 | } else if ((cell instanceof mdc) || (cell instanceof rc)) { |
|
1138 | 1138 | cell.unrender(); // Must unrender before we set_text. |
|
1139 | 1139 | cell.set_text(text+'\n\n'+lower_text); |
|
1140 | 1140 | if (render) { |
|
1141 | 1141 | // The rendered state of the final cell should match |
|
1142 | 1142 | // that of the original selected cell; |
|
1143 | 1143 | cell.render(); |
|
1144 | 1144 | } |
|
1145 | 1145 | }; |
|
1146 | 1146 | this.delete_cell(index+1); |
|
1147 | 1147 | this.select(this.find_cell_index(cell)); |
|
1148 | 1148 | }; |
|
1149 | 1149 | }; |
|
1150 | 1150 | |
|
1151 | 1151 | |
|
1152 | 1152 | // Cell collapsing and output clearing |
|
1153 | 1153 | |
|
1154 | 1154 | /** |
|
1155 | 1155 | * Hide a cell's output. |
|
1156 | 1156 | * |
|
1157 | 1157 | * @method collapse |
|
1158 | 1158 | * @param {Number} index A cell's numeric index |
|
1159 | 1159 | */ |
|
1160 | 1160 | Notebook.prototype.collapse = function (index) { |
|
1161 | 1161 | var i = this.index_or_selected(index); |
|
1162 | 1162 | this.get_cell(i).collapse(); |
|
1163 | 1163 | this.set_dirty(true); |
|
1164 | 1164 | }; |
|
1165 | 1165 | |
|
1166 | 1166 | /** |
|
1167 | 1167 | * Show a cell's output. |
|
1168 | 1168 | * |
|
1169 | 1169 | * @method expand |
|
1170 | 1170 | * @param {Number} index A cell's numeric index |
|
1171 | 1171 | */ |
|
1172 | 1172 | Notebook.prototype.expand = function (index) { |
|
1173 | 1173 | var i = this.index_or_selected(index); |
|
1174 | 1174 | this.get_cell(i).expand(); |
|
1175 | 1175 | this.set_dirty(true); |
|
1176 | 1176 | }; |
|
1177 | 1177 | |
|
1178 | 1178 | /** Toggle whether a cell's output is collapsed or expanded. |
|
1179 | 1179 | * |
|
1180 | 1180 | * @method toggle_output |
|
1181 | 1181 | * @param {Number} index A cell's numeric index |
|
1182 | 1182 | */ |
|
1183 | 1183 | Notebook.prototype.toggle_output = function (index) { |
|
1184 | 1184 | var i = this.index_or_selected(index); |
|
1185 | 1185 | this.get_cell(i).toggle_output(); |
|
1186 | 1186 | this.set_dirty(true); |
|
1187 | 1187 | }; |
|
1188 | 1188 | |
|
1189 | 1189 | /** |
|
1190 | 1190 | * Toggle a scrollbar for long cell outputs. |
|
1191 | 1191 | * |
|
1192 | 1192 | * @method toggle_output_scroll |
|
1193 | 1193 | * @param {Number} index A cell's numeric index |
|
1194 | 1194 | */ |
|
1195 | 1195 | Notebook.prototype.toggle_output_scroll = function (index) { |
|
1196 | 1196 | var i = this.index_or_selected(index); |
|
1197 | 1197 | this.get_cell(i).toggle_output_scroll(); |
|
1198 | 1198 | }; |
|
1199 | 1199 | |
|
1200 | 1200 | /** |
|
1201 | 1201 | * Hide each code cell's output area. |
|
1202 | 1202 | * |
|
1203 | 1203 | * @method collapse_all_output |
|
1204 | 1204 | */ |
|
1205 | 1205 | Notebook.prototype.collapse_all_output = function () { |
|
1206 | 1206 | var ncells = this.ncells(); |
|
1207 | 1207 | var cells = this.get_cells(); |
|
1208 | 1208 | for (var i=0; i<ncells; i++) { |
|
1209 | 1209 | if (cells[i] instanceof IPython.CodeCell) { |
|
1210 | 1210 | cells[i].output_area.collapse(); |
|
1211 | 1211 | } |
|
1212 | 1212 | }; |
|
1213 | 1213 | // this should not be set if the `collapse` key is removed from nbformat |
|
1214 | 1214 | this.set_dirty(true); |
|
1215 | 1215 | }; |
|
1216 | 1216 | |
|
1217 | 1217 | /** |
|
1218 | 1218 | * Expand each code cell's output area, and add a scrollbar for long output. |
|
1219 | 1219 | * |
|
1220 | 1220 | * @method scroll_all_output |
|
1221 | 1221 | */ |
|
1222 | 1222 | Notebook.prototype.scroll_all_output = function () { |
|
1223 | 1223 | var ncells = this.ncells(); |
|
1224 | 1224 | var cells = this.get_cells(); |
|
1225 | 1225 | for (var i=0; i<ncells; i++) { |
|
1226 | 1226 | if (cells[i] instanceof IPython.CodeCell) { |
|
1227 | 1227 | cells[i].output_area.expand(); |
|
1228 | 1228 | cells[i].output_area.scroll_if_long(); |
|
1229 | 1229 | } |
|
1230 | 1230 | }; |
|
1231 | 1231 | // this should not be set if the `collapse` key is removed from nbformat |
|
1232 | 1232 | this.set_dirty(true); |
|
1233 | 1233 | }; |
|
1234 | 1234 | |
|
1235 | 1235 | /** |
|
1236 | 1236 | * Expand each code cell's output area, and remove scrollbars. |
|
1237 | 1237 | * |
|
1238 | 1238 | * @method expand_all_output |
|
1239 | 1239 | */ |
|
1240 | 1240 | Notebook.prototype.expand_all_output = function () { |
|
1241 | 1241 | var ncells = this.ncells(); |
|
1242 | 1242 | var cells = this.get_cells(); |
|
1243 | 1243 | for (var i=0; i<ncells; i++) { |
|
1244 | 1244 | if (cells[i] instanceof IPython.CodeCell) { |
|
1245 | 1245 | cells[i].output_area.expand(); |
|
1246 | 1246 | cells[i].output_area.unscroll_area(); |
|
1247 | 1247 | } |
|
1248 | 1248 | }; |
|
1249 | 1249 | // this should not be set if the `collapse` key is removed from nbformat |
|
1250 | 1250 | this.set_dirty(true); |
|
1251 | 1251 | }; |
|
1252 | 1252 | |
|
1253 | 1253 | /** |
|
1254 | 1254 | * Clear each code cell's output area. |
|
1255 | 1255 | * |
|
1256 | 1256 | * @method clear_all_output |
|
1257 | 1257 | */ |
|
1258 | 1258 | Notebook.prototype.clear_all_output = function () { |
|
1259 | 1259 | var ncells = this.ncells(); |
|
1260 | 1260 | var cells = this.get_cells(); |
|
1261 | 1261 | for (var i=0; i<ncells; i++) { |
|
1262 | 1262 | if (cells[i] instanceof IPython.CodeCell) { |
|
1263 | 1263 | cells[i].clear_output(); |
|
1264 | 1264 | // Make all In[] prompts blank, as well |
|
1265 | 1265 | // TODO: make this configurable (via checkbox?) |
|
1266 | 1266 | cells[i].set_input_prompt(); |
|
1267 | 1267 | } |
|
1268 | 1268 | }; |
|
1269 | 1269 | this.set_dirty(true); |
|
1270 | 1270 | }; |
|
1271 | 1271 | |
|
1272 | 1272 | |
|
1273 | 1273 | // Other cell functions: line numbers, ... |
|
1274 | 1274 | |
|
1275 | 1275 | /** |
|
1276 | 1276 | * Toggle line numbers in the selected cell's input area. |
|
1277 | 1277 | * |
|
1278 | 1278 | * @method cell_toggle_line_numbers |
|
1279 | 1279 | */ |
|
1280 | 1280 | Notebook.prototype.cell_toggle_line_numbers = function() { |
|
1281 | 1281 | this.get_selected_cell().toggle_line_numbers(); |
|
1282 | 1282 | }; |
|
1283 | 1283 | |
|
1284 | 1284 | // Session related things |
|
1285 | 1285 | |
|
1286 | 1286 | /** |
|
1287 | 1287 | * Start a new session and set it on each code cell. |
|
1288 | 1288 | * |
|
1289 | 1289 | * @method start_session |
|
1290 | 1290 | */ |
|
1291 | 1291 | Notebook.prototype.start_session = function () { |
|
1292 | 1292 | this.session = new IPython.Session(this.notebook_name, this.notebook_path, this); |
|
1293 | 1293 | this.session.start($.proxy(this._session_started, this)); |
|
1294 | 1294 | }; |
|
1295 | 1295 | |
|
1296 | 1296 | |
|
1297 | 1297 | /** |
|
1298 | * Once a session is started, link the code cells to the kernel | |
|
1298 | * Once a session is started, link the code cells to the kernel and pass the | |
|
1299 | * comm manager to the widget manager | |
|
1299 | 1300 | * |
|
1300 | 1301 | */ |
|
1301 | 1302 | Notebook.prototype._session_started = function(){ |
|
1302 | 1303 | this.kernel = this.session.kernel; |
|
1304 | IPython.widget_manager.attach_comm_manager(this.kernel.comm_manager); | |
|
1303 | 1305 | var ncells = this.ncells(); |
|
1304 | 1306 | for (var i=0; i<ncells; i++) { |
|
1305 | 1307 | var cell = this.get_cell(i); |
|
1306 | 1308 | if (cell instanceof IPython.CodeCell) { |
|
1307 | 1309 | cell.set_kernel(this.session.kernel); |
|
1308 | 1310 | }; |
|
1309 | 1311 | }; |
|
1310 | 1312 | }; |
|
1311 | 1313 | |
|
1312 | 1314 | /** |
|
1313 | 1315 | * Prompt the user to restart the IPython kernel. |
|
1314 | 1316 | * |
|
1315 | 1317 | * @method restart_kernel |
|
1316 | 1318 | */ |
|
1317 | 1319 | Notebook.prototype.restart_kernel = function () { |
|
1318 | 1320 | var that = this; |
|
1319 | 1321 | IPython.dialog.modal({ |
|
1320 | 1322 | title : "Restart kernel or continue running?", |
|
1321 | 1323 | body : $("<p/>").html( |
|
1322 | 1324 | 'Do you want to restart the current kernel? You will lose all variables defined in it.' |
|
1323 | 1325 | ), |
|
1324 | 1326 | buttons : { |
|
1325 | 1327 | "Continue running" : {}, |
|
1326 | 1328 | "Restart" : { |
|
1327 | 1329 | "class" : "btn-danger", |
|
1328 | 1330 | "click" : function() { |
|
1329 | 1331 | that.session.restart_kernel(); |
|
1330 | 1332 | } |
|
1331 | 1333 | } |
|
1332 | 1334 | } |
|
1333 | 1335 | }); |
|
1334 | 1336 | }; |
|
1335 | 1337 | |
|
1336 | 1338 | /** |
|
1337 | 1339 | * Execute or render cell outputs and go into command mode. |
|
1338 | 1340 | * |
|
1339 | 1341 | * @method execute_cell |
|
1340 | 1342 | */ |
|
1341 | 1343 | Notebook.prototype.execute_cell = function () { |
|
1342 | 1344 | // mode = shift, ctrl, alt |
|
1343 | 1345 | var cell = this.get_selected_cell(); |
|
1344 | 1346 | var cell_index = this.find_cell_index(cell); |
|
1345 | 1347 | |
|
1346 | 1348 | cell.execute(); |
|
1347 | 1349 | this.command_mode(); |
|
1348 | 1350 | cell.focus_cell(); |
|
1349 | 1351 | this.set_dirty(true); |
|
1350 | 1352 | } |
|
1351 | 1353 | |
|
1352 | 1354 | /** |
|
1353 | 1355 | * Execute or render cell outputs and insert a new cell below. |
|
1354 | 1356 | * |
|
1355 | 1357 | * @method execute_cell_and_insert_below |
|
1356 | 1358 | */ |
|
1357 | 1359 | Notebook.prototype.execute_cell_and_insert_below = function () { |
|
1358 | 1360 | var cell = this.get_selected_cell(); |
|
1359 | 1361 | var cell_index = this.find_cell_index(cell); |
|
1360 | 1362 | |
|
1361 | 1363 | cell.execute(); |
|
1362 | 1364 | |
|
1363 | 1365 | // If we are at the end always insert a new cell and return |
|
1364 | 1366 | if (cell_index === (this.ncells()-1)) { |
|
1365 | 1367 | this.insert_cell_below('code'); |
|
1366 | 1368 | this.select(cell_index+1); |
|
1367 | 1369 | this.edit_mode(); |
|
1368 | 1370 | this.scroll_to_bottom(); |
|
1369 | 1371 | this.set_dirty(true); |
|
1370 | 1372 | return; |
|
1371 | 1373 | } |
|
1372 | 1374 | |
|
1373 | 1375 | // Only insert a new cell, if we ended up in an already populated cell |
|
1374 | 1376 | var next_text = this.get_cell(cell_index+1).get_text(); |
|
1375 | 1377 | if (/\S/.test(next_text) === true) { |
|
1376 | 1378 | this.insert_cell_below('code'); |
|
1377 | 1379 | } |
|
1378 | 1380 | this.select(cell_index+1); |
|
1379 | 1381 | this.edit_mode(); |
|
1380 | 1382 | this.set_dirty(true); |
|
1381 | 1383 | }; |
|
1382 | 1384 | |
|
1383 | 1385 | /** |
|
1384 | 1386 | * Execute or render cell outputs and select the next cell. |
|
1385 | 1387 | * |
|
1386 | 1388 | * @method execute_cell_and_select_below |
|
1387 | 1389 | */ |
|
1388 | 1390 | Notebook.prototype.execute_cell_and_select_below = function () { |
|
1389 | 1391 | |
|
1390 | 1392 | var cell = this.get_selected_cell(); |
|
1391 | 1393 | var cell_index = this.find_cell_index(cell); |
|
1392 | 1394 | |
|
1393 | 1395 | cell.execute(); |
|
1394 | 1396 | |
|
1395 | 1397 | // If we are at the end always insert a new cell and return |
|
1396 | 1398 | if (cell_index === (this.ncells()-1)) { |
|
1397 | 1399 | this.insert_cell_below('code'); |
|
1398 | 1400 | this.select(cell_index+1); |
|
1399 | 1401 | this.edit_mode(); |
|
1400 | 1402 | this.scroll_to_bottom(); |
|
1401 | 1403 | this.set_dirty(true); |
|
1402 | 1404 | return; |
|
1403 | 1405 | } |
|
1404 | 1406 | |
|
1405 | 1407 | this.select(cell_index+1); |
|
1406 | 1408 | this.get_cell(cell_index+1).focus_cell(); |
|
1407 | 1409 | this.set_dirty(true); |
|
1408 | 1410 | }; |
|
1409 | 1411 | |
|
1410 | 1412 | /** |
|
1411 | 1413 | * Execute all cells below the selected cell. |
|
1412 | 1414 | * |
|
1413 | 1415 | * @method execute_cells_below |
|
1414 | 1416 | */ |
|
1415 | 1417 | Notebook.prototype.execute_cells_below = function () { |
|
1416 | 1418 | this.execute_cell_range(this.get_selected_index(), this.ncells()); |
|
1417 | 1419 | this.scroll_to_bottom(); |
|
1418 | 1420 | }; |
|
1419 | 1421 | |
|
1420 | 1422 | /** |
|
1421 | 1423 | * Execute all cells above the selected cell. |
|
1422 | 1424 | * |
|
1423 | 1425 | * @method execute_cells_above |
|
1424 | 1426 | */ |
|
1425 | 1427 | Notebook.prototype.execute_cells_above = function () { |
|
1426 | 1428 | this.execute_cell_range(0, this.get_selected_index()); |
|
1427 | 1429 | }; |
|
1428 | 1430 | |
|
1429 | 1431 | /** |
|
1430 | 1432 | * Execute all cells. |
|
1431 | 1433 | * |
|
1432 | 1434 | * @method execute_all_cells |
|
1433 | 1435 | */ |
|
1434 | 1436 | Notebook.prototype.execute_all_cells = function () { |
|
1435 | 1437 | this.execute_cell_range(0, this.ncells()); |
|
1436 | 1438 | this.scroll_to_bottom(); |
|
1437 | 1439 | }; |
|
1438 | 1440 | |
|
1439 | 1441 | /** |
|
1440 | 1442 | * Execute a contiguous range of cells. |
|
1441 | 1443 | * |
|
1442 | 1444 | * @method execute_cell_range |
|
1443 | 1445 | * @param {Number} start Index of the first cell to execute (inclusive) |
|
1444 | 1446 | * @param {Number} end Index of the last cell to execute (exclusive) |
|
1445 | 1447 | */ |
|
1446 | 1448 | Notebook.prototype.execute_cell_range = function (start, end) { |
|
1447 | 1449 | for (var i=start; i<end; i++) { |
|
1448 | 1450 | this.select(i); |
|
1449 | 1451 | this.execute_cell(); |
|
1450 | 1452 | }; |
|
1451 | 1453 | }; |
|
1452 | 1454 | |
|
1453 | 1455 | // Persistance and loading |
|
1454 | 1456 | |
|
1455 | 1457 | /** |
|
1456 | 1458 | * Getter method for this notebook's name. |
|
1457 | 1459 | * |
|
1458 | 1460 | * @method get_notebook_name |
|
1459 | 1461 | * @return {String} This notebook's name |
|
1460 | 1462 | */ |
|
1461 | 1463 | Notebook.prototype.get_notebook_name = function () { |
|
1462 | 1464 | var nbname = this.notebook_name.substring(0,this.notebook_name.length-6); |
|
1463 | 1465 | return nbname; |
|
1464 | 1466 | }; |
|
1465 | 1467 | |
|
1466 | 1468 | /** |
|
1467 | 1469 | * Setter method for this notebook's name. |
|
1468 | 1470 | * |
|
1469 | 1471 | * @method set_notebook_name |
|
1470 | 1472 | * @param {String} name A new name for this notebook |
|
1471 | 1473 | */ |
|
1472 | 1474 | Notebook.prototype.set_notebook_name = function (name) { |
|
1473 | 1475 | this.notebook_name = name; |
|
1474 | 1476 | }; |
|
1475 | 1477 | |
|
1476 | 1478 | /** |
|
1477 | 1479 | * Check that a notebook's name is valid. |
|
1478 | 1480 | * |
|
1479 | 1481 | * @method test_notebook_name |
|
1480 | 1482 | * @param {String} nbname A name for this notebook |
|
1481 | 1483 | * @return {Boolean} True if the name is valid, false if invalid |
|
1482 | 1484 | */ |
|
1483 | 1485 | Notebook.prototype.test_notebook_name = function (nbname) { |
|
1484 | 1486 | nbname = nbname || ''; |
|
1485 | 1487 | if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) { |
|
1486 | 1488 | return true; |
|
1487 | 1489 | } else { |
|
1488 | 1490 | return false; |
|
1489 | 1491 | }; |
|
1490 | 1492 | }; |
|
1491 | 1493 | |
|
1492 | 1494 | /** |
|
1493 | 1495 | * Load a notebook from JSON (.ipynb). |
|
1494 | 1496 | * |
|
1495 | 1497 | * This currently handles one worksheet: others are deleted. |
|
1496 | 1498 | * |
|
1497 | 1499 | * @method fromJSON |
|
1498 | 1500 | * @param {Object} data JSON representation of a notebook |
|
1499 | 1501 | */ |
|
1500 | 1502 | Notebook.prototype.fromJSON = function (data) { |
|
1501 | 1503 | var content = data.content; |
|
1502 | 1504 | var ncells = this.ncells(); |
|
1503 | 1505 | var i; |
|
1504 | 1506 | for (i=0; i<ncells; i++) { |
|
1505 | 1507 | // Always delete cell 0 as they get renumbered as they are deleted. |
|
1506 | 1508 | this.delete_cell(0); |
|
1507 | 1509 | }; |
|
1508 | 1510 | // Save the metadata and name. |
|
1509 | 1511 | this.metadata = content.metadata; |
|
1510 | 1512 | this.notebook_name = data.name; |
|
1511 | 1513 | // Only handle 1 worksheet for now. |
|
1512 | 1514 | var worksheet = content.worksheets[0]; |
|
1513 | 1515 | if (worksheet !== undefined) { |
|
1514 | 1516 | if (worksheet.metadata) { |
|
1515 | 1517 | this.worksheet_metadata = worksheet.metadata; |
|
1516 | 1518 | } |
|
1517 | 1519 | var new_cells = worksheet.cells; |
|
1518 | 1520 | ncells = new_cells.length; |
|
1519 | 1521 | var cell_data = null; |
|
1520 | 1522 | var new_cell = null; |
|
1521 | 1523 | for (i=0; i<ncells; i++) { |
|
1522 | 1524 | cell_data = new_cells[i]; |
|
1523 | 1525 | // VERSIONHACK: plaintext -> raw |
|
1524 | 1526 | // handle never-released plaintext name for raw cells |
|
1525 | 1527 | if (cell_data.cell_type === 'plaintext'){ |
|
1526 | 1528 | cell_data.cell_type = 'raw'; |
|
1527 | 1529 | } |
|
1528 | 1530 | |
|
1529 | 1531 | new_cell = this.insert_cell_at_index(cell_data.cell_type, i); |
|
1530 | 1532 | new_cell.fromJSON(cell_data); |
|
1531 | 1533 | }; |
|
1532 | 1534 | }; |
|
1533 | 1535 | if (content.worksheets.length > 1) { |
|
1534 | 1536 | IPython.dialog.modal({ |
|
1535 | 1537 | title : "Multiple worksheets", |
|
1536 | 1538 | body : "This notebook has " + data.worksheets.length + " worksheets, " + |
|
1537 | 1539 | "but this version of IPython can only handle the first. " + |
|
1538 | 1540 | "If you save this notebook, worksheets after the first will be lost.", |
|
1539 | 1541 | buttons : { |
|
1540 | 1542 | OK : { |
|
1541 | 1543 | class : "btn-danger" |
|
1542 | 1544 | } |
|
1543 | 1545 | } |
|
1544 | 1546 | }); |
|
1545 | 1547 | } |
|
1546 | 1548 | }; |
|
1547 | 1549 | |
|
1548 | 1550 | /** |
|
1549 | 1551 | * Dump this notebook into a JSON-friendly object. |
|
1550 | 1552 | * |
|
1551 | 1553 | * @method toJSON |
|
1552 | 1554 | * @return {Object} A JSON-friendly representation of this notebook. |
|
1553 | 1555 | */ |
|
1554 | 1556 | Notebook.prototype.toJSON = function () { |
|
1555 | 1557 | var cells = this.get_cells(); |
|
1556 | 1558 | var ncells = cells.length; |
|
1557 | 1559 | var cell_array = new Array(ncells); |
|
1558 | 1560 | for (var i=0; i<ncells; i++) { |
|
1559 | 1561 | cell_array[i] = cells[i].toJSON(); |
|
1560 | 1562 | }; |
|
1561 | 1563 | var data = { |
|
1562 | 1564 | // Only handle 1 worksheet for now. |
|
1563 | 1565 | worksheets : [{ |
|
1564 | 1566 | cells: cell_array, |
|
1565 | 1567 | metadata: this.worksheet_metadata |
|
1566 | 1568 | }], |
|
1567 | 1569 | metadata : this.metadata |
|
1568 | 1570 | }; |
|
1569 | 1571 | return data; |
|
1570 | 1572 | }; |
|
1571 | 1573 | |
|
1572 | 1574 | /** |
|
1573 | 1575 | * Start an autosave timer, for periodically saving the notebook. |
|
1574 | 1576 | * |
|
1575 | 1577 | * @method set_autosave_interval |
|
1576 | 1578 | * @param {Integer} interval the autosave interval in milliseconds |
|
1577 | 1579 | */ |
|
1578 | 1580 | Notebook.prototype.set_autosave_interval = function (interval) { |
|
1579 | 1581 | var that = this; |
|
1580 | 1582 | // clear previous interval, so we don't get simultaneous timers |
|
1581 | 1583 | if (this.autosave_timer) { |
|
1582 | 1584 | clearInterval(this.autosave_timer); |
|
1583 | 1585 | } |
|
1584 | 1586 | |
|
1585 | 1587 | this.autosave_interval = this.minimum_autosave_interval = interval; |
|
1586 | 1588 | if (interval) { |
|
1587 | 1589 | this.autosave_timer = setInterval(function() { |
|
1588 | 1590 | if (that.dirty) { |
|
1589 | 1591 | that.save_notebook(); |
|
1590 | 1592 | } |
|
1591 | 1593 | }, interval); |
|
1592 | 1594 | $([IPython.events]).trigger("autosave_enabled.Notebook", interval); |
|
1593 | 1595 | } else { |
|
1594 | 1596 | this.autosave_timer = null; |
|
1595 | 1597 | $([IPython.events]).trigger("autosave_disabled.Notebook"); |
|
1596 | 1598 | }; |
|
1597 | 1599 | }; |
|
1598 | 1600 | |
|
1599 | 1601 | /** |
|
1600 | 1602 | * Save this notebook on the server. |
|
1601 | 1603 | * |
|
1602 | 1604 | * @method save_notebook |
|
1603 | 1605 | */ |
|
1604 | 1606 | Notebook.prototype.save_notebook = function (extra_settings) { |
|
1605 | 1607 | // Create a JSON model to be sent to the server. |
|
1606 | 1608 | var model = {}; |
|
1607 | 1609 | model.name = this.notebook_name; |
|
1608 | 1610 | model.path = this.notebook_path; |
|
1609 | 1611 | model.content = this.toJSON(); |
|
1610 | 1612 | model.content.nbformat = this.nbformat; |
|
1611 | 1613 | model.content.nbformat_minor = this.nbformat_minor; |
|
1612 | 1614 | // time the ajax call for autosave tuning purposes. |
|
1613 | 1615 | var start = new Date().getTime(); |
|
1614 | 1616 | // We do the call with settings so we can set cache to false. |
|
1615 | 1617 | var settings = { |
|
1616 | 1618 | processData : false, |
|
1617 | 1619 | cache : false, |
|
1618 | 1620 | type : "PUT", |
|
1619 | 1621 | data : JSON.stringify(model), |
|
1620 | 1622 | headers : {'Content-Type': 'application/json'}, |
|
1621 | 1623 | success : $.proxy(this.save_notebook_success, this, start), |
|
1622 | 1624 | error : $.proxy(this.save_notebook_error, this) |
|
1623 | 1625 | }; |
|
1624 | 1626 | if (extra_settings) { |
|
1625 | 1627 | for (var key in extra_settings) { |
|
1626 | 1628 | settings[key] = extra_settings[key]; |
|
1627 | 1629 | } |
|
1628 | 1630 | } |
|
1629 | 1631 | $([IPython.events]).trigger('notebook_saving.Notebook'); |
|
1630 | 1632 | var url = utils.url_join_encode( |
|
1631 | 1633 | this._baseProjectUrl, |
|
1632 | 1634 | 'api/notebooks', |
|
1633 | 1635 | this.notebook_path, |
|
1634 | 1636 | this.notebook_name |
|
1635 | 1637 | ); |
|
1636 | 1638 | $.ajax(url, settings); |
|
1637 | 1639 | }; |
|
1638 | 1640 | |
|
1639 | 1641 | /** |
|
1640 | 1642 | * Success callback for saving a notebook. |
|
1641 | 1643 | * |
|
1642 | 1644 | * @method save_notebook_success |
|
1643 | 1645 | * @param {Integer} start the time when the save request started |
|
1644 | 1646 | * @param {Object} data JSON representation of a notebook |
|
1645 | 1647 | * @param {String} status Description of response status |
|
1646 | 1648 | * @param {jqXHR} xhr jQuery Ajax object |
|
1647 | 1649 | */ |
|
1648 | 1650 | Notebook.prototype.save_notebook_success = function (start, data, status, xhr) { |
|
1649 | 1651 | this.set_dirty(false); |
|
1650 | 1652 | $([IPython.events]).trigger('notebook_saved.Notebook'); |
|
1651 | 1653 | this._update_autosave_interval(start); |
|
1652 | 1654 | if (this._checkpoint_after_save) { |
|
1653 | 1655 | this.create_checkpoint(); |
|
1654 | 1656 | this._checkpoint_after_save = false; |
|
1655 | 1657 | }; |
|
1656 | 1658 | }; |
|
1657 | 1659 | |
|
1658 | 1660 | /** |
|
1659 | 1661 | * update the autosave interval based on how long the last save took |
|
1660 | 1662 | * |
|
1661 | 1663 | * @method _update_autosave_interval |
|
1662 | 1664 | * @param {Integer} timestamp when the save request started |
|
1663 | 1665 | */ |
|
1664 | 1666 | Notebook.prototype._update_autosave_interval = function (start) { |
|
1665 | 1667 | var duration = (new Date().getTime() - start); |
|
1666 | 1668 | if (this.autosave_interval) { |
|
1667 | 1669 | // new save interval: higher of 10x save duration or parameter (default 30 seconds) |
|
1668 | 1670 | var interval = Math.max(10 * duration, this.minimum_autosave_interval); |
|
1669 | 1671 | // round to 10 seconds, otherwise we will be setting a new interval too often |
|
1670 | 1672 | interval = 10000 * Math.round(interval / 10000); |
|
1671 | 1673 | // set new interval, if it's changed |
|
1672 | 1674 | if (interval != this.autosave_interval) { |
|
1673 | 1675 | this.set_autosave_interval(interval); |
|
1674 | 1676 | } |
|
1675 | 1677 | } |
|
1676 | 1678 | }; |
|
1677 | 1679 | |
|
1678 | 1680 | /** |
|
1679 | 1681 | * Failure callback for saving a notebook. |
|
1680 | 1682 | * |
|
1681 | 1683 | * @method save_notebook_error |
|
1682 | 1684 | * @param {jqXHR} xhr jQuery Ajax object |
|
1683 | 1685 | * @param {String} status Description of response status |
|
1684 | 1686 | * @param {String} error HTTP error message |
|
1685 | 1687 | */ |
|
1686 | 1688 | Notebook.prototype.save_notebook_error = function (xhr, status, error) { |
|
1687 | 1689 | $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]); |
|
1688 | 1690 | }; |
|
1689 | 1691 | |
|
1690 | 1692 | Notebook.prototype.new_notebook = function(){ |
|
1691 | 1693 | var path = this.notebook_path; |
|
1692 | 1694 | var base_project_url = this._baseProjectUrl; |
|
1693 | 1695 | var settings = { |
|
1694 | 1696 | processData : false, |
|
1695 | 1697 | cache : false, |
|
1696 | 1698 | type : "POST", |
|
1697 | 1699 | dataType : "json", |
|
1698 | 1700 | async : false, |
|
1699 | 1701 | success : function (data, status, xhr){ |
|
1700 | 1702 | var notebook_name = data.name; |
|
1701 | 1703 | window.open( |
|
1702 | 1704 | utils.url_join_encode( |
|
1703 | 1705 | base_project_url, |
|
1704 | 1706 | 'notebooks', |
|
1705 | 1707 | path, |
|
1706 | 1708 | notebook_name |
|
1707 | 1709 | ), |
|
1708 | 1710 | '_blank' |
|
1709 | 1711 | ); |
|
1710 | 1712 | } |
|
1711 | 1713 | }; |
|
1712 | 1714 | var url = utils.url_join_encode( |
|
1713 | 1715 | base_project_url, |
|
1714 | 1716 | 'api/notebooks', |
|
1715 | 1717 | path |
|
1716 | 1718 | ); |
|
1717 | 1719 | $.ajax(url,settings); |
|
1718 | 1720 | }; |
|
1719 | 1721 | |
|
1720 | 1722 | |
|
1721 | 1723 | Notebook.prototype.copy_notebook = function(){ |
|
1722 | 1724 | var path = this.notebook_path; |
|
1723 | 1725 | var base_project_url = this._baseProjectUrl; |
|
1724 | 1726 | var settings = { |
|
1725 | 1727 | processData : false, |
|
1726 | 1728 | cache : false, |
|
1727 | 1729 | type : "POST", |
|
1728 | 1730 | dataType : "json", |
|
1729 | 1731 | data : JSON.stringify({copy_from : this.notebook_name}), |
|
1730 | 1732 | async : false, |
|
1731 | 1733 | success : function (data, status, xhr) { |
|
1732 | 1734 | window.open(utils.url_join_encode( |
|
1733 | 1735 | base_project_url, |
|
1734 | 1736 | 'notebooks', |
|
1735 | 1737 | data.path, |
|
1736 | 1738 | data.name |
|
1737 | 1739 | ), '_blank'); |
|
1738 | 1740 | } |
|
1739 | 1741 | }; |
|
1740 | 1742 | var url = utils.url_join_encode( |
|
1741 | 1743 | base_project_url, |
|
1742 | 1744 | 'api/notebooks', |
|
1743 | 1745 | path |
|
1744 | 1746 | ); |
|
1745 | 1747 | $.ajax(url,settings); |
|
1746 | 1748 | }; |
|
1747 | 1749 | |
|
1748 | 1750 | Notebook.prototype.rename = function (nbname) { |
|
1749 | 1751 | var that = this; |
|
1750 | 1752 | var data = {name: nbname + '.ipynb'}; |
|
1751 | 1753 | var settings = { |
|
1752 | 1754 | processData : false, |
|
1753 | 1755 | cache : false, |
|
1754 | 1756 | type : "PATCH", |
|
1755 | 1757 | data : JSON.stringify(data), |
|
1756 | 1758 | dataType: "json", |
|
1757 | 1759 | headers : {'Content-Type': 'application/json'}, |
|
1758 | 1760 | success : $.proxy(that.rename_success, this), |
|
1759 | 1761 | error : $.proxy(that.rename_error, this) |
|
1760 | 1762 | }; |
|
1761 | 1763 | $([IPython.events]).trigger('rename_notebook.Notebook', data); |
|
1762 | 1764 | var url = utils.url_join_encode( |
|
1763 | 1765 | this._baseProjectUrl, |
|
1764 | 1766 | 'api/notebooks', |
|
1765 | 1767 | this.notebook_path, |
|
1766 | 1768 | this.notebook_name |
|
1767 | 1769 | ); |
|
1768 | 1770 | $.ajax(url, settings); |
|
1769 | 1771 | }; |
|
1770 | 1772 | |
|
1771 | 1773 | |
|
1772 | 1774 | Notebook.prototype.rename_success = function (json, status, xhr) { |
|
1773 | 1775 | this.notebook_name = json.name; |
|
1774 | 1776 | var name = this.notebook_name; |
|
1775 | 1777 | var path = json.path; |
|
1776 | 1778 | this.session.rename_notebook(name, path); |
|
1777 | 1779 | $([IPython.events]).trigger('notebook_renamed.Notebook', json); |
|
1778 | 1780 | } |
|
1779 | 1781 | |
|
1780 | 1782 | Notebook.prototype.rename_error = function (xhr, status, error) { |
|
1781 | 1783 | var that = this; |
|
1782 | 1784 | var dialog = $('<div/>').append( |
|
1783 | 1785 | $("<p/>").addClass("rename-message") |
|
1784 | 1786 | .html('This notebook name already exists.') |
|
1785 | 1787 | ) |
|
1786 | 1788 | $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]); |
|
1787 | 1789 | IPython.dialog.modal({ |
|
1788 | 1790 | title: "Notebook Rename Error!", |
|
1789 | 1791 | body: dialog, |
|
1790 | 1792 | buttons : { |
|
1791 | 1793 | "Cancel": {}, |
|
1792 | 1794 | "OK": { |
|
1793 | 1795 | class: "btn-primary", |
|
1794 | 1796 | click: function () { |
|
1795 | 1797 | IPython.save_widget.rename_notebook(); |
|
1796 | 1798 | }} |
|
1797 | 1799 | }, |
|
1798 | 1800 | open : function (event, ui) { |
|
1799 | 1801 | var that = $(this); |
|
1800 | 1802 | // Upon ENTER, click the OK button. |
|
1801 | 1803 | that.find('input[type="text"]').keydown(function (event, ui) { |
|
1802 | 1804 | if (event.which === utils.keycodes.ENTER) { |
|
1803 | 1805 | that.find('.btn-primary').first().click(); |
|
1804 | 1806 | } |
|
1805 | 1807 | }); |
|
1806 | 1808 | that.find('input[type="text"]').focus(); |
|
1807 | 1809 | } |
|
1808 | 1810 | }); |
|
1809 | 1811 | } |
|
1810 | 1812 | |
|
1811 | 1813 | /** |
|
1812 | 1814 | * Request a notebook's data from the server. |
|
1813 | 1815 | * |
|
1814 | 1816 | * @method load_notebook |
|
1815 | 1817 | * @param {String} notebook_name and path A notebook to load |
|
1816 | 1818 | */ |
|
1817 | 1819 | Notebook.prototype.load_notebook = function (notebook_name, notebook_path) { |
|
1818 | 1820 | var that = this; |
|
1819 | 1821 | this.notebook_name = notebook_name; |
|
1820 | 1822 | this.notebook_path = notebook_path; |
|
1821 | 1823 | // We do the call with settings so we can set cache to false. |
|
1822 | 1824 | var settings = { |
|
1823 | 1825 | processData : false, |
|
1824 | 1826 | cache : false, |
|
1825 | 1827 | type : "GET", |
|
1826 | 1828 | dataType : "json", |
|
1827 | 1829 | success : $.proxy(this.load_notebook_success,this), |
|
1828 | 1830 | error : $.proxy(this.load_notebook_error,this), |
|
1829 | 1831 | }; |
|
1830 | 1832 | $([IPython.events]).trigger('notebook_loading.Notebook'); |
|
1831 | 1833 | var url = utils.url_join_encode( |
|
1832 | 1834 | this._baseProjectUrl, |
|
1833 | 1835 | 'api/notebooks', |
|
1834 | 1836 | this.notebook_path, |
|
1835 | 1837 | this.notebook_name |
|
1836 | 1838 | ); |
|
1837 | 1839 | $.ajax(url, settings); |
|
1838 | 1840 | }; |
|
1839 | 1841 | |
|
1840 | 1842 | /** |
|
1841 | 1843 | * Success callback for loading a notebook from the server. |
|
1842 | 1844 | * |
|
1843 | 1845 | * Load notebook data from the JSON response. |
|
1844 | 1846 | * |
|
1845 | 1847 | * @method load_notebook_success |
|
1846 | 1848 | * @param {Object} data JSON representation of a notebook |
|
1847 | 1849 | * @param {String} status Description of response status |
|
1848 | 1850 | * @param {jqXHR} xhr jQuery Ajax object |
|
1849 | 1851 | */ |
|
1850 | 1852 | Notebook.prototype.load_notebook_success = function (data, status, xhr) { |
|
1851 | 1853 | this.fromJSON(data); |
|
1852 | 1854 | if (this.ncells() === 0) { |
|
1853 | 1855 | this.insert_cell_below('code'); |
|
1854 | 1856 | this.select(0); |
|
1855 | 1857 | this.edit_mode(); |
|
1856 | 1858 | } else { |
|
1857 | 1859 | this.select(0); |
|
1858 | 1860 | this.command_mode(); |
|
1859 | 1861 | }; |
|
1860 | 1862 | this.set_dirty(false); |
|
1861 | 1863 | this.scroll_to_top(); |
|
1862 | 1864 | if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) { |
|
1863 | 1865 | var msg = "This notebook has been converted from an older " + |
|
1864 | 1866 | "notebook format (v"+data.orig_nbformat+") to the current notebook " + |
|
1865 | 1867 | "format (v"+data.nbformat+"). The next time you save this notebook, the " + |
|
1866 | 1868 | "newer notebook format will be used and older versions of IPython " + |
|
1867 | 1869 | "may not be able to read it. To keep the older version, close the " + |
|
1868 | 1870 | "notebook without saving it."; |
|
1869 | 1871 | IPython.dialog.modal({ |
|
1870 | 1872 | title : "Notebook converted", |
|
1871 | 1873 | body : msg, |
|
1872 | 1874 | buttons : { |
|
1873 | 1875 | OK : { |
|
1874 | 1876 | class : "btn-primary" |
|
1875 | 1877 | } |
|
1876 | 1878 | } |
|
1877 | 1879 | }); |
|
1878 | 1880 | } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) { |
|
1879 | 1881 | var that = this; |
|
1880 | 1882 | var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor; |
|
1881 | 1883 | var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor; |
|
1882 | 1884 | var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " + |
|
1883 | 1885 | this_vs + ". You can still work with this notebook, but some features " + |
|
1884 | 1886 | "introduced in later notebook versions may not be available." |
|
1885 | 1887 | |
|
1886 | 1888 | IPython.dialog.modal({ |
|
1887 | 1889 | title : "Newer Notebook", |
|
1888 | 1890 | body : msg, |
|
1889 | 1891 | buttons : { |
|
1890 | 1892 | OK : { |
|
1891 | 1893 | class : "btn-danger" |
|
1892 | 1894 | } |
|
1893 | 1895 | } |
|
1894 | 1896 | }); |
|
1895 | 1897 | |
|
1896 | 1898 | } |
|
1897 | 1899 | |
|
1898 | 1900 | // Create the session after the notebook is completely loaded to prevent |
|
1899 | 1901 | // code execution upon loading, which is a security risk. |
|
1900 | 1902 | if (this.session == null) { |
|
1901 | 1903 | this.start_session(); |
|
1902 | 1904 | } |
|
1903 | 1905 | // load our checkpoint list |
|
1904 | 1906 | this.list_checkpoints(); |
|
1905 | 1907 | |
|
1906 | 1908 | // load toolbar state |
|
1907 | 1909 | if (this.metadata.celltoolbar) { |
|
1908 | 1910 | IPython.CellToolbar.global_show(); |
|
1909 | 1911 | IPython.CellToolbar.activate_preset(this.metadata.celltoolbar); |
|
1910 | 1912 | } |
|
1911 | 1913 | |
|
1912 | 1914 | $([IPython.events]).trigger('notebook_loaded.Notebook'); |
|
1913 | 1915 | }; |
|
1914 | 1916 | |
|
1915 | 1917 | /** |
|
1916 | 1918 | * Failure callback for loading a notebook from the server. |
|
1917 | 1919 | * |
|
1918 | 1920 | * @method load_notebook_error |
|
1919 | 1921 | * @param {jqXHR} xhr jQuery Ajax object |
|
1920 | 1922 | * @param {String} status Description of response status |
|
1921 | 1923 | * @param {String} error HTTP error message |
|
1922 | 1924 | */ |
|
1923 | 1925 | Notebook.prototype.load_notebook_error = function (xhr, status, error) { |
|
1924 | 1926 | $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]); |
|
1925 | 1927 | if (xhr.status === 400) { |
|
1926 | 1928 | var msg = error; |
|
1927 | 1929 | } else if (xhr.status === 500) { |
|
1928 | 1930 | var msg = "An unknown error occurred while loading this notebook. " + |
|
1929 | 1931 | "This version can load notebook formats " + |
|
1930 | 1932 | "v" + this.nbformat + " or earlier."; |
|
1931 | 1933 | } |
|
1932 | 1934 | IPython.dialog.modal({ |
|
1933 | 1935 | title: "Error loading notebook", |
|
1934 | 1936 | body : msg, |
|
1935 | 1937 | buttons : { |
|
1936 | 1938 | "OK": {} |
|
1937 | 1939 | } |
|
1938 | 1940 | }); |
|
1939 | 1941 | } |
|
1940 | 1942 | |
|
1941 | 1943 | /********************* checkpoint-related *********************/ |
|
1942 | 1944 | |
|
1943 | 1945 | /** |
|
1944 | 1946 | * Save the notebook then immediately create a checkpoint. |
|
1945 | 1947 | * |
|
1946 | 1948 | * @method save_checkpoint |
|
1947 | 1949 | */ |
|
1948 | 1950 | Notebook.prototype.save_checkpoint = function () { |
|
1949 | 1951 | this._checkpoint_after_save = true; |
|
1950 | 1952 | this.save_notebook(); |
|
1951 | 1953 | }; |
|
1952 | 1954 | |
|
1953 | 1955 | /** |
|
1954 | 1956 | * Add a checkpoint for this notebook. |
|
1955 | 1957 | * for use as a callback from checkpoint creation. |
|
1956 | 1958 | * |
|
1957 | 1959 | * @method add_checkpoint |
|
1958 | 1960 | */ |
|
1959 | 1961 | Notebook.prototype.add_checkpoint = function (checkpoint) { |
|
1960 | 1962 | var found = false; |
|
1961 | 1963 | for (var i = 0; i < this.checkpoints.length; i++) { |
|
1962 | 1964 | var existing = this.checkpoints[i]; |
|
1963 | 1965 | if (existing.id == checkpoint.id) { |
|
1964 | 1966 | found = true; |
|
1965 | 1967 | this.checkpoints[i] = checkpoint; |
|
1966 | 1968 | break; |
|
1967 | 1969 | } |
|
1968 | 1970 | } |
|
1969 | 1971 | if (!found) { |
|
1970 | 1972 | this.checkpoints.push(checkpoint); |
|
1971 | 1973 | } |
|
1972 | 1974 | this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1]; |
|
1973 | 1975 | }; |
|
1974 | 1976 | |
|
1975 | 1977 | /** |
|
1976 | 1978 | * List checkpoints for this notebook. |
|
1977 | 1979 | * |
|
1978 | 1980 | * @method list_checkpoints |
|
1979 | 1981 | */ |
|
1980 | 1982 | Notebook.prototype.list_checkpoints = function () { |
|
1981 | 1983 | var url = utils.url_join_encode( |
|
1982 | 1984 | this._baseProjectUrl, |
|
1983 | 1985 | 'api/notebooks', |
|
1984 | 1986 | this.notebook_path, |
|
1985 | 1987 | this.notebook_name, |
|
1986 | 1988 | 'checkpoints' |
|
1987 | 1989 | ); |
|
1988 | 1990 | $.get(url).done( |
|
1989 | 1991 | $.proxy(this.list_checkpoints_success, this) |
|
1990 | 1992 | ).fail( |
|
1991 | 1993 | $.proxy(this.list_checkpoints_error, this) |
|
1992 | 1994 | ); |
|
1993 | 1995 | }; |
|
1994 | 1996 | |
|
1995 | 1997 | /** |
|
1996 | 1998 | * Success callback for listing checkpoints. |
|
1997 | 1999 | * |
|
1998 | 2000 | * @method list_checkpoint_success |
|
1999 | 2001 | * @param {Object} data JSON representation of a checkpoint |
|
2000 | 2002 | * @param {String} status Description of response status |
|
2001 | 2003 | * @param {jqXHR} xhr jQuery Ajax object |
|
2002 | 2004 | */ |
|
2003 | 2005 | Notebook.prototype.list_checkpoints_success = function (data, status, xhr) { |
|
2004 | 2006 | var data = $.parseJSON(data); |
|
2005 | 2007 | this.checkpoints = data; |
|
2006 | 2008 | if (data.length) { |
|
2007 | 2009 | this.last_checkpoint = data[data.length - 1]; |
|
2008 | 2010 | } else { |
|
2009 | 2011 | this.last_checkpoint = null; |
|
2010 | 2012 | } |
|
2011 | 2013 | $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]); |
|
2012 | 2014 | }; |
|
2013 | 2015 | |
|
2014 | 2016 | /** |
|
2015 | 2017 | * Failure callback for listing a checkpoint. |
|
2016 | 2018 | * |
|
2017 | 2019 | * @method list_checkpoint_error |
|
2018 | 2020 | * @param {jqXHR} xhr jQuery Ajax object |
|
2019 | 2021 | * @param {String} status Description of response status |
|
2020 | 2022 | * @param {String} error_msg HTTP error message |
|
2021 | 2023 | */ |
|
2022 | 2024 | Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) { |
|
2023 | 2025 | $([IPython.events]).trigger('list_checkpoints_failed.Notebook'); |
|
2024 | 2026 | }; |
|
2025 | 2027 | |
|
2026 | 2028 | /** |
|
2027 | 2029 | * Create a checkpoint of this notebook on the server from the most recent save. |
|
2028 | 2030 | * |
|
2029 | 2031 | * @method create_checkpoint |
|
2030 | 2032 | */ |
|
2031 | 2033 | Notebook.prototype.create_checkpoint = function () { |
|
2032 | 2034 | var url = utils.url_join_encode( |
|
2033 | 2035 | this._baseProjectUrl, |
|
2034 | 2036 | 'api/notebooks', |
|
2035 | 2037 | this.notebookPath(), |
|
2036 | 2038 | this.notebook_name, |
|
2037 | 2039 | 'checkpoints' |
|
2038 | 2040 | ); |
|
2039 | 2041 | $.post(url).done( |
|
2040 | 2042 | $.proxy(this.create_checkpoint_success, this) |
|
2041 | 2043 | ).fail( |
|
2042 | 2044 | $.proxy(this.create_checkpoint_error, this) |
|
2043 | 2045 | ); |
|
2044 | 2046 | }; |
|
2045 | 2047 | |
|
2046 | 2048 | /** |
|
2047 | 2049 | * Success callback for creating a checkpoint. |
|
2048 | 2050 | * |
|
2049 | 2051 | * @method create_checkpoint_success |
|
2050 | 2052 | * @param {Object} data JSON representation of a checkpoint |
|
2051 | 2053 | * @param {String} status Description of response status |
|
2052 | 2054 | * @param {jqXHR} xhr jQuery Ajax object |
|
2053 | 2055 | */ |
|
2054 | 2056 | Notebook.prototype.create_checkpoint_success = function (data, status, xhr) { |
|
2055 | 2057 | var data = $.parseJSON(data); |
|
2056 | 2058 | this.add_checkpoint(data); |
|
2057 | 2059 | $([IPython.events]).trigger('checkpoint_created.Notebook', data); |
|
2058 | 2060 | }; |
|
2059 | 2061 | |
|
2060 | 2062 | /** |
|
2061 | 2063 | * Failure callback for creating a checkpoint. |
|
2062 | 2064 | * |
|
2063 | 2065 | * @method create_checkpoint_error |
|
2064 | 2066 | * @param {jqXHR} xhr jQuery Ajax object |
|
2065 | 2067 | * @param {String} status Description of response status |
|
2066 | 2068 | * @param {String} error_msg HTTP error message |
|
2067 | 2069 | */ |
|
2068 | 2070 | Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) { |
|
2069 | 2071 | $([IPython.events]).trigger('checkpoint_failed.Notebook'); |
|
2070 | 2072 | }; |
|
2071 | 2073 | |
|
2072 | 2074 | Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) { |
|
2073 | 2075 | var that = this; |
|
2074 | 2076 | var checkpoint = checkpoint || this.last_checkpoint; |
|
2075 | 2077 | if ( ! checkpoint ) { |
|
2076 | 2078 | console.log("restore dialog, but no checkpoint to restore to!"); |
|
2077 | 2079 | return; |
|
2078 | 2080 | } |
|
2079 | 2081 | var body = $('<div/>').append( |
|
2080 | 2082 | $('<p/>').addClass("p-space").text( |
|
2081 | 2083 | "Are you sure you want to revert the notebook to " + |
|
2082 | 2084 | "the latest checkpoint?" |
|
2083 | 2085 | ).append( |
|
2084 | 2086 | $("<strong/>").text( |
|
2085 | 2087 | " This cannot be undone." |
|
2086 | 2088 | ) |
|
2087 | 2089 | ) |
|
2088 | 2090 | ).append( |
|
2089 | 2091 | $('<p/>').addClass("p-space").text("The checkpoint was last updated at:") |
|
2090 | 2092 | ).append( |
|
2091 | 2093 | $('<p/>').addClass("p-space").text( |
|
2092 | 2094 | Date(checkpoint.last_modified) |
|
2093 | 2095 | ).css("text-align", "center") |
|
2094 | 2096 | ); |
|
2095 | 2097 | |
|
2096 | 2098 | IPython.dialog.modal({ |
|
2097 | 2099 | title : "Revert notebook to checkpoint", |
|
2098 | 2100 | body : body, |
|
2099 | 2101 | buttons : { |
|
2100 | 2102 | Revert : { |
|
2101 | 2103 | class : "btn-danger", |
|
2102 | 2104 | click : function () { |
|
2103 | 2105 | that.restore_checkpoint(checkpoint.id); |
|
2104 | 2106 | } |
|
2105 | 2107 | }, |
|
2106 | 2108 | Cancel : {} |
|
2107 | 2109 | } |
|
2108 | 2110 | }); |
|
2109 | 2111 | } |
|
2110 | 2112 | |
|
2111 | 2113 | /** |
|
2112 | 2114 | * Restore the notebook to a checkpoint state. |
|
2113 | 2115 | * |
|
2114 | 2116 | * @method restore_checkpoint |
|
2115 | 2117 | * @param {String} checkpoint ID |
|
2116 | 2118 | */ |
|
2117 | 2119 | Notebook.prototype.restore_checkpoint = function (checkpoint) { |
|
2118 | 2120 | $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint); |
|
2119 | 2121 | var url = utils.url_join_encode( |
|
2120 | 2122 | this._baseProjectUrl, |
|
2121 | 2123 | 'api/notebooks', |
|
2122 | 2124 | this.notebookPath(), |
|
2123 | 2125 | this.notebook_name, |
|
2124 | 2126 | 'checkpoints', |
|
2125 | 2127 | checkpoint |
|
2126 | 2128 | ); |
|
2127 | 2129 | $.post(url).done( |
|
2128 | 2130 | $.proxy(this.restore_checkpoint_success, this) |
|
2129 | 2131 | ).fail( |
|
2130 | 2132 | $.proxy(this.restore_checkpoint_error, this) |
|
2131 | 2133 | ); |
|
2132 | 2134 | }; |
|
2133 | 2135 | |
|
2134 | 2136 | /** |
|
2135 | 2137 | * Success callback for restoring a notebook to a checkpoint. |
|
2136 | 2138 | * |
|
2137 | 2139 | * @method restore_checkpoint_success |
|
2138 | 2140 | * @param {Object} data (ignored, should be empty) |
|
2139 | 2141 | * @param {String} status Description of response status |
|
2140 | 2142 | * @param {jqXHR} xhr jQuery Ajax object |
|
2141 | 2143 | */ |
|
2142 | 2144 | Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) { |
|
2143 | 2145 | $([IPython.events]).trigger('checkpoint_restored.Notebook'); |
|
2144 | 2146 | this.load_notebook(this.notebook_name, this.notebook_path); |
|
2145 | 2147 | }; |
|
2146 | 2148 | |
|
2147 | 2149 | /** |
|
2148 | 2150 | * Failure callback for restoring a notebook to a checkpoint. |
|
2149 | 2151 | * |
|
2150 | 2152 | * @method restore_checkpoint_error |
|
2151 | 2153 | * @param {jqXHR} xhr jQuery Ajax object |
|
2152 | 2154 | * @param {String} status Description of response status |
|
2153 | 2155 | * @param {String} error_msg HTTP error message |
|
2154 | 2156 | */ |
|
2155 | 2157 | Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) { |
|
2156 | 2158 | $([IPython.events]).trigger('checkpoint_restore_failed.Notebook'); |
|
2157 | 2159 | }; |
|
2158 | 2160 | |
|
2159 | 2161 | /** |
|
2160 | 2162 | * Delete a notebook checkpoint. |
|
2161 | 2163 | * |
|
2162 | 2164 | * @method delete_checkpoint |
|
2163 | 2165 | * @param {String} checkpoint ID |
|
2164 | 2166 | */ |
|
2165 | 2167 | Notebook.prototype.delete_checkpoint = function (checkpoint) { |
|
2166 | 2168 | $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint); |
|
2167 | 2169 | var url = utils.url_join_encode( |
|
2168 | 2170 | this._baseProjectUrl, |
|
2169 | 2171 | 'api/notebooks', |
|
2170 | 2172 | this.notebookPath(), |
|
2171 | 2173 | this.notebook_name, |
|
2172 | 2174 | 'checkpoints', |
|
2173 | 2175 | checkpoint |
|
2174 | 2176 | ); |
|
2175 | 2177 | $.ajax(url, { |
|
2176 | 2178 | type: 'DELETE', |
|
2177 | 2179 | success: $.proxy(this.delete_checkpoint_success, this), |
|
2178 | 2180 | error: $.proxy(this.delete_notebook_error,this) |
|
2179 | 2181 | }); |
|
2180 | 2182 | }; |
|
2181 | 2183 | |
|
2182 | 2184 | /** |
|
2183 | 2185 | * Success callback for deleting a notebook checkpoint |
|
2184 | 2186 | * |
|
2185 | 2187 | * @method delete_checkpoint_success |
|
2186 | 2188 | * @param {Object} data (ignored, should be empty) |
|
2187 | 2189 | * @param {String} status Description of response status |
|
2188 | 2190 | * @param {jqXHR} xhr jQuery Ajax object |
|
2189 | 2191 | */ |
|
2190 | 2192 | Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) { |
|
2191 | 2193 | $([IPython.events]).trigger('checkpoint_deleted.Notebook', data); |
|
2192 | 2194 | this.load_notebook(this.notebook_name, this.notebook_path); |
|
2193 | 2195 | }; |
|
2194 | 2196 | |
|
2195 | 2197 | /** |
|
2196 | 2198 | * Failure callback for deleting a notebook checkpoint. |
|
2197 | 2199 | * |
|
2198 | 2200 | * @method delete_checkpoint_error |
|
2199 | 2201 | * @param {jqXHR} xhr jQuery Ajax object |
|
2200 | 2202 | * @param {String} status Description of response status |
|
2201 | 2203 | * @param {String} error_msg HTTP error message |
|
2202 | 2204 | */ |
|
2203 | 2205 | Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) { |
|
2204 | 2206 | $([IPython.events]).trigger('checkpoint_delete_failed.Notebook'); |
|
2205 | 2207 | }; |
|
2206 | 2208 | |
|
2207 | 2209 | |
|
2208 | 2210 | IPython.Notebook = Notebook; |
|
2209 | 2211 | |
|
2210 | 2212 | |
|
2211 | 2213 | return IPython; |
|
2212 | 2214 | |
|
2213 | 2215 | }(IPython)); No newline at end of file |
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 | 2 | // Copyright (C) 2013 The IPython Development Team |
|
3 | 3 | // |
|
4 | 4 | // Distributed under the terms of the BSD License. The full license is in |
|
5 | 5 | // the file COPYING, distributed as part of this software. |
|
6 | 6 | //---------------------------------------------------------------------------- |
|
7 | 7 | |
|
8 | 8 | //============================================================================ |
|
9 | 9 | // WidgetModel, WidgetView, and WidgetManager |
|
10 | 10 | //============================================================================ |
|
11 | 11 | /** |
|
12 | 12 | * Base Widget classes |
|
13 | 13 | * @module IPython |
|
14 | 14 | * @namespace IPython |
|
15 | 15 | * @submodule widget |
|
16 | 16 | */ |
|
17 | 17 | |
|
18 | 18 | "use strict"; |
|
19 | 19 | |
|
20 | 20 | // Use require.js 'define' method so that require.js is intelligent enough to |
|
21 | 21 | // syncronously load everything within this file when it is being 'required' |
|
22 | 22 | // elsewhere. |
|
23 | 23 | define(["components/underscore/underscore-min", |
|
24 | 24 | "components/backbone/backbone-min", |
|
25 | 25 | ], function(){ |
|
26 | 26 | |
|
27 | // Only run once on a notebook. | |
|
28 | if (IPython.notebook.widget_manager == undefined) { | |
|
29 | ||
|
30 |
|
|
|
31 | // WidgetModel class | |
|
32 | //-------------------------------------------------------------------- | |
|
33 | var WidgetModel = Backbone.Model.extend({ | |
|
34 | constructor: function(comm_manager, comm, widget_view_types) { | |
|
35 | this.comm_manager = comm_manager; | |
|
36 | this.widget_view_types = widget_view_types; | |
|
37 |
|
|
|
38 |
|
|
|
39 | this.msg_buffer = null; | |
|
40 | this.views = {}; | |
|
41 | ||
|
42 | // Remember comm associated with the model. | |
|
43 | this.comm = comm; | |
|
44 | comm.model = this; | |
|
45 | ||
|
46 | // Hook comm messages up to model. | |
|
47 | comm.on_close($.proxy(this.handle_comm_closed, this)); | |
|
48 | comm.on_msg($.proxy(this.handle_comm_msg, this)); | |
|
49 | ||
|
50 | return Backbone.Model.apply(this); | |
|
51 | }, | |
|
52 | ||
|
53 | ||
|
54 | update_other_views: function(caller) { | |
|
55 | this.last_modified_view = caller; | |
|
56 | this.save(this.changedAttributes(), {patch: true}); | |
|
57 | ||
|
58 |
for (var |
|
|
59 |
var view |
|
|
60 |
f |
|
|
61 |
|
|
|
62 | if (view !== caller) { | |
|
63 | view.update(); | |
|
64 | } | |
|
27 | ||
|
28 | //-------------------------------------------------------------------- | |
|
29 | // WidgetModel class | |
|
30 | //-------------------------------------------------------------------- | |
|
31 | var WidgetModel = Backbone.Model.extend({ | |
|
32 | constructor: function(comm_manager, comm, widget_view_types) { | |
|
33 | this.comm_manager = comm_manager; | |
|
34 | this.widget_view_types = widget_view_types; | |
|
35 | this.pending_msgs = 0; | |
|
36 | this.msg_throttle = 3; | |
|
37 | this.msg_buffer = null; | |
|
38 | this.views = {}; | |
|
39 | ||
|
40 | // Remember comm associated with the model. | |
|
41 | this.comm = comm; | |
|
42 | comm.model = this; | |
|
43 | ||
|
44 | // Hook comm messages up to model. | |
|
45 | comm.on_close($.proxy(this.handle_comm_closed, this)); | |
|
46 | comm.on_msg($.proxy(this.handle_comm_msg, this)); | |
|
47 | ||
|
48 | return Backbone.Model.apply(this); | |
|
49 | }, | |
|
50 | ||
|
51 | ||
|
52 | update_other_views: function(caller) { | |
|
53 | this.last_modified_view = caller; | |
|
54 | this.save(this.changedAttributes(), {patch: true}); | |
|
55 | ||
|
56 | for (var output_area in this.views) { | |
|
57 | var views = this.views[output_area]; | |
|
58 | for (var view_index in views) { | |
|
59 | var view = views[view_index]; | |
|
60 | if (view !== caller) { | |
|
61 | view.update(); | |
|
65 | 62 | } |
|
66 | 63 | } |
|
67 |
} |
|
|
68 | ||
|
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 { | |
|
64 | } | |
|
65 | }, | |
|
86 | 66 | |
|
87 | // Only decrease the pending message count if the buffer | |
|
88 | // doesn't get flushed (sent). | |
|
89 | --this.pending_msgs; | |
|
90 | } | |
|
91 |
|
|
|
67 | ||
|
68 | handle_status: function (output_area, msg) { | |
|
69 | //execution_state : ('busy', 'idle', 'starting') | |
|
70 | if (msg.content.execution_state=='idle') { | |
|
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 |
} |
|
|
94 | ||
|
95 | ||
|
96 | // Custom syncronization logic. | |
|
97 | handle_sync: function (method, options) { | |
|
98 | var model_json = this.toJSON(); | |
|
99 | ||
|
100 | // Only send updated state if the state hasn't been changed | |
|
101 | // during an update. | |
|
102 | if (!this.updating) { | |
|
103 | if (this.pending_msgs >= this.msg_throttle) { | |
|
104 | // The throttle has been exceeded, buffer the current msg so | |
|
105 | // it can be sent once the kernel has finished processing | |
|
106 | // some of the existing messages. | |
|
107 | if (method=='patch') { | |
|
108 |
|
|
|
109 |
|
|
|
110 | } | |
|
111 | for (var attr in options.attrs) { | |
|
112 | this.msg_buffer[attr] = options.attrs[attr]; | |
|
113 | } | |
|
114 | } else { | |
|
89 | } | |
|
90 | }, | |
|
91 | ||
|
92 | ||
|
93 | // Custom syncronization logic. | |
|
94 | handle_sync: function (method, options) { | |
|
95 | var model_json = this.toJSON(); | |
|
96 | ||
|
97 | // Only send updated state if the state hasn't been changed | |
|
98 | // during an update. | |
|
99 | if (!this.updating) { | |
|
100 | if (this.pending_msgs >= this.msg_throttle) { | |
|
101 | // The throttle has been exceeded, buffer the current msg so | |
|
102 | // it can be sent once the kernel has finished processing | |
|
103 | // some of the existing messages. | |
|
104 | if (method=='patch') { | |
|
105 | if (this.msg_buffer == null) { | |
|
115 | 106 | this.msg_buffer = $.extend({}, model_json); // Copy |
|
116 | 107 | } |
|
117 | ||
|
118 | } else { | |
|
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 | } | |
|
108 | for (var attr in options.attrs) { | |
|
109 | this.msg_buffer[attr] = options.attrs[attr]; | |
|
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}; | |
|
131 | var output_area = this.last_modified_view.output_area; | |
|
132 | var callbacks = this._make_callbacks(output_area); | |
|
133 | this.comm.send(data, callbacks); | |
|
134 |
|
|
|
115 | } else { | |
|
116 | // We haven't exceeded the throttle, send the message like | |
|
117 | // normal. If this is a patch operation, just send the | |
|
118 | // changes. | |
|
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 |
|
|
|
138 | // Since the comm is a one-way communication, assume the message | |
|
139 | // arrived. | |
|
140 | return model_json; | |
|
141 | }, | |
|
142 | ||
|
143 | ||
|
144 | // Handle incomming comm msg. | |
|
145 | handle_comm_msg: function (msg) { | |
|
146 | var method = msg.content.data.method; | |
|
147 | switch (method){ | |
|
148 | case 'display': | |
|
149 | ||
|
150 | // Try to get the cell index. | |
|
151 | var output_area = this._get_output_area(msg.parent_header.msg_id); | |
|
152 | if (output_area == null) { | |
|
153 | console.log("Could not determine where the display" + | |
|
154 | " message was from. Widget will not be displayed") | |
|
133 | } | |
|
134 | ||
|
135 | // Since the comm is a one-way communication, assume the message | |
|
136 | // arrived. | |
|
137 | return model_json; | |
|
138 | }, | |
|
139 | ||
|
140 | ||
|
141 | // Handle incomming comm msg. | |
|
142 | handle_comm_msg: function (msg) { | |
|
143 | var method = msg.content.data.method; | |
|
144 | switch (method){ | |
|
145 | case 'display': | |
|
146 | ||
|
147 | // Try to get the cell index. | |
|
148 | var output_area = this._get_output_area(msg.parent_header.msg_id); | |
|
149 | if (output_area == null) { | |
|
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 | 173 | } else { |
|
156 |
this. |
|
|
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 | } | |
|
174 | this.set(key, state[key]); | |
|
179 | 175 | } |
|
180 | 176 | } |
|
181 | this.id = this.comm.comm_id; | |
|
182 | this.save(); | |
|
183 | } finally { | |
|
184 | this.updating = false; | |
|
185 | 177 | } |
|
186 | }, | |
|
187 | ||
|
188 | ||
|
189 | // Handle when a widget is closed. | |
|
190 | handle_comm_closed: function (msg) { | |
|
191 | for (var output_area in this.views) { | |
|
192 | var views = this.views[output_area]; | |
|
193 | for (var view_index in views) { | |
|
194 | var view = views[view_index]; | |
|
195 | view.remove(); | |
|
196 | } | |
|
178 | this.id = this.comm.comm_id; | |
|
179 | this.save(); | |
|
180 | } finally { | |
|
181 | this.updating = false; | |
|
182 | } | |
|
183 | }, | |
|
184 | ||
|
185 | ||
|
186 | // Handle when a widget is closed. | |
|
187 | handle_comm_closed: function (msg) { | |
|
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 |
} |
|
|
199 | ||
|
200 | ||
|
201 | // Create view that represents the model. | |
|
202 | display_view: function (view_name, parent_comm_id, output_area) { | |
|
203 | var new_views = []; | |
|
204 | ||
|
205 | var displayed = false; | |
|
206 | if (parent_comm_id != undefined) { | |
|
207 | var parent_comm = this.comm_manager.comms[parent_comm_id]; | |
|
208 |
|
|
|
209 |
|
|
|
210 |
|
|
|
211 | var parent_view = parent_views[parent_view_index]; | |
|
212 | if (parent_view.display_child != undefined) { | |
|
213 | var view = this._create_view(view_name, output_area); | |
|
214 |
|
|
|
215 |
|
|
|
216 |
|
|
|
217 |
|
|
|
194 | } | |
|
195 | }, | |
|
196 | ||
|
197 | ||
|
198 | // Create view that represents the model. | |
|
199 | display_view: function (view_name, parent_comm_id, output_area) { | |
|
200 | var new_views = []; | |
|
201 | ||
|
202 | var displayed = false; | |
|
203 | if (parent_comm_id != undefined) { | |
|
204 | var parent_comm = this.comm_manager.comms[parent_comm_id]; | |
|
205 | var parent_model = parent_comm.model; | |
|
206 | var parent_views = parent_model.views[output_area]; | |
|
207 | for (var parent_view_index in parent_views) { | |
|
208 | var parent_view = parent_views[parent_view_index]; | |
|
209 | if (parent_view.display_child != undefined) { | |
|
210 | var view = this._create_view(view_name, output_area); | |
|
211 | new_views.push(view); | |
|
212 | parent_view.display_child(view); | |
|
213 | displayed = true; | |
|
218 | 214 | } |
|
219 | 215 | } |
|
220 | ||
|
221 | if (!displayed) { | |
|
222 | // No parent view is defined or exists. Add the view's | |
|
223 | // element to cell's widget div. | |
|
224 | var view = this._create_view(view_name, output_area); | |
|
225 | new_views.push(view); | |
|
226 | this._get_widget_area_element(output_area, true) | |
|
227 | .append(view.$el); | |
|
228 | ||
|
216 | } | |
|
217 | ||
|
218 | if (!displayed) { | |
|
219 | // No parent view is defined or exists. Add the view's | |
|
220 | // element to cell's widget div. | |
|
221 | var view = this._create_view(view_name, output_area); | |
|
222 | new_views.push(view); | |
|
223 | this._get_widget_area_element(output_area, true) | |
|
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 | ||
|
231 | for (var view_index in new_views) { | |
|
232 | var view = new_views[view_index]; | |
|
233 | view.update(); | |
|
252 | view.remove(); // Clean-up view | |
|
253 | if (that.views[output_area].length()==0) { | |
|
254 | delete that.views[output_area]; | |
|
234 | 255 | } |
|
235 | }, | |
|
236 | ||
|
237 | 256 | |
|
238 | // Create a view | |
|
239 | _create_view: function (view_name, output_area) { | |
|
240 | var view = new this.widget_view_types[view_name]({model: this}); | |
|
241 | view.render(); | |
|
242 | if (this.views[output_area]==undefined) { | |
|
243 | this.views[output_area] = [] | |
|
257 | // Close the comm if there are no views left. | |
|
258 | if (that.views.length()==0) { | |
|
259 | that.comm.close(); | |
|
244 | 260 | } |
|
245 | this.views[output_area].push(view); | |
|
246 | view.output_area = output_area; | |
|
247 | ||
|
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 | }, | |
|
261 | }); | |
|
262 | return view; | |
|
263 | }, | |
|
267 | 264 | |
|
268 | 265 | |
|
269 |
|
|
|
270 |
|
|
|
271 |
|
|
|
272 |
|
|
|
273 |
|
|
|
274 |
|
|
|
275 |
|
|
|
276 |
|
|
|
277 |
|
|
|
278 |
|
|
|
279 |
|
|
|
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 | }, | |
|
266 | // Build a callback dict. | |
|
267 | _make_callbacks: function (output_area) { | |
|
268 | var callbacks = {}; | |
|
269 | if (output_area != null) { | |
|
270 | var that = this; | |
|
271 | callbacks = { | |
|
272 | iopub : { | |
|
273 | output : $.proxy(output_area.handle_output, output_area), | |
|
274 | clear_output : $.proxy(output_area.handle_clear_output, output_area), | |
|
275 | status : function(msg){ | |
|
276 | that.handle_status(output_area, msg); | |
|
289 | 277 | }, |
|
290 | }; | |
|
291 | } | |
|
292 | return callbacks; | |
|
293 | }, | |
|
278 | get_output_area : function() { | |
|
279 | if (that.last_modified_view != undefined && | |
|
280 | that.last_modified_view.output_area != undefined) { | |
|
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 |
|
|
|
297 |
|
|
|
298 |
|
|
|
299 |
|
|
|
300 |
|
|
|
301 |
|
|
|
302 |
|
|
|
303 |
|
|
|
304 |
|
|
|
305 |
|
|
|
306 | } | |
|
293 | // Get the output area corresponding to the msg_id. | |
|
294 | // output_area is an instance of Ipython.OutputArea | |
|
295 | _get_output_area: function (msg_id) { | |
|
296 | ||
|
297 | // First, guess cell.execute triggered | |
|
298 | var cells = IPython.notebook.get_cells(); | |
|
299 | for (var cell_index in cells) { | |
|
300 | if (cells[cell_index].last_msg_id == msg_id) { | |
|
301 | var cell = IPython.notebook.get_cell(cell_index) | |
|
302 | return cell.output_area; | |
|
307 | 303 | } |
|
304 | } | |
|
308 | 305 | |
|
309 |
|
|
|
310 |
|
|
|
311 |
|
|
|
312 |
|
|
|
313 |
|
|
|
314 |
|
|
|
315 | } | |
|
306 | // Second, guess widget triggered | |
|
307 | var callbacks = this.comm_manager.kernel.get_callbacks_for_msg(msg_id) | |
|
308 | if (callbacks != undefined && callbacks.iopub != undefined && callbacks.iopub.get_output_area != undefined) { | |
|
309 | var output_area = callbacks.iopub.get_output_area(); | |
|
310 | if (output_area != null) { | |
|
311 | return output_area; | |
|
316 | 312 | } |
|
317 |
|
|
|
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 | }, | |
|
313 | } | |
|
347 | 314 | |
|
348 | update: function() { | |
|
349 | if (this.model.get('visible') != undefined) { | |
|
350 | if (this.visible != this.model.get('visible')) { | |
|
351 | this.visible = this.model.get('visible'); | |
|
352 | if (this.visible) { | |
|
353 | this.$el.show(); | |
|
354 | } else { | |
|
355 | this.$el.hide(); | |
|
356 | } | |
|
315 | // Not triggered by a widget or a cell | |
|
316 | return null; | |
|
317 | }, | |
|
318 | ||
|
319 | // Gets widget output area (as a JQuery element) from the | |
|
320 | // output_area (Ipython.OutputArea instance) | |
|
321 | _get_widget_area_element: function (output_area, show) { | |
|
322 | var widget_area = output_area.element | |
|
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 | ||
|
360 | if (this.model.css != undefined) { | |
|
361 |
|
|
|
362 |
|
|
|
363 | ||
|
364 | // Apply the css traits to all elements that match the selector. | |
|
365 |
|
|
|
366 |
|
|
|
367 | var css_traits = this.model.css[selector]; | |
|
368 |
|
|
|
369 |
|
|
|
370 |
|
|
|
371 |
|
|
|
355 | } | |
|
356 | ||
|
357 | if (this.model.css != undefined) { | |
|
358 | for (var selector in this.model.css) { | |
|
359 | if (this.model.css.hasOwnProperty(selector)) { | |
|
360 | ||
|
361 | // Apply the css traits to all elements that match the selector. | |
|
362 | var elements = this.get_selector_element(selector); | |
|
363 | if (elements.length > 0) { | |
|
364 | var css_traits = this.model.css[selector]; | |
|
365 | for (var css_key in css_traits) { | |
|
366 | if (css_traits.hasOwnProperty(css_key)) { | |
|
367 | elements.css(css_key, css_traits[css_key]); | |
|
372 | 368 | } |
|
373 | 369 | } |
|
374 | 370 | } |
|
375 | 371 | } |
|
376 | 372 | } |
|
377 | ||
|
378 | var add_class = this.model.get('_add_class'); | |
|
379 | if (add_class != undefined){ | |
|
380 | var add_class_calls = add_class[0]; | |
|
381 |
|
|
|
382 |
|
|
|
383 | var elements = this.get_selector_element(add_class[1]); | |
|
384 | if (elements.length > 0) { | |
|
385 |
|
|
|
386 | } | |
|
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; | |
|
373 | } | |
|
374 | ||
|
375 | var add_class = this.model.get('_add_class'); | |
|
376 | if (add_class != undefined){ | |
|
377 | var add_class_calls = add_class[0]; | |
|
378 | if (add_class_calls > this._add_class_calls) { | |
|
379 | this._add_class_calls = add_class_calls; | |
|
380 | var elements = this.get_selector_element(add_class[1]); | |
|
381 | if (elements.length > 0) { | |
|
382 | elements.addClass(add_class[2]); | |
|
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; | |
|
417 | }, | |
|
418 |
} |
|
|
419 | ||
|
420 | ||
|
421 | //-------------------------------------------------------------------- | |
|
422 | // WidgetManager class | |
|
423 | //-------------------------------------------------------------------- | |
|
424 | var WidgetManager = function(comm_manager){ | |
|
425 | this.comm_manager = comm_manager; | |
|
426 | this.widget_model_types = {}; | |
|
427 |
|
|
|
428 | ||
|
429 | var that = this; | |
|
430 | Backbone.sync = function(method, model, options, error) { | |
|
431 | var result = model.handle_sync(method, options); | |
|
432 | if (options.success) { | |
|
433 |
|
|
|
434 | } | |
|
435 |
} |
|
|
412 | } | |
|
413 | return elements; | |
|
414 | }, | |
|
415 | }); | |
|
416 | ||
|
417 | ||
|
418 | //-------------------------------------------------------------------- | |
|
419 | // WidgetManager class | |
|
420 | //-------------------------------------------------------------------- | |
|
421 | var WidgetManager = function(){ | |
|
422 | this.comm_manager = null; | |
|
423 | this.widget_model_types = {}; | |
|
424 | this.widget_view_types = {}; | |
|
425 | ||
|
426 | var that = this; | |
|
427 | Backbone.sync = function(method, model, options, error) { | |
|
428 | var result = model.handle_sync(method, options); | |
|
429 | if (options.success) { | |
|
430 | options.success(result); | |
|
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 |
|
|
|
440 |
|
|
|
441 |
|
|
|
446 | WidgetManager.prototype.register_widget_model = function (widget_model_name, widget_model_type) { | |
|
447 | // Register the widget with the comm manager. Make sure to pass this object's context | |
|
448 | // in so `this` works in the call back. | |
|
449 | if (this.comm_manager!=null) { | |
|
442 | 450 | this.comm_manager.register_target(widget_model_name, $.proxy(this.handle_com_open, this)); |
|
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 |
|
|
|
448 |
|
|
|
449 |
|
|
|
456 | WidgetManager.prototype.register_widget_view = function (widget_view_name, widget_view_type) { | |
|
457 | this.widget_view_types[widget_view_name] = widget_view_type; | |
|
458 | } | |
|
450 | 459 | |
|
451 | 460 | |
|
452 |
|
|
|
453 |
|
|
|
454 |
|
|
|
455 |
|
|
|
461 | WidgetManager.prototype.handle_com_open = function (comm, msg) { | |
|
462 | var widget_type_name = msg.content.target_name; | |
|
463 | var widget_model = new this.widget_model_types[widget_type_name](this.comm_manager, comm, this.widget_view_types); | |
|
464 | } | |
|
456 | 465 | |
|
457 | 466 | |
|
458 |
|
|
|
459 |
|
|
|
460 |
|
|
|
461 |
|
|
|
462 |
|
|
|
463 |
|
|
|
467 | //-------------------------------------------------------------------- | |
|
468 | // Init code | |
|
469 | //-------------------------------------------------------------------- | |
|
470 | IPython.WidgetManager = WidgetManager; | |
|
471 | IPython.WidgetModel = WidgetModel; | |
|
472 | IPython.WidgetView = WidgetView; | |
|
464 | 473 | |
|
465 |
|
|
|
474 | IPython.widget_manager = new WidgetManager(); | |
|
466 | 475 | |
|
467 | }; | |
|
468 | 476 | }); |
@@ -1,109 +1,109 b'' | |||
|
1 | 1 | |
|
2 |
|
|
|
2 | define(["notebook/js/widget"], function(){ | |
|
3 | 3 | |
|
4 | 4 | var BoolWidgetModel = IPython.WidgetModel.extend({}); |
|
5 |
IPython |
|
|
5 | IPython.widget_manager.register_widget_model('BoolWidgetModel', BoolWidgetModel); | |
|
6 | 6 | |
|
7 | 7 | var CheckboxView = IPython.WidgetView.extend({ |
|
8 | 8 | |
|
9 | 9 | // Called when view is rendered. |
|
10 | 10 | render : function(){ |
|
11 | 11 | this.$el = $('<div />') |
|
12 | 12 | .addClass('widget-hbox-single'); |
|
13 | 13 | this.$label = $('<div />') |
|
14 | 14 | .addClass('widget-hlabel') |
|
15 | 15 | .appendTo(this.$el) |
|
16 | 16 | .hide(); |
|
17 | 17 | var that = this; |
|
18 | 18 | this.$checkbox = $('<input />') |
|
19 | 19 | .attr('type', 'checkbox') |
|
20 | 20 | .click(function(el) { |
|
21 | 21 | that.user_invoked_update = true; |
|
22 | 22 | that.model.set('value', that.$checkbox.prop('checked')); |
|
23 | 23 | that.model.update_other_views(that); |
|
24 | 24 | that.user_invoked_update = false; |
|
25 | 25 | }) |
|
26 | 26 | .appendTo(this.$el); |
|
27 | 27 | |
|
28 | 28 | this.$el_to_style = this.$checkbox; // Set default element to style |
|
29 | 29 | this.update(); // Set defaults. |
|
30 | 30 | }, |
|
31 | 31 | |
|
32 | 32 | // Handles: Backend -> Frontend Sync |
|
33 | 33 | // Frontent -> Frontend Sync |
|
34 | 34 | update : function(){ |
|
35 | 35 | if (!this.user_invoked_update) { |
|
36 | 36 | this.$checkbox.prop('checked', this.model.get('value')); |
|
37 | 37 | |
|
38 | 38 | var disabled = this.model.get('disabled'); |
|
39 | 39 | this.$checkbox.prop('disabled', disabled); |
|
40 | 40 | |
|
41 | 41 | var description = this.model.get('description'); |
|
42 | 42 | if (description.length == 0) { |
|
43 | 43 | this.$label.hide(); |
|
44 | 44 | } else { |
|
45 | 45 | this.$label.html(description); |
|
46 | 46 | this.$label.show(); |
|
47 | 47 | } |
|
48 | 48 | } |
|
49 | 49 | return IPython.WidgetView.prototype.update.call(this); |
|
50 | 50 | }, |
|
51 | 51 | |
|
52 | 52 | }); |
|
53 | 53 | |
|
54 |
IPython |
|
|
54 | IPython.widget_manager.register_widget_view('CheckboxView', CheckboxView); | |
|
55 | 55 | |
|
56 | 56 | var ToggleButtonView = IPython.WidgetView.extend({ |
|
57 | 57 | |
|
58 | 58 | // Called when view is rendered. |
|
59 | 59 | render : function(){ |
|
60 | 60 | this.$el |
|
61 | 61 | .html(''); |
|
62 | 62 | |
|
63 | 63 | this.$button = $('<button />') |
|
64 | 64 | .addClass('btn') |
|
65 | 65 | .attr('type', 'button') |
|
66 | 66 | .attr('data-toggle', 'button') |
|
67 | 67 | .appendTo(this.$el); |
|
68 | 68 | this.$el_to_style = this.$button; // Set default element to style |
|
69 | 69 | |
|
70 | 70 | this.update(); // Set defaults. |
|
71 | 71 | }, |
|
72 | 72 | |
|
73 | 73 | // Handles: Backend -> Frontend Sync |
|
74 | 74 | // Frontent -> Frontend Sync |
|
75 | 75 | update : function(){ |
|
76 | 76 | if (!this.user_invoked_update) { |
|
77 | 77 | if (this.model.get('value')) { |
|
78 | 78 | this.$button.addClass('active'); |
|
79 | 79 | } else { |
|
80 | 80 | this.$button.removeClass('active'); |
|
81 | 81 | } |
|
82 | 82 | |
|
83 | 83 | var disabled = this.model.get('disabled'); |
|
84 | 84 | this.$button.prop('disabled', disabled); |
|
85 | 85 | |
|
86 | 86 | var description = this.model.get('description'); |
|
87 | 87 | if (description.length == 0) { |
|
88 | 88 | this.$button.html(' '); // Preserve button height |
|
89 | 89 | } else { |
|
90 | 90 | this.$button.html(description); |
|
91 | 91 | } |
|
92 | 92 | } |
|
93 | 93 | return IPython.WidgetView.prototype.update.call(this); |
|
94 | 94 | }, |
|
95 | 95 | |
|
96 | 96 | events: {"click button" : "handleClick"}, |
|
97 | 97 | |
|
98 | 98 | // Handles and validates user input. |
|
99 | 99 | handleClick: function(e) { |
|
100 | 100 | this.user_invoked_update = true; |
|
101 | 101 | this.model.set('value', ! $(e.target).hasClass('active')); |
|
102 | 102 | this.model.update_other_views(this); |
|
103 | 103 | this.user_invoked_update = false; |
|
104 | 104 | }, |
|
105 | 105 | }); |
|
106 | 106 | |
|
107 |
IPython |
|
|
107 | IPython.widget_manager.register_widget_view('ToggleButtonView', ToggleButtonView); | |
|
108 | 108 | |
|
109 | 109 | }); |
@@ -1,39 +1,39 b'' | |||
|
1 | 1 | |
|
2 |
|
|
|
2 | define(["notebook/js/widget"], function(){ | |
|
3 | 3 | |
|
4 | 4 | var ButtonWidgetModel = IPython.WidgetModel.extend({}); |
|
5 |
IPython |
|
|
5 | IPython.widget_manager.register_widget_model('ButtonWidgetModel', ButtonWidgetModel); | |
|
6 | 6 | |
|
7 | 7 | var ButtonView = IPython.WidgetView.extend({ |
|
8 | 8 | |
|
9 | 9 | // Called when view is rendered. |
|
10 | 10 | render : function(){ |
|
11 | 11 | var that = this; |
|
12 | 12 | this.$el = $("<button />") |
|
13 | 13 | .addClass('btn') |
|
14 | 14 | .click(function() { |
|
15 | 15 | that.model.set('clicks', that.model.get('clicks') + 1); |
|
16 | 16 | that.model.update_other_views(that); |
|
17 | 17 | }); |
|
18 | 18 | |
|
19 | 19 | this.update(); // Set defaults. |
|
20 | 20 | }, |
|
21 | 21 | |
|
22 | 22 | // Handles: Backend -> Frontend Sync |
|
23 | 23 | // Frontent -> Frontend Sync |
|
24 | 24 | update : function(){ |
|
25 | 25 | var description = this.model.get('description'); |
|
26 | 26 | if (description.length==0) { |
|
27 | 27 | this.$el.html(' '); // Preserve button height |
|
28 | 28 | } else { |
|
29 | 29 | this.$el.html(description); |
|
30 | 30 | } |
|
31 | 31 | |
|
32 | 32 | return IPython.WidgetView.prototype.update.call(this); |
|
33 | 33 | }, |
|
34 | 34 | |
|
35 | 35 | }); |
|
36 | 36 | |
|
37 |
IPython |
|
|
37 | IPython.widget_manager.register_widget_view('ButtonView', ButtonView); | |
|
38 | 38 | |
|
39 | 39 | }); |
@@ -1,46 +1,46 b'' | |||
|
1 |
|
|
|
1 | define(["notebook/js/widget"], function(){ | |
|
2 | 2 | var ContainerModel = IPython.WidgetModel.extend({}); |
|
3 |
IPython |
|
|
3 | IPython.widget_manager.register_widget_model('ContainerWidgetModel', ContainerModel); | |
|
4 | 4 | |
|
5 | 5 | var ContainerView = IPython.WidgetView.extend({ |
|
6 | 6 | |
|
7 | 7 | render: function(){ |
|
8 | 8 | this.$el = $('<div />') |
|
9 | 9 | .addClass('widget-container'); |
|
10 | 10 | }, |
|
11 | 11 | |
|
12 | 12 | update: function(){ |
|
13 | 13 | |
|
14 | 14 | // Apply flexible box model properties by adding and removing |
|
15 | 15 | // corrosponding CSS classes. |
|
16 | 16 | // Defined in IPython/html/static/base/less/flexbox.less |
|
17 | 17 | this.set_flex_property('vbox', this.model.get('_vbox')); |
|
18 | 18 | this.set_flex_property('hbox', this.model.get('_hbox')); |
|
19 | 19 | this.set_flex_property('start', this.model.get('_pack_start')); |
|
20 | 20 | this.set_flex_property('center', this.model.get('_pack_center')); |
|
21 | 21 | this.set_flex_property('end', this.model.get('_pack_end')); |
|
22 | 22 | this.set_flex_property('align-start', this.model.get('_align_start')); |
|
23 | 23 | this.set_flex_property('align-center', this.model.get('_align_center')); |
|
24 | 24 | this.set_flex_property('align-end', this.model.get('_align_end')); |
|
25 | 25 | this.set_flex_property('box-flex0', this.model.get('_flex0')); |
|
26 | 26 | this.set_flex_property('box-flex1', this.model.get('_flex1')); |
|
27 | 27 | this.set_flex_property('box-flex2', this.model.get('_flex2')); |
|
28 | 28 | |
|
29 | 29 | return IPython.WidgetView.prototype.update.call(this); |
|
30 | 30 | }, |
|
31 | 31 | |
|
32 | 32 | set_flex_property: function(property_name, enabled) { |
|
33 | 33 | if (enabled) { |
|
34 | 34 | this.$el.addClass(property_name); |
|
35 | 35 | } else { |
|
36 | 36 | this.$el.removeClass(property_name); |
|
37 | 37 | } |
|
38 | 38 | }, |
|
39 | 39 | |
|
40 | 40 | display_child: function(view) { |
|
41 | 41 | this.$el.append(view.$el); |
|
42 | 42 | }, |
|
43 | 43 | }); |
|
44 | 44 | |
|
45 |
IPython |
|
|
45 | IPython.widget_manager.register_widget_view('ContainerView', ContainerView); | |
|
46 | 46 | }); No newline at end of file |
@@ -1,4 +1,4 b'' | |||
|
1 |
|
|
|
1 | define(["notebook/js/widget"], function(){ | |
|
2 | 2 | var FloatWidgetModel = IPython.WidgetModel.extend({}); |
|
3 |
IPython |
|
|
3 | IPython.widget_manager.register_widget_model('FloatWidgetModel', FloatWidgetModel); | |
|
4 | 4 | }); No newline at end of file |
@@ -1,239 +1,239 b'' | |||
|
1 |
|
|
|
1 | define(["notebook/js/widget"], function(){ | |
|
2 | 2 | var FloatRangeWidgetModel = IPython.WidgetModel.extend({}); |
|
3 |
IPython |
|
|
3 | IPython.widget_manager.register_widget_model('FloatRangeWidgetModel', FloatRangeWidgetModel); | |
|
4 | 4 | |
|
5 | 5 | var FloatSliderView = IPython.WidgetView.extend({ |
|
6 | 6 | |
|
7 | 7 | // Called when view is rendered. |
|
8 | 8 | render : function(){ |
|
9 | 9 | this.$el |
|
10 | 10 | .addClass('widget-hbox-single') |
|
11 | 11 | .html(''); |
|
12 | 12 | this.$label = $('<div />') |
|
13 | 13 | .appendTo(this.$el) |
|
14 | 14 | .addClass('widget-hlabel') |
|
15 | 15 | .hide(); |
|
16 | 16 | this.$slider = $('<div />') |
|
17 | 17 | .slider({}) |
|
18 | 18 | .addClass('slider'); |
|
19 | 19 | |
|
20 | 20 | // Put the slider in a container |
|
21 | 21 | this.$slider_container = $('<div />') |
|
22 | 22 | .addClass('widget-hslider') |
|
23 | 23 | .append(this.$slider); |
|
24 | 24 | this.$el_to_style = this.$slider_container; // Set default element to style |
|
25 | 25 | this.$el.append(this.$slider_container); |
|
26 | 26 | |
|
27 | 27 | // Set defaults. |
|
28 | 28 | this.update(); |
|
29 | 29 | }, |
|
30 | 30 | |
|
31 | 31 | // Handles: Backend -> Frontend Sync |
|
32 | 32 | // Frontent -> Frontend Sync |
|
33 | 33 | update : function(){ |
|
34 | 34 | // Slider related keys. |
|
35 | 35 | var _keys = ['step', 'max', 'min', 'disabled']; |
|
36 | 36 | for (var index in _keys) { |
|
37 | 37 | var key = _keys[index]; |
|
38 | 38 | if (this.model.get(key) != undefined) { |
|
39 | 39 | this.$slider.slider("option", key, this.model.get(key)); |
|
40 | 40 | } |
|
41 | 41 | } |
|
42 | 42 | |
|
43 | 43 | // WORKAROUND FOR JQUERY SLIDER BUG. |
|
44 | 44 | // The horizontal position of the slider handle |
|
45 | 45 | // depends on the value of the slider at the time |
|
46 | 46 | // of orientation change. Before applying the new |
|
47 | 47 | // workaround, we set the value to the minimum to |
|
48 | 48 | // make sure that the horizontal placement of the |
|
49 | 49 | // handle in the vertical slider is always |
|
50 | 50 | // consistent. |
|
51 | 51 | var orientation = this.model.get('orientation'); |
|
52 | 52 | var value = this.model.get('min'); |
|
53 | 53 | this.$slider.slider('option', 'value', value); |
|
54 | 54 | this.$slider.slider('option', 'orientation', orientation); |
|
55 | 55 | var value = this.model.get('value'); |
|
56 | 56 | this.$slider.slider('option', 'value', value); |
|
57 | 57 | |
|
58 | 58 | // Use the right CSS classes for vertical & horizontal sliders |
|
59 | 59 | if (orientation=='vertical') { |
|
60 | 60 | this.$slider_container |
|
61 | 61 | .removeClass('widget-hslider') |
|
62 | 62 | .addClass('widget-vslider'); |
|
63 | 63 | this.$el |
|
64 | 64 | .removeClass('widget-hbox-single') |
|
65 | 65 | .addClass('widget-vbox-single'); |
|
66 | 66 | this.$label |
|
67 | 67 | .removeClass('widget-hlabel') |
|
68 | 68 | .addClass('widget-vlabel'); |
|
69 | 69 | |
|
70 | 70 | } else { |
|
71 | 71 | this.$slider_container |
|
72 | 72 | .removeClass('widget-vslider') |
|
73 | 73 | .addClass('widget-hslider'); |
|
74 | 74 | this.$el |
|
75 | 75 | .removeClass('widget-vbox-single') |
|
76 | 76 | .addClass('widget-hbox-single'); |
|
77 | 77 | this.$label |
|
78 | 78 | .removeClass('widget-vlabel') |
|
79 | 79 | .addClass('widget-hlabel'); |
|
80 | 80 | } |
|
81 | 81 | |
|
82 | 82 | var description = this.model.get('description'); |
|
83 | 83 | if (description.length == 0) { |
|
84 | 84 | this.$label.hide(); |
|
85 | 85 | } else { |
|
86 | 86 | this.$label.html(description); |
|
87 | 87 | this.$label.show(); |
|
88 | 88 | } |
|
89 | 89 | return IPython.WidgetView.prototype.update.call(this); |
|
90 | 90 | }, |
|
91 | 91 | |
|
92 | 92 | // Handles: User input |
|
93 | 93 | events: { "slide" : "handleSliderChange" }, |
|
94 | 94 | handleSliderChange: function(e, ui) { |
|
95 | 95 | this.model.set('value', ui.value); |
|
96 | 96 | this.model.update_other_views(this); |
|
97 | 97 | }, |
|
98 | 98 | }); |
|
99 | 99 | |
|
100 |
IPython |
|
|
100 | IPython.widget_manager.register_widget_view('FloatSliderView', FloatSliderView); | |
|
101 | 101 | |
|
102 | 102 | |
|
103 | 103 | var FloatTextView = IPython.WidgetView.extend({ |
|
104 | 104 | |
|
105 | 105 | // Called when view is rendered. |
|
106 | 106 | render : function(){ |
|
107 | 107 | this.$el |
|
108 | 108 | .addClass('widget-hbox-single') |
|
109 | 109 | .html(''); |
|
110 | 110 | this.$label = $('<div />') |
|
111 | 111 | .appendTo(this.$el) |
|
112 | 112 | .addClass('widget-hlabel') |
|
113 | 113 | .hide(); |
|
114 | 114 | this.$textbox = $('<input type="text" />') |
|
115 | 115 | .addClass('input') |
|
116 | 116 | .addClass('widget-numeric-text') |
|
117 | 117 | .appendTo(this.$el); |
|
118 | 118 | this.$el_to_style = this.$textbox; // Set default element to style |
|
119 | 119 | this.update(); // Set defaults. |
|
120 | 120 | }, |
|
121 | 121 | |
|
122 | 122 | // Handles: Backend -> Frontend Sync |
|
123 | 123 | // Frontent -> Frontend Sync |
|
124 | 124 | update : function(){ |
|
125 | 125 | var value = this.model.get('value'); |
|
126 | 126 | if (!this.changing && parseFloat(this.$textbox.val()) != value) { |
|
127 | 127 | this.$textbox.val(value); |
|
128 | 128 | } |
|
129 | 129 | |
|
130 | 130 | if (this.model.get('disabled')) { |
|
131 | 131 | this.$textbox.attr('disabled','disabled'); |
|
132 | 132 | } else { |
|
133 | 133 | this.$textbox.removeAttr('disabled'); |
|
134 | 134 | } |
|
135 | 135 | |
|
136 | 136 | var description = this.model.get('description'); |
|
137 | 137 | if (description.length == 0) { |
|
138 | 138 | this.$label.hide(); |
|
139 | 139 | } else { |
|
140 | 140 | this.$label.html(description); |
|
141 | 141 | this.$label.show(); |
|
142 | 142 | } |
|
143 | 143 | return IPython.WidgetView.prototype.update.call(this); |
|
144 | 144 | }, |
|
145 | 145 | |
|
146 | 146 | |
|
147 | 147 | events: {"keyup input" : "handleChanging", |
|
148 | 148 | "paste input" : "handleChanging", |
|
149 | 149 | "cut input" : "handleChanging", |
|
150 | 150 | "change input" : "handleChanged"}, // Fires only when control is validated or looses focus. |
|
151 | 151 | |
|
152 | 152 | // Handles and validates user input. |
|
153 | 153 | handleChanging: function(e) { |
|
154 | 154 | |
|
155 | 155 | // Try to parse value as a float. |
|
156 | 156 | var numericalValue = 0.0; |
|
157 | 157 | if (e.target.value != '') { |
|
158 | 158 | numericalValue = parseFloat(e.target.value); |
|
159 | 159 | } |
|
160 | 160 | |
|
161 | 161 | // If parse failed, reset value to value stored in model. |
|
162 | 162 | if (isNaN(numericalValue)) { |
|
163 | 163 | e.target.value = this.model.get('value'); |
|
164 | 164 | } else if (!isNaN(numericalValue)) { |
|
165 | 165 | if (this.model.get('max') != undefined) { |
|
166 | 166 | numericalValue = Math.min(this.model.get('max'), numericalValue); |
|
167 | 167 | } |
|
168 | 168 | if (this.model.get('min') != undefined) { |
|
169 | 169 | numericalValue = Math.max(this.model.get('min'), numericalValue); |
|
170 | 170 | } |
|
171 | 171 | |
|
172 | 172 | // Apply the value if it has changed. |
|
173 | 173 | if (numericalValue != this.model.get('value')) { |
|
174 | 174 | this.changing = true; |
|
175 | 175 | this.model.set('value', numericalValue); |
|
176 | 176 | this.model.update_other_views(this); |
|
177 | 177 | this.changing = false; |
|
178 | 178 | } |
|
179 | 179 | } |
|
180 | 180 | }, |
|
181 | 181 | |
|
182 | 182 | // Applies validated input. |
|
183 | 183 | handleChanged: function(e) { |
|
184 | 184 | // Update the textbox |
|
185 | 185 | if (this.model.get('value') != e.target.value) { |
|
186 | 186 | e.target.value = this.model.get('value'); |
|
187 | 187 | } |
|
188 | 188 | } |
|
189 | 189 | }); |
|
190 | 190 | |
|
191 |
IPython |
|
|
191 | IPython.widget_manager.register_widget_view('FloatTextView', FloatTextView); | |
|
192 | 192 | |
|
193 | 193 | |
|
194 | 194 | var ProgressView = IPython.WidgetView.extend({ |
|
195 | 195 | |
|
196 | 196 | // Called when view is rendered. |
|
197 | 197 | render : function(){ |
|
198 | 198 | this.$el |
|
199 | 199 | .addClass('widget-hbox-single') |
|
200 | 200 | .html(''); |
|
201 | 201 | this.$label = $('<div />') |
|
202 | 202 | .appendTo(this.$el) |
|
203 | 203 | .addClass('widget-hlabel') |
|
204 | 204 | .hide(); |
|
205 | 205 | this.$progress = $('<div />') |
|
206 | 206 | .addClass('progress') |
|
207 | 207 | .addClass('widget-progress') |
|
208 | 208 | .appendTo(this.$el); |
|
209 | 209 | this.$el_to_style = this.$progress; // Set default element to style |
|
210 | 210 | this.$bar = $('<div />') |
|
211 | 211 | .addClass('bar') |
|
212 | 212 | .css('width', '50%') |
|
213 | 213 | .appendTo(this.$progress); |
|
214 | 214 | this.update(); // Set defaults. |
|
215 | 215 | }, |
|
216 | 216 | |
|
217 | 217 | // Handles: Backend -> Frontend Sync |
|
218 | 218 | // Frontent -> Frontend Sync |
|
219 | 219 | update : function(){ |
|
220 | 220 | var value = this.model.get('value'); |
|
221 | 221 | var max = this.model.get('max'); |
|
222 | 222 | var min = this.model.get('min'); |
|
223 | 223 | var percent = 100.0 * (value - min) / (max - min); |
|
224 | 224 | this.$bar.css('width', percent + '%'); |
|
225 | 225 | |
|
226 | 226 | var description = this.model.get('description'); |
|
227 | 227 | if (description.length == 0) { |
|
228 | 228 | this.$label.hide(); |
|
229 | 229 | } else { |
|
230 | 230 | this.$label.html(description); |
|
231 | 231 | this.$label.show(); |
|
232 | 232 | } |
|
233 | 233 | return IPython.WidgetView.prototype.update.call(this); |
|
234 | 234 | }, |
|
235 | 235 | |
|
236 | 236 | }); |
|
237 | 237 | |
|
238 |
IPython |
|
|
238 | IPython.widget_manager.register_widget_view('ProgressView', ProgressView); | |
|
239 | 239 | }); |
@@ -1,4 +1,4 b'' | |||
|
1 |
|
|
|
1 | define(["notebook/js/widget"], function(){ | |
|
2 | 2 | var IntWidgetModel = IPython.WidgetModel.extend({}); |
|
3 |
IPython |
|
|
3 | IPython.widget_manager.register_widget_model('IntWidgetModel', IntWidgetModel); | |
|
4 | 4 | }); No newline at end of file |
@@ -1,191 +1,191 b'' | |||
|
1 |
|
|
|
1 | define(["notebook/js/widget"], function(){ | |
|
2 | 2 | var IntRangeWidgetModel = IPython.WidgetModel.extend({}); |
|
3 |
IPython |
|
|
3 | IPython.widget_manager.register_widget_model('IntRangeWidgetModel', IntRangeWidgetModel); | |
|
4 | 4 | |
|
5 | 5 | var IntSliderView = IPython.WidgetView.extend({ |
|
6 | 6 | |
|
7 | 7 | // Called when view is rendered. |
|
8 | 8 | render : function(){ |
|
9 | 9 | this.$el |
|
10 | 10 | .addClass('widget-hbox-single') |
|
11 | 11 | .html(''); |
|
12 | 12 | this.$label = $('<div />') |
|
13 | 13 | .appendTo(this.$el) |
|
14 | 14 | .addClass('widget-hlabel') |
|
15 | 15 | .hide(); |
|
16 | 16 | this.$slider = $('<div />') |
|
17 | 17 | .slider({}) |
|
18 | 18 | .addClass('slider'); |
|
19 | 19 | |
|
20 | 20 | // Put the slider in a container |
|
21 | 21 | this.$slider_container = $('<div />') |
|
22 | 22 | .addClass('widget-hslider') |
|
23 | 23 | .append(this.$slider); |
|
24 | 24 | this.$el_to_style = this.$slider_container; // Set default element to style |
|
25 | 25 | this.$el.append(this.$slider_container); |
|
26 | 26 | |
|
27 | 27 | // Set defaults. |
|
28 | 28 | this.update(); |
|
29 | 29 | }, |
|
30 | 30 | |
|
31 | 31 | // Handles: Backend -> Frontend Sync |
|
32 | 32 | // Frontent -> Frontend Sync |
|
33 | 33 | update : function(){ |
|
34 | 34 | // Slider related keys. |
|
35 | 35 | var _keys = ['step', 'max', 'min', 'disabled']; |
|
36 | 36 | for (var index in _keys) { |
|
37 | 37 | var key = _keys[index]; |
|
38 | 38 | if (this.model.get(key) != undefined) { |
|
39 | 39 | this.$slider.slider("option", key, this.model.get(key)); |
|
40 | 40 | } |
|
41 | 41 | } |
|
42 | 42 | |
|
43 | 43 | // WORKAROUND FOR JQUERY SLIDER BUG. |
|
44 | 44 | // The horizontal position of the slider handle |
|
45 | 45 | // depends on the value of the slider at the time |
|
46 | 46 | // of orientation change. Before applying the new |
|
47 | 47 | // workaround, we set the value to the minimum to |
|
48 | 48 | // make sure that the horizontal placement of the |
|
49 | 49 | // handle in the vertical slider is always |
|
50 | 50 | // consistent. |
|
51 | 51 | var orientation = this.model.get('orientation'); |
|
52 | 52 | var value = this.model.get('min'); |
|
53 | 53 | this.$slider.slider('option', 'value', value); |
|
54 | 54 | this.$slider.slider('option', 'orientation', orientation); |
|
55 | 55 | var value = this.model.get('value'); |
|
56 | 56 | this.$slider.slider('option', 'value', value); |
|
57 | 57 | |
|
58 | 58 | // Use the right CSS classes for vertical & horizontal sliders |
|
59 | 59 | if (orientation=='vertical') { |
|
60 | 60 | this.$slider_container |
|
61 | 61 | .removeClass('widget-hslider') |
|
62 | 62 | .addClass('widget-vslider'); |
|
63 | 63 | this.$el |
|
64 | 64 | .removeClass('widget-hbox-single') |
|
65 | 65 | .addClass('widget-vbox-single'); |
|
66 | 66 | this.$label |
|
67 | 67 | .removeClass('widget-hlabel') |
|
68 | 68 | .addClass('widget-vlabel'); |
|
69 | 69 | |
|
70 | 70 | } else { |
|
71 | 71 | this.$slider_container |
|
72 | 72 | .removeClass('widget-vslider') |
|
73 | 73 | .addClass('widget-hslider'); |
|
74 | 74 | this.$el |
|
75 | 75 | .removeClass('widget-vbox-single') |
|
76 | 76 | .addClass('widget-hbox-single'); |
|
77 | 77 | this.$label |
|
78 | 78 | .removeClass('widget-vlabel') |
|
79 | 79 | .addClass('widget-hlabel'); |
|
80 | 80 | } |
|
81 | 81 | |
|
82 | 82 | var description = this.model.get('description'); |
|
83 | 83 | if (description.length == 0) { |
|
84 | 84 | this.$label.hide(); |
|
85 | 85 | } else { |
|
86 | 86 | this.$label.html(description); |
|
87 | 87 | this.$label.show(); |
|
88 | 88 | } |
|
89 | 89 | return IPython.WidgetView.prototype.update.call(this); |
|
90 | 90 | }, |
|
91 | 91 | |
|
92 | 92 | // Handles: User input |
|
93 | 93 | events: { "slide" : "handleSliderChange" }, |
|
94 | 94 | handleSliderChange: function(e, ui) { |
|
95 | 95 | this.model.set('value', ~~ui.value); // Double bit-wise not to truncate decimel |
|
96 | 96 | this.model.update_other_views(this); |
|
97 | 97 | }, |
|
98 | 98 | }); |
|
99 | 99 | |
|
100 |
IPython |
|
|
100 | IPython.widget_manager.register_widget_view('IntSliderView', IntSliderView); | |
|
101 | 101 | |
|
102 | 102 | var IntTextView = IPython.WidgetView.extend({ |
|
103 | 103 | |
|
104 | 104 | // Called when view is rendered. |
|
105 | 105 | render : function(){ |
|
106 | 106 | this.$el |
|
107 | 107 | .addClass('widget-hbox-single') |
|
108 | 108 | .html(''); |
|
109 | 109 | this.$label = $('<div />') |
|
110 | 110 | .appendTo(this.$el) |
|
111 | 111 | .addClass('widget-hlabel') |
|
112 | 112 | .hide(); |
|
113 | 113 | this.$textbox = $('<input type="text" />') |
|
114 | 114 | .addClass('input') |
|
115 | 115 | .addClass('widget-numeric-text') |
|
116 | 116 | .appendTo(this.$el); |
|
117 | 117 | this.$el_to_style = this.$textbox; // Set default element to style |
|
118 | 118 | this.update(); // Set defaults. |
|
119 | 119 | }, |
|
120 | 120 | |
|
121 | 121 | // Handles: Backend -> Frontend Sync |
|
122 | 122 | // Frontent -> Frontend Sync |
|
123 | 123 | update : function(){ |
|
124 | 124 | var value = this.model.get('value'); |
|
125 | 125 | if (!this.changing && parseInt(this.$textbox.val()) != value) { |
|
126 | 126 | this.$textbox.val(value); |
|
127 | 127 | } |
|
128 | 128 | |
|
129 | 129 | if (this.model.get('disabled')) { |
|
130 | 130 | this.$textbox.attr('disabled','disabled'); |
|
131 | 131 | } else { |
|
132 | 132 | this.$textbox.removeAttr('disabled'); |
|
133 | 133 | } |
|
134 | 134 | |
|
135 | 135 | var description = this.model.get('description'); |
|
136 | 136 | if (description.length == 0) { |
|
137 | 137 | this.$label.hide(); |
|
138 | 138 | } else { |
|
139 | 139 | this.$label.html(description); |
|
140 | 140 | this.$label.show(); |
|
141 | 141 | } |
|
142 | 142 | return IPython.WidgetView.prototype.update.call(this); |
|
143 | 143 | }, |
|
144 | 144 | |
|
145 | 145 | |
|
146 | 146 | events: {"keyup input" : "handleChanging", |
|
147 | 147 | "paste input" : "handleChanging", |
|
148 | 148 | "cut input" : "handleChanging", |
|
149 | 149 | "change input" : "handleChanged"}, // Fires only when control is validated or looses focus. |
|
150 | 150 | |
|
151 | 151 | // Handles and validates user input. |
|
152 | 152 | handleChanging: function(e) { |
|
153 | 153 | |
|
154 | 154 | // Try to parse value as a float. |
|
155 | 155 | var numericalValue = 0; |
|
156 | 156 | if (e.target.value != '') { |
|
157 | 157 | numericalValue = parseInt(e.target.value); |
|
158 | 158 | } |
|
159 | 159 | |
|
160 | 160 | // If parse failed, reset value to value stored in model. |
|
161 | 161 | if (isNaN(numericalValue)) { |
|
162 | 162 | e.target.value = this.model.get('value'); |
|
163 | 163 | } else if (!isNaN(numericalValue)) { |
|
164 | 164 | if (this.model.get('max') != undefined) { |
|
165 | 165 | numericalValue = Math.min(this.model.get('max'), numericalValue); |
|
166 | 166 | } |
|
167 | 167 | if (this.model.get('min') != undefined) { |
|
168 | 168 | numericalValue = Math.max(this.model.get('min'), numericalValue); |
|
169 | 169 | } |
|
170 | 170 | |
|
171 | 171 | // Apply the value if it has changed. |
|
172 | 172 | if (numericalValue != this.model.get('value')) { |
|
173 | 173 | this.changing = true; |
|
174 | 174 | this.model.set('value', numericalValue); |
|
175 | 175 | this.model.update_other_views(this); |
|
176 | 176 | this.changing = false; |
|
177 | 177 | } |
|
178 | 178 | } |
|
179 | 179 | }, |
|
180 | 180 | |
|
181 | 181 | // Applies validated input. |
|
182 | 182 | handleChanged: function(e) { |
|
183 | 183 | // Update the textbox |
|
184 | 184 | if (this.model.get('value') != e.target.value) { |
|
185 | 185 | e.target.value = this.model.get('value'); |
|
186 | 186 | } |
|
187 | 187 | } |
|
188 | 188 | }); |
|
189 | 189 | |
|
190 |
IPython |
|
|
190 | IPython.widget_manager.register_widget_view('IntTextView', IntTextView); | |
|
191 | 191 | }); |
@@ -1,139 +1,139 b'' | |||
|
1 |
|
|
|
1 | define(["notebook/js/widget"], function(){ | |
|
2 | 2 | var MulticontainerModel = IPython.WidgetModel.extend({}); |
|
3 |
IPython |
|
|
3 | IPython.widget_manager.register_widget_model('MulticontainerWidgetModel', MulticontainerModel); | |
|
4 | 4 | |
|
5 | 5 | var AccordionView = IPython.WidgetView.extend({ |
|
6 | 6 | |
|
7 | 7 | render: function(){ |
|
8 | 8 | this.$el = $('<div />', {id: IPython.utils.uuid()}) |
|
9 | 9 | .addClass('accordion'); |
|
10 | 10 | this.containers = []; |
|
11 | 11 | }, |
|
12 | 12 | |
|
13 | 13 | update: function() { |
|
14 | 14 | // Set tab titles |
|
15 | 15 | var titles = this.model.get('_titles'); |
|
16 | 16 | for (var page_index in titles) { |
|
17 | 17 | |
|
18 | 18 | var accordian = this.containers[page_index] |
|
19 | 19 | if (accordian != undefined) { |
|
20 | 20 | accordian |
|
21 | 21 | .find('.accordion-heading') |
|
22 | 22 | .find('.accordion-toggle') |
|
23 | 23 | .html(titles[page_index]); |
|
24 | 24 | } |
|
25 | 25 | } |
|
26 | 26 | |
|
27 | 27 | return IPython.WidgetView.prototype.update.call(this); |
|
28 | 28 | }, |
|
29 | 29 | |
|
30 | 30 | display_child: function(view) { |
|
31 | 31 | |
|
32 | 32 | var index = this.containers.length; |
|
33 | 33 | var uuid = IPython.utils.uuid(); |
|
34 | 34 | var accordion_group = $('<div />') |
|
35 | 35 | .addClass('accordion-group') |
|
36 | 36 | .appendTo(this.$el); |
|
37 | 37 | var accordion_heading = $('<div />') |
|
38 | 38 | .addClass('accordion-heading') |
|
39 | 39 | .appendTo(accordion_group); |
|
40 | 40 | var accordion_toggle = $('<a />') |
|
41 | 41 | .addClass('accordion-toggle') |
|
42 | 42 | .attr('data-toggle', 'collapse') |
|
43 | 43 | .attr('data-parent', '#' + this.$el.attr('id')) |
|
44 | 44 | .attr('href', '#' + uuid) |
|
45 | 45 | .html('Page ' + index) |
|
46 | 46 | .appendTo(accordion_heading); |
|
47 | 47 | var accordion_body = $('<div />', {id: uuid}) |
|
48 | 48 | .addClass('accordion-body collapse') |
|
49 | 49 | .appendTo(accordion_group); |
|
50 | 50 | var accordion_inner = $('<div />') |
|
51 | 51 | .addClass('accordion-inner') |
|
52 | 52 | .appendTo(accordion_body); |
|
53 | 53 | this.containers.push(accordion_group); |
|
54 | 54 | |
|
55 | 55 | accordion_inner.append(view.$el); |
|
56 | 56 | this.update(); |
|
57 | 57 | }, |
|
58 | 58 | }); |
|
59 | 59 | |
|
60 |
IPython |
|
|
60 | IPython.widget_manager.register_widget_view('AccordionView', AccordionView); | |
|
61 | 61 | |
|
62 | 62 | var TabView = IPython.WidgetView.extend({ |
|
63 | 63 | |
|
64 | 64 | render: function(){ |
|
65 | 65 | this.$el = $('<div />'); |
|
66 | 66 | var uuid = IPython.utils.uuid(); |
|
67 | 67 | var that = this; |
|
68 | 68 | this.$tabs = $('<div />', {id: uuid}) |
|
69 | 69 | .addClass('nav') |
|
70 | 70 | .addClass('nav-tabs') |
|
71 | 71 | .appendTo(this.$el); |
|
72 | 72 | this.$tab_contents = $('<div />', {id: uuid + 'Content'}) |
|
73 | 73 | .addClass('tab-content') |
|
74 | 74 | .appendTo(this.$el); |
|
75 | 75 | |
|
76 | 76 | this.containers = []; |
|
77 | 77 | }, |
|
78 | 78 | |
|
79 | 79 | update: function() { |
|
80 | 80 | // Set tab titles |
|
81 | 81 | var titles = this.model.get('_titles'); |
|
82 | 82 | for (var page_index in titles) { |
|
83 | 83 | var tab_text = this.containers[page_index] |
|
84 | 84 | if (tab_text != undefined) { |
|
85 | 85 | tab_text.html(titles[page_index]); |
|
86 | 86 | } |
|
87 | 87 | } |
|
88 | 88 | |
|
89 | 89 | var selected_index = this.model.get('selected_index'); |
|
90 | 90 | if (0 <= selected_index && selected_index < this.containers.length) { |
|
91 | 91 | this.select_page(selected_index); |
|
92 | 92 | } |
|
93 | 93 | |
|
94 | 94 | return IPython.WidgetView.prototype.update.call(this); |
|
95 | 95 | }, |
|
96 | 96 | |
|
97 | 97 | display_child: function(view) { |
|
98 | 98 | |
|
99 | 99 | var index = this.containers.length; |
|
100 | 100 | var uuid = IPython.utils.uuid(); |
|
101 | 101 | |
|
102 | 102 | var that = this; |
|
103 | 103 | var tab = $('<li />') |
|
104 | 104 | .css('list-style-type', 'none') |
|
105 | 105 | .appendTo(this.$tabs); |
|
106 | 106 | var tab_text = $('<a />') |
|
107 | 107 | .attr('href', '#' + uuid) |
|
108 | 108 | .attr('data-toggle', 'tab') |
|
109 | 109 | .html('Page ' + index) |
|
110 | 110 | .appendTo(tab) |
|
111 | 111 | .click(function (e) { |
|
112 | 112 | that.model.set("selected_index", index); |
|
113 | 113 | that.model.update_other_views(that); |
|
114 | 114 | that.select_page(index); |
|
115 | 115 | }); |
|
116 | 116 | this.containers.push(tab_text); |
|
117 | 117 | |
|
118 | 118 | var contents_div = $('<div />', {id: uuid}) |
|
119 | 119 | .addClass('tab-pane') |
|
120 | 120 | .addClass('fade') |
|
121 | 121 | .append(view.$el) |
|
122 | 122 | .appendTo(this.$tab_contents); |
|
123 | 123 | |
|
124 | 124 | if (index==0) { |
|
125 | 125 | tab_text.tab('show'); |
|
126 | 126 | } |
|
127 | 127 | this.update(); |
|
128 | 128 | }, |
|
129 | 129 | |
|
130 | 130 | select_page: function(index) { |
|
131 | 131 | this.$tabs.find('li') |
|
132 | 132 | .removeClass('active'); |
|
133 | 133 | this.containers[index].tab('show'); |
|
134 | 134 | }, |
|
135 | 135 | }); |
|
136 | 136 | |
|
137 |
IPython |
|
|
137 | IPython.widget_manager.register_widget_view('TabView', TabView); | |
|
138 | 138 | |
|
139 | 139 | }); |
@@ -1,251 +1,251 b'' | |||
|
1 |
|
|
|
1 | define(["notebook/js/widget"], function(){ | |
|
2 | 2 | var SelectionWidgetModel = IPython.WidgetModel.extend({}); |
|
3 |
IPython |
|
|
3 | IPython.widget_manager.register_widget_model('SelectionWidgetModel', SelectionWidgetModel); | |
|
4 | 4 | |
|
5 | 5 | var DropdownView = IPython.WidgetView.extend({ |
|
6 | 6 | |
|
7 | 7 | // Called when view is rendered. |
|
8 | 8 | render : function(){ |
|
9 | 9 | |
|
10 | 10 | this.$el |
|
11 | 11 | .addClass('widget-hbox-single') |
|
12 | 12 | .html(''); |
|
13 | 13 | this.$label = $('<div />') |
|
14 | 14 | .appendTo(this.$el) |
|
15 | 15 | .addClass('widget-hlabel') |
|
16 | 16 | .hide(); |
|
17 | 17 | this.$buttongroup = $('<div />') |
|
18 | 18 | .addClass('widget_item') |
|
19 | 19 | .addClass('btn-group') |
|
20 | 20 | .appendTo(this.$el); |
|
21 | 21 | this.$el_to_style = this.$buttongroup; // Set default element to style |
|
22 | 22 | this.$droplabel = $('<button />') |
|
23 | 23 | .addClass('btn') |
|
24 | 24 | .addClass('widget-combo-btn') |
|
25 | 25 | .appendTo(this.$buttongroup); |
|
26 | 26 | this.$dropbutton = $('<button />') |
|
27 | 27 | .addClass('btn') |
|
28 | 28 | .addClass('dropdown-toggle') |
|
29 | 29 | .attr('data-toggle', 'dropdown') |
|
30 | 30 | .html('<span class="caret"></span>') |
|
31 | 31 | .appendTo(this.$buttongroup); |
|
32 | 32 | this.$droplist = $('<ul />') |
|
33 | 33 | .addClass('dropdown-menu') |
|
34 | 34 | .appendTo(this.$buttongroup); |
|
35 | 35 | |
|
36 | 36 | // Set defaults. |
|
37 | 37 | this.update(); |
|
38 | 38 | }, |
|
39 | 39 | |
|
40 | 40 | // Handles: Backend -> Frontend Sync |
|
41 | 41 | // Frontent -> Frontend Sync |
|
42 | 42 | update : function(){ |
|
43 | 43 | this.$droplabel.html(this.model.get('value')); |
|
44 | 44 | |
|
45 | 45 | var items = this.model.get('values'); |
|
46 | 46 | this.$droplist.html(''); |
|
47 | 47 | for (var index in items) { |
|
48 | 48 | var that = this; |
|
49 | 49 | var item_button = $('<a href="#"/>') |
|
50 | 50 | .html(items[index]) |
|
51 | 51 | .on('click', function(e){ |
|
52 | 52 | that.model.set('value', $(e.target).html(), this); |
|
53 | 53 | that.model.update_other_views(that); |
|
54 | 54 | }) |
|
55 | 55 | |
|
56 | 56 | this.$droplist.append($('<li />').append(item_button)) |
|
57 | 57 | } |
|
58 | 58 | |
|
59 | 59 | if (this.model.get('disabled')) { |
|
60 | 60 | this.$buttongroup.attr('disabled','disabled'); |
|
61 | 61 | this.$droplabel.attr('disabled','disabled'); |
|
62 | 62 | this.$dropbutton.attr('disabled','disabled'); |
|
63 | 63 | this.$droplist.attr('disabled','disabled'); |
|
64 | 64 | } else { |
|
65 | 65 | this.$buttongroup.removeAttr('disabled'); |
|
66 | 66 | this.$droplabel.removeAttr('disabled'); |
|
67 | 67 | this.$dropbutton.removeAttr('disabled'); |
|
68 | 68 | this.$droplist.removeAttr('disabled'); |
|
69 | 69 | } |
|
70 | 70 | |
|
71 | 71 | var description = this.model.get('description'); |
|
72 | 72 | if (description.length == 0) { |
|
73 | 73 | this.$label.hide(); |
|
74 | 74 | } else { |
|
75 | 75 | this.$label.html(description); |
|
76 | 76 | this.$label.show(); |
|
77 | 77 | } |
|
78 | 78 | return IPython.WidgetView.prototype.update.call(this); |
|
79 | 79 | }, |
|
80 | 80 | |
|
81 | 81 | }); |
|
82 | 82 | |
|
83 |
IPython |
|
|
83 | IPython.widget_manager.register_widget_view('DropdownView', DropdownView); | |
|
84 | 84 | |
|
85 | 85 | var RadioButtonsView = IPython.WidgetView.extend({ |
|
86 | 86 | |
|
87 | 87 | // Called when view is rendered. |
|
88 | 88 | render : function(){ |
|
89 | 89 | this.$el |
|
90 | 90 | .addClass('widget-hbox') |
|
91 | 91 | .html(''); |
|
92 | 92 | this.$label = $('<div />') |
|
93 | 93 | .appendTo(this.$el) |
|
94 | 94 | .addClass('widget-hlabel') |
|
95 | 95 | .hide(); |
|
96 | 96 | this.$container = $('<div />') |
|
97 | 97 | .appendTo(this.$el) |
|
98 | 98 | .addClass('widget-container') |
|
99 | 99 | .addClass('vbox'); |
|
100 | 100 | this.$el_to_style = this.$container; // Set default element to style |
|
101 | 101 | this.update(); |
|
102 | 102 | }, |
|
103 | 103 | |
|
104 | 104 | // Handles: Backend -> Frontend Sync |
|
105 | 105 | // Frontent -> Frontend Sync |
|
106 | 106 | update : function(){ |
|
107 | 107 | |
|
108 | 108 | // Add missing items to the DOM. |
|
109 | 109 | var items = this.model.get('values'); |
|
110 | 110 | var disabled = this.model.get('disabled'); |
|
111 | 111 | for (var index in items) { |
|
112 | 112 | var item_query = ' :input[value="' + items[index] + '"]'; |
|
113 | 113 | if (this.$el.find(item_query).length == 0) { |
|
114 | 114 | var $label = $('<label />') |
|
115 | 115 | .addClass('radio') |
|
116 | 116 | .html(items[index]) |
|
117 | 117 | .appendTo(this.$container); |
|
118 | 118 | |
|
119 | 119 | var that = this; |
|
120 | 120 | $('<input />') |
|
121 | 121 | .attr('type', 'radio') |
|
122 | 122 | .addClass(this.model) |
|
123 | 123 | .val(items[index]) |
|
124 | 124 | .prependTo($label) |
|
125 | 125 | .on('click', function(e){ |
|
126 | 126 | that.model.set('value', $(e.target).val(), this); |
|
127 | 127 | that.model.update_other_views(that); |
|
128 | 128 | }); |
|
129 | 129 | } |
|
130 | 130 | |
|
131 | 131 | var $item_element = this.$container.find(item_query); |
|
132 | 132 | if (this.model.get('value') == items[index]) { |
|
133 | 133 | $item_element.prop('checked', true); |
|
134 | 134 | } else { |
|
135 | 135 | $item_element.prop('checked', false); |
|
136 | 136 | } |
|
137 | 137 | $item_element.prop('disabled', disabled); |
|
138 | 138 | } |
|
139 | 139 | |
|
140 | 140 | // Remove items that no longer exist. |
|
141 | 141 | this.$container.find('input').each(function(i, obj) { |
|
142 | 142 | var value = $(obj).val(); |
|
143 | 143 | var found = false; |
|
144 | 144 | for (var index in items) { |
|
145 | 145 | if (items[index] == value) { |
|
146 | 146 | found = true; |
|
147 | 147 | break; |
|
148 | 148 | } |
|
149 | 149 | } |
|
150 | 150 | |
|
151 | 151 | if (!found) { |
|
152 | 152 | $(obj).parent().remove(); |
|
153 | 153 | } |
|
154 | 154 | }); |
|
155 | 155 | |
|
156 | 156 | var description = this.model.get('description'); |
|
157 | 157 | if (description.length == 0) { |
|
158 | 158 | this.$label.hide(); |
|
159 | 159 | } else { |
|
160 | 160 | this.$label.html(description); |
|
161 | 161 | this.$label.show(); |
|
162 | 162 | } |
|
163 | 163 | return IPython.WidgetView.prototype.update.call(this); |
|
164 | 164 | }, |
|
165 | 165 | |
|
166 | 166 | }); |
|
167 | 167 | |
|
168 |
IPython |
|
|
168 | IPython.widget_manager.register_widget_view('RadioButtonsView', RadioButtonsView); | |
|
169 | 169 | |
|
170 | 170 | |
|
171 | 171 | var ToggleButtonsView = IPython.WidgetView.extend({ |
|
172 | 172 | |
|
173 | 173 | // Called when view is rendered. |
|
174 | 174 | render : function(){ |
|
175 | 175 | this.$el |
|
176 | 176 | .addClass('widget-hbox-single') |
|
177 | 177 | .html(''); |
|
178 | 178 | this.$label = $('<div />') |
|
179 | 179 | .appendTo(this.$el) |
|
180 | 180 | .addClass('widget-hlabel') |
|
181 | 181 | .hide(); |
|
182 | 182 | this.$buttongroup = $('<div />') |
|
183 | 183 | .addClass('btn-group') |
|
184 | 184 | .attr('data-toggle', 'buttons-radio') |
|
185 | 185 | .appendTo(this.$el); |
|
186 | 186 | this.$el_to_style = this.$buttongroup; // Set default element to style |
|
187 | 187 | this.update(); |
|
188 | 188 | }, |
|
189 | 189 | |
|
190 | 190 | // Handles: Backend -> Frontend Sync |
|
191 | 191 | // Frontent -> Frontend Sync |
|
192 | 192 | update : function(){ |
|
193 | 193 | |
|
194 | 194 | // Add missing items to the DOM. |
|
195 | 195 | var items = this.model.get('values'); |
|
196 | 196 | var disabled = this.model.get('disabled'); |
|
197 | 197 | for (var index in items) { |
|
198 | 198 | var item_query = ' :contains("' + items[index] + '")'; |
|
199 | 199 | if (this.$buttongroup.find(item_query).length == 0) { |
|
200 | 200 | |
|
201 | 201 | var that = this; |
|
202 | 202 | $('<button />') |
|
203 | 203 | .attr('type', 'button') |
|
204 | 204 | .addClass('btn') |
|
205 | 205 | .html(items[index]) |
|
206 | 206 | .appendTo(this.$buttongroup) |
|
207 | 207 | .on('click', function(e){ |
|
208 | 208 | that.model.set('value', $(e.target).html(), this); |
|
209 | 209 | that.model.update_other_views(that); |
|
210 | 210 | }); |
|
211 | 211 | } |
|
212 | 212 | |
|
213 | 213 | var $item_element = this.$buttongroup.find(item_query); |
|
214 | 214 | if (this.model.get('value') == items[index]) { |
|
215 | 215 | $item_element.addClass('active'); |
|
216 | 216 | } else { |
|
217 | 217 | $item_element.removeClass('active'); |
|
218 | 218 | } |
|
219 | 219 | $item_element.prop('disabled', disabled); |
|
220 | 220 | } |
|
221 | 221 | |
|
222 | 222 | // Remove items that no longer exist. |
|
223 | 223 | this.$buttongroup.find('button').each(function(i, obj) { |
|
224 | 224 | var value = $(obj).html(); |
|
225 | 225 | var found = false; |
|
226 | 226 | for (var index in items) { |
|
227 | 227 | if (items[index] == value) { |
|
228 | 228 | found = true; |
|
229 | 229 | break; |
|
230 | 230 | } |
|
231 | 231 | } |
|
232 | 232 | |
|
233 | 233 | if (!found) { |
|
234 | 234 | $(obj).remove(); |
|
235 | 235 | } |
|
236 | 236 | }); |
|
237 | 237 | |
|
238 | 238 | var description = this.model.get('description'); |
|
239 | 239 | if (description.length == 0) { |
|
240 | 240 | this.$label.hide(); |
|
241 | 241 | } else { |
|
242 | 242 | this.$label.html(description); |
|
243 | 243 | this.$label.show(); |
|
244 | 244 | } |
|
245 | 245 | return IPython.WidgetView.prototype.update.call(this); |
|
246 | 246 | }, |
|
247 | 247 | |
|
248 | 248 | }); |
|
249 | 249 | |
|
250 |
IPython |
|
|
250 | IPython.widget_manager.register_widget_view('ToggleButtonsView', ToggleButtonsView); | |
|
251 | 251 | }); |
@@ -1,131 +1,131 b'' | |||
|
1 |
|
|
|
1 | define(["notebook/js/widget"], function(){ | |
|
2 | 2 | var StringWidgetModel = IPython.WidgetModel.extend({}); |
|
3 |
IPython |
|
|
3 | IPython.widget_manager.register_widget_model('StringWidgetModel', StringWidgetModel); | |
|
4 | 4 | |
|
5 | 5 | var LabelView = IPython.WidgetView.extend({ |
|
6 | 6 | |
|
7 | 7 | // Called when view is rendered. |
|
8 | 8 | render : function(){ |
|
9 | 9 | this.$el = $('<div />'); |
|
10 | 10 | this.update(); // Set defaults. |
|
11 | 11 | }, |
|
12 | 12 | |
|
13 | 13 | // Handles: Backend -> Frontend Sync |
|
14 | 14 | // Frontent -> Frontend Sync |
|
15 | 15 | update : function(){ |
|
16 | 16 | this.$el.html(this.model.get('value')); |
|
17 | 17 | return IPython.WidgetView.prototype.update.call(this); |
|
18 | 18 | }, |
|
19 | 19 | |
|
20 | 20 | }); |
|
21 | 21 | |
|
22 |
IPython |
|
|
22 | IPython.widget_manager.register_widget_view('LabelView', LabelView); | |
|
23 | 23 | |
|
24 | 24 | var TextAreaView = IPython.WidgetView.extend({ |
|
25 | 25 | |
|
26 | 26 | // Called when view is rendered. |
|
27 | 27 | render : function(){ |
|
28 | 28 | this.$el |
|
29 | 29 | .addClass('widget-hbox') |
|
30 | 30 | .html(''); |
|
31 | 31 | this.$label = $('<div />') |
|
32 | 32 | .appendTo(this.$el) |
|
33 | 33 | .addClass('widget-hlabel') |
|
34 | 34 | .hide(); |
|
35 | 35 | this.$textbox = $('<textarea />') |
|
36 | 36 | .attr('rows', 5) |
|
37 | 37 | .addClass('widget-text') |
|
38 | 38 | .appendTo(this.$el); |
|
39 | 39 | this.$el_to_style = this.$textbox; // Set default element to style |
|
40 | 40 | this.update(); // Set defaults. |
|
41 | 41 | }, |
|
42 | 42 | |
|
43 | 43 | // Handles: Backend -> Frontend Sync |
|
44 | 44 | // Frontent -> Frontend Sync |
|
45 | 45 | update : function(){ |
|
46 | 46 | if (!this.user_invoked_update) { |
|
47 | 47 | this.$textbox.val(this.model.get('value')); |
|
48 | 48 | } |
|
49 | 49 | |
|
50 | 50 | var disabled = this.model.get('disabled'); |
|
51 | 51 | this.$textbox.prop('disabled', disabled); |
|
52 | 52 | |
|
53 | 53 | var description = this.model.get('description'); |
|
54 | 54 | if (description.length == 0) { |
|
55 | 55 | this.$label.hide(); |
|
56 | 56 | } else { |
|
57 | 57 | this.$label.html(description); |
|
58 | 58 | this.$label.show(); |
|
59 | 59 | } |
|
60 | 60 | return IPython.WidgetView.prototype.update.call(this); |
|
61 | 61 | }, |
|
62 | 62 | |
|
63 | 63 | events: {"keyup textarea" : "handleChanging", |
|
64 | 64 | "paste textarea" : "handleChanging", |
|
65 | 65 | "cut textarea" : "handleChanging"}, |
|
66 | 66 | |
|
67 | 67 | // Handles and validates user input. |
|
68 | 68 | handleChanging: function(e) { |
|
69 | 69 | this.user_invoked_update = true; |
|
70 | 70 | this.model.set('value', e.target.value); |
|
71 | 71 | this.model.update_other_views(this); |
|
72 | 72 | this.user_invoked_update = false; |
|
73 | 73 | }, |
|
74 | 74 | }); |
|
75 | 75 | |
|
76 |
IPython |
|
|
76 | IPython.widget_manager.register_widget_view('TextAreaView', TextAreaView); | |
|
77 | 77 | |
|
78 | 78 | var TextBoxView = IPython.WidgetView.extend({ |
|
79 | 79 | |
|
80 | 80 | // Called when view is rendered. |
|
81 | 81 | render : function(){ |
|
82 | 82 | this.$el |
|
83 | 83 | .addClass('widget-hbox-single') |
|
84 | 84 | .html(''); |
|
85 | 85 | this.$label = $('<div />') |
|
86 | 86 | .addClass('widget-hlabel') |
|
87 | 87 | .appendTo(this.$el) |
|
88 | 88 | .hide(); |
|
89 | 89 | this.$textbox = $('<input type="text" />') |
|
90 | 90 | .addClass('input') |
|
91 | 91 | .addClass('widget-text') |
|
92 | 92 | .appendTo(this.$el); |
|
93 | 93 | this.$el_to_style = this.$textbox; // Set default element to style |
|
94 | 94 | this.update(); // Set defaults. |
|
95 | 95 | }, |
|
96 | 96 | |
|
97 | 97 | // Handles: Backend -> Frontend Sync |
|
98 | 98 | // Frontent -> Frontend Sync |
|
99 | 99 | update : function(){ |
|
100 | 100 | if (!this.user_invoked_update) { |
|
101 | 101 | this.$textbox.val(this.model.get('value')); |
|
102 | 102 | } |
|
103 | 103 | |
|
104 | 104 | var disabled = this.model.get('disabled'); |
|
105 | 105 | this.$textbox.prop('disabled', disabled); |
|
106 | 106 | |
|
107 | 107 | var description = this.model.get('description'); |
|
108 | 108 | if (description.length == 0) { |
|
109 | 109 | this.$label.hide(); |
|
110 | 110 | } else { |
|
111 | 111 | this.$label.html(description); |
|
112 | 112 | this.$label.show(); |
|
113 | 113 | } |
|
114 | 114 | return IPython.WidgetView.prototype.update.call(this); |
|
115 | 115 | }, |
|
116 | 116 | |
|
117 | 117 | events: {"keyup input" : "handleChanging", |
|
118 | 118 | "paste input" : "handleChanging", |
|
119 | 119 | "cut input" : "handleChanging"}, |
|
120 | 120 | |
|
121 | 121 | // Handles and validates user input. |
|
122 | 122 | handleChanging: function(e) { |
|
123 | 123 | this.user_invoked_update = true; |
|
124 | 124 | this.model.set('value', e.target.value); |
|
125 | 125 | this.model.update_other_views(this); |
|
126 | 126 | this.user_invoked_update = false; |
|
127 | 127 | }, |
|
128 | 128 | }); |
|
129 | 129 | |
|
130 |
IPython |
|
|
130 | IPython.widget_manager.register_widget_view('TextBoxView', TextBoxView); | |
|
131 | 131 | }); |
@@ -1,12 +1,12 b'' | |||
|
1 |
from .widget import Widget |
|
|
1 | from .widget import Widget | |
|
2 | 2 | |
|
3 | 3 | from .widget_bool import BoolWidget |
|
4 | 4 | from .widget_button import ButtonWidget |
|
5 | 5 | from .widget_container import ContainerWidget |
|
6 | 6 | from .widget_float import FloatWidget |
|
7 | 7 | from .widget_float_range import FloatRangeWidget |
|
8 | 8 | from .widget_int import IntWidget |
|
9 | 9 | from .widget_int_range import IntRangeWidget |
|
10 | 10 | from .widget_multicontainer import MulticontainerWidget |
|
11 | 11 | from .widget_selection import SelectionWidget |
|
12 | 12 | from .widget_string import StringWidget |
@@ -1,365 +1,352 b'' | |||
|
1 | 1 | """Base Widget class. Allows user to create widgets in the backend that render |
|
2 | 2 | in the IPython notebook frontend. |
|
3 | 3 | """ |
|
4 | 4 | #----------------------------------------------------------------------------- |
|
5 | 5 | # Copyright (c) 2013, the IPython Development Team. |
|
6 | 6 | # |
|
7 | 7 | # Distributed under the terms of the Modified BSD License. |
|
8 | 8 | # |
|
9 | 9 | # The full license is in the file COPYING.txt, distributed with this software. |
|
10 | 10 | #----------------------------------------------------------------------------- |
|
11 | 11 | |
|
12 | 12 | #----------------------------------------------------------------------------- |
|
13 | 13 | # Imports |
|
14 | 14 | #----------------------------------------------------------------------------- |
|
15 | 15 | from copy import copy |
|
16 | 16 | from glob import glob |
|
17 | 17 | import uuid |
|
18 | 18 | import sys |
|
19 | 19 | import os |
|
20 | 20 | import inspect |
|
21 | 21 | |
|
22 | 22 | import IPython |
|
23 | 23 | from IPython.kernel.comm import Comm |
|
24 | 24 | from IPython.config import LoggingConfigurable |
|
25 | 25 | from IPython.utils.traitlets import Unicode, Dict, List, Instance, Bool |
|
26 | 26 | from IPython.display import Javascript, display |
|
27 | 27 | from IPython.utils.py3compat import string_types |
|
28 | 28 | |
|
29 | #----------------------------------------------------------------------------- | |
|
30 | # Shared | |
|
31 | #----------------------------------------------------------------------------- | |
|
32 | def init_widget_js(): | |
|
33 | path = os.path.split(os.path.abspath( __file__ ))[0] | |
|
34 | for filepath in glob(os.path.join(path, "*.py")): | |
|
35 | filename = os.path.split(filepath)[1] | |
|
36 | name = filename.rsplit('.', 1)[0] | |
|
37 | if not (name == 'widget' or name == '__init__') and name.startswith('widget_'): | |
|
38 | # Remove 'widget_' from the start of the name before compiling the path. | |
|
39 | js_path = 'static/notebook/js/widgets/%s.js' % name[7:] | |
|
40 | display(Javascript(data='$.getScript($("body").data("baseProjectUrl") + "%s");' % js_path), exclude="text/plain") | |
|
41 | ||
|
42 | 29 | |
|
43 | 30 | #----------------------------------------------------------------------------- |
|
44 | 31 | # Classes |
|
45 | 32 | #----------------------------------------------------------------------------- |
|
46 | 33 | class Widget(LoggingConfigurable): |
|
47 | 34 | |
|
48 | 35 | # Shared declarations |
|
49 | 36 | _keys = [] |
|
50 | 37 | |
|
51 | 38 | # Public declarations |
|
52 | 39 | target_name = Unicode('widget', help="""Name of the backbone model |
|
53 | 40 | registered in the frontend to create and sync this widget with.""") |
|
54 | 41 | default_view_name = Unicode(help="""Default view registered in the frontend |
|
55 | 42 | to use to represent the widget.""") |
|
56 | 43 | parent = Instance('IPython.html.widgets.widget.Widget') |
|
57 | 44 | visible = Bool(True, help="Whether or not the widget is visible.") |
|
58 | 45 | |
|
59 | 46 | def _parent_changed(self, name, old, new): |
|
60 | 47 | if self._displayed: |
|
61 | 48 | raise Exception('Parent cannot be set because widget has been displayed.') |
|
62 | 49 | elif new == self: |
|
63 | 50 | raise Exception('Parent cannot be set to self.') |
|
64 | 51 | else: |
|
65 | 52 | |
|
66 | 53 | # Parent/child association |
|
67 | 54 | if new is not None and not self in new._children: |
|
68 | 55 | new._children.append(self) |
|
69 | 56 | if old is not None and self in old._children: |
|
70 | 57 | old._children.remove(self) |
|
71 | 58 | |
|
72 | 59 | # Private/protected declarations |
|
73 | 60 | _property_lock = False |
|
74 | 61 | _css = Dict() # Internal CSS property dict |
|
75 | 62 | _add_class = List() # Used to add a js class to a DOM element (call#, selector, class_name) |
|
76 | 63 | _remove_class = List() # Used to remove a js class from a DOM element (call#, selector, class_name) |
|
77 | 64 | _displayed = False |
|
78 | 65 | _comm = None |
|
79 | 66 | |
|
80 | 67 | |
|
81 | 68 | def __init__(self, **kwargs): |
|
82 | 69 | """Public constructor |
|
83 | 70 | |
|
84 | 71 | Parameters |
|
85 | 72 | ---------- |
|
86 | 73 | parent : Widget instance (optional) |
|
87 | 74 | Widget that this widget instance is child of. When the widget is |
|
88 | 75 | displayed in the frontend, it's corresponding view will be made |
|
89 | 76 | child of the parent's view if the parent's view exists already. If |
|
90 | 77 | the parent's view is displayed, it will automatically display this |
|
91 | 78 | widget's default view as it's child. The default view can be set |
|
92 | 79 | via the default_view_name property. |
|
93 | 80 | """ |
|
94 | 81 | self._children = [] |
|
95 | 82 | self._add_class = [0] |
|
96 | 83 | self._remove_class = [0] |
|
97 | 84 | self._display_callbacks = [] |
|
98 | 85 | super(Widget, self).__init__(**kwargs) |
|
99 | 86 | |
|
100 | 87 | # Register after init to allow default values to be specified |
|
101 | 88 | self.on_trait_change(self._handle_property_changed, self.keys) |
|
102 | 89 | |
|
103 | 90 | |
|
104 | 91 | def __del__(self): |
|
105 | 92 | """Object disposal""" |
|
106 | 93 | self.close() |
|
107 | 94 | |
|
108 | 95 | |
|
109 | 96 | def close(self): |
|
110 | 97 | """Close method. Closes the widget which closes the underlying comm. |
|
111 | 98 | When the comm is closed, all of the widget views are automatically |
|
112 | 99 | removed from the frontend.""" |
|
113 | 100 | self._comm.close() |
|
114 | 101 | del self._comm |
|
115 | 102 | |
|
116 | 103 | |
|
117 | 104 | # Properties |
|
118 | 105 | def _get_keys(self): |
|
119 | 106 | keys = ['visible', '_css', '_add_class', '_remove_class'] |
|
120 | 107 | keys.extend(self._keys) |
|
121 | 108 | return keys |
|
122 | 109 | keys = property(_get_keys) |
|
123 | 110 | |
|
124 | 111 | |
|
125 | 112 | # Event handlers |
|
126 | 113 | def _handle_msg(self, msg): |
|
127 | 114 | """Called when a msg is recieved from the frontend""" |
|
128 | 115 | # Handle backbone sync methods CREATE, PATCH, and UPDATE |
|
129 | 116 | sync_method = msg['content']['data']['sync_method'] |
|
130 | 117 | sync_data = msg['content']['data']['sync_data'] |
|
131 | 118 | self._handle_recieve_state(sync_data) # handles all methods |
|
132 | 119 | |
|
133 | 120 | |
|
134 | 121 | def _handle_recieve_state(self, sync_data): |
|
135 | 122 | """Called when a state is recieved from the frontend.""" |
|
136 | 123 | self._property_lock = True |
|
137 | 124 | try: |
|
138 | 125 | |
|
139 | 126 | # Use _keys instead of keys - Don't get retrieve the css from the client side. |
|
140 | 127 | for name in self._keys: |
|
141 | 128 | if name in sync_data: |
|
142 | 129 | setattr(self, name, sync_data[name]) |
|
143 | 130 | finally: |
|
144 | 131 | self._property_lock = False |
|
145 | 132 | |
|
146 | 133 | |
|
147 | 134 | def _handle_property_changed(self, name, old, new): |
|
148 | 135 | """Called when a proeprty has been changed.""" |
|
149 | 136 | if not self._property_lock and self._comm is not None: |
|
150 | 137 | # TODO: Validate properties. |
|
151 | 138 | # Send new state to frontend |
|
152 | 139 | self.send_state(key=name) |
|
153 | 140 | |
|
154 | 141 | |
|
155 | 142 | def _handle_close(self): |
|
156 | 143 | """Called when the comm is closed by the frontend.""" |
|
157 | 144 | self._comm = None |
|
158 | 145 | |
|
159 | 146 | |
|
160 | 147 | # Public methods |
|
161 | 148 | def send_state(self, key=None): |
|
162 | 149 | """Sends the widget state, or a piece of it, to the frontend. |
|
163 | 150 | |
|
164 | 151 | Parameters |
|
165 | 152 | ---------- |
|
166 | 153 | key : unicode (optional) |
|
167 | 154 | A single property's name to sync with the frontend. |
|
168 | 155 | """ |
|
169 | 156 | if self._comm is not None: |
|
170 | 157 | state = {} |
|
171 | 158 | |
|
172 | 159 | # If a key is provided, just send the state of that key. |
|
173 | 160 | keys = [] |
|
174 | 161 | if key is None: |
|
175 | 162 | keys.extend(self.keys) |
|
176 | 163 | else: |
|
177 | 164 | keys.append(key) |
|
178 | 165 | for key in self.keys: |
|
179 | 166 | try: |
|
180 | 167 | state[key] = getattr(self, key) |
|
181 | 168 | except Exception as e: |
|
182 | 169 | pass # Eat errors, nom nom nom |
|
183 | 170 | self._comm.send({"method": "update", |
|
184 | 171 | "state": state}) |
|
185 | 172 | |
|
186 | 173 | |
|
187 | 174 | def get_css(self, key, selector=""): |
|
188 | 175 | """Get a CSS property of the widget. Note, this function does not |
|
189 | 176 | actually request the CSS from the front-end; Only properties that have |
|
190 | 177 | been set with set_css can be read. |
|
191 | 178 | |
|
192 | 179 | Parameters |
|
193 | 180 | ---------- |
|
194 | 181 | key: unicode |
|
195 | 182 | CSS key |
|
196 | 183 | selector: unicode (optional) |
|
197 | 184 | JQuery selector used when the CSS key/value was set. |
|
198 | 185 | """ |
|
199 | 186 | if selector in self._css and key in self._css[selector]: |
|
200 | 187 | return self._css[selector][key] |
|
201 | 188 | else: |
|
202 | 189 | return None |
|
203 | 190 | |
|
204 | 191 | |
|
205 | 192 | def set_css(self, *args, **kwargs): |
|
206 | 193 | """Set one or more CSS properties of the widget (shared among all of the |
|
207 | 194 | views). This function has two signatures: |
|
208 | 195 | - set_css(css_dict, [selector='']) |
|
209 | 196 | - set_css(key, value, [selector='']) |
|
210 | 197 | |
|
211 | 198 | Parameters |
|
212 | 199 | ---------- |
|
213 | 200 | css_dict : dict |
|
214 | 201 | CSS key/value pairs to apply |
|
215 | 202 | key: unicode |
|
216 | 203 | CSS key |
|
217 | 204 | value |
|
218 | 205 | CSS value |
|
219 | 206 | selector: unicode (optional) |
|
220 | 207 | JQuery selector to use to apply the CSS key/value. |
|
221 | 208 | """ |
|
222 | 209 | selector = kwargs.get('selector', '') |
|
223 | 210 | |
|
224 | 211 | # Signature 1: set_css(css_dict, [selector='']) |
|
225 | 212 | if len(args) == 1: |
|
226 | 213 | if isinstance(args[0], dict): |
|
227 | 214 | for (key, value) in args[0].items(): |
|
228 | 215 | self.set_css(key, value, selector=selector) |
|
229 | 216 | else: |
|
230 | 217 | raise Exception('css_dict must be a dict.') |
|
231 | 218 | |
|
232 | 219 | # Signature 2: set_css(key, value, [selector='']) |
|
233 | 220 | elif len(args) == 2 or len(args) == 3: |
|
234 | 221 | |
|
235 | 222 | # Selector can be a positional arg if it's the 3rd value |
|
236 | 223 | if len(args) == 3: |
|
237 | 224 | selector = args[2] |
|
238 | 225 | if selector not in self._css: |
|
239 | 226 | self._css[selector] = {} |
|
240 | 227 | |
|
241 | 228 | # Only update the property if it has changed. |
|
242 | 229 | key = args[0] |
|
243 | 230 | value = args[1] |
|
244 | 231 | if not (key in self._css[selector] and value in self._css[selector][key]): |
|
245 | 232 | self._css[selector][key] = value |
|
246 | 233 | self.send_state('_css') # Send new state to client. |
|
247 | 234 | else: |
|
248 | 235 | raise Exception('set_css only accepts 1-3 arguments') |
|
249 | 236 | |
|
250 | 237 | |
|
251 | 238 | def add_class(self, class_name, selector=""): |
|
252 | 239 | """Add class[es] to a DOM element |
|
253 | 240 | |
|
254 | 241 | Parameters |
|
255 | 242 | ---------- |
|
256 | 243 | class_name: unicode |
|
257 | 244 | Class name(s) to add to the DOM element(s). Multiple class names |
|
258 | 245 | must be space separated. |
|
259 | 246 | selector: unicode (optional) |
|
260 | 247 | JQuery selector to select the DOM element(s) that the class(es) will |
|
261 | 248 | be added to. |
|
262 | 249 | """ |
|
263 | 250 | self._add_class = [self._add_class[0] + 1, selector, class_name] |
|
264 | 251 | self.send_state(key='_add_class') |
|
265 | 252 | |
|
266 | 253 | |
|
267 | 254 | def remove_class(self, class_name, selector=""): |
|
268 | 255 | """Remove class[es] from a DOM element |
|
269 | 256 | |
|
270 | 257 | Parameters |
|
271 | 258 | ---------- |
|
272 | 259 | class_name: unicode |
|
273 | 260 | Class name(s) to remove from the DOM element(s). Multiple class |
|
274 | 261 | names must be space separated. |
|
275 | 262 | selector: unicode (optional) |
|
276 | 263 | JQuery selector to select the DOM element(s) that the class(es) will |
|
277 | 264 | be removed from. |
|
278 | 265 | """ |
|
279 | 266 | self._remove_class = [self._remove_class[0] + 1, selector, class_name] |
|
280 | 267 | self.send_state(key='_remove_class') |
|
281 | 268 | |
|
282 | 269 | |
|
283 | 270 | def on_displayed(self, callback, remove=False): |
|
284 | 271 | """Register a callback to be called when the widget has been displayed |
|
285 | 272 | |
|
286 | 273 | Parameters |
|
287 | 274 | ---------- |
|
288 | 275 | callback: method handler |
|
289 | 276 | Can have a signature of: |
|
290 | 277 | - callback() |
|
291 | 278 | - callback(sender) |
|
292 | 279 | - callback(sender, view_name) |
|
293 | 280 | remove: bool |
|
294 | 281 | True if the callback should be unregistered.""" |
|
295 | 282 | if remove: |
|
296 | 283 | self._display_callbacks.remove(callback) |
|
297 | 284 | elif not callback in self._display_callbacks: |
|
298 | 285 | self._display_callbacks.append(callback) |
|
299 | 286 | |
|
300 | 287 | |
|
301 | 288 | def handle_displayed(self, view_name): |
|
302 | 289 | """Called when a view has been displayed for this widget instance |
|
303 | 290 | |
|
304 | 291 | Parameters |
|
305 | 292 | ---------- |
|
306 | 293 | view_name: unicode |
|
307 | 294 | Name of the view that was displayed.""" |
|
308 | 295 | for handler in self._display_callbacks: |
|
309 | 296 | if callable(handler): |
|
310 | 297 | argspec = inspect.getargspec(handler) |
|
311 | 298 | nargs = len(argspec[0]) |
|
312 | 299 | |
|
313 | 300 | # Bound methods have an additional 'self' argument |
|
314 | 301 | if isinstance(handler, types.MethodType): |
|
315 | 302 | nargs -= 1 |
|
316 | 303 | |
|
317 | 304 | # Call the callback |
|
318 | 305 | if nargs == 0: |
|
319 | 306 | handler() |
|
320 | 307 | elif nargs == 1: |
|
321 | 308 | handler(self) |
|
322 | 309 | elif nargs == 2: |
|
323 | 310 | handler(self, view_name) |
|
324 | 311 | else: |
|
325 | 312 | raise TypeError('Widget display callback must ' \ |
|
326 | 313 | 'accept 0-2 arguments, not %d.' % nargs) |
|
327 | 314 | |
|
328 | 315 | |
|
329 | 316 | # Support methods |
|
330 | 317 | def _repr_widget_(self, view_name=None): |
|
331 | 318 | """Function that is called when `IPython.display.display` is called on |
|
332 | 319 | the widget. |
|
333 | 320 | |
|
334 | 321 | Parameters |
|
335 | 322 | ---------- |
|
336 | 323 | view_name: unicode (optional) |
|
337 | 324 | View to display in the frontend. Overrides default_view_name.""" |
|
338 | 325 | |
|
339 | 326 | if not view_name: |
|
340 | 327 | view_name = self.default_view_name |
|
341 | 328 | |
|
342 | 329 | # Create a comm. |
|
343 | 330 | if self._comm is None: |
|
344 | 331 | self._comm = Comm(target_name=self.target_name) |
|
345 | 332 | self._comm.on_msg(self._handle_msg) |
|
346 | 333 | self._comm.on_close(self._handle_close) |
|
347 | 334 | |
|
348 | 335 | # Make sure model is syncronized |
|
349 | 336 | self.send_state() |
|
350 | 337 | |
|
351 | 338 | # Show view. |
|
352 | 339 | if self.parent is None or self.parent._comm is None: |
|
353 | 340 | self._comm.send({"method": "display", "view_name": view_name}) |
|
354 | 341 | else: |
|
355 | 342 | self._comm.send({"method": "display", |
|
356 | 343 | "view_name": view_name, |
|
357 | 344 | "parent": self.parent._comm.comm_id}) |
|
358 | 345 | self._displayed = True |
|
359 | 346 | self.handle_displayed(view_name) |
|
360 | 347 | |
|
361 | 348 | # Now display children if any. |
|
362 | 349 | for child in self._children: |
|
363 | 350 | if child != self: |
|
364 | 351 | child._repr_widget_() |
|
365 | 352 | return None |
@@ -1,295 +1,220 b'' | |||
|
1 | 1 | { |
|
2 | 2 | "metadata": { |
|
3 | 3 | "cell_tags": [ |
|
4 | 4 | [ |
|
5 | 5 | "<None>", |
|
6 | 6 | null |
|
7 | 7 | ] |
|
8 | 8 | ], |
|
9 | 9 | "name": "" |
|
10 | 10 | }, |
|
11 | 11 | "nbformat": 3, |
|
12 | 12 | "nbformat_minor": 0, |
|
13 | 13 | "worksheets": [ |
|
14 | 14 | { |
|
15 | 15 | "cells": [ |
|
16 | 16 | { |
|
17 | 17 | "cell_type": "code", |
|
18 | 18 | "collapsed": false, |
|
19 | 19 | "input": [ |
|
20 | 20 | "from IPython.html import widgets # Widget definitions\n", |
|
21 |
"from IPython.display import display # Used to display widgets in the notebook |
|
|
22 | "\n", | |
|
23 | "# Enable widgets in this notebook\n", | |
|
24 | "widgets.init_widget_js()" | |
|
21 | "from IPython.display import display # Used to display widgets in the notebook" | |
|
25 | 22 | ], |
|
26 | 23 | "language": "python", |
|
27 | 24 | "metadata": {}, |
|
28 | "outputs": [ | |
|
29 | { | |
|
30 | "javascript": [ | |
|
31 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/button.js\");" | |
|
32 | ], | |
|
33 | "metadata": {}, | |
|
34 | "output_type": "display_data" | |
|
35 | }, | |
|
36 | { | |
|
37 | "javascript": [ | |
|
38 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/int_range.js\");" | |
|
39 | ], | |
|
40 | "metadata": {}, | |
|
41 | "output_type": "display_data" | |
|
42 | }, | |
|
43 | { | |
|
44 | "javascript": [ | |
|
45 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/string.js\");" | |
|
46 | ], | |
|
47 | "metadata": {}, | |
|
48 | "output_type": "display_data" | |
|
49 | }, | |
|
50 | { | |
|
51 | "javascript": [ | |
|
52 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/multicontainer.js\");" | |
|
53 | ], | |
|
54 | "metadata": {}, | |
|
55 | "output_type": "display_data" | |
|
56 | }, | |
|
57 | { | |
|
58 | "javascript": [ | |
|
59 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/bool.js\");" | |
|
60 | ], | |
|
61 | "metadata": {}, | |
|
62 | "output_type": "display_data" | |
|
63 | }, | |
|
64 | { | |
|
65 | "javascript": [ | |
|
66 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/int.js\");" | |
|
67 | ], | |
|
68 | "metadata": {}, | |
|
69 | "output_type": "display_data" | |
|
70 | }, | |
|
71 | { | |
|
72 | "javascript": [ | |
|
73 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/selection.js\");" | |
|
74 | ], | |
|
75 | "metadata": {}, | |
|
76 | "output_type": "display_data" | |
|
77 | }, | |
|
78 | { | |
|
79 | "javascript": [ | |
|
80 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/float.js\");" | |
|
81 | ], | |
|
82 | "metadata": {}, | |
|
83 | "output_type": "display_data" | |
|
84 | }, | |
|
85 | { | |
|
86 | "javascript": [ | |
|
87 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/float_range.js\");" | |
|
88 | ], | |
|
89 | "metadata": {}, | |
|
90 | "output_type": "display_data" | |
|
91 | }, | |
|
92 | { | |
|
93 | "javascript": [ | |
|
94 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/container.js\");" | |
|
95 | ], | |
|
96 | "metadata": {}, | |
|
97 | "output_type": "display_data" | |
|
98 | } | |
|
99 | ], | |
|
25 | "outputs": [], | |
|
100 | 26 | "prompt_number": 1 |
|
101 | 27 | }, |
|
102 | 28 | { |
|
103 | 29 | "cell_type": "heading", |
|
104 | 30 | "level": 1, |
|
105 | 31 | "metadata": {}, |
|
106 | 32 | "source": [ |
|
107 | 33 | "Custom Widget" |
|
108 | 34 | ] |
|
109 | 35 | }, |
|
110 | 36 | { |
|
111 | 37 | "cell_type": "code", |
|
112 | 38 | "collapsed": false, |
|
113 | 39 | "input": [ |
|
114 | 40 | "# Import the base Widget class and the traitlets Unicode class.\n", |
|
115 | 41 | "from IPython.html.widgets import Widget\n", |
|
116 | 42 | "from IPython.utils.traitlets import Unicode, Int\n", |
|
117 | 43 | "\n", |
|
118 | 44 | "# Define our FileWidget and its target model and default view.\n", |
|
119 | 45 | "class FileWidget(Widget):\n", |
|
120 | 46 | " target_name = Unicode('FileWidgetModel')\n", |
|
121 | 47 | " default_view_name = Unicode('FilePickerView')\n", |
|
122 | 48 | " \n", |
|
123 | 49 | " # Define the custom state properties to sync with the front-end\n", |
|
124 | 50 | " _keys = ['value', 'filename']\n", |
|
125 | 51 | " value = Unicode('')\n", |
|
126 | 52 | " filename = Unicode('')\n", |
|
127 | 53 | " on_failed = Int(0)" |
|
128 | 54 | ], |
|
129 | 55 | "language": "python", |
|
130 | 56 | "metadata": {}, |
|
131 | 57 | "outputs": [], |
|
132 | 58 | "prompt_number": 2 |
|
133 | 59 | }, |
|
134 | 60 | { |
|
135 | 61 | "cell_type": "code", |
|
136 | 62 | "collapsed": false, |
|
137 | 63 | "input": [ |
|
138 | 64 | "%%javascript\n", |
|
139 | 65 | "\n", |
|
140 | 66 | "require([\"notebook/js/widget\"], function(){\n", |
|
141 | 67 | " \n", |
|
142 | 68 | " // Define the FileModel and register it with the widget manager.\n", |
|
143 | 69 | " var FileModel = IPython.WidgetModel.extend({});\n", |
|
144 |
" IPython. |
|
|
70 | " IPython.widget_manager.register_widget_model('FileWidgetModel', FileModel);\n", | |
|
145 | 71 | " \n", |
|
146 | 72 | " // Define the FilePickerView\n", |
|
147 | 73 | " var FilePickerView = IPython.WidgetView.extend({\n", |
|
148 | 74 | " \n", |
|
149 | 75 | " render: function(){\n", |
|
150 | 76 | " var that = this;\n", |
|
151 | 77 | " this.$el = $('<input />')\n", |
|
152 | 78 | " .attr('type', 'file')\n", |
|
153 | 79 | " .change(function(evt){ that.handleFileChange(evt) });\n", |
|
154 | 80 | " },\n", |
|
155 | 81 | " \n", |
|
156 | 82 | " // Handles: User input\n", |
|
157 | 83 | " handleFileChange: function(evt) { \n", |
|
158 | 84 | " \n", |
|
159 | 85 | " //Retrieve the first (and only!) File from the FileList object\n", |
|
160 | 86 | " var that = this;\n", |
|
161 | 87 | " var f = evt.target.files[0];\n", |
|
162 | 88 | " if (f) {\n", |
|
163 | 89 | " var r = new FileReader();\n", |
|
164 | 90 | " r.onload = function(e) {\n", |
|
165 | 91 | " that.model.set('value', e.target.result);\n", |
|
166 | 92 | " that.model.update_other_views(that);\n", |
|
167 | 93 | " }\n", |
|
168 | 94 | " r.readAsText(f);\n", |
|
169 | 95 | " } else {\n", |
|
170 | 96 | " this.model.set('on_failed', this.model.get('on_failed') + 1);\n", |
|
171 | 97 | " this.model.update_other_views(this);\n", |
|
172 | 98 | " }\n", |
|
173 | 99 | " this.model.set('filename', f.name);\n", |
|
174 | 100 | " this.model.update_other_views(this);\n", |
|
175 | 101 | " },\n", |
|
176 | 102 | " });\n", |
|
177 | 103 | " \n", |
|
178 | 104 | " // Register the DatePickerView with the widget manager.\n", |
|
179 |
" IPython. |
|
|
105 | " IPython.widget_manager.register_widget_view('FilePickerView', FilePickerView);\n", | |
|
180 | 106 | "});" |
|
181 | 107 | ], |
|
182 | 108 | "language": "python", |
|
183 | 109 | "metadata": {}, |
|
184 | 110 | "outputs": [ |
|
185 | 111 | { |
|
186 | 112 | "javascript": [ |
|
187 | 113 | "\n", |
|
188 | 114 | "require([\"notebook/js/widget\"], function(){\n", |
|
189 | 115 | " \n", |
|
190 | 116 | " // Define the FileModel and register it with the widget manager.\n", |
|
191 | 117 | " var FileModel = IPython.WidgetModel.extend({});\n", |
|
192 |
" IPython. |
|
|
118 | " IPython.widget_manager.register_widget_model('FileWidgetModel', FileModel);\n", | |
|
193 | 119 | " \n", |
|
194 | 120 | " // Define the FilePickerView\n", |
|
195 | 121 | " var FilePickerView = IPython.WidgetView.extend({\n", |
|
196 | 122 | " \n", |
|
197 | 123 | " render: function(){\n", |
|
198 | 124 | " var that = this;\n", |
|
199 | 125 | " this.$el = $('<input />')\n", |
|
200 | 126 | " .attr('type', 'file')\n", |
|
201 | 127 | " .change(function(evt){ that.handleFileChange(evt) });\n", |
|
202 | 128 | " },\n", |
|
203 | 129 | " \n", |
|
204 | 130 | " // Handles: User input\n", |
|
205 | " events: { \"change\" : \"handleFileChange\" }, \n", | |
|
206 | 131 | " handleFileChange: function(evt) { \n", |
|
207 | 132 | " \n", |
|
208 | 133 | " //Retrieve the first (and only!) File from the FileList object\n", |
|
209 | 134 | " var that = this;\n", |
|
210 | 135 | " var f = evt.target.files[0];\n", |
|
211 | 136 | " if (f) {\n", |
|
212 | 137 | " var r = new FileReader();\n", |
|
213 | 138 | " r.onload = function(e) {\n", |
|
214 | 139 | " that.model.set('value', e.target.result);\n", |
|
215 | 140 | " that.model.update_other_views(that);\n", |
|
216 | 141 | " }\n", |
|
217 | 142 | " r.readAsText(f);\n", |
|
218 | 143 | " } else {\n", |
|
219 | 144 | " this.model.set('on_failed', this.model.get('on_failed') + 1);\n", |
|
220 | 145 | " this.model.update_other_views(this);\n", |
|
221 | 146 | " }\n", |
|
222 | 147 | " this.model.set('filename', f.name);\n", |
|
223 | 148 | " this.model.update_other_views(this);\n", |
|
224 | 149 | " },\n", |
|
225 | 150 | " });\n", |
|
226 | 151 | " \n", |
|
227 | 152 | " // Register the DatePickerView with the widget manager.\n", |
|
228 |
" IPython. |
|
|
153 | " IPython.widget_manager.register_widget_view('FilePickerView', FilePickerView);\n", | |
|
229 | 154 | "});" |
|
230 | 155 | ], |
|
231 | 156 | "metadata": {}, |
|
232 | 157 | "output_type": "display_data", |
|
233 | 158 | "text": [ |
|
234 |
"<IPython.core.display.Javascript at 0x |
|
|
159 | "<IPython.core.display.Javascript at 0x319fe90>" | |
|
235 | 160 | ] |
|
236 | 161 | } |
|
237 | 162 | ], |
|
238 | 163 | "prompt_number": 3 |
|
239 | 164 | }, |
|
240 | 165 | { |
|
241 | 166 | "cell_type": "heading", |
|
242 | 167 | "level": 1, |
|
243 | 168 | "metadata": {}, |
|
244 | 169 | "source": [ |
|
245 | 170 | "Usage" |
|
246 | 171 | ] |
|
247 | 172 | }, |
|
248 | 173 | { |
|
249 | 174 | "cell_type": "code", |
|
250 | 175 | "collapsed": false, |
|
251 | 176 | "input": [ |
|
252 | 177 | "file_widget = FileWidget()\n", |
|
253 | 178 | "display(file_widget)\n", |
|
254 | 179 | "\n", |
|
255 | 180 | "def file_loading():\n", |
|
256 | 181 | " print \"Loading %s\" % file_widget.filename\n", |
|
257 | 182 | "\n", |
|
258 | 183 | "def file_loaded():\n", |
|
259 | 184 | " print \"Loaded, file contents: %s\" % file_widget.value\n", |
|
260 | 185 | "\n", |
|
261 | 186 | "def file_failed(name, old_value, new_value):\n", |
|
262 | 187 | " if new_value > old_value:\n", |
|
263 | 188 | " print \"Could not load file contents of %s\" % file_widget.filename\n", |
|
264 | 189 | "\n", |
|
265 | 190 | "\n", |
|
266 | 191 | "file_widget.on_trait_change(file_loading, 'filename')\n", |
|
267 | 192 | "file_widget.on_trait_change(file_loaded, 'value')\n", |
|
268 | 193 | "file_widget.on_trait_change(file_failed, 'on_failed')" |
|
269 | 194 | ], |
|
270 | 195 | "language": "python", |
|
271 | 196 | "metadata": {}, |
|
272 | 197 | "outputs": [ |
|
273 | 198 | { |
|
274 | 199 | "output_type": "stream", |
|
275 | 200 | "stream": "stdout", |
|
276 | 201 | "text": [ |
|
277 | 202 | "Loading test.txt\n" |
|
278 | 203 | ] |
|
279 | 204 | }, |
|
280 | 205 | { |
|
281 | 206 | "output_type": "stream", |
|
282 | 207 | "stream": "stdout", |
|
283 | 208 | "text": [ |
|
284 | 209 | "Loaded, file contents: \n", |
|
285 | 210 | "hello world!\n" |
|
286 | 211 | ] |
|
287 | 212 | } |
|
288 | 213 | ], |
|
289 | 214 | "prompt_number": 4 |
|
290 | 215 | } |
|
291 | 216 | ], |
|
292 | 217 | "metadata": {} |
|
293 | 218 | } |
|
294 | 219 | ] |
|
295 | 220 | } No newline at end of file |
@@ -1,391 +1,323 b'' | |||
|
1 | 1 | { |
|
2 | 2 | "metadata": { |
|
3 | "cell_tags": [ | |
|
4 | [ | |
|
5 | "<None>", | |
|
6 | null | |
|
7 | ] | |
|
8 | ], | |
|
3 | 9 | "name": "" |
|
4 | 10 | }, |
|
5 | 11 | "nbformat": 3, |
|
6 | 12 | "nbformat_minor": 0, |
|
7 | 13 | "worksheets": [ |
|
8 | 14 | { |
|
9 | 15 | "cells": [ |
|
10 | 16 | { |
|
11 | 17 | "cell_type": "markdown", |
|
12 | 18 | "metadata": {}, |
|
13 | 19 | "source": [ |
|
14 | "To enable the use IPython widgets in the notebook, the widget namespace and display function need to be imported. The Javascript dependencies need to be loaded via `IPython.html.widgets.init_widget_js()`. This method needs to be called each time the notebook webpage is refreshed." | |
|
20 | "To use IPython widgets in the notebook, the widget namespace and display function need to be imported." | |
|
15 | 21 | ] |
|
16 | 22 | }, |
|
17 | 23 | { |
|
18 | 24 | "cell_type": "code", |
|
19 | 25 | "collapsed": false, |
|
20 | 26 | "input": [ |
|
21 | 27 | "from IPython.html import widgets # Widget definitions\n", |
|
22 |
"from IPython.display import display # Used to display widgets in the notebook |
|
|
23 | "\n", | |
|
24 | "# Enable widgets in this notebook\n", | |
|
25 | "widgets.init_widget_js()" | |
|
28 | "from IPython.display import display # Used to display widgets in the notebook" | |
|
26 | 29 | ], |
|
27 | 30 | "language": "python", |
|
28 | 31 | "metadata": {}, |
|
29 | "outputs": [ | |
|
30 | { | |
|
31 | "javascript": [ | |
|
32 | "$.getScript(\"/static/notebook/js/widgets/bool.js\");" | |
|
33 | ], | |
|
34 | "metadata": {}, | |
|
35 | "output_type": "display_data" | |
|
36 | }, | |
|
37 | { | |
|
38 | "javascript": [ | |
|
39 | "$.getScript(\"/static/notebook/js/widgets/int_range.js\");" | |
|
40 | ], | |
|
41 | "metadata": {}, | |
|
42 | "output_type": "display_data" | |
|
43 | }, | |
|
44 | { | |
|
45 | "javascript": [ | |
|
46 | "$.getScript(\"/static/notebook/js/widgets/int.js\");" | |
|
47 | ], | |
|
48 | "metadata": {}, | |
|
49 | "output_type": "display_data" | |
|
50 | }, | |
|
51 | { | |
|
52 | "javascript": [ | |
|
53 | "$.getScript(\"/static/notebook/js/widgets/selection.js\");" | |
|
54 | ], | |
|
55 | "metadata": {}, | |
|
56 | "output_type": "display_data" | |
|
57 | }, | |
|
58 | { | |
|
59 | "javascript": [ | |
|
60 | "$.getScript(\"/static/notebook/js/widgets/string.js\");" | |
|
61 | ], | |
|
62 | "metadata": {}, | |
|
63 | "output_type": "display_data" | |
|
64 | }, | |
|
65 | { | |
|
66 | "javascript": [ | |
|
67 | "$.getScript(\"/static/notebook/js/widgets/float.js\");" | |
|
68 | ], | |
|
69 | "metadata": {}, | |
|
70 | "output_type": "display_data" | |
|
71 | }, | |
|
72 | { | |
|
73 | "javascript": [ | |
|
74 | "$.getScript(\"/static/notebook/js/widgets/container.js\");" | |
|
75 | ], | |
|
76 | "metadata": {}, | |
|
77 | "output_type": "display_data" | |
|
78 | }, | |
|
79 | { | |
|
80 | "javascript": [ | |
|
81 | "$.getScript(\"/static/notebook/js/widgets/multicontainer.js\");" | |
|
82 | ], | |
|
83 | "metadata": {}, | |
|
84 | "output_type": "display_data" | |
|
85 | }, | |
|
86 | { | |
|
87 | "javascript": [ | |
|
88 | "$.getScript(\"/static/notebook/js/widgets/button.js\");" | |
|
89 | ], | |
|
90 | "metadata": {}, | |
|
91 | "output_type": "display_data" | |
|
92 | }, | |
|
93 | { | |
|
94 | "javascript": [ | |
|
95 | "$.getScript(\"/static/notebook/js/widgets/float_range.js\");" | |
|
96 | ], | |
|
97 | "metadata": {}, | |
|
98 | "output_type": "display_data" | |
|
99 | } | |
|
100 | ], | |
|
32 | "outputs": [], | |
|
101 | 33 | "prompt_number": 1 |
|
102 | 34 | }, |
|
103 | 35 | { |
|
104 | 36 | "cell_type": "heading", |
|
105 | 37 | "level": 1, |
|
106 | 38 | "metadata": {}, |
|
107 | 39 | "source": [ |
|
108 | 40 | "Basic Widgets" |
|
109 | 41 | ] |
|
110 | 42 | }, |
|
111 | 43 | { |
|
112 | 44 | "cell_type": "markdown", |
|
113 | 45 | "metadata": {}, |
|
114 | 46 | "source": [ |
|
115 | 47 | "The IPython notebook comes preloaded with basic widgets that represent common data types. These widgets are\n", |
|
116 | 48 | "\n", |
|
117 | 49 | "- BoolWidget : boolean \n", |
|
118 | 50 | "- FloatRangeWidget : bounded float \n", |
|
119 | 51 | "- FloatWidget : unbounded float \n", |
|
120 | 52 | "- IntRangeWidget : bounded integer \n", |
|
121 | 53 | "- IntWidget : unbounded integer \n", |
|
122 | 54 | "- SelectionWidget : enumeration \n", |
|
123 | 55 | "- StringWidget : string \n", |
|
124 | 56 | "\n", |
|
125 | 57 | "A few special widgets are also included, that can be used to capture events and change how other widgets are displayed. These widgets are\n", |
|
126 | 58 | "\n", |
|
127 | 59 | "- ButtonWidget \n", |
|
128 | 60 | "- ContainerWidget \n", |
|
129 | 61 | "- MulticontainerWidget \n", |
|
130 | 62 | "\n", |
|
131 | 63 | "To see the complete list of widgets, one can execute the following" |
|
132 | 64 | ] |
|
133 | 65 | }, |
|
134 | 66 | { |
|
135 | 67 | "cell_type": "code", |
|
136 | 68 | "collapsed": false, |
|
137 | 69 | "input": [ |
|
138 | 70 | "[widget for widget in dir(widgets) if widget.endswith('Widget')]" |
|
139 | 71 | ], |
|
140 | 72 | "language": "python", |
|
141 | 73 | "metadata": {}, |
|
142 | 74 | "outputs": [ |
|
143 | 75 | { |
|
144 | 76 | "metadata": {}, |
|
145 | 77 | "output_type": "pyout", |
|
146 | 78 | "prompt_number": 2, |
|
147 | 79 | "text": [ |
|
148 | 80 | "['BoolWidget',\n", |
|
149 | 81 | " 'ButtonWidget',\n", |
|
150 | 82 | " 'ContainerWidget',\n", |
|
151 | 83 | " 'FloatRangeWidget',\n", |
|
152 | 84 | " 'FloatWidget',\n", |
|
153 | 85 | " 'IntRangeWidget',\n", |
|
154 | 86 | " 'IntWidget',\n", |
|
155 | 87 | " 'MulticontainerWidget',\n", |
|
156 | 88 | " 'SelectionWidget',\n", |
|
157 | 89 | " 'StringWidget',\n", |
|
158 | 90 | " 'Widget']" |
|
159 | 91 | ] |
|
160 | 92 | } |
|
161 | 93 | ], |
|
162 | 94 | "prompt_number": 2 |
|
163 | 95 | }, |
|
164 | 96 | { |
|
165 | 97 | "cell_type": "markdown", |
|
166 | 98 | "metadata": {}, |
|
167 | 99 | "source": [ |
|
168 | 100 | "The basic widgets can all be constructed without arguments. The following creates a FloatRangeWidget without displaying it" |
|
169 | 101 | ] |
|
170 | 102 | }, |
|
171 | 103 | { |
|
172 | 104 | "cell_type": "code", |
|
173 | 105 | "collapsed": false, |
|
174 | 106 | "input": [ |
|
175 | 107 | "mywidget = widgets.FloatRangeWidget()" |
|
176 | 108 | ], |
|
177 | 109 | "language": "python", |
|
178 | 110 | "metadata": {}, |
|
179 | 111 | "outputs": [], |
|
180 | 112 | "prompt_number": 3 |
|
181 | 113 | }, |
|
182 | 114 | { |
|
183 | 115 | "cell_type": "markdown", |
|
184 | 116 | "metadata": {}, |
|
185 | 117 | "source": [ |
|
186 | 118 | "Constructing a widget does not display it on the page. To display a widget, the widget must be passed to the IPython `display(object)` method. `mywidget` is displayed by" |
|
187 | 119 | ] |
|
188 | 120 | }, |
|
189 | 121 | { |
|
190 | 122 | "cell_type": "code", |
|
191 | 123 | "collapsed": false, |
|
192 | 124 | "input": [ |
|
193 | 125 | "display(mywidget)" |
|
194 | 126 | ], |
|
195 | 127 | "language": "python", |
|
196 | 128 | "metadata": {}, |
|
197 | 129 | "outputs": [], |
|
198 | 130 | "prompt_number": 4 |
|
199 | 131 | }, |
|
200 | 132 | { |
|
201 | 133 | "cell_type": "markdown", |
|
202 | 134 | "metadata": {}, |
|
203 | 135 | "source": [ |
|
204 | 136 | "It's important to realize that widgets are not the same as output, even though they are displayed with `display`. Widgets are drawn in a special widget area. That area is marked with a close button which allows you to collapse the widgets. Widgets cannot be interleaved with output. Doing so would break the ability to make simple animations using `clear_output`.\n", |
|
205 | 137 | "\n", |
|
206 | 138 | "Widgets are manipulated via special instance properties (traitlets). The names of these instance properties are listed in the widget's `keys` property (as seen below). A few of these properties are common to most, if not all, widgets. The common properties are `value`, `description`, `visible`, and `disabled`. `_css`, `_add_class`, and `_remove_class` are internal properties that exist in all widgets and should not be modified." |
|
207 | 139 | ] |
|
208 | 140 | }, |
|
209 | 141 | { |
|
210 | 142 | "cell_type": "code", |
|
211 | 143 | "collapsed": false, |
|
212 | 144 | "input": [ |
|
213 | 145 | "mywidget.keys" |
|
214 | 146 | ], |
|
215 | 147 | "language": "python", |
|
216 | 148 | "metadata": {}, |
|
217 | 149 | "outputs": [ |
|
218 | 150 | { |
|
219 | 151 | "metadata": {}, |
|
220 | 152 | "output_type": "pyout", |
|
221 | 153 | "prompt_number": 5, |
|
222 | 154 | "text": [ |
|
223 | 155 | "['visible',\n", |
|
224 | 156 | " '_css',\n", |
|
225 | 157 | " '_add_class',\n", |
|
226 | 158 | " '_remove_class',\n", |
|
227 | 159 | " 'value',\n", |
|
228 | 160 | " 'step',\n", |
|
229 | 161 | " 'max',\n", |
|
230 | 162 | " 'min',\n", |
|
231 | 163 | " 'disabled',\n", |
|
232 | 164 | " 'orientation',\n", |
|
233 | 165 | " 'description']" |
|
234 | 166 | ] |
|
235 | 167 | } |
|
236 | 168 | ], |
|
237 | 169 | "prompt_number": 5 |
|
238 | 170 | }, |
|
239 | 171 | { |
|
240 | 172 | "cell_type": "markdown", |
|
241 | 173 | "metadata": {}, |
|
242 | 174 | "source": [ |
|
243 | 175 | "Changing a widget's property value will automatically update that widget everywhere it is displayed in the notebook. Here the value of `mywidget` is set. The slider shown above (after input 4) updates automatically to the new value. In reverse, changing the value of the displayed widget will update the property's value." |
|
244 | 176 | ] |
|
245 | 177 | }, |
|
246 | 178 | { |
|
247 | 179 | "cell_type": "code", |
|
248 | 180 | "collapsed": false, |
|
249 | 181 | "input": [ |
|
250 | 182 | "mywidget.value = 25.0" |
|
251 | 183 | ], |
|
252 | 184 | "language": "python", |
|
253 | 185 | "metadata": {}, |
|
254 | 186 | "outputs": [], |
|
255 | 187 | "prompt_number": 6 |
|
256 | 188 | }, |
|
257 | 189 | { |
|
258 | 190 | "cell_type": "markdown", |
|
259 | 191 | "metadata": {}, |
|
260 | 192 | "source": [ |
|
261 | 193 | "After changing the widget's value in the notebook by hand to 0.0 (sliding the bar to the far left)." |
|
262 | 194 | ] |
|
263 | 195 | }, |
|
264 | 196 | { |
|
265 | 197 | "cell_type": "code", |
|
266 | 198 | "collapsed": false, |
|
267 | 199 | "input": [ |
|
268 | 200 | "mywidget.value" |
|
269 | 201 | ], |
|
270 | 202 | "language": "python", |
|
271 | 203 | "metadata": {}, |
|
272 | 204 | "outputs": [ |
|
273 | 205 | { |
|
274 | 206 | "metadata": {}, |
|
275 | 207 | "output_type": "pyout", |
|
276 | 208 | "prompt_number": 7, |
|
277 | 209 | "text": [ |
|
278 | 210 | "0.0" |
|
279 | 211 | ] |
|
280 | 212 | } |
|
281 | 213 | ], |
|
282 | 214 | "prompt_number": 7 |
|
283 | 215 | }, |
|
284 | 216 | { |
|
285 | 217 | "cell_type": "markdown", |
|
286 | 218 | "metadata": {}, |
|
287 | 219 | "source": [ |
|
288 | 220 | "Widget property values can also be set with kwargs during the construction of the widget (as seen below)." |
|
289 | 221 | ] |
|
290 | 222 | }, |
|
291 | 223 | { |
|
292 | 224 | "cell_type": "code", |
|
293 | 225 | "collapsed": false, |
|
294 | 226 | "input": [ |
|
295 | 227 | "mysecondwidget = widgets.SelectionWidget(values=[\"Item A\", \"Item B\", \"Item C\"], value=\"Nothing Selected\")\n", |
|
296 | 228 | "display(mysecondwidget)" |
|
297 | 229 | ], |
|
298 | 230 | "language": "python", |
|
299 | 231 | "metadata": {}, |
|
300 | 232 | "outputs": [], |
|
301 | 233 | "prompt_number": 8 |
|
302 | 234 | }, |
|
303 | 235 | { |
|
304 | 236 | "cell_type": "heading", |
|
305 | 237 | "level": 1, |
|
306 | 238 | "metadata": {}, |
|
307 | 239 | "source": [ |
|
308 | 240 | "Views" |
|
309 | 241 | ] |
|
310 | 242 | }, |
|
311 | 243 | { |
|
312 | 244 | "cell_type": "markdown", |
|
313 | 245 | "metadata": {}, |
|
314 | 246 | "source": [ |
|
315 | 247 | "The data types that most of the widgets represent can be displayed more than one way. A `view` is a visual representation of a widget in the notebook. In the example in the section above, the default `view` for the `FloatRangeWidget` is used. The default view is set in the widgets `default_view_name` instance property (as seen below)." |
|
316 | 248 | ] |
|
317 | 249 | }, |
|
318 | 250 | { |
|
319 | 251 | "cell_type": "code", |
|
320 | 252 | "collapsed": false, |
|
321 | 253 | "input": [ |
|
322 | 254 | "mywidget.default_view_name" |
|
323 | 255 | ], |
|
324 | 256 | "language": "python", |
|
325 | 257 | "metadata": {}, |
|
326 | 258 | "outputs": [ |
|
327 | 259 | { |
|
328 | 260 | "metadata": {}, |
|
329 | 261 | "output_type": "pyout", |
|
330 | 262 | "prompt_number": 9, |
|
331 | 263 | "text": [ |
|
332 | 264 | "u'FloatSliderView'" |
|
333 | 265 | ] |
|
334 | 266 | } |
|
335 | 267 | ], |
|
336 | 268 | "prompt_number": 9 |
|
337 | 269 | }, |
|
338 | 270 | { |
|
339 | 271 | "cell_type": "markdown", |
|
340 | 272 | "metadata": {}, |
|
341 | 273 | "source": [ |
|
342 | 274 | "When a widget is displayed using `display(...)`, the `default_view_name` is used to determine what view type should be used to display the widget. View names are case sensitive. Sometimes the default view isn't the best view to represent a piece of data. To change what view is used, either the `default_view_name` can be changed or the `view_name` kwarg of `display` can be set. This also can be used to display one widget multiple ways in one output (as seen below)." |
|
343 | 275 | ] |
|
344 | 276 | }, |
|
345 | 277 | { |
|
346 | 278 | "cell_type": "code", |
|
347 | 279 | "collapsed": false, |
|
348 | 280 | "input": [ |
|
349 | 281 | "display(mywidget)\n", |
|
350 | 282 | "display(mywidget, view_name=\"FloatTextView\")" |
|
351 | 283 | ], |
|
352 | 284 | "language": "python", |
|
353 | 285 | "metadata": {}, |
|
354 | 286 | "outputs": [], |
|
355 | 287 | "prompt_number": 10 |
|
356 | 288 | }, |
|
357 | 289 | { |
|
358 | 290 | "cell_type": "markdown", |
|
359 | 291 | "metadata": {}, |
|
360 | 292 | "source": [ |
|
361 | 293 | "Some views work with multiple different widget types and some views only work with one. The complete list of views and supported widgets is below. The default views are italicized.\n", |
|
362 | 294 | "\n", |
|
363 | 295 | "| Widget Name | View Names |\n", |
|
364 | 296 | "|:-----------------------|:--------------------|\n", |
|
365 | 297 | "| BoolWidget | *CheckboxView* |\n", |
|
366 | 298 | "| | ToggleButtonView |\n", |
|
367 | 299 | "| ButtonWidget | *ButtonView* |\n", |
|
368 | 300 | "| ContainerWidget | *ContainerView* |\n", |
|
369 | 301 | "| FloatRangeWidget | *FloatSliderView* |\n", |
|
370 | 302 | "| | FloatTextView |\n", |
|
371 | 303 | "| | ProgressView |\n", |
|
372 | 304 | "| FloatWidget | *FloatTextView* |\n", |
|
373 | 305 | "| IntRangeWidget | *IntSliderView* |\n", |
|
374 | 306 | "| | IntTextView |\n", |
|
375 | 307 | "| | ProgressView |\n", |
|
376 | 308 | "| IntWidget | *IntTextView* |\n", |
|
377 | 309 | "| MulticontainerWidget | AccordionView |\n", |
|
378 | 310 | "| | *TabView* |\n", |
|
379 | 311 | "| SelectionWidget | ToggleButtonsView |\n", |
|
380 | 312 | "| | RadioButtonsView |\n", |
|
381 | 313 | "| | *DropdownView* |\n", |
|
382 | 314 | "| StringWidget | LabelView |\n", |
|
383 | 315 | "| | TextAreaView |\n", |
|
384 | 316 | "| | *TextBoxView* |\n" |
|
385 | 317 | ] |
|
386 | 318 | } |
|
387 | 319 | ], |
|
388 | 320 | "metadata": {} |
|
389 | 321 | } |
|
390 | 322 | ] |
|
391 | 323 | } No newline at end of file |
@@ -1,310 +1,242 b'' | |||
|
1 | 1 | { |
|
2 | 2 | "metadata": { |
|
3 | "cell_tags": [ | |
|
4 | [ | |
|
5 | "<None>", | |
|
6 | null | |
|
7 | ] | |
|
8 | ], | |
|
3 | 9 | "name": "" |
|
4 | 10 | }, |
|
5 | 11 | "nbformat": 3, |
|
6 | 12 | "nbformat_minor": 0, |
|
7 | 13 | "worksheets": [ |
|
8 | 14 | { |
|
9 | 15 | "cells": [ |
|
10 | 16 | { |
|
11 | 17 | "cell_type": "code", |
|
12 | 18 | "collapsed": false, |
|
13 | 19 | "input": [ |
|
14 | 20 | "from IPython.html import widgets # Widget definitions\n", |
|
15 |
"from IPython.display import display # Used to display widgets in the notebook |
|
|
16 | "\n", | |
|
17 | "# Enable widgets in this notebook\n", | |
|
18 | "widgets.init_widget_js()" | |
|
21 | "from IPython.display import display # Used to display widgets in the notebook" | |
|
19 | 22 | ], |
|
20 | 23 | "language": "python", |
|
21 | 24 | "metadata": {}, |
|
22 | "outputs": [ | |
|
23 | { | |
|
24 | "javascript": [ | |
|
25 | "$.getScript(\"/static/notebook/js/widgets/bool.js\");" | |
|
26 | ], | |
|
27 | "metadata": {}, | |
|
28 | "output_type": "display_data" | |
|
29 | }, | |
|
30 | { | |
|
31 | "javascript": [ | |
|
32 | "$.getScript(\"/static/notebook/js/widgets/int_range.js\");" | |
|
33 | ], | |
|
34 | "metadata": {}, | |
|
35 | "output_type": "display_data" | |
|
36 | }, | |
|
37 | { | |
|
38 | "javascript": [ | |
|
39 | "$.getScript(\"/static/notebook/js/widgets/int.js\");" | |
|
40 | ], | |
|
41 | "metadata": {}, | |
|
42 | "output_type": "display_data" | |
|
43 | }, | |
|
44 | { | |
|
45 | "javascript": [ | |
|
46 | "$.getScript(\"/static/notebook/js/widgets/selection.js\");" | |
|
47 | ], | |
|
48 | "metadata": {}, | |
|
49 | "output_type": "display_data" | |
|
50 | }, | |
|
51 | { | |
|
52 | "javascript": [ | |
|
53 | "$.getScript(\"/static/notebook/js/widgets/string.js\");" | |
|
54 | ], | |
|
55 | "metadata": {}, | |
|
56 | "output_type": "display_data" | |
|
57 | }, | |
|
58 | { | |
|
59 | "javascript": [ | |
|
60 | "$.getScript(\"/static/notebook/js/widgets/float.js\");" | |
|
61 | ], | |
|
62 | "metadata": {}, | |
|
63 | "output_type": "display_data" | |
|
64 | }, | |
|
65 | { | |
|
66 | "javascript": [ | |
|
67 | "$.getScript(\"/static/notebook/js/widgets/container.js\");" | |
|
68 | ], | |
|
69 | "metadata": {}, | |
|
70 | "output_type": "display_data" | |
|
71 | }, | |
|
72 | { | |
|
73 | "javascript": [ | |
|
74 | "$.getScript(\"/static/notebook/js/widgets/multicontainer.js\");" | |
|
75 | ], | |
|
76 | "metadata": {}, | |
|
77 | "output_type": "display_data" | |
|
78 | }, | |
|
79 | { | |
|
80 | "javascript": [ | |
|
81 | "$.getScript(\"/static/notebook/js/widgets/button.js\");" | |
|
82 | ], | |
|
83 | "metadata": {}, | |
|
84 | "output_type": "display_data" | |
|
85 | }, | |
|
86 | { | |
|
87 | "javascript": [ | |
|
88 | "$.getScript(\"/static/notebook/js/widgets/float_range.js\");" | |
|
89 | ], | |
|
90 | "metadata": {}, | |
|
91 | "output_type": "display_data" | |
|
92 | } | |
|
93 | ], | |
|
25 | "outputs": [], | |
|
94 | 26 | "prompt_number": 1 |
|
95 | 27 | }, |
|
96 | 28 | { |
|
97 | 29 | "cell_type": "heading", |
|
98 | 30 | "level": 1, |
|
99 | 31 | "metadata": {}, |
|
100 | 32 | "source": [ |
|
101 | 33 | "Traitlet Events" |
|
102 | 34 | ] |
|
103 | 35 | }, |
|
104 | 36 | { |
|
105 | 37 | "cell_type": "markdown", |
|
106 | 38 | "metadata": {}, |
|
107 | 39 | "source": [ |
|
108 | 40 | "The widget properties are IPython traitlets. Traitlets are eventful. To handle property value changes, the `on_trait_change` method of the widget can be used to register an event handling callback. The doc string for `on_trait_change` can be seen below. Both the `name` and `remove` properties are optional." |
|
109 | 41 | ] |
|
110 | 42 | }, |
|
111 | 43 | { |
|
112 | 44 | "cell_type": "code", |
|
113 | 45 | "collapsed": false, |
|
114 | 46 | "input": [ |
|
115 | 47 | "print(widgets.Widget.on_trait_change.__doc__)" |
|
116 | 48 | ], |
|
117 | 49 | "language": "python", |
|
118 | 50 | "metadata": {}, |
|
119 | 51 | "outputs": [ |
|
120 | 52 | { |
|
121 | 53 | "output_type": "stream", |
|
122 | 54 | "stream": "stdout", |
|
123 | 55 | "text": [ |
|
124 | 56 | "Setup a handler to be called when a trait changes.\n", |
|
125 | 57 | "\n", |
|
126 | 58 | " This is used to setup dynamic notifications of trait changes.\n", |
|
127 | 59 | "\n", |
|
128 | 60 | " Static handlers can be created by creating methods on a HasTraits\n", |
|
129 | 61 | " subclass with the naming convention '_[traitname]_changed'. Thus,\n", |
|
130 | 62 | " to create static handler for the trait 'a', create the method\n", |
|
131 | 63 | " _a_changed(self, name, old, new) (fewer arguments can be used, see\n", |
|
132 | 64 | " below).\n", |
|
133 | 65 | "\n", |
|
134 | 66 | " Parameters\n", |
|
135 | 67 | " ----------\n", |
|
136 | 68 | " handler : callable\n", |
|
137 | 69 | " A callable that is called when a trait changes. Its\n", |
|
138 | 70 | " signature can be handler(), handler(name), handler(name, new)\n", |
|
139 | 71 | " or handler(name, old, new).\n", |
|
140 | 72 | " name : list, str, None\n", |
|
141 | 73 | " If None, the handler will apply to all traits. If a list\n", |
|
142 | 74 | " of str, handler will apply to all names in the list. If a\n", |
|
143 | 75 | " str, the handler will apply just to that name.\n", |
|
144 | 76 | " remove : bool\n", |
|
145 | 77 | " If False (the default), then install the handler. If True\n", |
|
146 | 78 | " then unintall it.\n", |
|
147 | 79 | " \n" |
|
148 | 80 | ] |
|
149 | 81 | } |
|
150 | 82 | ], |
|
151 | 83 | "prompt_number": 2 |
|
152 | 84 | }, |
|
153 | 85 | { |
|
154 | 86 | "cell_type": "markdown", |
|
155 | 87 | "metadata": {}, |
|
156 | 88 | "source": [ |
|
157 | 89 | "Mentioned in the doc string, the callback registered can have 4 possible signatures:\n", |
|
158 | 90 | "\n", |
|
159 | 91 | "- callback()\n", |
|
160 | 92 | "- callback(trait_name)\n", |
|
161 | 93 | "- callback(trait_name, new_value)\n", |
|
162 | 94 | "- callback(trait_name, old_value, new_value)\n", |
|
163 | 95 | "\n", |
|
164 | 96 | "An example of how to output an IntRangeWiget's value as it is changed can be seen below." |
|
165 | 97 | ] |
|
166 | 98 | }, |
|
167 | 99 | { |
|
168 | 100 | "cell_type": "code", |
|
169 | 101 | "collapsed": false, |
|
170 | 102 | "input": [ |
|
171 | 103 | "intrange = widgets.IntRangeWidget()\n", |
|
172 | 104 | "display(intrange)\n", |
|
173 | 105 | "\n", |
|
174 | 106 | "def on_value_change(name, value):\n", |
|
175 | 107 | " print value\n", |
|
176 | 108 | "\n", |
|
177 | 109 | "intrange.on_trait_change(on_value_change, 'value')" |
|
178 | 110 | ], |
|
179 | 111 | "language": "python", |
|
180 | 112 | "metadata": {}, |
|
181 | 113 | "outputs": [], |
|
182 | 114 | "prompt_number": 3 |
|
183 | 115 | }, |
|
184 | 116 | { |
|
185 | 117 | "cell_type": "heading", |
|
186 | 118 | "level": 1, |
|
187 | 119 | "metadata": {}, |
|
188 | 120 | "source": [ |
|
189 | 121 | "Specialized Events" |
|
190 | 122 | ] |
|
191 | 123 | }, |
|
192 | 124 | { |
|
193 | 125 | "cell_type": "heading", |
|
194 | 126 | "level": 2, |
|
195 | 127 | "metadata": {}, |
|
196 | 128 | "source": [ |
|
197 | 129 | "Button On Click Event" |
|
198 | 130 | ] |
|
199 | 131 | }, |
|
200 | 132 | { |
|
201 | 133 | "cell_type": "markdown", |
|
202 | 134 | "metadata": {}, |
|
203 | 135 | "source": [ |
|
204 | 136 | "The `ButtonWidget` is a special widget, like the `ContainerWidget` and `MulticontainerWidget`, that isn't used to represent a data type. Instead the button widget is used to handle mouse clicks. The `on_click` method of the `ButtonWidget` can be used to register a click even handler. The doc string of the `on_click` can be seen below." |
|
205 | 137 | ] |
|
206 | 138 | }, |
|
207 | 139 | { |
|
208 | 140 | "cell_type": "code", |
|
209 | 141 | "collapsed": false, |
|
210 | 142 | "input": [ |
|
211 | 143 | "print(widgets.ButtonWidget.on_click.__doc__)" |
|
212 | 144 | ], |
|
213 | 145 | "language": "python", |
|
214 | 146 | "metadata": {}, |
|
215 | 147 | "outputs": [ |
|
216 | 148 | { |
|
217 | 149 | "output_type": "stream", |
|
218 | 150 | "stream": "stdout", |
|
219 | 151 | "text": [ |
|
220 | 152 | "Register a callback to execute when the button is clicked. The\n", |
|
221 | 153 | " callback can either accept no parameters or one sender parameter:\n", |
|
222 | 154 | " - callback()\n", |
|
223 | 155 | " - callback(sender)\n", |
|
224 | 156 | " If the callback has a sender parameter, the ButtonWidget instance that\n", |
|
225 | 157 | " called the callback will be passed into the method as the sender.\n", |
|
226 | 158 | "\n", |
|
227 | 159 | " Parameters\n", |
|
228 | 160 | " ----------\n", |
|
229 | 161 | " remove : bool (optional)\n", |
|
230 | 162 | " Set to true to remove the callback from the list of callbacks.\n" |
|
231 | 163 | ] |
|
232 | 164 | } |
|
233 | 165 | ], |
|
234 | 166 | "prompt_number": 4 |
|
235 | 167 | }, |
|
236 | 168 | { |
|
237 | 169 | "cell_type": "markdown", |
|
238 | 170 | "metadata": {}, |
|
239 | 171 | "source": [ |
|
240 | 172 | "Button clicks are tracked by the `clicks` property of the button widget. By using the `on_click` method and the `clicks` property, a button that outputs how many times it has been clicked is shown below." |
|
241 | 173 | ] |
|
242 | 174 | }, |
|
243 | 175 | { |
|
244 | 176 | "cell_type": "code", |
|
245 | 177 | "collapsed": false, |
|
246 | 178 | "input": [ |
|
247 | 179 | "button = widgets.ButtonWidget(description=\"Click Me!\")\n", |
|
248 | 180 | "display(button)\n", |
|
249 | 181 | "\n", |
|
250 | 182 | "def on_button_clicked(sender):\n", |
|
251 | 183 | " print(\"Button clicked %d times.\" % sender.clicks)\n", |
|
252 | 184 | "\n", |
|
253 | 185 | "button.on_click(on_button_clicked)" |
|
254 | 186 | ], |
|
255 | 187 | "language": "python", |
|
256 | 188 | "metadata": {}, |
|
257 | 189 | "outputs": [ |
|
258 | 190 | { |
|
259 | 191 | "output_type": "stream", |
|
260 | 192 | "stream": "stdout", |
|
261 | 193 | "text": [ |
|
262 | 194 | "Button clicked 1 times.\n" |
|
263 | 195 | ] |
|
264 | 196 | }, |
|
265 | 197 | { |
|
266 | 198 | "output_type": "stream", |
|
267 | 199 | "stream": "stdout", |
|
268 | 200 | "text": [ |
|
269 | 201 | "Button clicked 2 times.\n" |
|
270 | 202 | ] |
|
271 | 203 | }, |
|
272 | 204 | { |
|
273 | 205 | "output_type": "stream", |
|
274 | 206 | "stream": "stdout", |
|
275 | 207 | "text": [ |
|
276 | 208 | "Button clicked 3 times.\n" |
|
277 | 209 | ] |
|
278 | 210 | } |
|
279 | 211 | ], |
|
280 | 212 | "prompt_number": 5 |
|
281 | 213 | }, |
|
282 | 214 | { |
|
283 | 215 | "cell_type": "markdown", |
|
284 | 216 | "metadata": {}, |
|
285 | 217 | "source": [ |
|
286 | 218 | "Event handlers can also be used to create widgets. In the example below, clicking a button spawns another button with a description equal to how many times the parent button had been clicked at the time." |
|
287 | 219 | ] |
|
288 | 220 | }, |
|
289 | 221 | { |
|
290 | 222 | "cell_type": "code", |
|
291 | 223 | "collapsed": false, |
|
292 | 224 | "input": [ |
|
293 | 225 | "def show_button(sender=None):\n", |
|
294 | 226 | " button = widgets.ButtonWidget()\n", |
|
295 | 227 | " button.description = \"%d\" % (sender.clicks if sender is not None else 0)\n", |
|
296 | 228 | " display(button)\n", |
|
297 | 229 | " button.on_click(show_button)\n", |
|
298 | 230 | "show_button()\n", |
|
299 | 231 | " " |
|
300 | 232 | ], |
|
301 | 233 | "language": "python", |
|
302 | 234 | "metadata": {}, |
|
303 | 235 | "outputs": [], |
|
304 | 236 | "prompt_number": 6 |
|
305 | 237 | } |
|
306 | 238 | ], |
|
307 | 239 | "metadata": {} |
|
308 | 240 | } |
|
309 | 241 | ] |
|
310 | 242 | } No newline at end of file |
@@ -1,326 +1,258 b'' | |||
|
1 | 1 | { |
|
2 | 2 | "metadata": { |
|
3 | "cell_tags": [ | |
|
4 | [ | |
|
5 | "<None>", | |
|
6 | null | |
|
7 | ] | |
|
8 | ], | |
|
3 | 9 | "name": "" |
|
4 | 10 | }, |
|
5 | 11 | "nbformat": 3, |
|
6 | 12 | "nbformat_minor": 0, |
|
7 | 13 | "worksheets": [ |
|
8 | 14 | { |
|
9 | 15 | "cells": [ |
|
10 | 16 | { |
|
11 | 17 | "cell_type": "code", |
|
12 | 18 | "collapsed": false, |
|
13 | 19 | "input": [ |
|
14 | 20 | "from IPython.html import widgets # Widget definitions\n", |
|
15 |
"from IPython.display import display # Used to display widgets in the notebook |
|
|
16 | "\n", | |
|
17 | "# Enable widgets in this notebook\n", | |
|
18 | "widgets.init_widget_js()" | |
|
21 | "from IPython.display import display # Used to display widgets in the notebook" | |
|
19 | 22 | ], |
|
20 | 23 | "language": "python", |
|
21 | 24 | "metadata": {}, |
|
22 | "outputs": [ | |
|
23 | { | |
|
24 | "javascript": [ | |
|
25 | "$.getScript(\"/static/notebook/js/widgets/bool.js\");" | |
|
26 | ], | |
|
27 | "metadata": {}, | |
|
28 | "output_type": "display_data" | |
|
29 | }, | |
|
30 | { | |
|
31 | "javascript": [ | |
|
32 | "$.getScript(\"/static/notebook/js/widgets/int_range.js\");" | |
|
33 | ], | |
|
34 | "metadata": {}, | |
|
35 | "output_type": "display_data" | |
|
36 | }, | |
|
37 | { | |
|
38 | "javascript": [ | |
|
39 | "$.getScript(\"/static/notebook/js/widgets/int.js\");" | |
|
40 | ], | |
|
41 | "metadata": {}, | |
|
42 | "output_type": "display_data" | |
|
43 | }, | |
|
44 | { | |
|
45 | "javascript": [ | |
|
46 | "$.getScript(\"/static/notebook/js/widgets/selection.js\");" | |
|
47 | ], | |
|
48 | "metadata": {}, | |
|
49 | "output_type": "display_data" | |
|
50 | }, | |
|
51 | { | |
|
52 | "javascript": [ | |
|
53 | "$.getScript(\"/static/notebook/js/widgets/string.js\");" | |
|
54 | ], | |
|
55 | "metadata": {}, | |
|
56 | "output_type": "display_data" | |
|
57 | }, | |
|
58 | { | |
|
59 | "javascript": [ | |
|
60 | "$.getScript(\"/static/notebook/js/widgets/float.js\");" | |
|
61 | ], | |
|
62 | "metadata": {}, | |
|
63 | "output_type": "display_data" | |
|
64 | }, | |
|
65 | { | |
|
66 | "javascript": [ | |
|
67 | "$.getScript(\"/static/notebook/js/widgets/container.js\");" | |
|
68 | ], | |
|
69 | "metadata": {}, | |
|
70 | "output_type": "display_data" | |
|
71 | }, | |
|
72 | { | |
|
73 | "javascript": [ | |
|
74 | "$.getScript(\"/static/notebook/js/widgets/multicontainer.js\");" | |
|
75 | ], | |
|
76 | "metadata": {}, | |
|
77 | "output_type": "display_data" | |
|
78 | }, | |
|
79 | { | |
|
80 | "javascript": [ | |
|
81 | "$.getScript(\"/static/notebook/js/widgets/button.js\");" | |
|
82 | ], | |
|
83 | "metadata": {}, | |
|
84 | "output_type": "display_data" | |
|
85 | }, | |
|
86 | { | |
|
87 | "javascript": [ | |
|
88 | "$.getScript(\"/static/notebook/js/widgets/float_range.js\");" | |
|
89 | ], | |
|
90 | "metadata": {}, | |
|
91 | "output_type": "display_data" | |
|
92 | } | |
|
93 | ], | |
|
25 | "outputs": [], | |
|
94 | 26 | "prompt_number": 1 |
|
95 | 27 | }, |
|
96 | 28 | { |
|
97 | 29 | "cell_type": "heading", |
|
98 | 30 | "level": 1, |
|
99 | 31 | "metadata": {}, |
|
100 | 32 | "source": [ |
|
101 | 33 | "Parent/Child Relationships" |
|
102 | 34 | ] |
|
103 | 35 | }, |
|
104 | 36 | { |
|
105 | 37 | "cell_type": "markdown", |
|
106 | 38 | "metadata": {}, |
|
107 | 39 | "source": [ |
|
108 | 40 | "To display widget A inside widget B, widget A must be a child of widget B. With IPython widgets, the widgets are instances that live in the back-end (usally Python). There can be multiple views displayed in the front-end that represent one widget in the backend. Each view can be displayed at a different time, or even displayed two or more times in the same output. Because of this, the parent of a widget can only be set before the widget has been displayed.\n", |
|
109 | 41 | "\n", |
|
110 | 42 | "Every widget has a `parent` property. This property can be set via a kwarg in the widget's constructor or after construction, but before display. Calling display on an object with children automatically displays those children too (as seen below)." |
|
111 | 43 | ] |
|
112 | 44 | }, |
|
113 | 45 | { |
|
114 | 46 | "cell_type": "code", |
|
115 | 47 | "collapsed": false, |
|
116 | 48 | "input": [ |
|
117 | 49 | "container = widgets.MulticontainerWidget()\n", |
|
118 | 50 | "\n", |
|
119 | 51 | "floatrange = widgets.FloatRangeWidget(parent=container) # You can set the parent in the constructor,\n", |
|
120 | 52 | "\n", |
|
121 | 53 | "string = widgets.StringWidget()\n", |
|
122 | 54 | "string.parent = container # or after the widget has been created.\n", |
|
123 | 55 | "\n", |
|
124 | 56 | "display(container) # Displays the `container` and all of it's children." |
|
125 | 57 | ], |
|
126 | 58 | "language": "python", |
|
127 | 59 | "metadata": {}, |
|
128 | 60 | "outputs": [], |
|
129 | 61 | "prompt_number": 2 |
|
130 | 62 | }, |
|
131 | 63 | { |
|
132 | 64 | "cell_type": "markdown", |
|
133 | 65 | "metadata": {}, |
|
134 | 66 | "source": [ |
|
135 | 67 | "Children can also be added to parents after the parent has been displayed. If the children are added after the parent has already been displayed, the children must be displayed themselves.\n", |
|
136 | 68 | "\n", |
|
137 | 69 | "In the example below, the IntRangeWidget is never rendered since display was called on the parent before the parent/child relationship was established." |
|
138 | 70 | ] |
|
139 | 71 | }, |
|
140 | 72 | { |
|
141 | 73 | "cell_type": "code", |
|
142 | 74 | "collapsed": false, |
|
143 | 75 | "input": [ |
|
144 | 76 | "container = widgets.MulticontainerWidget()\n", |
|
145 | 77 | "display(container)\n", |
|
146 | 78 | "\n", |
|
147 | 79 | "intrange = widgets.IntRangeWidget(parent=container) # Never gets displayed." |
|
148 | 80 | ], |
|
149 | 81 | "language": "python", |
|
150 | 82 | "metadata": {}, |
|
151 | 83 | "outputs": [], |
|
152 | 84 | "prompt_number": 3 |
|
153 | 85 | }, |
|
154 | 86 | { |
|
155 | 87 | "cell_type": "markdown", |
|
156 | 88 | "metadata": {}, |
|
157 | 89 | "source": [ |
|
158 | 90 | "Calling display on the child fixes the problem." |
|
159 | 91 | ] |
|
160 | 92 | }, |
|
161 | 93 | { |
|
162 | 94 | "cell_type": "code", |
|
163 | 95 | "collapsed": false, |
|
164 | 96 | "input": [ |
|
165 | 97 | "container = widgets.MulticontainerWidget()\n", |
|
166 | 98 | "display(container)\n", |
|
167 | 99 | "\n", |
|
168 | 100 | "intrange = widgets.IntRangeWidget(parent=container)\n", |
|
169 | 101 | "display(intrange) # This line is needed since the `container` has already been displayed." |
|
170 | 102 | ], |
|
171 | 103 | "language": "python", |
|
172 | 104 | "metadata": {}, |
|
173 | 105 | "outputs": [], |
|
174 | 106 | "prompt_number": 4 |
|
175 | 107 | }, |
|
176 | 108 | { |
|
177 | 109 | "cell_type": "heading", |
|
178 | 110 | "level": 1, |
|
179 | 111 | "metadata": {}, |
|
180 | 112 | "source": [ |
|
181 | 113 | "Changing Child Views" |
|
182 | 114 | ] |
|
183 | 115 | }, |
|
184 | 116 | { |
|
185 | 117 | "cell_type": "markdown", |
|
186 | 118 | "metadata": {}, |
|
187 | 119 | "source": [ |
|
188 | 120 | "The view used to display a widget must defined by the time the widget is displayed. If children widgets are to be displayed along with the parent in one call, their `view_name`s can't be set since all of the widgets are sharing the same display call. Instead, their `default_view_name`s must be set (as seen below)." |
|
189 | 121 | ] |
|
190 | 122 | }, |
|
191 | 123 | { |
|
192 | 124 | "cell_type": "code", |
|
193 | 125 | "collapsed": false, |
|
194 | 126 | "input": [ |
|
195 | 127 | "container = widgets.MulticontainerWidget()\n", |
|
196 | 128 | "\n", |
|
197 | 129 | "floatrange = widgets.FloatRangeWidget(parent=container)\n", |
|
198 | 130 | "floatrange.default_view_name = \"FloatTextView\" # It can be set as a property.\n", |
|
199 | 131 | "\n", |
|
200 | 132 | "string = widgets.StringWidget(default_view_name = \"TextAreaView\") # It can also be set in the constructor.\n", |
|
201 | 133 | "string.parent = container\n", |
|
202 | 134 | "\n", |
|
203 | 135 | "display(container)" |
|
204 | 136 | ], |
|
205 | 137 | "language": "python", |
|
206 | 138 | "metadata": {}, |
|
207 | 139 | "outputs": [], |
|
208 | 140 | "prompt_number": 5 |
|
209 | 141 | }, |
|
210 | 142 | { |
|
211 | 143 | "cell_type": "markdown", |
|
212 | 144 | "metadata": {}, |
|
213 | 145 | "source": [ |
|
214 | 146 | "However, if the children are displayed after the parent, their `view_name` can also be set like normal. Both methods will work. The code below produces the same output as the code above." |
|
215 | 147 | ] |
|
216 | 148 | }, |
|
217 | 149 | { |
|
218 | 150 | "cell_type": "code", |
|
219 | 151 | "collapsed": false, |
|
220 | 152 | "input": [ |
|
221 | 153 | "container = widgets.MulticontainerWidget()\n", |
|
222 | 154 | "display(container)\n", |
|
223 | 155 | "\n", |
|
224 | 156 | "floatrange = widgets.FloatRangeWidget()\n", |
|
225 | 157 | "floatrange.parent=container\n", |
|
226 | 158 | "display(floatrange, view_name = \"FloatTextView\") # view_name can be set during display.\n", |
|
227 | 159 | "\n", |
|
228 | 160 | "string = widgets.StringWidget()\n", |
|
229 | 161 | "string.parent = container\n", |
|
230 | 162 | "string.default_view_name = \"TextAreaView\" # Setting default_view_name still works.\n", |
|
231 | 163 | "display(string)\n" |
|
232 | 164 | ], |
|
233 | 165 | "language": "python", |
|
234 | 166 | "metadata": {}, |
|
235 | 167 | "outputs": [], |
|
236 | 168 | "prompt_number": 6 |
|
237 | 169 | }, |
|
238 | 170 | { |
|
239 | 171 | "cell_type": "heading", |
|
240 | 172 | "level": 1, |
|
241 | 173 | "metadata": {}, |
|
242 | 174 | "source": [ |
|
243 | 175 | "Visibility" |
|
244 | 176 | ] |
|
245 | 177 | }, |
|
246 | 178 | { |
|
247 | 179 | "cell_type": "markdown", |
|
248 | 180 | "metadata": {}, |
|
249 | 181 | "source": [ |
|
250 | 182 | "Sometimes it's necessary to hide/show widget views in place, without ruining the order that they have been displayed on the page. Using the `display` method, the views are always added to the end of their respective containers. Instead the `visibility` property of widgets can be used to hide/show widgets that have already been displayed (as seen below)." |
|
251 | 183 | ] |
|
252 | 184 | }, |
|
253 | 185 | { |
|
254 | 186 | "cell_type": "code", |
|
255 | 187 | "collapsed": false, |
|
256 | 188 | "input": [ |
|
257 | 189 | "string = widgets.StringWidget(value=\"Hello World!\")\n", |
|
258 | 190 | "display(string, view_name=\"LabelView\") " |
|
259 | 191 | ], |
|
260 | 192 | "language": "python", |
|
261 | 193 | "metadata": {}, |
|
262 | 194 | "outputs": [], |
|
263 | 195 | "prompt_number": 7 |
|
264 | 196 | }, |
|
265 | 197 | { |
|
266 | 198 | "cell_type": "code", |
|
267 | 199 | "collapsed": false, |
|
268 | 200 | "input": [ |
|
269 | 201 | "string.visible=False" |
|
270 | 202 | ], |
|
271 | 203 | "language": "python", |
|
272 | 204 | "metadata": {}, |
|
273 | 205 | "outputs": [], |
|
274 | 206 | "prompt_number": 8 |
|
275 | 207 | }, |
|
276 | 208 | { |
|
277 | 209 | "cell_type": "code", |
|
278 | 210 | "collapsed": false, |
|
279 | 211 | "input": [ |
|
280 | 212 | "string.visible=True" |
|
281 | 213 | ], |
|
282 | 214 | "language": "python", |
|
283 | 215 | "metadata": {}, |
|
284 | 216 | "outputs": [], |
|
285 | 217 | "prompt_number": 9 |
|
286 | 218 | }, |
|
287 | 219 | { |
|
288 | 220 | "cell_type": "markdown", |
|
289 | 221 | "metadata": {}, |
|
290 | 222 | "source": [ |
|
291 | 223 | "In the example below, a form is rendered which conditionally displays widgets depending on the state of other widgets. Try toggling the student checkbox." |
|
292 | 224 | ] |
|
293 | 225 | }, |
|
294 | 226 | { |
|
295 | 227 | "cell_type": "code", |
|
296 | 228 | "collapsed": false, |
|
297 | 229 | "input": [ |
|
298 | 230 | "form = widgets.ContainerWidget()\n", |
|
299 | 231 | "first = widgets.StringWidget(description=\"First Name:\", parent=form)\n", |
|
300 | 232 | "last = widgets.StringWidget(description=\"Last Name:\", parent=form)\n", |
|
301 | 233 | "\n", |
|
302 | 234 | "student = widgets.BoolWidget(description=\"Student:\", value=False, parent=form)\n", |
|
303 | 235 | "school_info = widgets.ContainerWidget(visible=False, parent=form)\n", |
|
304 | 236 | "school = widgets.StringWidget(description=\"School:\", parent=school_info)\n", |
|
305 | 237 | "grade = widgets.IntRangeWidget(description=\"Grade:\", min=0, max=12, default_view_name='IntTextView', parent=school_info)\n", |
|
306 | 238 | "\n", |
|
307 | 239 | "pet = widgets.StringWidget(description=\"Pet's Name:\", parent=form)\n", |
|
308 | 240 | "display(form)\n", |
|
309 | 241 | "\n", |
|
310 | 242 | "def on_student_toggle(name, value):\n", |
|
311 | 243 | " if value:\n", |
|
312 | 244 | " school_info.visible = True\n", |
|
313 | 245 | " else:\n", |
|
314 | 246 | " school_info.visible = False\n", |
|
315 | 247 | "student.on_trait_change(on_student_toggle, 'value')\n" |
|
316 | 248 | ], |
|
317 | 249 | "language": "python", |
|
318 | 250 | "metadata": {}, |
|
319 | 251 | "outputs": [], |
|
320 | 252 | "prompt_number": 10 |
|
321 | 253 | } |
|
322 | 254 | ], |
|
323 | 255 | "metadata": {} |
|
324 | 256 | } |
|
325 | 257 | ] |
|
326 | 258 | } No newline at end of file |
@@ -1,405 +1,337 b'' | |||
|
1 | 1 | { |
|
2 | 2 | "metadata": { |
|
3 | "cell_tags": [ | |
|
4 | [ | |
|
5 | "<None>", | |
|
6 | null | |
|
7 | ] | |
|
8 | ], | |
|
3 | 9 | "name": "" |
|
4 | 10 | }, |
|
5 | 11 | "nbformat": 3, |
|
6 | 12 | "nbformat_minor": 0, |
|
7 | 13 | "worksheets": [ |
|
8 | 14 | { |
|
9 | 15 | "cells": [ |
|
10 | 16 | { |
|
11 | 17 | "cell_type": "code", |
|
12 | 18 | "collapsed": false, |
|
13 | 19 | "input": [ |
|
14 | 20 | "from IPython.html import widgets # Widget definitions\n", |
|
15 |
"from IPython.display import display # Used to display widgets in the notebook |
|
|
16 | "\n", | |
|
17 | "# Enable widgets in this notebook\n", | |
|
18 | "widgets.init_widget_js()" | |
|
21 | "from IPython.display import display # Used to display widgets in the notebook" | |
|
19 | 22 | ], |
|
20 | 23 | "language": "python", |
|
21 | 24 | "metadata": {}, |
|
22 | "outputs": [ | |
|
23 | { | |
|
24 | "javascript": [ | |
|
25 | "$.getScript(\"../static/notebook/js/widgets/bool.js\");" | |
|
26 | ], | |
|
27 | "metadata": {}, | |
|
28 | "output_type": "display_data" | |
|
29 | }, | |
|
30 | { | |
|
31 | "javascript": [ | |
|
32 | "$.getScript(\"../static/notebook/js/widgets/int_range.js\");" | |
|
33 | ], | |
|
34 | "metadata": {}, | |
|
35 | "output_type": "display_data" | |
|
36 | }, | |
|
37 | { | |
|
38 | "javascript": [ | |
|
39 | "$.getScript(\"../static/notebook/js/widgets/int.js\");" | |
|
40 | ], | |
|
41 | "metadata": {}, | |
|
42 | "output_type": "display_data" | |
|
43 | }, | |
|
44 | { | |
|
45 | "javascript": [ | |
|
46 | "$.getScript(\"../static/notebook/js/widgets/selection.js\");" | |
|
47 | ], | |
|
48 | "metadata": {}, | |
|
49 | "output_type": "display_data" | |
|
50 | }, | |
|
51 | { | |
|
52 | "javascript": [ | |
|
53 | "$.getScript(\"../static/notebook/js/widgets/string.js\");" | |
|
54 | ], | |
|
55 | "metadata": {}, | |
|
56 | "output_type": "display_data" | |
|
57 | }, | |
|
58 | { | |
|
59 | "javascript": [ | |
|
60 | "$.getScript(\"../static/notebook/js/widgets/float.js\");" | |
|
61 | ], | |
|
62 | "metadata": {}, | |
|
63 | "output_type": "display_data" | |
|
64 | }, | |
|
65 | { | |
|
66 | "javascript": [ | |
|
67 | "$.getScript(\"../static/notebook/js/widgets/container.js\");" | |
|
68 | ], | |
|
69 | "metadata": {}, | |
|
70 | "output_type": "display_data" | |
|
71 | }, | |
|
72 | { | |
|
73 | "javascript": [ | |
|
74 | "$.getScript(\"../static/notebook/js/widgets/multicontainer.js\");" | |
|
75 | ], | |
|
76 | "metadata": {}, | |
|
77 | "output_type": "display_data" | |
|
78 | }, | |
|
79 | { | |
|
80 | "javascript": [ | |
|
81 | "$.getScript(\"../static/notebook/js/widgets/button.js\");" | |
|
82 | ], | |
|
83 | "metadata": {}, | |
|
84 | "output_type": "display_data" | |
|
85 | }, | |
|
86 | { | |
|
87 | "javascript": [ | |
|
88 | "$.getScript(\"../static/notebook/js/widgets/float_range.js\");" | |
|
89 | ], | |
|
90 | "metadata": {}, | |
|
91 | "output_type": "display_data" | |
|
92 | } | |
|
93 | ], | |
|
25 | "outputs": [], | |
|
94 | 26 | "prompt_number": 1 |
|
95 | 27 | }, |
|
96 | 28 | { |
|
97 | 29 | "cell_type": "heading", |
|
98 | 30 | "level": 1, |
|
99 | 31 | "metadata": {}, |
|
100 | 32 | "source": [ |
|
101 | 33 | "CSS" |
|
102 | 34 | ] |
|
103 | 35 | }, |
|
104 | 36 | { |
|
105 | 37 | "cell_type": "markdown", |
|
106 | 38 | "metadata": {}, |
|
107 | 39 | "source": [ |
|
108 | 40 | "When trying to design an attractive widget GUI, styling becomes important. Widget views are DOM (document object model) elements that can be controlled with CSS. There are two helper methods defined on widget that allow the manipulation of the widget's CSS. The first is the `set_css` method, whos doc string is displayed below. This method allows one or more CSS attributes to be set at once. " |
|
109 | 41 | ] |
|
110 | 42 | }, |
|
111 | 43 | { |
|
112 | 44 | "cell_type": "code", |
|
113 | 45 | "collapsed": false, |
|
114 | 46 | "input": [ |
|
115 | 47 | "print(widgets.Widget.set_css.__doc__)" |
|
116 | 48 | ], |
|
117 | 49 | "language": "python", |
|
118 | 50 | "metadata": {}, |
|
119 | 51 | "outputs": [ |
|
120 | 52 | { |
|
121 | 53 | "output_type": "stream", |
|
122 | 54 | "stream": "stdout", |
|
123 | 55 | "text": [ |
|
124 | 56 | "Set one or more CSS properties of the widget (shared among all of the \n", |
|
125 | 57 | " views). This function has two signatures:\n", |
|
126 | 58 | " - set_css(css_dict, [selector=''])\n", |
|
127 | 59 | " - set_css(key, value, [selector=''])\n", |
|
128 | 60 | "\n", |
|
129 | 61 | " Parameters\n", |
|
130 | 62 | " ----------\n", |
|
131 | 63 | " css_dict : dict\n", |
|
132 | 64 | " CSS key/value pairs to apply\n", |
|
133 | 65 | " key: unicode\n", |
|
134 | 66 | " CSS key\n", |
|
135 | 67 | " value\n", |
|
136 | 68 | " CSS value\n", |
|
137 | 69 | " selector: unicode (optional)\n", |
|
138 | 70 | " JQuery selector to use to apply the CSS key/value.\n", |
|
139 | 71 | " \n" |
|
140 | 72 | ] |
|
141 | 73 | } |
|
142 | 74 | ], |
|
143 | 75 | "prompt_number": 2 |
|
144 | 76 | }, |
|
145 | 77 | { |
|
146 | 78 | "cell_type": "markdown", |
|
147 | 79 | "metadata": {}, |
|
148 | 80 | "source": [ |
|
149 | 81 | "The second is `get_css` which allows CSS attributes that have been set to be read. Note that this method will only read CSS attributes that have been set using the `set_css` method. `get_css`'s doc string is displayed below." |
|
150 | 82 | ] |
|
151 | 83 | }, |
|
152 | 84 | { |
|
153 | 85 | "cell_type": "code", |
|
154 | 86 | "collapsed": false, |
|
155 | 87 | "input": [ |
|
156 | 88 | "print(widgets.Widget.get_css.__doc__)" |
|
157 | 89 | ], |
|
158 | 90 | "language": "python", |
|
159 | 91 | "metadata": {}, |
|
160 | 92 | "outputs": [ |
|
161 | 93 | { |
|
162 | 94 | "output_type": "stream", |
|
163 | 95 | "stream": "stdout", |
|
164 | 96 | "text": [ |
|
165 | 97 | "Get a CSS property of the widget. Note, this function does not \n", |
|
166 | 98 | " actually request the CSS from the front-end; Only properties that have \n", |
|
167 | 99 | " been set with set_css can be read.\n", |
|
168 | 100 | "\n", |
|
169 | 101 | " Parameters\n", |
|
170 | 102 | " ----------\n", |
|
171 | 103 | " key: unicode\n", |
|
172 | 104 | " CSS key\n", |
|
173 | 105 | " selector: unicode (optional)\n", |
|
174 | 106 | " JQuery selector used when the CSS key/value was set.\n", |
|
175 | 107 | " \n" |
|
176 | 108 | ] |
|
177 | 109 | } |
|
178 | 110 | ], |
|
179 | 111 | "prompt_number": 3 |
|
180 | 112 | }, |
|
181 | 113 | { |
|
182 | 114 | "cell_type": "markdown", |
|
183 | 115 | "metadata": {}, |
|
184 | 116 | "source": [ |
|
185 | 117 | "Below is an example that applies CSS attributes to a container to emphasize text." |
|
186 | 118 | ] |
|
187 | 119 | }, |
|
188 | 120 | { |
|
189 | 121 | "cell_type": "code", |
|
190 | 122 | "collapsed": false, |
|
191 | 123 | "input": [ |
|
192 | 124 | "container = widgets.ContainerWidget()\n", |
|
193 | 125 | "\n", |
|
194 | 126 | "# set_css used to set a single CSS attribute.\n", |
|
195 | 127 | "container.set_css('border', '3px solid black') # Border the container\n", |
|
196 | 128 | "\n", |
|
197 | 129 | "# set_css used to set multiple CSS attributes.\n", |
|
198 | 130 | "container.set_css({'padding': '6px', # Add padding to the container\n", |
|
199 | 131 | " 'background': 'yellow'}) # Fill the container yellow\n", |
|
200 | 132 | "\n", |
|
201 | 133 | "label = widgets.StringWidget(default_view_name=\"LabelView\", parent=container)\n", |
|
202 | 134 | "label.value = \"<strong>ALERT: </strong> Hello World!\"\n", |
|
203 | 135 | "\n", |
|
204 | 136 | "display(container)" |
|
205 | 137 | ], |
|
206 | 138 | "language": "python", |
|
207 | 139 | "metadata": {}, |
|
208 | 140 | "outputs": [], |
|
209 | 141 | "prompt_number": 4 |
|
210 | 142 | }, |
|
211 | 143 | { |
|
212 | 144 | "cell_type": "heading", |
|
213 | 145 | "level": 1, |
|
214 | 146 | "metadata": {}, |
|
215 | 147 | "source": [ |
|
216 | 148 | "DOM Classes" |
|
217 | 149 | ] |
|
218 | 150 | }, |
|
219 | 151 | { |
|
220 | 152 | "cell_type": "markdown", |
|
221 | 153 | "metadata": {}, |
|
222 | 154 | "source": [ |
|
223 | 155 | "In some cases it's necessary to apply DOM classes to your widgets. DOM classes allow DOM elements to be indentified by Javascript and CSS. The notebook defines its own set of classes to stylize its elements. The `add_class` widget method allows you to add DOM classes to your widget's definition. The `add_class` method's doc string can be seen below." |
|
224 | 156 | ] |
|
225 | 157 | }, |
|
226 | 158 | { |
|
227 | 159 | "cell_type": "code", |
|
228 | 160 | "collapsed": false, |
|
229 | 161 | "input": [ |
|
230 | 162 | "print(widgets.Widget.add_class.__doc__)" |
|
231 | 163 | ], |
|
232 | 164 | "language": "python", |
|
233 | 165 | "metadata": {}, |
|
234 | 166 | "outputs": [ |
|
235 | 167 | { |
|
236 | 168 | "output_type": "stream", |
|
237 | 169 | "stream": "stdout", |
|
238 | 170 | "text": [ |
|
239 | 171 | "Add class[es] to a DOM element\n", |
|
240 | 172 | "\n", |
|
241 | 173 | " Parameters\n", |
|
242 | 174 | " ----------\n", |
|
243 | 175 | " class_name: unicode\n", |
|
244 | 176 | " Class name(s) to add to the DOM element(s). Multiple class names \n", |
|
245 | 177 | " must be space separated.\n", |
|
246 | 178 | " selector: unicode (optional)\n", |
|
247 | 179 | " JQuery selector to select the DOM element(s) that the class(es) will \n", |
|
248 | 180 | " be added to.\n", |
|
249 | 181 | " \n" |
|
250 | 182 | ] |
|
251 | 183 | } |
|
252 | 184 | ], |
|
253 | 185 | "prompt_number": 5 |
|
254 | 186 | }, |
|
255 | 187 | { |
|
256 | 188 | "cell_type": "markdown", |
|
257 | 189 | "metadata": {}, |
|
258 | 190 | "source": [ |
|
259 | 191 | "Since `add_class` if a DOM operation, it will only affect widgets that have been displayed. `add_class` must be called after the widget has been displayed. Extending the example above, the corners of the container can be rounded by adding the `corner-all` notebook class to the container (as seen below). " |
|
260 | 192 | ] |
|
261 | 193 | }, |
|
262 | 194 | { |
|
263 | 195 | "cell_type": "code", |
|
264 | 196 | "collapsed": false, |
|
265 | 197 | "input": [ |
|
266 | 198 | "container = widgets.ContainerWidget()\n", |
|
267 | 199 | "container.set_css({'border': '3px solid black',\n", |
|
268 | 200 | " 'padding': '6px',\n", |
|
269 | 201 | " 'background': 'yellow'}) \n", |
|
270 | 202 | "\n", |
|
271 | 203 | "label = widgets.StringWidget(default_view_name=\"LabelView\", parent=container) \n", |
|
272 | 204 | "label.value = \"<strong>ALERT: </strong> Hello World!\"\n", |
|
273 | 205 | "\n", |
|
274 | 206 | "display(container)\n", |
|
275 | 207 | "container.add_class('corner-all') # Must be called AFTER display" |
|
276 | 208 | ], |
|
277 | 209 | "language": "python", |
|
278 | 210 | "metadata": {}, |
|
279 | 211 | "outputs": [], |
|
280 | 212 | "prompt_number": 6 |
|
281 | 213 | }, |
|
282 | 214 | { |
|
283 | 215 | "cell_type": "markdown", |
|
284 | 216 | "metadata": {}, |
|
285 | 217 | "source": [ |
|
286 | 218 | "The IPython notebook uses bootstrap for styling. The example above can be simplified by using a bootstrap class (as seen below). Bootstrap documentation can be found at http://getbootstrap.com/\u200e ." |
|
287 | 219 | ] |
|
288 | 220 | }, |
|
289 | 221 | { |
|
290 | 222 | "cell_type": "code", |
|
291 | 223 | "collapsed": false, |
|
292 | 224 | "input": [ |
|
293 | 225 | "label = widgets.StringWidget(value = \"<strong>ALERT: </strong> Hello World!\")\n", |
|
294 | 226 | "display(label, view_name=\"LabelView\")\n", |
|
295 | 227 | "\n", |
|
296 | 228 | "# Apply twitter bootstrap alert class to the label.\n", |
|
297 | 229 | "label.add_class(\"alert\")" |
|
298 | 230 | ], |
|
299 | 231 | "language": "python", |
|
300 | 232 | "metadata": {}, |
|
301 | 233 | "outputs": [], |
|
302 | 234 | "prompt_number": 7 |
|
303 | 235 | }, |
|
304 | 236 | { |
|
305 | 237 | "cell_type": "markdown", |
|
306 | 238 | "metadata": {}, |
|
307 | 239 | "source": [ |
|
308 | 240 | "The example below shows how bootstrap classes can be used to change button apearance." |
|
309 | 241 | ] |
|
310 | 242 | }, |
|
311 | 243 | { |
|
312 | 244 | "cell_type": "code", |
|
313 | 245 | "collapsed": false, |
|
314 | 246 | "input": [ |
|
315 | 247 | "# List of the bootstrap button styles\n", |
|
316 | 248 | "button_classes = ['Default', 'btn-primary', 'btn-info', 'btn-success', \n", |
|
317 | 249 | " 'btn-warning', 'btn-danger', 'btn-inverse', 'btn-link']\n", |
|
318 | 250 | "\n", |
|
319 | 251 | "# Create each button and apply the style. Also add margin to the buttons so they space\n", |
|
320 | 252 | "# themselves nicely.\n", |
|
321 | 253 | "for i in range(8):\n", |
|
322 | 254 | " button = widgets.ButtonWidget(description=button_classes[i])\n", |
|
323 | 255 | " button.set_css(\"margin\", \"5px\")\n", |
|
324 | 256 | " display(button)\n", |
|
325 | 257 | " if i > 0: # Don't add a class the first button.\n", |
|
326 | 258 | " button.add_class(button_classes[i])\n", |
|
327 | 259 | " " |
|
328 | 260 | ], |
|
329 | 261 | "language": "python", |
|
330 | 262 | "metadata": {}, |
|
331 | 263 | "outputs": [], |
|
332 | 264 | "prompt_number": 8 |
|
333 | 265 | }, |
|
334 | 266 | { |
|
335 | 267 | "cell_type": "markdown", |
|
336 | 268 | "metadata": {}, |
|
337 | 269 | "source": [ |
|
338 | 270 | "It's also useful to be able to remove DOM classes from widgets. The `remove_class` widget method allows you to remove classes from widgets that have been displayed. Like `add_widget`, it must be called after the widget has been displayed. The doc string of `remove_class` can be seen below." |
|
339 | 271 | ] |
|
340 | 272 | }, |
|
341 | 273 | { |
|
342 | 274 | "cell_type": "code", |
|
343 | 275 | "collapsed": false, |
|
344 | 276 | "input": [ |
|
345 | 277 | "print(widgets.Widget.remove_class.__doc__)" |
|
346 | 278 | ], |
|
347 | 279 | "language": "python", |
|
348 | 280 | "metadata": {}, |
|
349 | 281 | "outputs": [ |
|
350 | 282 | { |
|
351 | 283 | "output_type": "stream", |
|
352 | 284 | "stream": "stdout", |
|
353 | 285 | "text": [ |
|
354 | 286 | "Remove class[es] from a DOM element\n", |
|
355 | 287 | "\n", |
|
356 | 288 | " Parameters\n", |
|
357 | 289 | " ----------\n", |
|
358 | 290 | " class_name: unicode\n", |
|
359 | 291 | " Class name(s) to remove from the DOM element(s). Multiple class \n", |
|
360 | 292 | " names must be space separated.\n", |
|
361 | 293 | " selector: unicode (optional)\n", |
|
362 | 294 | " JQuery selector to select the DOM element(s) that the class(es) will \n", |
|
363 | 295 | " be removed from.\n", |
|
364 | 296 | " \n" |
|
365 | 297 | ] |
|
366 | 298 | } |
|
367 | 299 | ], |
|
368 | 300 | "prompt_number": 9 |
|
369 | 301 | }, |
|
370 | 302 | { |
|
371 | 303 | "cell_type": "markdown", |
|
372 | 304 | "metadata": {}, |
|
373 | 305 | "source": [ |
|
374 | 306 | "The example below animates an alert using different bootstrap styles." |
|
375 | 307 | ] |
|
376 | 308 | }, |
|
377 | 309 | { |
|
378 | 310 | "cell_type": "code", |
|
379 | 311 | "collapsed": false, |
|
380 | 312 | "input": [ |
|
381 | 313 | "import time\n", |
|
382 | 314 | "label = widgets.StringWidget(value = \"<strong>ALERT: </strong> Hello World!\")\n", |
|
383 | 315 | "display(label, view_name=\"LabelView\")\n", |
|
384 | 316 | "\n", |
|
385 | 317 | "# Apply twitter bootstrap alert class to the label.\n", |
|
386 | 318 | "label.add_class(\"alert\")\n", |
|
387 | 319 | "\n", |
|
388 | 320 | "# Animate through additional bootstrap label styles 3 times\n", |
|
389 | 321 | "additional_alert_styles = ['alert-error', 'alert-info', 'alert-success']\n", |
|
390 | 322 | "for i in range(3 * len(additional_alert_styles)):\n", |
|
391 | 323 | " label.add_class(additional_alert_styles[i % 3])\n", |
|
392 | 324 | " label.remove_class(additional_alert_styles[(i-1) % 3])\n", |
|
393 | 325 | " time.sleep(1)\n", |
|
394 | 326 | " " |
|
395 | 327 | ], |
|
396 | 328 | "language": "python", |
|
397 | 329 | "metadata": {}, |
|
398 | 330 | "outputs": [], |
|
399 | 331 | "prompt_number": 10 |
|
400 | 332 | } |
|
401 | 333 | ], |
|
402 | 334 | "metadata": {} |
|
403 | 335 | } |
|
404 | 336 | ] |
|
405 | 337 | } No newline at end of file |
@@ -1,378 +1,304 b'' | |||
|
1 | 1 | { |
|
2 | 2 | "metadata": { |
|
3 | 3 | "cell_tags": [ |
|
4 | 4 | [ |
|
5 | 5 | "<None>", |
|
6 | 6 | null |
|
7 | 7 | ] |
|
8 | 8 | ], |
|
9 | 9 | "name": "" |
|
10 | 10 | }, |
|
11 | 11 | "nbformat": 3, |
|
12 | 12 | "nbformat_minor": 0, |
|
13 | 13 | "worksheets": [ |
|
14 | 14 | { |
|
15 | 15 | "cells": [ |
|
16 | 16 | { |
|
17 | 17 | "cell_type": "code", |
|
18 | 18 | "collapsed": false, |
|
19 | 19 | "input": [ |
|
20 | 20 | "from IPython.html import widgets # Widget definitions\n", |
|
21 |
"from IPython.display import display # Used to display widgets in the notebook |
|
|
22 | "\n", | |
|
23 | "# Enable widgets in this notebook\n", | |
|
24 | "widgets.init_widget_js()" | |
|
21 | "from IPython.display import display # Used to display widgets in the notebook" | |
|
25 | 22 | ], |
|
26 | 23 | "language": "python", |
|
27 | 24 | "metadata": {}, |
|
28 | "outputs": [ | |
|
29 | { | |
|
30 | "javascript": [ | |
|
31 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/button.js\");" | |
|
32 | ], | |
|
33 | "metadata": {}, | |
|
34 | "output_type": "display_data" | |
|
35 | }, | |
|
36 | { | |
|
37 | "javascript": [ | |
|
38 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/int_range.js\");" | |
|
39 | ], | |
|
40 | "metadata": {}, | |
|
41 | "output_type": "display_data" | |
|
42 | }, | |
|
43 | { | |
|
44 | "javascript": [ | |
|
45 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/string.js\");" | |
|
46 | ], | |
|
47 | "metadata": {}, | |
|
48 | "output_type": "display_data" | |
|
49 | }, | |
|
50 | { | |
|
51 | "javascript": [ | |
|
52 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/multicontainer.js\");" | |
|
53 | ], | |
|
54 | "metadata": {}, | |
|
55 | "output_type": "display_data" | |
|
56 | }, | |
|
57 | { | |
|
58 | "javascript": [ | |
|
59 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/bool.js\");" | |
|
60 | ], | |
|
61 | "metadata": {}, | |
|
62 | "output_type": "display_data" | |
|
63 | }, | |
|
64 | { | |
|
65 | "javascript": [ | |
|
66 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/int.js\");" | |
|
67 | ], | |
|
68 | "metadata": {}, | |
|
69 | "output_type": "display_data" | |
|
70 | }, | |
|
71 | { | |
|
72 | "javascript": [ | |
|
73 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/selection.js\");" | |
|
74 | ], | |
|
75 | "metadata": {}, | |
|
76 | "output_type": "display_data" | |
|
77 | }, | |
|
78 | { | |
|
79 | "javascript": [ | |
|
80 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/float.js\");" | |
|
81 | ], | |
|
82 | "metadata": {}, | |
|
83 | "output_type": "display_data" | |
|
84 | }, | |
|
85 | { | |
|
86 | "javascript": [ | |
|
87 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/float_range.js\");" | |
|
88 | ], | |
|
89 | "metadata": {}, | |
|
90 | "output_type": "display_data" | |
|
91 | }, | |
|
92 | { | |
|
93 | "javascript": [ | |
|
94 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/container.js\");" | |
|
95 | ], | |
|
96 | "metadata": {}, | |
|
97 | "output_type": "display_data" | |
|
98 | } | |
|
99 | ], | |
|
25 | "outputs": [], | |
|
100 | 26 | "prompt_number": 1 |
|
101 | 27 | }, |
|
102 | 28 | { |
|
103 | 29 | "cell_type": "heading", |
|
104 | 30 | "level": 1, |
|
105 | 31 | "metadata": {}, |
|
106 | 32 | "source": [ |
|
107 | 33 | "Alignment" |
|
108 | 34 | ] |
|
109 | 35 | }, |
|
110 | 36 | { |
|
111 | 37 | "cell_type": "markdown", |
|
112 | 38 | "metadata": {}, |
|
113 | 39 | "source": [ |
|
114 | 40 | "Most widgets have a `description` property which allows a label for the widget to be defined. The label of the widget has a fixed minimum width. The text of the label is always right aligned and the widget is left aligned (as seen below) " |
|
115 | 41 | ] |
|
116 | 42 | }, |
|
117 | 43 | { |
|
118 | 44 | "cell_type": "code", |
|
119 | 45 | "collapsed": false, |
|
120 | 46 | "input": [ |
|
121 | 47 | "display(widgets.StringWidget(description=\"a:\"))\n", |
|
122 | 48 | "display(widgets.StringWidget(description=\"aa:\"))\n", |
|
123 | 49 | "display(widgets.StringWidget(description=\"aaa:\"))" |
|
124 | 50 | ], |
|
125 | 51 | "language": "python", |
|
126 | 52 | "metadata": {}, |
|
127 | 53 | "outputs": [], |
|
128 | 54 | "prompt_number": 2 |
|
129 | 55 | }, |
|
130 | 56 | { |
|
131 | 57 | "cell_type": "markdown", |
|
132 | 58 | "metadata": {}, |
|
133 | 59 | "source": [ |
|
134 | 60 | "If a label is longer than the minimum width, the widget is shifted to the right (as seen below)." |
|
135 | 61 | ] |
|
136 | 62 | }, |
|
137 | 63 | { |
|
138 | 64 | "cell_type": "code", |
|
139 | 65 | "collapsed": false, |
|
140 | 66 | "input": [ |
|
141 | 67 | "display(widgets.StringWidget(description=\"a:\"))\n", |
|
142 | 68 | "display(widgets.StringWidget(description=\"aa:\"))\n", |
|
143 | 69 | "display(widgets.StringWidget(description=\"aaa:\"))\n", |
|
144 | 70 | "display(widgets.StringWidget(description=\"aaaaaaaaaaaaaaaaaa:\"))" |
|
145 | 71 | ], |
|
146 | 72 | "language": "python", |
|
147 | 73 | "metadata": {}, |
|
148 | 74 | "outputs": [], |
|
149 | 75 | "prompt_number": 3 |
|
150 | 76 | }, |
|
151 | 77 | { |
|
152 | 78 | "cell_type": "markdown", |
|
153 | 79 | "metadata": {}, |
|
154 | 80 | "source": [ |
|
155 | 81 | "If a `description` is not set for the widget, the label is not displayed (as seen below)." |
|
156 | 82 | ] |
|
157 | 83 | }, |
|
158 | 84 | { |
|
159 | 85 | "cell_type": "code", |
|
160 | 86 | "collapsed": false, |
|
161 | 87 | "input": [ |
|
162 | 88 | "display(widgets.StringWidget(description=\"a:\"))\n", |
|
163 | 89 | "display(widgets.StringWidget(description=\"aa:\"))\n", |
|
164 | 90 | "display(widgets.StringWidget(description=\"aaa:\"))\n", |
|
165 | 91 | "display(widgets.StringWidget())" |
|
166 | 92 | ], |
|
167 | 93 | "language": "python", |
|
168 | 94 | "metadata": {}, |
|
169 | 95 | "outputs": [], |
|
170 | 96 | "prompt_number": 4 |
|
171 | 97 | }, |
|
172 | 98 | { |
|
173 | 99 | "cell_type": "heading", |
|
174 | 100 | "level": 1, |
|
175 | 101 | "metadata": {}, |
|
176 | 102 | "source": [ |
|
177 | 103 | "Custom Alignment" |
|
178 | 104 | ] |
|
179 | 105 | }, |
|
180 | 106 | { |
|
181 | 107 | "cell_type": "markdown", |
|
182 | 108 | "metadata": {}, |
|
183 | 109 | "source": [ |
|
184 | 110 | "`ContainerWidget`s allow for custom alignment of widgets. The `hbox` and `vbox` methods (parameterless) cause the `ContainerWidget` to both horizontally and vertically align its children. The following example compares `vbox` to `hbox`." |
|
185 | 111 | ] |
|
186 | 112 | }, |
|
187 | 113 | { |
|
188 | 114 | "cell_type": "code", |
|
189 | 115 | "collapsed": false, |
|
190 | 116 | "input": [ |
|
191 | 117 | "child_style = {\n", |
|
192 | 118 | " 'background': '#77CC77',\n", |
|
193 | 119 | " 'padding': '25px',\n", |
|
194 | 120 | " 'margin': '5px',\n", |
|
195 | 121 | " 'font-size': 'xx-large',\n", |
|
196 | 122 | " 'color': 'white',\n", |
|
197 | 123 | "}\n", |
|
198 | 124 | "\n", |
|
199 | 125 | "def make_container(title):\n", |
|
200 | 126 | " display(widgets.StringWidget(default_view_name='LabelView', value='<h2><br>' + title + '</h2>'))\n", |
|
201 | 127 | " container = widgets.ContainerWidget()\n", |
|
202 | 128 | " container.set_css('background', '#999999')\n", |
|
203 | 129 | " display(container)\n", |
|
204 | 130 | " return container\n", |
|
205 | 131 | "\n", |
|
206 | 132 | "def fill_container(container):\n", |
|
207 | 133 | " components = []\n", |
|
208 | 134 | " for i in range(3):\n", |
|
209 | 135 | " components.append(widgets.StringWidget(parent=container, default_view_name='LabelView', value=\"ABC\"[i]))\n", |
|
210 | 136 | " components[i].set_css(child_style)\n", |
|
211 | 137 | " display(components[i])\n", |
|
212 | 138 | " \n", |
|
213 | 139 | "container = make_container('VBox')\n", |
|
214 | 140 | "container.vbox()\n", |
|
215 | 141 | "fill_container(container)\n", |
|
216 | 142 | "\n", |
|
217 | 143 | "container = make_container('HBox')\n", |
|
218 | 144 | "container.hbox()\n", |
|
219 | 145 | "fill_container(container)\n" |
|
220 | 146 | ], |
|
221 | 147 | "language": "python", |
|
222 | 148 | "metadata": {}, |
|
223 | 149 | "outputs": [], |
|
224 | 150 | "prompt_number": 5 |
|
225 | 151 | }, |
|
226 | 152 | { |
|
227 | 153 | "cell_type": "markdown", |
|
228 | 154 | "metadata": {}, |
|
229 | 155 | "source": [ |
|
230 | 156 | "The `ContainerWidget` `pack_start`, `pack_center`, and `pack_end` methods (parameterless) adjust the alignment of the widgets on the axis that they are being rendered on. Below is an example of the different alignments." |
|
231 | 157 | ] |
|
232 | 158 | }, |
|
233 | 159 | { |
|
234 | 160 | "cell_type": "code", |
|
235 | 161 | "collapsed": false, |
|
236 | 162 | "input": [ |
|
237 | 163 | "container = make_container('HBox Pack Start')\n", |
|
238 | 164 | "container.hbox()\n", |
|
239 | 165 | "container.pack_start()\n", |
|
240 | 166 | "fill_container(container)\n", |
|
241 | 167 | " \n", |
|
242 | 168 | "container = make_container('HBox Pack Center')\n", |
|
243 | 169 | "container.hbox()\n", |
|
244 | 170 | "container.pack_center()\n", |
|
245 | 171 | "fill_container(container)\n", |
|
246 | 172 | " \n", |
|
247 | 173 | "container = make_container('HBox Pack End')\n", |
|
248 | 174 | "container.hbox()\n", |
|
249 | 175 | "container.pack_end()\n", |
|
250 | 176 | "fill_container(container)" |
|
251 | 177 | ], |
|
252 | 178 | "language": "python", |
|
253 | 179 | "metadata": {}, |
|
254 | 180 | "outputs": [], |
|
255 | 181 | "prompt_number": 6 |
|
256 | 182 | }, |
|
257 | 183 | { |
|
258 | 184 | "cell_type": "markdown", |
|
259 | 185 | "metadata": {}, |
|
260 | 186 | "source": [ |
|
261 | 187 | "The `ContainerWidget` `flex0`, `flex1`, and `flex2` methods (parameterless) modify the containers flexibility. Changing a container flexibility affects how and if the container will occupy the remaining space. Setting `flex0` has the same result as setting no flex. Below is an example of different flex configurations. The number on the boxes correspond to the applied flex." |
|
262 | 188 | ] |
|
263 | 189 | }, |
|
264 | 190 | { |
|
265 | 191 | "cell_type": "code", |
|
266 | 192 | "collapsed": false, |
|
267 | 193 | "input": [ |
|
268 | 194 | "def fill_container(container, flexes):\n", |
|
269 | 195 | " components = []\n", |
|
270 | 196 | " for i in range(len(flexes)):\n", |
|
271 | 197 | " components.append(widgets.ContainerWidget(parent=container))\n", |
|
272 | 198 | " components[i].set_css(child_style)\n", |
|
273 | 199 | " \n", |
|
274 | 200 | " label = widgets.StringWidget(parent=components[i], default_view_name='LabelView', value=str(flexes[i]))\n", |
|
275 | 201 | " \n", |
|
276 | 202 | " if flexes[i] == 0:\n", |
|
277 | 203 | " components[i].flex0()\n", |
|
278 | 204 | " elif flexes[i] == 1:\n", |
|
279 | 205 | " components[i].flex1()\n", |
|
280 | 206 | " elif flexes[i] == 2:\n", |
|
281 | 207 | " components[i].flex2()\n", |
|
282 | 208 | " display(components[i])\n", |
|
283 | 209 | " \n", |
|
284 | 210 | "container = make_container('Different Flex Configurations')\n", |
|
285 | 211 | "container.hbox()\n", |
|
286 | 212 | "fill_container(container, [0, 0, 0])\n", |
|
287 | 213 | " \n", |
|
288 | 214 | "container = make_container('')\n", |
|
289 | 215 | "container.hbox()\n", |
|
290 | 216 | "fill_container(container, [0, 0, 1])\n", |
|
291 | 217 | " \n", |
|
292 | 218 | "container = make_container('')\n", |
|
293 | 219 | "container.hbox()\n", |
|
294 | 220 | "fill_container(container, [0, 1, 1])\n", |
|
295 | 221 | " \n", |
|
296 | 222 | "container = make_container('')\n", |
|
297 | 223 | "container.hbox()\n", |
|
298 | 224 | "fill_container(container, [0, 2, 2])\n", |
|
299 | 225 | " \n", |
|
300 | 226 | "container = make_container('')\n", |
|
301 | 227 | "container.hbox()\n", |
|
302 | 228 | "fill_container(container, [0, 1, 2])\n", |
|
303 | 229 | " \n", |
|
304 | 230 | "container = make_container('')\n", |
|
305 | 231 | "container.hbox()\n", |
|
306 | 232 | "fill_container(container, [1, 1, 2])" |
|
307 | 233 | ], |
|
308 | 234 | "language": "python", |
|
309 | 235 | "metadata": {}, |
|
310 | 236 | "outputs": [], |
|
311 | 237 | "prompt_number": 7 |
|
312 | 238 | }, |
|
313 | 239 | { |
|
314 | 240 | "cell_type": "markdown", |
|
315 | 241 | "metadata": {}, |
|
316 | 242 | "source": [ |
|
317 | 243 | "The `ContainerWidget` `align_start`, `align_center`, and `align_end` methods (parameterless) adjust the alignment of the widgets on the axis perpindicular to the one that they are being rendered on. Below is an example of the different alignments." |
|
318 | 244 | ] |
|
319 | 245 | }, |
|
320 | 246 | { |
|
321 | 247 | "cell_type": "code", |
|
322 | 248 | "collapsed": false, |
|
323 | 249 | "input": [ |
|
324 | 250 | "def fill_container(container):\n", |
|
325 | 251 | " components = []\n", |
|
326 | 252 | " for i in range(3):\n", |
|
327 | 253 | " components.append(widgets.StringWidget(parent=container, default_view_name='LabelView', value=\"ABC\"[i]))\n", |
|
328 | 254 | " components[i].set_css(child_style)\n", |
|
329 | 255 | " components[i].set_css('height', str((i+1) * 50) + 'px')\n", |
|
330 | 256 | " display(components[i])\n", |
|
331 | 257 | "\n", |
|
332 | 258 | "container = make_container('HBox Align Start')\n", |
|
333 | 259 | "container.hbox()\n", |
|
334 | 260 | "container.align_start()\n", |
|
335 | 261 | "fill_container(container)\n", |
|
336 | 262 | " \n", |
|
337 | 263 | "container = make_container('HBox Align Center')\n", |
|
338 | 264 | "container.hbox()\n", |
|
339 | 265 | "container.align_center()\n", |
|
340 | 266 | "fill_container(container)\n", |
|
341 | 267 | " \n", |
|
342 | 268 | "container = make_container('HBox Align End')\n", |
|
343 | 269 | "container.hbox()\n", |
|
344 | 270 | "container.align_end()\n", |
|
345 | 271 | "fill_container(container)" |
|
346 | 272 | ], |
|
347 | 273 | "language": "python", |
|
348 | 274 | "metadata": {}, |
|
349 | 275 | "outputs": [], |
|
350 | 276 | "prompt_number": 8 |
|
351 | 277 | }, |
|
352 | 278 | { |
|
353 | 279 | "cell_type": "markdown", |
|
354 | 280 | "metadata": {}, |
|
355 | 281 | "source": [ |
|
356 | 282 | "By default the widget area is a `vbox`; however, there are many uses for a `hbox`. The example below uses a `hbox` to display a set of vertical sliders, like an equalizer." |
|
357 | 283 | ] |
|
358 | 284 | }, |
|
359 | 285 | { |
|
360 | 286 | "cell_type": "code", |
|
361 | 287 | "collapsed": false, |
|
362 | 288 | "input": [ |
|
363 | 289 | "container = widgets.ContainerWidget()\n", |
|
364 | 290 | "container.hbox()\n", |
|
365 | 291 | "for i in range(15):\n", |
|
366 | 292 | " widgets.FloatRangeWidget(orientation='vertical', parent=container, description=str(i+1), value=50.0)\n", |
|
367 | 293 | "display(container)" |
|
368 | 294 | ], |
|
369 | 295 | "language": "python", |
|
370 | 296 | "metadata": {}, |
|
371 | 297 | "outputs": [], |
|
372 | 298 | "prompt_number": 9 |
|
373 | 299 | } |
|
374 | 300 | ], |
|
375 | 301 | "metadata": {} |
|
376 | 302 | } |
|
377 | 303 | ] |
|
378 | 304 | } No newline at end of file |
@@ -1,1278 +1,1203 b'' | |||
|
1 | 1 | { |
|
2 | 2 | "metadata": { |
|
3 | 3 | "cell_tags": [ |
|
4 | 4 | [ |
|
5 | 5 | "<None>", |
|
6 | 6 | null |
|
7 | 7 | ] |
|
8 | 8 | ], |
|
9 | 9 | "name": "" |
|
10 | 10 | }, |
|
11 | 11 | "nbformat": 3, |
|
12 | 12 | "nbformat_minor": 0, |
|
13 | 13 | "worksheets": [ |
|
14 | 14 | { |
|
15 | 15 | "cells": [ |
|
16 | 16 | { |
|
17 | 17 | "cell_type": "markdown", |
|
18 | 18 | "metadata": {}, |
|
19 | 19 | "source": [ |
|
20 | 20 | "Before reading, the author recommends the reader to review\n", |
|
21 | 21 | "\n", |
|
22 | 22 | "- [MVC prgramming](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller)\n", |
|
23 | 23 | "- [Backbone.js](https://www.codeschool.com/courses/anatomy-of-backbonejs)\n", |
|
24 | 24 | "- [The widget IPEP](https://github.com/ipython/ipython/wiki/IPEP-23%3A-Backbone.js-Widgets)\n", |
|
25 | 25 | "- [The original widget PR discussion](https://github.com/ipython/ipython/pull/4374)" |
|
26 | 26 | ] |
|
27 | 27 | }, |
|
28 | 28 | { |
|
29 | 29 | "cell_type": "code", |
|
30 | 30 | "collapsed": false, |
|
31 | 31 | "input": [ |
|
32 | 32 | "from IPython.html import widgets # Widget definitions\n", |
|
33 |
"from IPython.display import display # Used to display widgets in the notebook |
|
|
34 | "\n", | |
|
35 | "# Enable widgets in this notebook\n", | |
|
36 | "widgets.init_widget_js()" | |
|
33 | "from IPython.display import display # Used to display widgets in the notebook" | |
|
37 | 34 | ], |
|
38 | 35 | "language": "python", |
|
39 | 36 | "metadata": {}, |
|
40 | "outputs": [ | |
|
41 | { | |
|
42 | "javascript": [ | |
|
43 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/button.js\");" | |
|
44 | ], | |
|
45 | "metadata": {}, | |
|
46 | "output_type": "display_data" | |
|
47 | }, | |
|
48 | { | |
|
49 | "javascript": [ | |
|
50 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/int_range.js\");" | |
|
51 | ], | |
|
52 | "metadata": {}, | |
|
53 | "output_type": "display_data" | |
|
54 | }, | |
|
55 | { | |
|
56 | "javascript": [ | |
|
57 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/string.js\");" | |
|
58 | ], | |
|
59 | "metadata": {}, | |
|
60 | "output_type": "display_data" | |
|
61 | }, | |
|
62 | { | |
|
63 | "javascript": [ | |
|
64 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/multicontainer.js\");" | |
|
65 | ], | |
|
66 | "metadata": {}, | |
|
67 | "output_type": "display_data" | |
|
68 | }, | |
|
69 | { | |
|
70 | "javascript": [ | |
|
71 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/bool.js\");" | |
|
72 | ], | |
|
73 | "metadata": {}, | |
|
74 | "output_type": "display_data" | |
|
75 | }, | |
|
76 | { | |
|
77 | "javascript": [ | |
|
78 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/int.js\");" | |
|
79 | ], | |
|
80 | "metadata": {}, | |
|
81 | "output_type": "display_data" | |
|
82 | }, | |
|
83 | { | |
|
84 | "javascript": [ | |
|
85 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/selection.js\");" | |
|
86 | ], | |
|
87 | "metadata": {}, | |
|
88 | "output_type": "display_data" | |
|
89 | }, | |
|
90 | { | |
|
91 | "javascript": [ | |
|
92 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/float.js\");" | |
|
93 | ], | |
|
94 | "metadata": {}, | |
|
95 | "output_type": "display_data" | |
|
96 | }, | |
|
97 | { | |
|
98 | "javascript": [ | |
|
99 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/float_range.js\");" | |
|
100 | ], | |
|
101 | "metadata": {}, | |
|
102 | "output_type": "display_data" | |
|
103 | }, | |
|
104 | { | |
|
105 | "javascript": [ | |
|
106 | "$.getScript($(\"body\").data(\"baseProjectUrl\") + \"static/notebook/js/widgets/container.js\");" | |
|
107 | ], | |
|
108 | "metadata": {}, | |
|
109 | "output_type": "display_data" | |
|
110 | } | |
|
111 | ], | |
|
37 | "outputs": [], | |
|
112 | 38 | "prompt_number": 1 |
|
113 | 39 | }, |
|
114 | 40 | { |
|
115 | 41 | "cell_type": "heading", |
|
116 | 42 | "level": 1, |
|
117 | 43 | "metadata": {}, |
|
118 | 44 | "source": [ |
|
119 | 45 | "Abstract" |
|
120 | 46 | ] |
|
121 | 47 | }, |
|
122 | 48 | { |
|
123 | 49 | "cell_type": "markdown", |
|
124 | 50 | "metadata": {}, |
|
125 | 51 | "source": [ |
|
126 | 52 | "This notebook implements a custom date picker widget. The purpose of this notebook is to demonstrate the widget creation process. To create a custom widget, custom Python and JavaScript is required." |
|
127 | 53 | ] |
|
128 | 54 | }, |
|
129 | 55 | { |
|
130 | 56 | "cell_type": "heading", |
|
131 | 57 | "level": 1, |
|
132 | 58 | "metadata": {}, |
|
133 | 59 | "source": [ |
|
134 | 60 | "Section 1 - Basics" |
|
135 | 61 | ] |
|
136 | 62 | }, |
|
137 | 63 | { |
|
138 | 64 | "cell_type": "heading", |
|
139 | 65 | "level": 2, |
|
140 | 66 | "metadata": {}, |
|
141 | 67 | "source": [ |
|
142 | 68 | "Python" |
|
143 | 69 | ] |
|
144 | 70 | }, |
|
145 | 71 | { |
|
146 | 72 | "cell_type": "markdown", |
|
147 | 73 | "metadata": {}, |
|
148 | 74 | "source": [ |
|
149 | 75 | "When starting a project like this, it is often easiest to make an overly simplified base to verify that the underlying framework is working as expected. To start we will create an empty widget and make sure that it can be rendered. The first step is to create the widget in Python." |
|
150 | 76 | ] |
|
151 | 77 | }, |
|
152 | 78 | { |
|
153 | 79 | "cell_type": "code", |
|
154 | 80 | "collapsed": false, |
|
155 | 81 | "input": [ |
|
156 | 82 | "# Import the base Widget class and the traitlets Unicode class.\n", |
|
157 | 83 | "from IPython.html.widgets import Widget\n", |
|
158 | 84 | "from IPython.utils.traitlets import Unicode\n", |
|
159 | 85 | "\n", |
|
160 | 86 | "# Define our DateWidget and its target model and default view.\n", |
|
161 | 87 | "class DateWidget(Widget):\n", |
|
162 | 88 | " target_name = Unicode('DateWidgetModel')\n", |
|
163 | 89 | " default_view_name = Unicode('DatePickerView')" |
|
164 | 90 | ], |
|
165 | 91 | "language": "python", |
|
166 | 92 | "metadata": {}, |
|
167 | 93 | "outputs": [], |
|
168 | 94 | "prompt_number": 2 |
|
169 | 95 | }, |
|
170 | 96 | { |
|
171 | 97 | "cell_type": "markdown", |
|
172 | 98 | "metadata": {}, |
|
173 | 99 | "source": [ |
|
174 | 100 | "- **target_name** is a special `Widget` property that tells the widget framework which Backbone model in the front-end corresponds to this widget.\n", |
|
175 | 101 | "- **default_view_name** is the default Backbone view to display when the user calls `display` to display an instance of this widget.\n" |
|
176 | 102 | ] |
|
177 | 103 | }, |
|
178 | 104 | { |
|
179 | 105 | "cell_type": "heading", |
|
180 | 106 | "level": 2, |
|
181 | 107 | "metadata": {}, |
|
182 | 108 | "source": [ |
|
183 | 109 | "JavaScript" |
|
184 | 110 | ] |
|
185 | 111 | }, |
|
186 | 112 | { |
|
187 | 113 | "cell_type": "markdown", |
|
188 | 114 | "metadata": {}, |
|
189 | 115 | "source": [ |
|
190 | 116 | "In the IPython notebook [require.js](http://requirejs.org/) is used to load JavaScript dependencies. All IPython widget code depends on `notebook/js/widget.js`. In it the base widget model, base view, and widget manager are defined. We need to use require.js to include this file:" |
|
191 | 117 | ] |
|
192 | 118 | }, |
|
193 | 119 | { |
|
194 | 120 | "cell_type": "code", |
|
195 | 121 | "collapsed": false, |
|
196 | 122 | "input": [ |
|
197 | 123 | "%%javascript\n", |
|
198 | 124 | "\n", |
|
199 | 125 | "require([\"notebook/js/widget\"], function(){\n", |
|
200 | 126 | "\n", |
|
201 | 127 | "});" |
|
202 | 128 | ], |
|
203 | 129 | "language": "python", |
|
204 | 130 | "metadata": {}, |
|
205 | 131 | "outputs": [ |
|
206 | 132 | { |
|
207 | 133 | "javascript": [ |
|
208 | 134 | "\n", |
|
209 | 135 | "require([\"notebook/js/widget\"], function(){\n", |
|
210 | 136 | "\n", |
|
211 | 137 | "});" |
|
212 | 138 | ], |
|
213 | 139 | "metadata": {}, |
|
214 | 140 | "output_type": "display_data", |
|
215 | 141 | "text": [ |
|
216 |
"<IPython.core.display.Javascript at 0x |
|
|
142 | "<IPython.core.display.Javascript at 0x21f8f10>" | |
|
217 | 143 | ] |
|
218 | 144 | } |
|
219 | 145 | ], |
|
220 | 146 | "prompt_number": 3 |
|
221 | 147 | }, |
|
222 | 148 | { |
|
223 | 149 | "cell_type": "markdown", |
|
224 | 150 | "metadata": {}, |
|
225 | 151 | "source": [ |
|
226 | 152 | "The next step is to add a definition for the widget's model. It's important to extend the `IPython.WidgetModel` which extends the Backbone.js base model instead of trying to extend the Backbone.js base model directly. After defining the model, it needs to be registed with the widget manager using the `target_name` used in the Python code." |
|
227 | 153 | ] |
|
228 | 154 | }, |
|
229 | 155 | { |
|
230 | 156 | "cell_type": "code", |
|
231 | 157 | "collapsed": false, |
|
232 | 158 | "input": [ |
|
233 | 159 | "%%javascript\n", |
|
234 | 160 | "\n", |
|
235 | 161 | "require([\"notebook/js/widget\"], function(){\n", |
|
236 | 162 | " \n", |
|
237 | 163 | " // Define the DateModel and register it with the widget manager.\n", |
|
238 | 164 | " var DateModel = IPython.WidgetModel.extend({});\n", |
|
239 |
" IPython. |
|
|
165 | " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n", | |
|
240 | 166 | "});" |
|
241 | 167 | ], |
|
242 | 168 | "language": "python", |
|
243 | 169 | "metadata": {}, |
|
244 | 170 | "outputs": [ |
|
245 | 171 | { |
|
246 | 172 | "javascript": [ |
|
247 | 173 | "\n", |
|
248 | 174 | "require([\"notebook/js/widget\"], function(){\n", |
|
249 | 175 | " \n", |
|
250 | 176 | " // Define the DateModel and register it with the widget manager.\n", |
|
251 | 177 | " var DateModel = IPython.WidgetModel.extend({});\n", |
|
252 |
" IPython. |
|
|
178 | " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n", | |
|
253 | 179 | "});" |
|
254 | 180 | ], |
|
255 | 181 | "metadata": {}, |
|
256 | 182 | "output_type": "display_data", |
|
257 | 183 | "text": [ |
|
258 |
"<IPython.core.display.Javascript at 0x |
|
|
184 | "<IPython.core.display.Javascript at 0x21f8ed0>" | |
|
259 | 185 | ] |
|
260 | 186 | } |
|
261 | 187 | ], |
|
262 | 188 | "prompt_number": 4 |
|
263 | 189 | }, |
|
264 | 190 | { |
|
265 | 191 | "cell_type": "markdown", |
|
266 | 192 | "metadata": {}, |
|
267 | 193 | "source": [ |
|
268 | 194 | "Now that the model is defined, we need to define a view that can be used to represent the model. To do this, the `IPython.WidgetView` is extended. A render function must be defined. The render function is used to render a widget view instance to the DOM. For now the render function renders a div that contains the text *Hello World!* Lastly, the view needs to be registered with the widget manager like the model was.\n", |
|
269 | 195 | "\n", |
|
270 | 196 | "**Final JavaScript code below:**" |
|
271 | 197 | ] |
|
272 | 198 | }, |
|
273 | 199 | { |
|
274 | 200 | "cell_type": "code", |
|
275 | 201 | "collapsed": false, |
|
276 | 202 | "input": [ |
|
277 | 203 | "%%javascript\n", |
|
278 | 204 | "\n", |
|
279 | 205 | "require([\"notebook/js/widget\"], function(){\n", |
|
280 | 206 | " \n", |
|
281 | 207 | " // Define the DateModel and register it with the widget manager.\n", |
|
282 | 208 | " var DateModel = IPython.WidgetModel.extend({});\n", |
|
283 |
" IPython. |
|
|
209 | " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n", | |
|
284 | 210 | " \n", |
|
285 | 211 | " // Define the DatePickerView\n", |
|
286 | 212 | " var DatePickerView = IPython.WidgetView.extend({\n", |
|
287 | 213 | " \n", |
|
288 | 214 | " render: function(){\n", |
|
289 | 215 | " this.$el = $('<div />')\n", |
|
290 | 216 | " .html('Hello World!');\n", |
|
291 | 217 | " },\n", |
|
292 | 218 | " });\n", |
|
293 | 219 | " \n", |
|
294 | 220 | " // Register the DatePickerView with the widget manager.\n", |
|
295 |
" IPython. |
|
|
221 | " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n", | |
|
296 | 222 | "});" |
|
297 | 223 | ], |
|
298 | 224 | "language": "python", |
|
299 | 225 | "metadata": {}, |
|
300 | 226 | "outputs": [ |
|
301 | 227 | { |
|
302 | 228 | "javascript": [ |
|
303 | 229 | "\n", |
|
304 | 230 | "require([\"notebook/js/widget\"], function(){\n", |
|
305 | 231 | " \n", |
|
306 | 232 | " // Define the DateModel and register it with the widget manager.\n", |
|
307 | 233 | " var DateModel = IPython.WidgetModel.extend({});\n", |
|
308 |
" IPython. |
|
|
234 | " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n", | |
|
309 | 235 | " \n", |
|
310 | 236 | " // Define the DatePickerView\n", |
|
311 | 237 | " var DatePickerView = IPython.WidgetView.extend({\n", |
|
312 | 238 | " \n", |
|
313 | 239 | " render: function(){\n", |
|
314 | 240 | " this.$el = $('<div />')\n", |
|
315 | 241 | " .html('Hello World!');\n", |
|
316 | 242 | " },\n", |
|
317 | 243 | " });\n", |
|
318 | 244 | " \n", |
|
319 | 245 | " // Register the DatePickerView with the widget manager.\n", |
|
320 |
" IPython. |
|
|
246 | " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n", | |
|
321 | 247 | "});" |
|
322 | 248 | ], |
|
323 | 249 | "metadata": {}, |
|
324 | 250 | "output_type": "display_data", |
|
325 | 251 | "text": [ |
|
326 |
"<IPython.core.display.Javascript at 0x |
|
|
252 | "<IPython.core.display.Javascript at 0x21f8cd0>" | |
|
327 | 253 | ] |
|
328 | 254 | } |
|
329 | 255 | ], |
|
330 | 256 | "prompt_number": 5 |
|
331 | 257 | }, |
|
332 | 258 | { |
|
333 | 259 | "cell_type": "heading", |
|
334 | 260 | "level": 2, |
|
335 | 261 | "metadata": {}, |
|
336 | 262 | "source": [ |
|
337 | 263 | "Test" |
|
338 | 264 | ] |
|
339 | 265 | }, |
|
340 | 266 | { |
|
341 | 267 | "cell_type": "markdown", |
|
342 | 268 | "metadata": {}, |
|
343 | 269 | "source": [ |
|
344 | 270 | "To test, create the widget the same way that the other widgets are created." |
|
345 | 271 | ] |
|
346 | 272 | }, |
|
347 | 273 | { |
|
348 | 274 | "cell_type": "code", |
|
349 | 275 | "collapsed": false, |
|
350 | 276 | "input": [ |
|
351 | 277 | "my_widget = DateWidget()\n", |
|
352 | 278 | "display(my_widget)" |
|
353 | 279 | ], |
|
354 | 280 | "language": "python", |
|
355 | 281 | "metadata": {}, |
|
356 | 282 | "outputs": [], |
|
357 | 283 | "prompt_number": 6 |
|
358 | 284 | }, |
|
359 | 285 | { |
|
360 | 286 | "cell_type": "heading", |
|
361 | 287 | "level": 1, |
|
362 | 288 | "metadata": {}, |
|
363 | 289 | "source": [ |
|
364 | 290 | "Section 2 - Something useful" |
|
365 | 291 | ] |
|
366 | 292 | }, |
|
367 | 293 | { |
|
368 | 294 | "cell_type": "heading", |
|
369 | 295 | "level": 2, |
|
370 | 296 | "metadata": {}, |
|
371 | 297 | "source": [ |
|
372 | 298 | "Python" |
|
373 | 299 | ] |
|
374 | 300 | }, |
|
375 | 301 | { |
|
376 | 302 | "cell_type": "markdown", |
|
377 | 303 | "metadata": {}, |
|
378 | 304 | "source": [ |
|
379 | 305 | "In the last section we created a simple widget that displayed *Hello World!* There was no custom state information associated with the widget. To make an actual date widget, we need to add a property that will be synced between the Python model and the JavaScript model. The new property must be a traitlet property so the widget machinery can automatically handle it. The property needs to be added to the the `_keys` list. The `_keys` list tells the widget machinery what traitlets should be synced with the front-end. Adding this to the code from the last section:" |
|
380 | 306 | ] |
|
381 | 307 | }, |
|
382 | 308 | { |
|
383 | 309 | "cell_type": "code", |
|
384 | 310 | "collapsed": false, |
|
385 | 311 | "input": [ |
|
386 | 312 | "# Import the base Widget class and the traitlets Unicode class.\n", |
|
387 | 313 | "from IPython.html.widgets import Widget\n", |
|
388 | 314 | "from IPython.utils.traitlets import Unicode\n", |
|
389 | 315 | "\n", |
|
390 | 316 | "# Define our DateWidget and its target model and default view.\n", |
|
391 | 317 | "class DateWidget(Widget):\n", |
|
392 | 318 | " target_name = Unicode('DateWidgetModel')\n", |
|
393 | 319 | " default_view_name = Unicode('DatePickerView')\n", |
|
394 | 320 | " \n", |
|
395 | 321 | " # Define the custom state properties to sync with the front-end\n", |
|
396 | 322 | " _keys = ['value']\n", |
|
397 | 323 | " value = Unicode()" |
|
398 | 324 | ], |
|
399 | 325 | "language": "python", |
|
400 | 326 | "metadata": {}, |
|
401 | 327 | "outputs": [], |
|
402 | 328 | "prompt_number": 7 |
|
403 | 329 | }, |
|
404 | 330 | { |
|
405 | 331 | "cell_type": "heading", |
|
406 | 332 | "level": 2, |
|
407 | 333 | "metadata": {}, |
|
408 | 334 | "source": [ |
|
409 | 335 | "JavaScript" |
|
410 | 336 | ] |
|
411 | 337 | }, |
|
412 | 338 | { |
|
413 | 339 | "cell_type": "markdown", |
|
414 | 340 | "metadata": {}, |
|
415 | 341 | "source": [ |
|
416 | 342 | "In the JavaScript there is no need to define the same properties in the JavaScript model. When the JavaScript model is created for the first time, it copies all of the attributes from the Python model. We need to replace *Hello World!* with an actual HTML date picker widget." |
|
417 | 343 | ] |
|
418 | 344 | }, |
|
419 | 345 | { |
|
420 | 346 | "cell_type": "code", |
|
421 | 347 | "collapsed": false, |
|
422 | 348 | "input": [ |
|
423 | 349 | "%%javascript\n", |
|
424 | 350 | "\n", |
|
425 | 351 | "require([\"notebook/js/widget\"], function(){\n", |
|
426 | 352 | " \n", |
|
427 | 353 | " // Define the DateModel and register it with the widget manager.\n", |
|
428 | 354 | " var DateModel = IPython.WidgetModel.extend({});\n", |
|
429 |
" IPython. |
|
|
355 | " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n", | |
|
430 | 356 | " \n", |
|
431 | 357 | " // Define the DatePickerView\n", |
|
432 | 358 | " var DatePickerView = IPython.WidgetView.extend({\n", |
|
433 | 359 | " \n", |
|
434 | 360 | " render: function(){\n", |
|
435 | 361 | " \n", |
|
436 | 362 | " // Create a div to hold our widget.\n", |
|
437 | 363 | " this.$el = $('<div />');\n", |
|
438 | 364 | " \n", |
|
439 | 365 | " // Create the date picker control.\n", |
|
440 | 366 | " this.$date = $('<input />')\n", |
|
441 | 367 | " .attr('type', 'date');\n", |
|
442 | 368 | " },\n", |
|
443 | 369 | " });\n", |
|
444 | 370 | " \n", |
|
445 | 371 | " // Register the DatePickerView with the widget manager.\n", |
|
446 |
" IPython. |
|
|
372 | " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n", | |
|
447 | 373 | "});" |
|
448 | 374 | ], |
|
449 | 375 | "language": "python", |
|
450 | 376 | "metadata": {}, |
|
451 | 377 | "outputs": [ |
|
452 | 378 | { |
|
453 | 379 | "javascript": [ |
|
454 | 380 | "\n", |
|
455 | 381 | "require([\"notebook/js/widget\"], function(){\n", |
|
456 | 382 | " \n", |
|
457 | 383 | " // Define the DateModel and register it with the widget manager.\n", |
|
458 | 384 | " var DateModel = IPython.WidgetModel.extend({});\n", |
|
459 |
" IPython. |
|
|
385 | " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n", | |
|
460 | 386 | " \n", |
|
461 | 387 | " // Define the DatePickerView\n", |
|
462 | 388 | " var DatePickerView = IPython.WidgetView.extend({\n", |
|
463 | 389 | " \n", |
|
464 | 390 | " render: function(){\n", |
|
465 | 391 | " \n", |
|
466 | 392 | " // Create a div to hold our widget.\n", |
|
467 | 393 | " this.$el = $('<div />');\n", |
|
468 | 394 | " \n", |
|
469 | 395 | " // Create the date picker control.\n", |
|
470 | 396 | " this.$date = $('<input />')\n", |
|
471 | 397 | " .attr('type', 'date');\n", |
|
472 | 398 | " },\n", |
|
473 | 399 | " });\n", |
|
474 | 400 | " \n", |
|
475 | 401 | " // Register the DatePickerView with the widget manager.\n", |
|
476 |
" IPython. |
|
|
402 | " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n", | |
|
477 | 403 | "});" |
|
478 | 404 | ], |
|
479 | 405 | "metadata": {}, |
|
480 | 406 | "output_type": "display_data", |
|
481 | 407 | "text": [ |
|
482 |
"<IPython.core.display.Javascript at 0x |
|
|
408 | "<IPython.core.display.Javascript at 0x21fc310>" | |
|
483 | 409 | ] |
|
484 | 410 | } |
|
485 | 411 | ], |
|
486 | 412 | "prompt_number": 8 |
|
487 | 413 | }, |
|
488 | 414 | { |
|
489 | 415 | "cell_type": "markdown", |
|
490 | 416 | "metadata": {}, |
|
491 | 417 | "source": [ |
|
492 | 418 | "In order to get the HTML date picker to update itself with the value set in the back-end, we need to implement an `update()` method." |
|
493 | 419 | ] |
|
494 | 420 | }, |
|
495 | 421 | { |
|
496 | 422 | "cell_type": "code", |
|
497 | 423 | "collapsed": false, |
|
498 | 424 | "input": [ |
|
499 | 425 | "%%javascript\n", |
|
500 | 426 | "\n", |
|
501 | 427 | "require([\"notebook/js/widget\"], function(){\n", |
|
502 | 428 | " \n", |
|
503 | 429 | " // Define the DateModel and register it with the widget manager.\n", |
|
504 | 430 | " var DateModel = IPython.WidgetModel.extend({});\n", |
|
505 |
" IPython. |
|
|
431 | " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n", | |
|
506 | 432 | " \n", |
|
507 | 433 | " // Define the DatePickerView\n", |
|
508 | 434 | " var DatePickerView = IPython.WidgetView.extend({\n", |
|
509 | 435 | " \n", |
|
510 | 436 | " render: function(){\n", |
|
511 | 437 | " \n", |
|
512 | 438 | " // Create a div to hold our widget.\n", |
|
513 | 439 | " this.$el = $('<div />');\n", |
|
514 | 440 | " \n", |
|
515 | 441 | " // Create the date picker control.\n", |
|
516 | 442 | " this.$date = $('<input />')\n", |
|
517 | 443 | " .attr('type', 'date');\n", |
|
518 | 444 | " },\n", |
|
519 | 445 | " \n", |
|
520 | 446 | " update: function() {\n", |
|
521 | 447 | " \n", |
|
522 | 448 | " // Set the value of the date control and then call base.\n", |
|
523 | 449 | " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n", |
|
524 | 450 | " return IPython.WidgetView.prototype.update.call(this);\n", |
|
525 | 451 | " },\n", |
|
526 | 452 | " });\n", |
|
527 | 453 | " \n", |
|
528 | 454 | " // Register the DatePickerView with the widget manager.\n", |
|
529 |
" IPython. |
|
|
455 | " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n", | |
|
530 | 456 | "});" |
|
531 | 457 | ], |
|
532 | 458 | "language": "python", |
|
533 | 459 | "metadata": {}, |
|
534 | 460 | "outputs": [ |
|
535 | 461 | { |
|
536 | 462 | "javascript": [ |
|
537 | 463 | "\n", |
|
538 | 464 | "require([\"notebook/js/widget\"], function(){\n", |
|
539 | 465 | " \n", |
|
540 | 466 | " // Define the DateModel and register it with the widget manager.\n", |
|
541 | 467 | " var DateModel = IPython.WidgetModel.extend({});\n", |
|
542 |
" IPython. |
|
|
468 | " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n", | |
|
543 | 469 | " \n", |
|
544 | 470 | " // Define the DatePickerView\n", |
|
545 | 471 | " var DatePickerView = IPython.WidgetView.extend({\n", |
|
546 | 472 | " \n", |
|
547 | 473 | " render: function(){\n", |
|
548 | 474 | " \n", |
|
549 | 475 | " // Create a div to hold our widget.\n", |
|
550 | 476 | " this.$el = $('<div />');\n", |
|
551 | 477 | " \n", |
|
552 | 478 | " // Create the date picker control.\n", |
|
553 | 479 | " this.$date = $('<input />')\n", |
|
554 | 480 | " .attr('type', 'date');\n", |
|
555 | 481 | " },\n", |
|
556 | 482 | " \n", |
|
557 | 483 | " update: function() {\n", |
|
558 | 484 | " \n", |
|
559 | 485 | " // Set the value of the date control and then call base.\n", |
|
560 | 486 | " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n", |
|
561 | 487 | " return IPython.WidgetView.prototype.update.call(this);\n", |
|
562 | 488 | " },\n", |
|
563 | 489 | " });\n", |
|
564 | 490 | " \n", |
|
565 | 491 | " // Register the DatePickerView with the widget manager.\n", |
|
566 |
" IPython. |
|
|
492 | " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n", | |
|
567 | 493 | "});" |
|
568 | 494 | ], |
|
569 | 495 | "metadata": {}, |
|
570 | 496 | "output_type": "display_data", |
|
571 | 497 | "text": [ |
|
572 |
"<IPython.core.display.Javascript at 0x |
|
|
498 | "<IPython.core.display.Javascript at 0x21fc290>" | |
|
573 | 499 | ] |
|
574 | 500 | } |
|
575 | 501 | ], |
|
576 | 502 | "prompt_number": 9 |
|
577 | 503 | }, |
|
578 | 504 | { |
|
579 | 505 | "cell_type": "markdown", |
|
580 | 506 | "metadata": {}, |
|
581 | 507 | "source": [ |
|
582 | 508 | "To get the changed value from the front-end to publish itself to the back-end, we need to listen to the change event triggered by the HTM date control and set the value in the model. By setting the `this.$el` property of the view, we break the Backbone powered event handling. To fix this, a call to `this.delegateEvents()` must be added after `this.$el` is set. \n", |
|
583 | 509 | "\n", |
|
584 | 510 | "After the date change event fires and the new value is set in the model, it's very important that we call `update_other_views(this)` to make the other views on the page update and to let the widget machinery know which view changed the model. This is important because the widget machinery needs to know which cell to route the message callbacks to.\n", |
|
585 | 511 | "\n", |
|
586 | 512 | "**Final JavaScript code below:**" |
|
587 | 513 | ] |
|
588 | 514 | }, |
|
589 | 515 | { |
|
590 | 516 | "cell_type": "code", |
|
591 | 517 | "collapsed": false, |
|
592 | 518 | "input": [ |
|
593 | 519 | "%%javascript\n", |
|
594 | 520 | "\n", |
|
595 | 521 | "require([\"notebook/js/widget\"], function(){\n", |
|
596 | 522 | " \n", |
|
597 | 523 | " // Define the DateModel and register it with the widget manager.\n", |
|
598 | 524 | " var DateModel = IPython.WidgetModel.extend({});\n", |
|
599 |
" IPython. |
|
|
525 | " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n", | |
|
600 | 526 | " \n", |
|
601 | 527 | " // Define the DatePickerView\n", |
|
602 | 528 | " var DatePickerView = IPython.WidgetView.extend({\n", |
|
603 | 529 | " \n", |
|
604 | 530 | " render: function(){\n", |
|
605 | 531 | " \n", |
|
606 | 532 | " // Create a div to hold our widget.\n", |
|
607 | 533 | " this.$el = $('<div />');\n", |
|
608 | 534 | " this.delegateEvents();\n", |
|
609 | 535 | " \n", |
|
610 | 536 | " // Create the date picker control.\n", |
|
611 | 537 | " this.$date = $('<input />')\n", |
|
612 | 538 | " .attr('type', 'date')\n", |
|
613 | 539 | " .appendTo(this.$el);\n", |
|
614 | 540 | " },\n", |
|
615 | 541 | " \n", |
|
616 | 542 | " update: function() {\n", |
|
617 | 543 | " \n", |
|
618 | 544 | " // Set the value of the date control and then call base.\n", |
|
619 | 545 | " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n", |
|
620 | 546 | " return IPython.WidgetView.prototype.update.call(this);\n", |
|
621 | 547 | " },\n", |
|
622 | 548 | " \n", |
|
623 | 549 | " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n", |
|
624 | 550 | " events: {\"change\": \"handle_date_change\"},\n", |
|
625 | 551 | " \n", |
|
626 | 552 | " // Callback for when the date is changed.\n", |
|
627 | 553 | " handle_date_change: function(event) {\n", |
|
628 | 554 | " this.model.set('value', this.$date.val());\n", |
|
629 | 555 | " this.model.update_other_views(this);\n", |
|
630 | 556 | " },\n", |
|
631 | 557 | " \n", |
|
632 | 558 | " });\n", |
|
633 | 559 | " \n", |
|
634 | 560 | " // Register the DatePickerView with the widget manager.\n", |
|
635 |
" IPython. |
|
|
561 | " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n", | |
|
636 | 562 | "});" |
|
637 | 563 | ], |
|
638 | 564 | "language": "python", |
|
639 | 565 | "metadata": {}, |
|
640 | 566 | "outputs": [ |
|
641 | 567 | { |
|
642 | 568 | "javascript": [ |
|
643 | 569 | "\n", |
|
644 | 570 | "require([\"notebook/js/widget\"], function(){\n", |
|
645 | 571 | " \n", |
|
646 | 572 | " // Define the DateModel and register it with the widget manager.\n", |
|
647 | 573 | " var DateModel = IPython.WidgetModel.extend({});\n", |
|
648 |
" IPython. |
|
|
574 | " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n", | |
|
649 | 575 | " \n", |
|
650 | 576 | " // Define the DatePickerView\n", |
|
651 | 577 | " var DatePickerView = IPython.WidgetView.extend({\n", |
|
652 | 578 | " \n", |
|
653 | 579 | " render: function(){\n", |
|
654 | 580 | " \n", |
|
655 | 581 | " // Create a div to hold our widget.\n", |
|
656 | 582 | " this.$el = $('<div />');\n", |
|
657 | 583 | " this.delegateEvents();\n", |
|
658 | 584 | " \n", |
|
659 | 585 | " // Create the date picker control.\n", |
|
660 | 586 | " this.$date = $('<input />')\n", |
|
661 | 587 | " .attr('type', 'date')\n", |
|
662 | 588 | " .appendTo(this.$el);\n", |
|
663 | 589 | " },\n", |
|
664 | 590 | " \n", |
|
665 | 591 | " update: function() {\n", |
|
666 | 592 | " \n", |
|
667 | 593 | " // Set the value of the date control and then call base.\n", |
|
668 | 594 | " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n", |
|
669 | 595 | " return IPython.WidgetView.prototype.update.call(this);\n", |
|
670 | 596 | " },\n", |
|
671 | 597 | " \n", |
|
672 | 598 | " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n", |
|
673 | 599 | " events: {\"change\": \"handle_date_change\"},\n", |
|
674 | 600 | " \n", |
|
675 | 601 | " // Callback for when the date is changed.\n", |
|
676 | 602 | " handle_date_change: function(event) {\n", |
|
677 | 603 | " this.model.set('value', this.$date.val());\n", |
|
678 | 604 | " this.model.update_other_views(this);\n", |
|
679 | 605 | " },\n", |
|
680 | 606 | " \n", |
|
681 | 607 | " });\n", |
|
682 | 608 | " \n", |
|
683 | 609 | " // Register the DatePickerView with the widget manager.\n", |
|
684 |
" IPython. |
|
|
610 | " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n", | |
|
685 | 611 | "});" |
|
686 | 612 | ], |
|
687 | 613 | "metadata": {}, |
|
688 | 614 | "output_type": "display_data", |
|
689 | 615 | "text": [ |
|
690 |
"<IPython.core.display.Javascript at 0x |
|
|
616 | "<IPython.core.display.Javascript at 0x21fc3d0>" | |
|
691 | 617 | ] |
|
692 | 618 | } |
|
693 | 619 | ], |
|
694 | 620 | "prompt_number": 10 |
|
695 | 621 | }, |
|
696 | 622 | { |
|
697 | 623 | "cell_type": "heading", |
|
698 | 624 | "level": 2, |
|
699 | 625 | "metadata": {}, |
|
700 | 626 | "source": [ |
|
701 | 627 | "Test" |
|
702 | 628 | ] |
|
703 | 629 | }, |
|
704 | 630 | { |
|
705 | 631 | "cell_type": "markdown", |
|
706 | 632 | "metadata": {}, |
|
707 | 633 | "source": [ |
|
708 | 634 | "To test, create the widget the same way that the other widgets are created." |
|
709 | 635 | ] |
|
710 | 636 | }, |
|
711 | 637 | { |
|
712 | 638 | "cell_type": "code", |
|
713 | 639 | "collapsed": false, |
|
714 | 640 | "input": [ |
|
715 | 641 | "my_widget = DateWidget()\n", |
|
716 | 642 | "display(my_widget)" |
|
717 | 643 | ], |
|
718 | 644 | "language": "python", |
|
719 | 645 | "metadata": {}, |
|
720 | 646 | "outputs": [], |
|
721 | 647 | "prompt_number": 11 |
|
722 | 648 | }, |
|
723 | 649 | { |
|
724 | 650 | "cell_type": "markdown", |
|
725 | 651 | "metadata": {}, |
|
726 | 652 | "source": [ |
|
727 | 653 | "Display the widget again to make sure that both views remain in sync." |
|
728 | 654 | ] |
|
729 | 655 | }, |
|
730 | 656 | { |
|
731 | 657 | "cell_type": "code", |
|
732 | 658 | "collapsed": false, |
|
733 | 659 | "input": [ |
|
734 | 660 | "display(my_widget)" |
|
735 | 661 | ], |
|
736 | 662 | "language": "python", |
|
737 | 663 | "metadata": {}, |
|
738 | 664 | "outputs": [], |
|
739 | 665 | "prompt_number": 12 |
|
740 | 666 | }, |
|
741 | 667 | { |
|
742 | 668 | "cell_type": "markdown", |
|
743 | 669 | "metadata": {}, |
|
744 | 670 | "source": [ |
|
745 | 671 | "Read the date from Python" |
|
746 | 672 | ] |
|
747 | 673 | }, |
|
748 | 674 | { |
|
749 | 675 | "cell_type": "code", |
|
750 | 676 | "collapsed": false, |
|
751 | 677 | "input": [ |
|
752 | 678 | "my_widget.value" |
|
753 | 679 | ], |
|
754 | 680 | "language": "python", |
|
755 | 681 | "metadata": {}, |
|
756 | 682 | "outputs": [ |
|
757 | 683 | { |
|
758 | 684 | "metadata": {}, |
|
759 | 685 | "output_type": "pyout", |
|
760 | 686 | "prompt_number": 13, |
|
761 | 687 | "text": [ |
|
762 | "u''" | |
|
688 | "u'2013-11-14'" | |
|
763 | 689 | ] |
|
764 | 690 | } |
|
765 | 691 | ], |
|
766 | 692 | "prompt_number": 13 |
|
767 | 693 | }, |
|
768 | 694 | { |
|
769 | 695 | "cell_type": "markdown", |
|
770 | 696 | "metadata": {}, |
|
771 | 697 | "source": [ |
|
772 | 698 | "Set the date from Python" |
|
773 | 699 | ] |
|
774 | 700 | }, |
|
775 | 701 | { |
|
776 | 702 | "cell_type": "code", |
|
777 | 703 | "collapsed": false, |
|
778 | 704 | "input": [ |
|
779 | 705 | "my_widget.value = \"1999-12-01\" # December 1st, 1999" |
|
780 | 706 | ], |
|
781 | 707 | "language": "python", |
|
782 | 708 | "metadata": {}, |
|
783 | 709 | "outputs": [], |
|
784 | 710 | "prompt_number": 14 |
|
785 | 711 | }, |
|
786 | 712 | { |
|
787 | 713 | "cell_type": "heading", |
|
788 | 714 | "level": 1, |
|
789 | 715 | "metadata": {}, |
|
790 | 716 | "source": [ |
|
791 | 717 | "Section 3 - Extra credit" |
|
792 | 718 | ] |
|
793 | 719 | }, |
|
794 | 720 | { |
|
795 | 721 | "cell_type": "markdown", |
|
796 | 722 | "metadata": {}, |
|
797 | 723 | "source": [ |
|
798 | 724 | "In the last section we created a fully working date picker widget. Now we will add custom validation and support for labels. Currently only the ISO date format \"YYYY-MM-DD\" is supported. We will add support for all of the date formats recognized by the 3rd party Python dateutil library." |
|
799 | 725 | ] |
|
800 | 726 | }, |
|
801 | 727 | { |
|
802 | 728 | "cell_type": "heading", |
|
803 | 729 | "level": 2, |
|
804 | 730 | "metadata": {}, |
|
805 | 731 | "source": [ |
|
806 | 732 | "Python" |
|
807 | 733 | ] |
|
808 | 734 | }, |
|
809 | 735 | { |
|
810 | 736 | "cell_type": "markdown", |
|
811 | 737 | "metadata": {}, |
|
812 | 738 | "source": [ |
|
813 | 739 | "The traitlet machinery searches the class that the trait is defined in for methods with \"`_changed`\" suffixed onto their names. Any method with the format \"`X_changed`\" will be called when \"`X`\" is modified. We can take advantage of this to perform validation and parsing of different date string formats. Below a method that listens to value has been added to the DateWidget." |
|
814 | 740 | ] |
|
815 | 741 | }, |
|
816 | 742 | { |
|
817 | 743 | "cell_type": "code", |
|
818 | 744 | "collapsed": false, |
|
819 | 745 | "input": [ |
|
820 | 746 | "# Import the base Widget class and the traitlets Unicode class.\n", |
|
821 | 747 | "from IPython.html.widgets import Widget\n", |
|
822 | 748 | "from IPython.utils.traitlets import Unicode\n", |
|
823 | 749 | "\n", |
|
824 | 750 | "# Define our DateWidget and its target model and default view.\n", |
|
825 | 751 | "class DateWidget(Widget):\n", |
|
826 | 752 | " target_name = Unicode('DateWidgetModel')\n", |
|
827 | 753 | " default_view_name = Unicode('DatePickerView')\n", |
|
828 | 754 | " \n", |
|
829 | 755 | " # Define the custom state properties to sync with the front-end\n", |
|
830 | 756 | " _keys = ['value']\n", |
|
831 | 757 | " value = Unicode()\n", |
|
832 | 758 | " \n", |
|
833 | 759 | " # This function automatically gets called by the traitlet machinery when\n", |
|
834 | 760 | " # value is modified because of this function's name.\n", |
|
835 | 761 | " def _value_changed(self, name, old_value, new_value):\n", |
|
836 | 762 | " pass\n", |
|
837 | 763 | " " |
|
838 | 764 | ], |
|
839 | 765 | "language": "python", |
|
840 | 766 | "metadata": {}, |
|
841 | 767 | "outputs": [], |
|
842 | 768 | "prompt_number": 15 |
|
843 | 769 | }, |
|
844 | 770 | { |
|
845 | 771 | "cell_type": "markdown", |
|
846 | 772 | "metadata": {}, |
|
847 | 773 | "source": [ |
|
848 | 774 | "Now the function that parses the date string and only sets it in the correct format can be added." |
|
849 | 775 | ] |
|
850 | 776 | }, |
|
851 | 777 | { |
|
852 | 778 | "cell_type": "code", |
|
853 | 779 | "collapsed": false, |
|
854 | 780 | "input": [ |
|
855 | 781 | "# Import the dateutil library to parse date strings.\n", |
|
856 | 782 | "from dateutil import parser\n", |
|
857 | 783 | "\n", |
|
858 | 784 | "# Import the base Widget class and the traitlets Unicode class.\n", |
|
859 | 785 | "from IPython.html.widgets import Widget\n", |
|
860 | 786 | "from IPython.utils.traitlets import Unicode\n", |
|
861 | 787 | "\n", |
|
862 | 788 | "# Define our DateWidget and its target model and default view.\n", |
|
863 | 789 | "class DateWidget(Widget):\n", |
|
864 | 790 | " target_name = Unicode('DateWidgetModel')\n", |
|
865 | 791 | " default_view_name = Unicode('DatePickerView')\n", |
|
866 | 792 | " \n", |
|
867 | 793 | " # Define the custom state properties to sync with the front-end\n", |
|
868 | 794 | " _keys = ['value']\n", |
|
869 | 795 | " value = Unicode()\n", |
|
870 | 796 | " \n", |
|
871 | 797 | " # This function automatically gets called by the traitlet machinery when\n", |
|
872 | 798 | " # value is modified because of this function's name.\n", |
|
873 | 799 | " def _value_changed(self, name, old_value, new_value):\n", |
|
874 | 800 | " \n", |
|
875 | 801 | " # Parse the date time value.\n", |
|
876 | 802 | " try:\n", |
|
877 | 803 | " parsed_date = parser.parse(new_value)\n", |
|
878 | 804 | " parsed_date_string = parsed_date.strftime(\"%Y-%m-%d\")\n", |
|
879 | 805 | " except:\n", |
|
880 | 806 | " parsed_date_string = ''\n", |
|
881 | 807 | " \n", |
|
882 | 808 | " # Set the parsed date string if the current date string is different.\n", |
|
883 | 809 | " if self.value != parsed_date_string:\n", |
|
884 | 810 | " self.value = parsed_date_string" |
|
885 | 811 | ], |
|
886 | 812 | "language": "python", |
|
887 | 813 | "metadata": {}, |
|
888 | 814 | "outputs": [], |
|
889 | 815 | "prompt_number": 16 |
|
890 | 816 | }, |
|
891 | 817 | { |
|
892 | 818 | "cell_type": "markdown", |
|
893 | 819 | "metadata": {}, |
|
894 | 820 | "source": [ |
|
895 | 821 | "The standard property name used for widget labels is `description`. In the code block below, `description` has been added to the Python widget." |
|
896 | 822 | ] |
|
897 | 823 | }, |
|
898 | 824 | { |
|
899 | 825 | "cell_type": "code", |
|
900 | 826 | "collapsed": false, |
|
901 | 827 | "input": [ |
|
902 | 828 | "# Import the dateutil library to parse date strings.\n", |
|
903 | 829 | "from dateutil import parser\n", |
|
904 | 830 | "\n", |
|
905 | 831 | "# Import the base Widget class and the traitlets Unicode class.\n", |
|
906 | 832 | "from IPython.html.widgets import Widget\n", |
|
907 | 833 | "from IPython.utils.traitlets import Unicode\n", |
|
908 | 834 | "\n", |
|
909 | 835 | "# Define our DateWidget and its target model and default view.\n", |
|
910 | 836 | "class DateWidget(Widget):\n", |
|
911 | 837 | " target_name = Unicode('DateWidgetModel')\n", |
|
912 | 838 | " default_view_name = Unicode('DatePickerView')\n", |
|
913 | 839 | " \n", |
|
914 | 840 | " # Define the custom state properties to sync with the front-end\n", |
|
915 | 841 | " _keys = ['value', 'description']\n", |
|
916 | 842 | " value = Unicode()\n", |
|
917 | 843 | " description = Unicode()\n", |
|
918 | 844 | " \n", |
|
919 | 845 | " # This function automatically gets called by the traitlet machinery when\n", |
|
920 | 846 | " # value is modified because of this function's name.\n", |
|
921 | 847 | " def _value_changed(self, name, old_value, new_value):\n", |
|
922 | 848 | " \n", |
|
923 | 849 | " # Parse the date time value.\n", |
|
924 | 850 | " try:\n", |
|
925 | 851 | " parsed_date = parser.parse(new_value)\n", |
|
926 | 852 | " parsed_date_string = parsed_date.strftime(\"%Y-%m-%d\")\n", |
|
927 | 853 | " except:\n", |
|
928 | 854 | " parsed_date_string = ''\n", |
|
929 | 855 | " \n", |
|
930 | 856 | " # Set the parsed date string if the current date string is different.\n", |
|
931 | 857 | " if self.value != parsed_date_string:\n", |
|
932 | 858 | " self.value = parsed_date_string" |
|
933 | 859 | ], |
|
934 | 860 | "language": "python", |
|
935 | 861 | "metadata": {}, |
|
936 | 862 | "outputs": [], |
|
937 | 863 | "prompt_number": 17 |
|
938 | 864 | }, |
|
939 | 865 | { |
|
940 | 866 | "cell_type": "markdown", |
|
941 | 867 | "metadata": {}, |
|
942 | 868 | "source": [ |
|
943 | 869 | "Finally, a callback list is added so the user can perform custom validation. If any one of the callbacks returns False, the new date time is not set.\n", |
|
944 | 870 | "\n", |
|
945 | 871 | "**Final Python code below:**" |
|
946 | 872 | ] |
|
947 | 873 | }, |
|
948 | 874 | { |
|
949 | 875 | "cell_type": "code", |
|
950 | 876 | "collapsed": false, |
|
951 | 877 | "input": [ |
|
952 | 878 | "# Import the dateutil library to parse date strings.\n", |
|
953 | 879 | "from dateutil import parser\n", |
|
954 | 880 | "\n", |
|
955 | 881 | "# Import the base Widget class and the traitlets Unicode class.\n", |
|
956 | 882 | "from IPython.html.widgets import Widget\n", |
|
957 | 883 | "from IPython.utils.traitlets import Unicode\n", |
|
958 | 884 | "\n", |
|
959 | 885 | "# Define our DateWidget and its target model and default view.\n", |
|
960 | 886 | "class DateWidget(Widget):\n", |
|
961 | 887 | " target_name = Unicode('DateWidgetModel')\n", |
|
962 | 888 | " default_view_name = Unicode('DatePickerView')\n", |
|
963 | 889 | " \n", |
|
964 | 890 | " # Define the custom state properties to sync with the front-end\n", |
|
965 | 891 | " _keys = ['value', 'description']\n", |
|
966 | 892 | " value = Unicode()\n", |
|
967 | 893 | " description = Unicode()\n", |
|
968 | 894 | " \n", |
|
969 | 895 | " def __init__(self, **kwargs):\n", |
|
970 | 896 | " super(DateWidget, self).__init__(**kwargs)\n", |
|
971 | 897 | " self._validation_callbacks = []\n", |
|
972 | 898 | " \n", |
|
973 | 899 | " # This function automatically gets called by the traitlet machinery when\n", |
|
974 | 900 | " # value is modified because of this function's name.\n", |
|
975 | 901 | " def _value_changed(self, name, old_value, new_value):\n", |
|
976 | 902 | " \n", |
|
977 | 903 | " # Parse the date time value.\n", |
|
978 | 904 | " try:\n", |
|
979 | 905 | " parsed_date = parser.parse(new_value)\n", |
|
980 | 906 | " parsed_date_string = parsed_date.strftime(\"%Y-%m-%d\")\n", |
|
981 | 907 | " except:\n", |
|
982 | 908 | " parsed_date = None\n", |
|
983 | 909 | " parsed_date_string = ''\n", |
|
984 | 910 | " \n", |
|
985 | 911 | " # Set the parsed date string if the current date string is different.\n", |
|
986 | 912 | " if old_value != new_value:\n", |
|
987 | 913 | " if self.handle_validate(parsed_date):\n", |
|
988 | 914 | " self.value = parsed_date_string\n", |
|
989 | 915 | " else:\n", |
|
990 | 916 | " self.value = old_value\n", |
|
991 | 917 | " self.send_state() # The traitlet event won't fire since the value isn't changing.\n", |
|
992 | 918 | " # We need to force the back-end to send the front-end the state\n", |
|
993 | 919 | " # to make sure that the date control date doesn't change.\n", |
|
994 | 920 | " \n", |
|
995 | 921 | " \n", |
|
996 | 922 | " # Allow the user to register custom validation callbacks.\n", |
|
997 | 923 | " # callback(new value as a datetime object)\n", |
|
998 | 924 | " def on_validate(self, callback, remove=False):\n", |
|
999 | 925 | " if remove and callback in self._validation_callbacks:\n", |
|
1000 | 926 | " self._validation_callbacks.remove(callback)\n", |
|
1001 | 927 | " elif (not remove) and (not callback in self._validation_callbacks):\n", |
|
1002 | 928 | " self._validation_callbacks.append(callback)\n", |
|
1003 | 929 | " \n", |
|
1004 | 930 | " # Call user validation callbacks. Return True if valid.\n", |
|
1005 | 931 | " def handle_validate(self, new_value):\n", |
|
1006 | 932 | " for callback in self._validation_callbacks:\n", |
|
1007 | 933 | " if not callback(new_value):\n", |
|
1008 | 934 | " return False\n", |
|
1009 | 935 | " return True\n", |
|
1010 | 936 | " " |
|
1011 | 937 | ], |
|
1012 | 938 | "language": "python", |
|
1013 | 939 | "metadata": {}, |
|
1014 | 940 | "outputs": [], |
|
1015 | 941 | "prompt_number": 18 |
|
1016 | 942 | }, |
|
1017 | 943 | { |
|
1018 | 944 | "cell_type": "heading", |
|
1019 | 945 | "level": 2, |
|
1020 | 946 | "metadata": {}, |
|
1021 | 947 | "source": [ |
|
1022 | 948 | "JavaScript" |
|
1023 | 949 | ] |
|
1024 | 950 | }, |
|
1025 | 951 | { |
|
1026 | 952 | "cell_type": "markdown", |
|
1027 | 953 | "metadata": {}, |
|
1028 | 954 | "source": [ |
|
1029 | 955 | "Using the Javascript code from the last section, we add a label to the date time object. The label is a div with the `widget-hlabel` class applied to it. The `widget-hlabel` is a class provided by the widget framework that applies special styling to a div to make it look like the rest of the horizontal labels used with the built in widgets. Similar to the `widget-hlabel` class is the `widget-hbox-single` class. The `widget-hbox-single` class applies special styling to widget containers that store a single line horizontal widget. \n", |
|
1030 | 956 | "\n", |
|
1031 | 957 | "We hide the label if the description value is blank." |
|
1032 | 958 | ] |
|
1033 | 959 | }, |
|
1034 | 960 | { |
|
1035 | 961 | "cell_type": "code", |
|
1036 | 962 | "collapsed": false, |
|
1037 | 963 | "input": [ |
|
1038 | 964 | "%%javascript\n", |
|
1039 | 965 | "\n", |
|
1040 | 966 | "require([\"notebook/js/widget\"], function(){\n", |
|
1041 | 967 | " \n", |
|
1042 | 968 | " // Define the DateModel and register it with the widget manager.\n", |
|
1043 | 969 | " var DateModel = IPython.WidgetModel.extend({});\n", |
|
1044 |
" IPython. |
|
|
970 | " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n", | |
|
1045 | 971 | " \n", |
|
1046 | 972 | " // Define the DatePickerView\n", |
|
1047 | 973 | " var DatePickerView = IPython.WidgetView.extend({\n", |
|
1048 | 974 | " \n", |
|
1049 | 975 | " render: function(){\n", |
|
1050 | 976 | " \n", |
|
1051 | 977 | " // Create a div to hold our widget.\n", |
|
1052 | 978 | " this.$el = $('<div />')\n", |
|
1053 | 979 | " .addClass('widget-hbox-single'); // Apply this class to the widget container to make\n", |
|
1054 | 980 | " // it fit with the other built in widgets.\n", |
|
1055 | 981 | " this.delegateEvents();\n", |
|
1056 | 982 | " \n", |
|
1057 | 983 | " // Create a label.\n", |
|
1058 | 984 | " this.$label = $('<div />')\n", |
|
1059 | 985 | " .addClass('widget-hlabel')\n", |
|
1060 | 986 | " .appendTo(this.$el)\n", |
|
1061 | 987 | " .hide(); // Hide the label by default.\n", |
|
1062 | 988 | " \n", |
|
1063 | 989 | " // Create the date picker control.\n", |
|
1064 | 990 | " this.$date = $('<input />')\n", |
|
1065 | 991 | " .attr('type', 'date')\n", |
|
1066 | 992 | " .appendTo(this.$el);\n", |
|
1067 | 993 | " },\n", |
|
1068 | 994 | " \n", |
|
1069 | 995 | " update: function() {\n", |
|
1070 | 996 | " \n", |
|
1071 | 997 | " // Set the value of the date control and then call base.\n", |
|
1072 | 998 | " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n", |
|
1073 | 999 | " \n", |
|
1074 | 1000 | " // Hide or show the label depending on the existance of a description.\n", |
|
1075 | 1001 | " var description = this.model.get('description');\n", |
|
1076 | 1002 | " if (description == undefined || description == '') {\n", |
|
1077 | 1003 | " this.$label.hide();\n", |
|
1078 | 1004 | " } else {\n", |
|
1079 | 1005 | " this.$label.show();\n", |
|
1080 | 1006 | " this.$label.html(description);\n", |
|
1081 | 1007 | " }\n", |
|
1082 | 1008 | " \n", |
|
1083 | 1009 | " return IPython.WidgetView.prototype.update.call(this);\n", |
|
1084 | 1010 | " },\n", |
|
1085 | 1011 | " \n", |
|
1086 | 1012 | " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n", |
|
1087 | 1013 | " events: {\"change\": \"handle_date_change\"},\n", |
|
1088 | 1014 | " \n", |
|
1089 | 1015 | " // Callback for when the date is changed.\n", |
|
1090 | 1016 | " handle_date_change: function(event) {\n", |
|
1091 | 1017 | " this.model.set('value', this.$date.val());\n", |
|
1092 | 1018 | " this.model.update_other_views(this);\n", |
|
1093 | 1019 | " },\n", |
|
1094 | 1020 | " \n", |
|
1095 | 1021 | " });\n", |
|
1096 | 1022 | " \n", |
|
1097 | 1023 | " // Register the DatePickerView with the widget manager.\n", |
|
1098 |
" IPython. |
|
|
1024 | " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n", | |
|
1099 | 1025 | "});" |
|
1100 | 1026 | ], |
|
1101 | 1027 | "language": "python", |
|
1102 | 1028 | "metadata": {}, |
|
1103 | 1029 | "outputs": [ |
|
1104 | 1030 | { |
|
1105 | 1031 | "javascript": [ |
|
1106 | 1032 | "\n", |
|
1107 | 1033 | "require([\"notebook/js/widget\"], function(){\n", |
|
1108 | 1034 | " \n", |
|
1109 | 1035 | " // Define the DateModel and register it with the widget manager.\n", |
|
1110 | 1036 | " var DateModel = IPython.WidgetModel.extend({});\n", |
|
1111 |
" IPython. |
|
|
1037 | " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n", | |
|
1112 | 1038 | " \n", |
|
1113 | 1039 | " // Define the DatePickerView\n", |
|
1114 | 1040 | " var DatePickerView = IPython.WidgetView.extend({\n", |
|
1115 | 1041 | " \n", |
|
1116 | 1042 | " render: function(){\n", |
|
1117 | 1043 | " \n", |
|
1118 | 1044 | " // Create a div to hold our widget.\n", |
|
1119 | 1045 | " this.$el = $('<div />')\n", |
|
1120 | 1046 | " .addClass('widget-hbox-single'); // Apply this class to the widget container to make\n", |
|
1121 | 1047 | " // it fit with the other built in widgets.\n", |
|
1122 | 1048 | " this.delegateEvents();\n", |
|
1123 | 1049 | " \n", |
|
1124 | 1050 | " // Create a label.\n", |
|
1125 | 1051 | " this.$label = $('<div />')\n", |
|
1126 | 1052 | " .addClass('widget-hlabel')\n", |
|
1127 | 1053 | " .appendTo(this.$el)\n", |
|
1128 | 1054 | " .hide(); // Hide the label by default.\n", |
|
1129 | 1055 | " \n", |
|
1130 | 1056 | " // Create the date picker control.\n", |
|
1131 | 1057 | " this.$date = $('<input />')\n", |
|
1132 | 1058 | " .attr('type', 'date')\n", |
|
1133 | 1059 | " .appendTo(this.$el);\n", |
|
1134 | 1060 | " },\n", |
|
1135 | 1061 | " \n", |
|
1136 | 1062 | " update: function() {\n", |
|
1137 | 1063 | " \n", |
|
1138 | 1064 | " // Set the value of the date control and then call base.\n", |
|
1139 | 1065 | " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n", |
|
1140 | 1066 | " \n", |
|
1141 | 1067 | " // Hide or show the label depending on the existance of a description.\n", |
|
1142 | 1068 | " var description = this.model.get('description');\n", |
|
1143 | 1069 | " if (description == undefined || description == '') {\n", |
|
1144 | 1070 | " this.$label.hide();\n", |
|
1145 | 1071 | " } else {\n", |
|
1146 | 1072 | " this.$label.show();\n", |
|
1147 | 1073 | " this.$label.html(description);\n", |
|
1148 | 1074 | " }\n", |
|
1149 | 1075 | " \n", |
|
1150 | 1076 | " return IPython.WidgetView.prototype.update.call(this);\n", |
|
1151 | 1077 | " },\n", |
|
1152 | 1078 | " \n", |
|
1153 | 1079 | " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n", |
|
1154 | 1080 | " events: {\"change\": \"handle_date_change\"},\n", |
|
1155 | 1081 | " \n", |
|
1156 | 1082 | " // Callback for when the date is changed.\n", |
|
1157 | 1083 | " handle_date_change: function(event) {\n", |
|
1158 | 1084 | " this.model.set('value', this.$date.val());\n", |
|
1159 | 1085 | " this.model.update_other_views(this);\n", |
|
1160 | 1086 | " },\n", |
|
1161 | 1087 | " \n", |
|
1162 | 1088 | " });\n", |
|
1163 | 1089 | " \n", |
|
1164 | 1090 | " // Register the DatePickerView with the widget manager.\n", |
|
1165 |
" IPython. |
|
|
1091 | " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n", | |
|
1166 | 1092 | "});" |
|
1167 | 1093 | ], |
|
1168 | 1094 | "metadata": {}, |
|
1169 | 1095 | "output_type": "display_data", |
|
1170 | 1096 | "text": [ |
|
1171 |
"<IPython.core.display.Javascript at 0x |
|
|
1097 | "<IPython.core.display.Javascript at 0x221a850>" | |
|
1172 | 1098 | ] |
|
1173 | 1099 | } |
|
1174 | 1100 | ], |
|
1175 | 1101 | "prompt_number": 19 |
|
1176 | 1102 | }, |
|
1177 | 1103 | { |
|
1178 | 1104 | "cell_type": "heading", |
|
1179 | 1105 | "level": 2, |
|
1180 | 1106 | "metadata": {}, |
|
1181 | 1107 | "source": [ |
|
1182 | 1108 | "Test" |
|
1183 | 1109 | ] |
|
1184 | 1110 | }, |
|
1185 | 1111 | { |
|
1186 | 1112 | "cell_type": "markdown", |
|
1187 | 1113 | "metadata": {}, |
|
1188 | 1114 | "source": [ |
|
1189 | 1115 | "To test the drawing of the label we create the widget like normal but supply the additional description property a value." |
|
1190 | 1116 | ] |
|
1191 | 1117 | }, |
|
1192 | 1118 | { |
|
1193 | 1119 | "cell_type": "code", |
|
1194 | 1120 | "collapsed": false, |
|
1195 | 1121 | "input": [ |
|
1196 | 1122 | "# Add some additional widgets for aesthetic purpose\n", |
|
1197 | 1123 | "display(widgets.StringWidget(description=\"First:\"))\n", |
|
1198 | 1124 | "display(widgets.StringWidget(description=\"Last:\"))\n", |
|
1199 | 1125 | "\n", |
|
1200 | 1126 | "my_widget = DateWidget(description=\"DOB:\")\n", |
|
1201 | 1127 | "display(my_widget)" |
|
1202 | 1128 | ], |
|
1203 | 1129 | "language": "python", |
|
1204 | 1130 | "metadata": {}, |
|
1205 | 1131 | "outputs": [], |
|
1206 | 1132 | "prompt_number": 20 |
|
1207 | 1133 | }, |
|
1208 | 1134 | { |
|
1209 | 1135 | "cell_type": "markdown", |
|
1210 | 1136 | "metadata": {}, |
|
1211 | 1137 | "source": [ |
|
1212 | 1138 | "Since the date widget uses `value` and `description`, we can also display its value using a `TextBoxView`. The allows us to look at the raw date value being passed to and from the back-end and front-end." |
|
1213 | 1139 | ] |
|
1214 | 1140 | }, |
|
1215 | 1141 | { |
|
1216 | 1142 | "cell_type": "code", |
|
1217 | 1143 | "collapsed": false, |
|
1218 | 1144 | "input": [ |
|
1219 | "\n", | |
|
1220 | 1145 | "display(my_widget, view_name=\"TextBoxView\")" |
|
1221 | 1146 | ], |
|
1222 | 1147 | "language": "python", |
|
1223 | 1148 | "metadata": {}, |
|
1224 | 1149 | "outputs": [], |
|
1225 | 1150 | "prompt_number": 21 |
|
1226 | 1151 | }, |
|
1227 | 1152 | { |
|
1228 | 1153 | "cell_type": "markdown", |
|
1229 | 1154 | "metadata": {}, |
|
1230 | 1155 | "source": [ |
|
1231 | 1156 | "Now we will try to create a widget that only accepts dates in the year 2013. We render the widget without a description to verify that it can still render without a label." |
|
1232 | 1157 | ] |
|
1233 | 1158 | }, |
|
1234 | 1159 | { |
|
1235 | 1160 | "cell_type": "code", |
|
1236 | 1161 | "collapsed": false, |
|
1237 | 1162 | "input": [ |
|
1238 | 1163 | "my_widget = DateWidget()\n", |
|
1239 | 1164 | "display(my_widget)\n", |
|
1240 | 1165 | "\n", |
|
1241 | 1166 | "def validate_date(date):\n", |
|
1242 | 1167 | " return not date is None and date.year == 2013\n", |
|
1243 | 1168 | "my_widget.on_validate(validate_date)" |
|
1244 | 1169 | ], |
|
1245 | 1170 | "language": "python", |
|
1246 | 1171 | "metadata": {}, |
|
1247 | 1172 | "outputs": [], |
|
1248 | 1173 | "prompt_number": 22 |
|
1249 | 1174 | }, |
|
1250 | 1175 | { |
|
1251 | 1176 | "cell_type": "code", |
|
1252 | 1177 | "collapsed": false, |
|
1253 | 1178 | "input": [ |
|
1254 | 1179 | "# Try setting a valid date\n", |
|
1255 | 1180 | "my_widget.value = \"December 2, 2013\"" |
|
1256 | 1181 | ], |
|
1257 | 1182 | "language": "python", |
|
1258 | 1183 | "metadata": {}, |
|
1259 | 1184 | "outputs": [], |
|
1260 | 1185 | "prompt_number": 23 |
|
1261 | 1186 | }, |
|
1262 | 1187 | { |
|
1263 | 1188 | "cell_type": "code", |
|
1264 | 1189 | "collapsed": false, |
|
1265 | 1190 | "input": [ |
|
1266 | 1191 | "# Try setting an invalid date\n", |
|
1267 | 1192 | "my_widget.value = \"June 12, 1999\"" |
|
1268 | 1193 | ], |
|
1269 | 1194 | "language": "python", |
|
1270 | 1195 | "metadata": {}, |
|
1271 | 1196 | "outputs": [], |
|
1272 | 1197 | "prompt_number": 24 |
|
1273 | 1198 | } |
|
1274 | 1199 | ], |
|
1275 | 1200 | "metadata": {} |
|
1276 | 1201 | } |
|
1277 | 1202 | ] |
|
1278 | 1203 | } No newline at end of file |
General Comments 0
You need to be logged in to leave comments.
Login now