##// END OF EJS Templates
Made scroll to bottom use msgs...
Jonathan Frederic -
Show More
@@ -1,603 +1,625 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
2 // Copyright (C) 2013 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // WidgetModel, WidgetView, and WidgetManager
9 // WidgetModel, WidgetView, and WidgetManager
10 //============================================================================
10 //============================================================================
11 /**
11 /**
12 * Base Widget classes
12 * Base Widget classes
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule widget
15 * @submodule widget
16 */
16 */
17
17
18 "use strict";
18 "use strict";
19
19
20 // Use require.js 'define' method so that require.js is intelligent enough to
20 // Use require.js 'define' method so that require.js is intelligent enough to
21 // syncronously load everything within this file when it is being 'required'
21 // syncronously load everything within this file when it is being 'required'
22 // elsewhere.
22 // elsewhere.
23 define(["components/underscore/underscore-min",
23 define(["components/underscore/underscore-min",
24 "components/backbone/backbone-min",
24 "components/backbone/backbone-min",
25 ], function(underscore, backbone){
25 ], function(underscore, backbone){
26
26
27
27
28 //--------------------------------------------------------------------
28 //--------------------------------------------------------------------
29 // WidgetModel class
29 // WidgetModel class
30 //--------------------------------------------------------------------
30 //--------------------------------------------------------------------
31 var WidgetModel = Backbone.Model.extend({
31 var WidgetModel = Backbone.Model.extend({
32 constructor: function(comm_manager, comm, widget_manager) {
32 constructor: function(comm_manager, comm, widget_manager) {
33 this.comm_manager = comm_manager;
33 this.comm_manager = comm_manager;
34 this.widget_manager = widget_manager;
34 this.widget_manager = widget_manager;
35 this.pending_msgs = 0;
35 this.pending_msgs = 0;
36 this.msg_throttle = 3;
36 this.msg_throttle = 3;
37 this.msg_buffer = null;
37 this.msg_buffer = null;
38 this.views = {};
38 this.views = {};
39 this._custom_msg_callbacks = [];
39
40
40 // Remember comm associated with the model.
41 // Remember comm associated with the model.
41 this.comm = comm;
42 this.comm = comm;
42 comm.model = this;
43 comm.model = this;
43
44
44 // Hook comm messages up to model.
45 // Hook comm messages up to model.
45 comm.on_close($.proxy(this._handle_comm_closed, this));
46 comm.on_close($.proxy(this._handle_comm_closed, this));
46 comm.on_msg($.proxy(this._handle_comm_msg, this));
47 comm.on_msg($.proxy(this._handle_comm_msg, this));
47
48
48 return Backbone.Model.apply(this);
49 return Backbone.Model.apply(this);
49 },
50 },
50
51
51
52
52 update_other_views: function(caller) {
53 update_other_views: function(caller) {
53 this.last_modified_view = caller;
54 this.last_modified_view = caller;
54 this.save(this.changedAttributes(), {patch: true});
55 this.save(this.changedAttributes(), {patch: true});
55
56
56 for (var cell in this.views) {
57 for (var cell in this.views) {
57 var views = this.views[cell];
58 var views = this.views[cell];
58 for (var view_index in views) {
59 for (var view_index in views) {
59 var view = views[view_index];
60 var view = views[view_index];
60 if (view !== caller) {
61 if (view !== caller) {
61 view.update();
62 view.update();
62 }
63 }
63 }
64 }
64 }
65 }
65 },
66 },
66
67
67 send: function(content) {
68
69 send: function(content, cell) {
68
70
69 // Used the last modified view as the sender of the message. This
71 // Used the last modified view as the sender of the message. This
70 // will insure that any python code triggered by the sent message
72 // will insure that any python code triggered by the sent message
71 // can create and display widgets and output.
73 // can create and display widgets and output.
72 var cell = null;
74 if (cell === undefined) {
73 if (this.last_modified_view != undefined &&
75 if (this.last_modified_view != undefined &&
74 this.last_modified_view.cell != undefined) {
76 this.last_modified_view.cell != undefined) {
75 cell = this.last_modified_view.cell;
77 cell = this.last_modified_view.cell;
78 }
76 }
79 }
77 var callbacks = this._make_callbacks(cell);
80 var callbacks = this._make_callbacks(cell);
78 var data = {'custom_content': content};
81 var data = {'custom_content': content};
79 this.comm.send(data, callbacks);
82 this.comm.send(data, callbacks);
80 },
83 },
81
84
82
85
83 on_view_displayed: function (callback) {
86 on_view_displayed: function (callback) {
84 this._view_displayed_callback = callback;
87 this._view_displayed_callback = callback;
85 },
88 },
86
89
87
90
88 on_close: function (callback) {
91 on_close: function (callback) {
89 this._close_callback = callback;
92 this._close_callback = callback;
90 },
93 },
91
94
92
95
93 on_msg: function (callback) {
96 on_msg: function (callback, remove) {
94 this._msg_callback = callback;
97 if (remove) {
98 var found_index = -1;
99 for (var index in this._custom_msg_callbacks) {
100 if (callback === this._custom_msg_callbacks[index]) {
101 found_index = index;
102 break;
103 }
104 }
105
106 if (found_index >= 0) {
107 this._custom_msg_callbacks.splice(found_index, 1);
108 }
109 } else {
110 this._custom_msg_callbacks.push(callback)
111 }
95 },
112 },
96
113
97
114
98 _handle_custom_msg: function (content) {
115 _handle_custom_msg: function (content) {
99 if (this._msg_callback) {
116 for (var index in this._custom_msg_callbacks) {
100 try {
117 try {
101 this._msg_callback(content);
118 this._custom_msg_callbacks[index](content);
102 } catch (e) {
119 } catch (e) {
103 console.log("Exception in widget model msg callback", e, content);
120 console.log("Exception in widget model msg callback", e, content);
104 }
121 }
105 }
122 }
106 },
123 },
107
124
108
125
109 // Handle when a widget is closed.
126 // Handle when a widget is closed.
110 _handle_comm_closed: function (msg) {
127 _handle_comm_closed: function (msg) {
111 this._execute_views_method('remove');
128 this._execute_views_method('remove');
112 delete this.comm.model; // Delete ref so GC will collect widget model.
129 delete this.comm.model; // Delete ref so GC will collect widget model.
113 },
130 },
114
131
115
132
116 // Handle incomming comm msg.
133 // Handle incomming comm msg.
117 _handle_comm_msg: function (msg) {
134 _handle_comm_msg: function (msg) {
118 var method = msg.content.data.method;
135 var method = msg.content.data.method;
119 switch (method){
136 switch (method){
120 case 'display':
137 case 'display':
121
138
122 // Try to get the cell.
139 // Try to get the cell.
123 var cell = this._get_msg_cell(msg.parent_header.msg_id);
140 var cell = this._get_msg_cell(msg.parent_header.msg_id);
124 if (cell == null) {
141 if (cell == null) {
125 console.log("Could not determine where the display" +
142 console.log("Could not determine where the display" +
126 " message was from. Widget will not be displayed")
143 " message was from. Widget will not be displayed")
127 } else {
144 } else {
128 this._display_view(msg.content.data.view_name,
145 this._display_view(msg.content.data.view_name,
129 msg.content.data.parent,
146 msg.content.data.parent,
130 cell);
147 cell);
131 }
148 }
132 break;
149 break;
133 case 'update':
150 case 'update':
134 this._handle_update(msg.content.data.state);
151 this._handle_update(msg.content.data.state);
135 break;
152 break;
136 case 'add_class':
153 case 'add_class':
137 case 'remove_class':
154 case 'remove_class':
138 var selector = msg.content.data.selector;
155 var selector = msg.content.data.selector;
139 var class_list = msg.content.data.class_list;
156 var class_list = msg.content.data.class_list;
140 this._execute_views_method(method, selector, class_list);
157 this._execute_views_method(method, selector, class_list);
141 break;
158 break;
142 case 'custom':
159 case 'custom':
143 this._handle_custom_msg(msg.content.data.custom_content);
160 this._handle_custom_msg(msg.content.data.custom_content);
144 break;
161 break;
145 }
162 }
146 },
163 },
147
164
148
165
149 // Handle when a widget is updated via the python side.
166 // Handle when a widget is updated via the python side.
150 _handle_update: function (state) {
167 _handle_update: function (state) {
151 this.updating = true;
168 this.updating = true;
152 try {
169 try {
153 for (var key in state) {
170 for (var key in state) {
154 if (state.hasOwnProperty(key)) {
171 if (state.hasOwnProperty(key)) {
155 if (key == "_css"){
172 if (key == "_css"){
156
173
157 // Set the css value of the model as an attribute
174 // Set the css value of the model as an attribute
158 // instead of a backbone trait because we are only
175 // instead of a backbone trait because we are only
159 // interested in backend css -> frontend css. In
176 // interested in backend css -> frontend css. In
160 // other words, if the css dict changes in the
177 // other words, if the css dict changes in the
161 // frontend, we don't need to push the changes to
178 // frontend, we don't need to push the changes to
162 // the backend.
179 // the backend.
163 this.css = state[key];
180 this.css = state[key];
164 } else {
181 } else {
165 this.set(key, state[key]);
182 this.set(key, state[key]);
166 }
183 }
167 }
184 }
168 }
185 }
169 this.id = this.comm.comm_id;
186 this.id = this.comm.comm_id;
170 this.save();
187 this.save();
171 } finally {
188 } finally {
172 this.updating = false;
189 this.updating = false;
173 }
190 }
174 },
191 },
175
192
176
193
177 _handle_status: function (cell, msg) {
194 _handle_status: function (cell, msg) {
178 //execution_state : ('busy', 'idle', 'starting')
195 //execution_state : ('busy', 'idle', 'starting')
179 if (msg.content.execution_state=='idle') {
196 if (msg.content.execution_state=='idle') {
180
197
181 // Send buffer if this message caused another message to be
198 // Send buffer if this message caused another message to be
182 // throttled.
199 // throttled.
183 if (this.msg_buffer != null &&
200 if (this.msg_buffer != null &&
184 this.msg_throttle == this.pending_msgs) {
201 this.msg_throttle == this.pending_msgs) {
185
202
186 var cell = this._get_msg_cell(msg.parent_header.msg_id);
203 var cell = this._get_msg_cell(msg.parent_header.msg_id);
187 var callbacks = this._make_callbacks(cell);
204 var callbacks = this._make_callbacks(cell);
188 var data = {sync_method: 'update', sync_data: this.msg_buffer};
205 var data = {sync_method: 'update', sync_data: this.msg_buffer};
189 this.comm.send(data, callbacks);
206 this.comm.send(data, callbacks);
190 this.msg_buffer = null;
207 this.msg_buffer = null;
191 } else {
208 } else {
192
209
193 // Only decrease the pending message count if the buffer
210 // Only decrease the pending message count if the buffer
194 // doesn't get flushed (sent).
211 // doesn't get flushed (sent).
195 --this.pending_msgs;
212 --this.pending_msgs;
196 }
213 }
197 }
214 }
198 },
215 },
199
216
200
217
201 // Custom syncronization logic.
218 // Custom syncronization logic.
202 _handle_sync: function (method, options) {
219 _handle_sync: function (method, options) {
203 var model_json = this.toJSON();
220 var model_json = this.toJSON();
204
221
205 // Only send updated state if the state hasn't been changed
222 // Only send updated state if the state hasn't been changed
206 // during an update.
223 // during an update.
207 if (!this.updating) {
224 if (!this.updating) {
208 if (this.pending_msgs >= this.msg_throttle) {
225 if (this.pending_msgs >= this.msg_throttle) {
209 // The throttle has been exceeded, buffer the current msg so
226 // The throttle has been exceeded, buffer the current msg so
210 // it can be sent once the kernel has finished processing
227 // it can be sent once the kernel has finished processing
211 // some of the existing messages.
228 // some of the existing messages.
212 if (method=='patch') {
229 if (method=='patch') {
213 if (this.msg_buffer == null) {
230 if (this.msg_buffer == null) {
214 this.msg_buffer = $.extend({}, model_json); // Copy
231 this.msg_buffer = $.extend({}, model_json); // Copy
215 }
232 }
216 for (var attr in options.attrs) {
233 for (var attr in options.attrs) {
217 this.msg_buffer[attr] = options.attrs[attr];
234 this.msg_buffer[attr] = options.attrs[attr];
218 }
235 }
219 } else {
236 } else {
220 this.msg_buffer = $.extend({}, model_json); // Copy
237 this.msg_buffer = $.extend({}, model_json); // Copy
221 }
238 }
222
239
223 } else {
240 } else {
224 // We haven't exceeded the throttle, send the message like
241 // We haven't exceeded the throttle, send the message like
225 // normal. If this is a patch operation, just send the
242 // normal. If this is a patch operation, just send the
226 // changes.
243 // changes.
227 var send_json = model_json;
244 var send_json = model_json;
228 if (method=='patch') {
245 if (method=='patch') {
229 send_json = {};
246 send_json = {};
230 for (var attr in options.attrs) {
247 for (var attr in options.attrs) {
231 send_json[attr] = options.attrs[attr];
248 send_json[attr] = options.attrs[attr];
232 }
249 }
233 }
250 }
234
251
235 var data = {sync_method: method, sync_data: send_json};
252 var data = {sync_method: method, sync_data: send_json};
236
253
237 var cell = null;
254 var cell = null;
238 if (this.last_modified_view != undefined && this.last_modified_view != null) {
255 if (this.last_modified_view != undefined && this.last_modified_view != null) {
239 cell = this.last_modified_view.cell;
256 cell = this.last_modified_view.cell;
240 }
257 }
241
258
242 var callbacks = this._make_callbacks(cell);
259 var callbacks = this._make_callbacks(cell);
243 this.comm.send(data, callbacks);
260 this.comm.send(data, callbacks);
244 this.pending_msgs++;
261 this.pending_msgs++;
245 }
262 }
246 }
263 }
247
264
248 // Since the comm is a one-way communication, assume the message
265 // Since the comm is a one-way communication, assume the message
249 // arrived.
266 // arrived.
250 return model_json;
267 return model_json;
251 },
268 },
252
269
253
270
254 _handle_view_displayed: function(view) {
271 _handle_view_displayed: function(view) {
255 if (this._view_displayed_callback) {
272 if (this._view_displayed_callback) {
256 try {
273 try {
257 this._view_displayed_callback(view)
274 this._view_displayed_callback(view)
258 } catch (e) {
275 } catch (e) {
259 console.log("Exception in widget model view displayed callback", e, view, this);
276 console.log("Exception in widget model view displayed callback", e, view, this);
260 }
277 }
261 }
278 }
262 },
279 },
263
280
264
281
265 _execute_views_method: function (/* method_name, [argument0], [argument1], [...] */) {
282 _execute_views_method: function (/* method_name, [argument0], [argument1], [...] */) {
266 var method_name = arguments[0];
283 var method_name = arguments[0];
267 var args = null;
284 var args = null;
268 if (arguments.length > 1) {
285 if (arguments.length > 1) {
269 args = [].splice.call(arguments,1);
286 args = [].splice.call(arguments,1);
270 }
287 }
271
288
272 for (var cell in this.views) {
289 for (var cell in this.views) {
273 var views = this.views[cell];
290 var views = this.views[cell];
274 for (var view_index in views) {
291 for (var view_index in views) {
275 var view = views[view_index];
292 var view = views[view_index];
276 var method = view[method_name];
293 var method = view[method_name];
277 if (args === null) {
294 if (args === null) {
278 method.apply(view);
295 method.apply(view);
279 } else {
296 } else {
280 method.apply(view, args);
297 method.apply(view, args);
281 }
298 }
282 }
299 }
283 }
300 }
284 },
301 },
285
302
286
303
287 // Create view that represents the model.
304 // Create view that represents the model.
288 _display_view: function (view_name, parent_comm_id, cell) {
305 _display_view: function (view_name, parent_comm_id, cell) {
289 var new_views = [];
306 var new_views = [];
290
307
291 // Try creating and adding the view to it's parent.
308 // Try creating and adding the view to it's parent.
292 var displayed = false;
309 var displayed = false;
293 if (parent_comm_id != undefined) {
310 if (parent_comm_id != undefined) {
294 var parent_comm = this.comm_manager.comms[parent_comm_id];
311 var parent_comm = this.comm_manager.comms[parent_comm_id];
295 var parent_model = parent_comm.model;
312 var parent_model = parent_comm.model;
296 var parent_views = parent_model.views[cell];
313 var parent_views = parent_model.views[cell];
297 for (var parent_view_index in parent_views) {
314 for (var parent_view_index in parent_views) {
298 var parent_view = parent_views[parent_view_index];
315 var parent_view = parent_views[parent_view_index];
299 if (parent_view.display_child != undefined) {
316 if (parent_view.display_child != undefined) {
300 var view = this._create_view(view_name, cell);
317 var view = this._create_view(view_name, cell);
301 if (view != null) {
318 if (view != null) {
302 new_views.push(view);
319 new_views.push(view);
303 parent_view.display_child(view);
320 parent_view.display_child(view);
304 displayed = true;
321 displayed = true;
305 this._handle_view_displayed(view);
322 this._handle_view_displayed(view);
306 }
323 }
307 }
324 }
308 }
325 }
309 }
326 }
310
327
311 // If no parent view is defined or exists. Add the view's
328 // If no parent view is defined or exists. Add the view's
312 // element to cell's widget div.
329 // element to cell's widget div.
313 if (!displayed) {
330 if (!displayed) {
314 var view = this._create_view(view_name, cell);
331 var view = this._create_view(view_name, cell);
315 if (view != null) {
332 if (view != null) {
316 new_views.push(view);
333 new_views.push(view);
317
334
318 if (cell.widget_subarea != undefined && cell.widget_subarea != null) {
335 if (cell.widget_subarea != undefined && cell.widget_subarea != null) {
319 cell.widget_area.show();
336 cell.widget_area.show();
320 cell.widget_subarea.append(view.$el);
337 cell.widget_subarea.append(view.$el);
321 this._handle_view_displayed(view);
338 this._handle_view_displayed(view);
322 }
339 }
323 }
340 }
324 }
341 }
325
342
326 // Force the new view(s) to update their selves
343 // Force the new view(s) to update their selves
327 for (var view_index in new_views) {
344 for (var view_index in new_views) {
328 var view = new_views[view_index];
345 var view = new_views[view_index];
329 view.update();
346 view.update();
330 }
347 }
331 },
348 },
332
349
333
350
334 // Create a view
351 // Create a view
335 _create_view: function (view_name, cell) {
352 _create_view: function (view_name, cell) {
336 var view_type = this.widget_manager.widget_view_types[view_name];
353 var view_type = this.widget_manager.widget_view_types[view_name];
337 if (view_type != undefined && view_type != null) {
354 if (view_type != undefined && view_type != null) {
338 var view = new view_type({model: this});
355 var view = new view_type({model: this});
339 view.render();
356 view.render();
340 if (this.views[cell]==undefined) {
357 if (this.views[cell]==undefined) {
341 this.views[cell] = []
358 this.views[cell] = []
342 }
359 }
343 this.views[cell].push(view);
360 this.views[cell].push(view);
344 view.cell = cell;
361 view.cell = cell;
345
362
346 // Handle when the view element is remove from the page.
363 // Handle when the view element is remove from the page.
347 var that = this;
364 var that = this;
348 view.$el.on("remove", function(){
365 view.$el.on("remove", function(){
349 var index = that.views[cell].indexOf(view);
366 var index = that.views[cell].indexOf(view);
350 if (index > -1) {
367 if (index > -1) {
351 that.views[cell].splice(index, 1);
368 that.views[cell].splice(index, 1);
352 }
369 }
353 view.remove(); // Clean-up view
370 view.remove(); // Clean-up view
354 if (that.views[cell].length()==0) {
371 if (that.views[cell].length()==0) {
355 delete that.views[cell];
372 delete that.views[cell];
356 }
373 }
357
374
358 // Close the comm if there are no views left.
375 // Close the comm if there are no views left.
359 if (that.views.length()==0) {
376 if (that.views.length()==0) {
360 if (that._close_callback) {
377 if (that._close_callback) {
361 try {
378 try {
362 that._close_callback(that)
379 that._close_callback(that)
363 } catch (e) {
380 } catch (e) {
364 console.log("Exception in widget model close callback", e, that);
381 console.log("Exception in widget model close callback", e, that);
365 }
382 }
366 }
383 }
367 that.comm.close();
384 that.comm.close();
368 delete that.comm.model; // Delete ref so GC will collect widget model.
385 delete that.comm.model; // Delete ref so GC will collect widget model.
369 }
386 }
370 });
387 });
371 return view;
388 return view;
372 }
389 }
373 return null;
390 return null;
374 },
391 },
375
392
376
393
377 // Build a callback dict.
394 // Build a callback dict.
378 _make_callbacks: function (cell) {
395 _make_callbacks: function (cell) {
379 var callbacks = {};
396 var callbacks = {};
380 if (cell != null) {
397 if (cell != null) {
381
398
382 // Try to get output handlers
399 // Try to get output handlers
383 var handle_output = null;
400 var handle_output = null;
384 var handle_clear_output = null;
401 var handle_clear_output = null;
385 if (cell.output_area != undefined && cell.output_area != null) {
402 if (cell.output_area != undefined && cell.output_area != null) {
386 handle_output = $.proxy(cell.output_area.handle_output, cell.output_area);
403 handle_output = $.proxy(cell.output_area.handle_output, cell.output_area);
387 handle_clear_output = $.proxy(cell.output_area.handle_clear_output, cell.output_area);
404 handle_clear_output = $.proxy(cell.output_area.handle_clear_output, cell.output_area);
388 }
405 }
389
406
390 // Create callback dict usign what is known
407 // Create callback dict usign what is known
391 var that = this;
408 var that = this;
392 callbacks = {
409 callbacks = {
393 iopub : {
410 iopub : {
394 output : handle_output,
411 output : handle_output,
395 clear_output : handle_clear_output,
412 clear_output : handle_clear_output,
396
413
397 status : function(msg){
414 status : function(msg){
398 that._handle_status(cell, msg);
415 that._handle_status(cell, msg);
399 },
416 },
400
417
401 // Special function only registered by widget messages.
418 // Special function only registered by widget messages.
402 // Allows us to get the cell for a message so we know
419 // Allows us to get the cell for a message so we know
403 // where to add widgets if the code requires it.
420 // where to add widgets if the code requires it.
404 get_cell : function() {
421 get_cell : function() {
405 return cell;
422 return cell;
406 },
423 },
407 },
424 },
408 };
425 };
409 }
426 }
410 return callbacks;
427 return callbacks;
411 },
428 },
412
429
413
430
414 // Get the output area corresponding to the msg_id.
431 // Get the output area corresponding to the msg_id.
415 // cell is an instance of IPython.Cell
432 // cell is an instance of IPython.Cell
416 _get_msg_cell: function (msg_id) {
433 _get_msg_cell: function (msg_id) {
417
434
418 // First, check to see if the msg was triggered by cell execution.
435 // First, check to see if the msg was triggered by cell execution.
419 var cell = this.widget_manager.get_msg_cell(msg_id);
436 var cell = this.widget_manager.get_msg_cell(msg_id);
420 if (cell != null) {
437 if (cell != null) {
421 return cell;
438 return cell;
422 }
439 }
423
440
424 // Second, check to see if a get_cell callback was defined
441 // Second, check to see if a get_cell callback was defined
425 // for the message. get_cell callbacks are registered for
442 // for the message. get_cell callbacks are registered for
426 // widget messages, so this block is actually checking to see if the
443 // widget messages, so this block is actually checking to see if the
427 // message was triggered by a widget.
444 // message was triggered by a widget.
428 var kernel = this.comm_manager.kernel;
445 var kernel = this.comm_manager.kernel;
429 var callbacks = kernel.get_callbacks_for_msg(msg_id);
446 var callbacks = kernel.get_callbacks_for_msg(msg_id);
430 if (callbacks != undefined &&
447 if (callbacks != undefined &&
431 callbacks.iopub != undefined &&
448 callbacks.iopub != undefined &&
432 callbacks.iopub.get_cell != undefined) {
449 callbacks.iopub.get_cell != undefined) {
433
450
434 return callbacks.iopub.get_cell();
451 return callbacks.iopub.get_cell();
435 }
452 }
436
453
437 // Not triggered by a cell or widget (no get_cell callback
454 // Not triggered by a cell or widget (no get_cell callback
438 // exists).
455 // exists).
439 return null;
456 return null;
440 },
457 },
441
458
442 });
459 });
443
460
444
461
445 //--------------------------------------------------------------------
462 //--------------------------------------------------------------------
446 // WidgetView class
463 // WidgetView class
447 //--------------------------------------------------------------------
464 //--------------------------------------------------------------------
448 var WidgetView = Backbone.View.extend({
465 var WidgetView = Backbone.View.extend({
449
466
450 initialize: function() {
467 initialize: function() {
451 this.visible = true;
468 this.visible = true;
452 this.model.on('change',this.update,this);
469 this.model.on('change',this.update,this);
453 },
470 },
454
471
455 add_class: function(selector, class_list){
472 add_class: function(selector, class_list){
456 var elements = this._get_selector_element(selector);
473 var elements = this._get_selector_element(selector);
457 if (elements.length > 0) {
474 if (elements.length > 0) {
458 elements.addClass(class_list);
475 elements.addClass(class_list);
459 }
476 }
460 },
477 },
461
478
462 remove_class: function(selector, class_list){
479 remove_class: function(selector, class_list){
463 var elements = this._get_selector_element(selector);
480 var elements = this._get_selector_element(selector);
464 if (elements.length > 0) {
481 if (elements.length > 0) {
465 elements.removeClass(class_list);
482 elements.removeClass(class_list);
466 }
483 }
467 },
484 },
485
486
487 send: function(content) {
488 this.model.send({event: 'click'}, this.cell);
489 },
468
490
469 update: function() {
491 update: function() {
470 if (this.model.get('visible') != undefined) {
492 if (this.model.get('visible') != undefined) {
471 if (this.visible != this.model.get('visible')) {
493 if (this.visible != this.model.get('visible')) {
472 this.visible = this.model.get('visible');
494 this.visible = this.model.get('visible');
473 if (this.visible) {
495 if (this.visible) {
474 this.$el.show();
496 this.$el.show();
475 } else {
497 } else {
476 this.$el.hide();
498 this.$el.hide();
477 }
499 }
478 }
500 }
479 }
501 }
480
502
481 if (this.model.css != undefined) {
503 if (this.model.css != undefined) {
482 for (var selector in this.model.css) {
504 for (var selector in this.model.css) {
483 if (this.model.css.hasOwnProperty(selector)) {
505 if (this.model.css.hasOwnProperty(selector)) {
484
506
485 // Apply the css traits to all elements that match the selector.
507 // Apply the css traits to all elements that match the selector.
486 var elements = this._get_selector_element(selector);
508 var elements = this._get_selector_element(selector);
487 if (elements.length > 0) {
509 if (elements.length > 0) {
488 var css_traits = this.model.css[selector];
510 var css_traits = this.model.css[selector];
489 for (var css_key in css_traits) {
511 for (var css_key in css_traits) {
490 if (css_traits.hasOwnProperty(css_key)) {
512 if (css_traits.hasOwnProperty(css_key)) {
491 elements.css(css_key, css_traits[css_key]);
513 elements.css(css_key, css_traits[css_key]);
492 }
514 }
493 }
515 }
494 }
516 }
495 }
517 }
496 }
518 }
497 }
519 }
498 },
520 },
499
521
500 _get_selector_element: function(selector) {
522 _get_selector_element: function(selector) {
501 // Get the elements via the css selector. If the selector is
523 // Get the elements via the css selector. If the selector is
502 // blank, apply the style to the $el_to_style element. If
524 // blank, apply the style to the $el_to_style element. If
503 // the $el_to_style element is not defined, use apply the
525 // the $el_to_style element is not defined, use apply the
504 // style to the view's element.
526 // style to the view's element.
505 var elements = this.$el.find(selector);
527 var elements = this.$el.find(selector);
506 if (selector===undefined || selector===null || selector=='') {
528 if (selector===undefined || selector===null || selector=='') {
507 if (this.$el_to_style == undefined) {
529 if (this.$el_to_style == undefined) {
508 elements = this.$el;
530 elements = this.$el;
509 } else {
531 } else {
510 elements = this.$el_to_style;
532 elements = this.$el_to_style;
511 }
533 }
512 }
534 }
513 return elements;
535 return elements;
514 },
536 },
515 });
537 });
516
538
517
539
518 //--------------------------------------------------------------------
540 //--------------------------------------------------------------------
519 // WidgetManager class
541 // WidgetManager class
520 //--------------------------------------------------------------------
542 //--------------------------------------------------------------------
521 var WidgetManager = function(){
543 var WidgetManager = function(){
522 this.comm_manager = null;
544 this.comm_manager = null;
523 this.widget_model_types = {};
545 this.widget_model_types = {};
524 this.widget_view_types = {};
546 this.widget_view_types = {};
525
547
526 var that = this;
548 var that = this;
527 Backbone.sync = function(method, model, options, error) {
549 Backbone.sync = function(method, model, options, error) {
528 var result = model._handle_sync(method, options);
550 var result = model._handle_sync(method, options);
529 if (options.success) {
551 if (options.success) {
530 options.success(result);
552 options.success(result);
531 }
553 }
532 };
554 };
533 }
555 }
534
556
535
557
536 WidgetManager.prototype.attach_comm_manager = function (comm_manager) {
558 WidgetManager.prototype.attach_comm_manager = function (comm_manager) {
537 this.comm_manager = comm_manager;
559 this.comm_manager = comm_manager;
538
560
539 // Register already register widget model types with the comm manager.
561 // Register already register widget model types with the comm manager.
540 for (var widget_model_name in this.widget_model_types) {
562 for (var widget_model_name in this.widget_model_types) {
541 this.comm_manager.register_target(widget_model_name, $.proxy(this._handle_com_open, this));
563 this.comm_manager.register_target(widget_model_name, $.proxy(this._handle_com_open, this));
542 }
564 }
543 }
565 }
544
566
545
567
546 WidgetManager.prototype.register_widget_model = function (widget_model_name, widget_model_type) {
568 WidgetManager.prototype.register_widget_model = function (widget_model_name, widget_model_type) {
547 // Register the widget with the comm manager. Make sure to pass this object's context
569 // Register the widget with the comm manager. Make sure to pass this object's context
548 // in so `this` works in the call back.
570 // in so `this` works in the call back.
549 if (this.comm_manager!=null) {
571 if (this.comm_manager!=null) {
550 this.comm_manager.register_target(widget_model_name, $.proxy(this._handle_com_open, this));
572 this.comm_manager.register_target(widget_model_name, $.proxy(this._handle_com_open, this));
551 }
573 }
552 this.widget_model_types[widget_model_name] = widget_model_type;
574 this.widget_model_types[widget_model_name] = widget_model_type;
553 }
575 }
554
576
555
577
556 WidgetManager.prototype.register_widget_view = function (widget_view_name, widget_view_type) {
578 WidgetManager.prototype.register_widget_view = function (widget_view_name, widget_view_type) {
557 this.widget_view_types[widget_view_name] = widget_view_type;
579 this.widget_view_types[widget_view_name] = widget_view_type;
558 }
580 }
559
581
560
582
561 WidgetManager.prototype.get_msg_cell = function (msg_id) {
583 WidgetManager.prototype.get_msg_cell = function (msg_id) {
562 if (IPython.notebook != undefined && IPython.notebook != null) {
584 if (IPython.notebook != undefined && IPython.notebook != null) {
563 return IPython.notebook.get_msg_cell(msg_id);
585 return IPython.notebook.get_msg_cell(msg_id);
564 }
586 }
565 }
587 }
566
588
567
589
568 WidgetManager.prototype.on_create_widget = function (callback) {
590 WidgetManager.prototype.on_create_widget = function (callback) {
569 this._create_widget_callback = callback;
591 this._create_widget_callback = callback;
570 }
592 }
571
593
572
594
573 WidgetManager.prototype._handle_create_widget = function (widget_model) {
595 WidgetManager.prototype._handle_create_widget = function (widget_model) {
574 if (this._create_widget_callback) {
596 if (this._create_widget_callback) {
575 try {
597 try {
576 this._create_widget_callback(widget_model);
598 this._create_widget_callback(widget_model);
577 } catch (e) {
599 } catch (e) {
578 console.log("Exception in WidgetManager callback", e, widget_model);
600 console.log("Exception in WidgetManager callback", e, widget_model);
579 }
601 }
580 }
602 }
581 }
603 }
582
604
583
605
584 WidgetManager.prototype._handle_com_open = function (comm, msg) {
606 WidgetManager.prototype._handle_com_open = function (comm, msg) {
585 var widget_type_name = msg.content.target_name;
607 var widget_type_name = msg.content.target_name;
586 var widget_model = new this.widget_model_types[widget_type_name](this.comm_manager, comm, this);
608 var widget_model = new this.widget_model_types[widget_type_name](this.comm_manager, comm, this);
587 this._handle_create_widget(widget_model);
609 this._handle_create_widget(widget_model);
588 }
610 }
589
611
590
612
591 //--------------------------------------------------------------------
613 //--------------------------------------------------------------------
592 // Init code
614 // Init code
593 //--------------------------------------------------------------------
615 //--------------------------------------------------------------------
594 IPython.WidgetManager = WidgetManager;
616 IPython.WidgetManager = WidgetManager;
595 IPython.WidgetModel = WidgetModel;
617 IPython.WidgetModel = WidgetModel;
596 IPython.WidgetView = WidgetView;
618 IPython.WidgetView = WidgetView;
597
619
598 if (IPython.widget_manager==undefined || IPython.widget_manager==null) {
620 if (IPython.widget_manager==undefined || IPython.widget_manager==null) {
599 IPython.widget_manager = new WidgetManager();
621 IPython.widget_manager = new WidgetManager();
600 }
622 }
601
623
602 return IPython.widget_manager;
624 return IPython.widget_manager;
603 });
625 });
@@ -1,60 +1,59 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
2 // Copyright (C) 2013 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // ButtonWidget
9 // ButtonWidget
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 **/
15 **/
16
16
17 define(["notebook/js/widget"], function(widget_manager){
17 define(["notebook/js/widget"], function(widget_manager){
18
18
19 var ButtonWidgetModel = IPython.WidgetModel.extend({});
19 var ButtonWidgetModel = IPython.WidgetModel.extend({});
20 widget_manager.register_widget_model('ButtonWidgetModel', ButtonWidgetModel);
20 widget_manager.register_widget_model('ButtonWidgetModel', ButtonWidgetModel);
21
21
22 var ButtonView = IPython.WidgetView.extend({
22 var ButtonView = IPython.WidgetView.extend({
23
23
24 // Called when view is rendered.
24 // Called when view is rendered.
25 render : function(){
25 render : function(){
26 var that = this;
26 var that = this;
27 this.setElement($("<button />")
27 this.setElement($("<button />")
28 .addClass('btn'));
28 .addClass('btn'));
29
29
30 this.update(); // Set defaults.
30 this.update(); // Set defaults.
31 },
31 },
32
32
33 // Handles: Backend -> Frontend Sync
33 // Handles: Backend -> Frontend Sync
34 // Frontent -> Frontend Sync
34 // Frontent -> Frontend Sync
35 update : function(){
35 update : function(){
36 var description = this.model.get('description');
36 var description = this.model.get('description');
37 description = description.replace(/ /g, '&nbsp;', 'm');
37 description = description.replace(/ /g, '&nbsp;', 'm');
38 description = description.replace(/\n/g, '<br>\n', 'm');
38 description = description.replace(/\n/g, '<br>\n', 'm');
39 if (description.length == 0) {
39 if (description.length == 0) {
40 this.$el.html('&nbsp;'); // Preserve button height
40 this.$el.html('&nbsp;'); // Preserve button height
41 } else {
41 } else {
42 this.$el.html(description);
42 this.$el.html(description);
43 }
43 }
44
44
45 return IPython.WidgetView.prototype.update.call(this);
45 return IPython.WidgetView.prototype.update.call(this);
46 },
46 },
47
47
48 events: {
48 events: {
49 'click': '_handle_click',
49 'click': '_handle_click',
50 },
50 },
51
51
52 _handle_click: function(){
52 _handle_click: function(){
53 this.model.last_modified_view = this; // For callbacks.
53 this.send({event: 'click'});
54 this.model.send({event: 'click'});
55 },
54 },
56 });
55 });
57
56
58 widget_manager.register_widget_view('ButtonView', ButtonView);
57 widget_manager.register_widget_view('ButtonView', ButtonView);
59
58
60 });
59 });
@@ -1,161 +1,167 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
2 // Copyright (C) 2013 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // StringWidget
9 // StringWidget
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 **/
15 **/
16
16
17 define(["notebook/js/widget"], function(widget_manager){
17 define(["notebook/js/widget"], function(widget_manager){
18 var StringWidgetModel = IPython.WidgetModel.extend({});
18 var StringWidgetModel = IPython.WidgetModel.extend({});
19 widget_manager.register_widget_model('StringWidgetModel', StringWidgetModel);
19 widget_manager.register_widget_model('StringWidgetModel', StringWidgetModel);
20
20
21 var LabelView = IPython.WidgetView.extend({
21 var LabelView = IPython.WidgetView.extend({
22
22
23 // Called when view is rendered.
23 // Called when view is rendered.
24 render : function(){
24 render : function(){
25 this.update(); // Set defaults.
25 this.update(); // Set defaults.
26 },
26 },
27
27
28 // Handles: Backend -> Frontend Sync
28 // Handles: Backend -> Frontend Sync
29 // Frontent -> Frontend Sync
29 // Frontent -> Frontend Sync
30 update : function(){
30 update : function(){
31 this.$el.html(this.model.get('value'));
31 this.$el.html(this.model.get('value'));
32 return IPython.WidgetView.prototype.update.call(this);
32 return IPython.WidgetView.prototype.update.call(this);
33 },
33 },
34
34
35 });
35 });
36
36
37 widget_manager.register_widget_view('LabelView', LabelView);
37 widget_manager.register_widget_view('LabelView', LabelView);
38
38
39 var TextAreaView = IPython.WidgetView.extend({
39 var TextAreaView = IPython.WidgetView.extend({
40
40
41 // Called when view is rendered.
41 // Called when view is rendered.
42 render : function(){
42 render: function(){
43 this.$el
43 this.$el
44 .addClass('widget-hbox')
44 .addClass('widget-hbox')
45 .html('');
45 .html('');
46 this.$label = $('<div />')
46 this.$label = $('<div />')
47 .appendTo(this.$el)
47 .appendTo(this.$el)
48 .addClass('widget-hlabel')
48 .addClass('widget-hlabel')
49 .hide();
49 .hide();
50 this.$textbox = $('<textarea />')
50 this.$textbox = $('<textarea />')
51 .attr('rows', 5)
51 .attr('rows', 5)
52 .addClass('widget-text')
52 .addClass('widget-text')
53 .appendTo(this.$el);
53 .appendTo(this.$el);
54 this.$el_to_style = this.$textbox; // Set default element to style
54 this.$el_to_style = this.$textbox; // Set default element to style
55 this.update(); // Set defaults.
55 this.update(); // Set defaults.
56
57 this.on_msg();
58 },
59
60
61 _handle_textarea_msg: function (content){
62 if (content.method == "scroll_to_bottom") {
63 this.scroll_to_bottom();
64 }
65 },
66
67
68 scroll_to_bottom: function (){
69 this.$textbox.scrollTop(this.$textbox[0].scrollHeight);
56 },
70 },
71
57
72
58 // Handles: Backend -> Frontend Sync
73 // Handles: Backend -> Frontend Sync
59 // Frontent -> Frontend Sync
74 // Frontent -> Frontend Sync
60 update : function(){
75 update: function(){
61 if (!this.user_invoked_update) {
76 if (!this.user_invoked_update) {
62 this.$textbox.val(this.model.get('value'));
77 this.$textbox.val(this.model.get('value'));
63 }
78 }
64
79
65 if (this.last_scroll_to_bottom == undefined) {
66 this.last_scroll_to_bottom = 0;
67 }
68 if (this.last_scroll_to_bottom < this.model.get('scroll_to_bottoms')) {
69 this.last_scroll_to_bottom < this.model.get('scroll_to_bottoms');
70 this.$textbox.scrollTop(this.$textbox[0].scrollHeight);
71 }
72
73 var disabled = this.model.get('disabled');
80 var disabled = this.model.get('disabled');
74 this.$textbox.prop('disabled', disabled);
81 this.$textbox.prop('disabled', disabled);
75
82
76 var description = this.model.get('description');
83 var description = this.model.get('description');
77 if (description.length == 0) {
84 if (description.length == 0) {
78 this.$label.hide();
85 this.$label.hide();
79 } else {
86 } else {
80 this.$label.html(description);
87 this.$label.html(description);
81 this.$label.show();
88 this.$label.show();
82 }
89 }
83 return IPython.WidgetView.prototype.update.call(this);
90 return IPython.WidgetView.prototype.update.call(this);
84 },
91 },
85
92
86 events: {"keyup textarea" : "handleChanging",
93 events: {"keyup textarea": "handleChanging",
87 "paste textarea" : "handleChanging",
94 "paste textarea": "handleChanging",
88 "cut textarea" : "handleChanging"},
95 "cut textarea": "handleChanging"},
89
96
90 // Handles and validates user input.
97 // Handles and validates user input.
91 handleChanging: function(e) {
98 handleChanging: function(e) {
92 this.user_invoked_update = true;
99 this.user_invoked_update = true;
93 this.model.set('value', e.target.value);
100 this.model.set('value', e.target.value);
94 this.model.update_other_views(this);
101 this.model.update_other_views(this);
95 this.user_invoked_update = false;
102 this.user_invoked_update = false;
96 },
103 },
97 });
104 });
98
105
99 widget_manager.register_widget_view('TextAreaView', TextAreaView);
106 widget_manager.register_widget_view('TextAreaView', TextAreaView);
100
107
101 var TextBoxView = IPython.WidgetView.extend({
108 var TextBoxView = IPython.WidgetView.extend({
102
109
103 // Called when view is rendered.
110 // Called when view is rendered.
104 render : function(){
111 render: function(){
105 this.$el
112 this.$el
106 .addClass('widget-hbox-single')
113 .addClass('widget-hbox-single')
107 .html('');
114 .html('');
108 this.$label = $('<div />')
115 this.$label = $('<div />')
109 .addClass('widget-hlabel')
116 .addClass('widget-hlabel')
110 .appendTo(this.$el)
117 .appendTo(this.$el)
111 .hide();
118 .hide();
112 this.$textbox = $('<input type="text" />')
119 this.$textbox = $('<input type="text" />')
113 .addClass('input')
120 .addClass('input')
114 .addClass('widget-text')
121 .addClass('widget-text')
115 .appendTo(this.$el);
122 .appendTo(this.$el);
116 this.$el_to_style = this.$textbox; // Set default element to style
123 this.$el_to_style = this.$textbox; // Set default element to style
117 this.update(); // Set defaults.
124 this.update(); // Set defaults.
118 },
125 },
119
126
120 // Handles: Backend -> Frontend Sync
127 // Handles: Backend -> Frontend Sync
121 // Frontent -> Frontend Sync
128 // Frontent -> Frontend Sync
122 update : function(){
129 update: function(){
123 if (this.$textbox.val() != this.model.get('value')) {
130 if (this.$textbox.val() != this.model.get('value')) {
124 this.$textbox.val(this.model.get('value'));
131 this.$textbox.val(this.model.get('value'));
125 }
132 }
126
133
127 var disabled = this.model.get('disabled');
134 var disabled = this.model.get('disabled');
128 this.$textbox.prop('disabled', disabled);
135 this.$textbox.prop('disabled', disabled);
129
136
130 var description = this.model.get('description');
137 var description = this.model.get('description');
131 if (description.length == 0) {
138 if (description.length == 0) {
132 this.$label.hide();
139 this.$label.hide();
133 } else {
140 } else {
134 this.$label.html(description);
141 this.$label.html(description);
135 this.$label.show();
142 this.$label.show();
136 }
143 }
137 return IPython.WidgetView.prototype.update.call(this);
144 return IPython.WidgetView.prototype.update.call(this);
138 },
145 },
139
146
140 events: {"keyup input" : "handleChanging",
147 events: {"keyup input": "handleChanging",
141 "paste input" : "handleChanging",
148 "paste input": "handleChanging",
142 "cut input" : "handleChanging",
149 "cut input": "handleChanging",
143 "keypress input" : "handleKeypress"},
150 "keypress input": "handleKeypress"},
144
151
145 // Handles and validates user input.
152 // Handles and validates user input.
146 handleChanging: function(e) {
153 handleChanging: function(e) {
147 this.model.set('value', e.target.value);
154 this.model.set('value', e.target.value);
148 this.model.update_other_views(this);
155 this.model.update_other_views(this);
149 },
156 },
150
157
151 // Handles text submition
158 // Handles text submition
152 handleKeypress: function(e) {
159 handleKeypress: function(e) {
153 if (e.keyCode == 13) { // Return key
160 if (e.keyCode == 13) { // Return key
154 this.model.last_modified_view = this; // For callbacks.
161 this.send({event: 'submit'});
155 this.model.send({event: 'submit'});
156 }
162 }
157 },
163 },
158 });
164 });
159
165
160 widget_manager.register_widget_view('TextBoxView', TextBoxView);
166 widget_manager.register_widget_view('TextBoxView', TextBoxView);
161 });
167 });
@@ -1,112 +1,111 b''
1 """StringWidget class.
1 """StringWidget class.
2
2
3 Represents a unicode string using a widget.
3 Represents a unicode string using a widget.
4 """
4 """
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 import inspect
16 import inspect
17 import types
17 import types
18
18
19 from .widget import Widget
19 from .widget import Widget
20 from IPython.utils.traitlets import Unicode, Bool, List, Int
20 from IPython.utils.traitlets import Unicode, Bool, List, Int
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Classes
23 # Classes
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 class StringWidget(Widget):
25 class StringWidget(Widget):
26 target_name = Unicode('StringWidgetModel')
26 target_name = Unicode('StringWidgetModel')
27 default_view_name = Unicode('TextBoxView')
27 default_view_name = Unicode('TextBoxView')
28
28
29 # Keys
29 # Keys
30 _keys = ['value', 'disabled', 'description', 'scroll_to_bottoms']
30 _keys = ['value', 'disabled', 'description']
31 value = Unicode(help="String value")
31 value = Unicode(help="String value")
32 disabled = Bool(False, help="Enable or disable user changes")
32 disabled = Bool(False, help="Enable or disable user changes")
33 description = Unicode(help="Description of the value this widget represents")
33 description = Unicode(help="Description of the value this widget represents")
34 scroll_to_bottoms = Int(0, help="Used to scroll a TextAreaView to the bottom")
35
34
36
35
37 def __init__(self, **kwargs):
36 def __init__(self, **kwargs):
38 super(StringWidget, self).__init__(**kwargs)
37 super(StringWidget, self).__init__(**kwargs)
39 self._submission_callbacks = []
38 self._submission_callbacks = []
40 self.on_msg(self._handle_string_msg)
39 self.on_msg(self._handle_string_msg)
41
40
42
41
43 def scroll_to_bottom(self):
42 def scroll_to_bottom(self):
44 self.scroll_to_bottoms += 1
43 self._comm.send({"method": "scroll_to_bottom"})
45
44
46
45
47 def on_click(self, callback, remove=False):
46 def on_click(self, callback, remove=False):
48 """Register a callback to execute when the button is clicked. The
47 """Register a callback to execute when the button is clicked. The
49 callback can either accept no parameters or one sender parameter:
48 callback can either accept no parameters or one sender parameter:
50 - callback()
49 - callback()
51 - callback(sender)
50 - callback(sender)
52 If the callback has a sender parameter, the ButtonWidget instance that
51 If the callback has a sender parameter, the ButtonWidget instance that
53 called the callback will be passed into the method as the sender.
52 called the callback will be passed into the method as the sender.
54
53
55 Parameters
54 Parameters
56 ----------
55 ----------
57 remove : bool (optional)
56 remove : bool (optional)
58 Set to true to remove the callback from the list of callbacks."""
57 Set to true to remove the callback from the list of callbacks."""
59 if remove:
58 if remove:
60 self._click_handlers.remove(callback)
59 self._click_handlers.remove(callback)
61 elif not callback in self._click_handlers:
60 elif not callback in self._click_handlers:
62 self._click_handlers.append(callback)
61 self._click_handlers.append(callback)
63
62
64
63
65 def _handle_string_msg(self, content):
64 def _handle_string_msg(self, content):
66 """Handle a msg from the front-end
65 """Handle a msg from the front-end
67
66
68 Parameters
67 Parameters
69 ----------
68 ----------
70 content: dict
69 content: dict
71 Content of the msg."""
70 Content of the msg."""
72 if 'event' in content and content['event'] == 'submit':
71 if 'event' in content and content['event'] == 'submit':
73 self._handle_submit()
72 self._handle_submit()
74
73
75
74
76 def on_submit(self, callback, remove=False):
75 def on_submit(self, callback, remove=False):
77 """Register a callback to handle text submission (triggered when the
76 """Register a callback to handle text submission (triggered when the
78 user clicks enter).
77 user clicks enter).
79
78
80 Parameters
79 Parameters
81 callback: Method handle
80 callback: Method handle
82 Function to be called when the text has been submitted. Function
81 Function to be called when the text has been submitted. Function
83 can have two possible signatures:
82 can have two possible signatures:
84 callback()
83 callback()
85 callback(sender)
84 callback(sender)
86 remove: bool (optional)
85 remove: bool (optional)
87 Whether or not to unregister the callback"""
86 Whether or not to unregister the callback"""
88 if remove and callback in self._submission_callbacks:
87 if remove and callback in self._submission_callbacks:
89 self._submission_callbacks.remove(callback)
88 self._submission_callbacks.remove(callback)
90 elif not remove and not callback in self._submission_callbacks:
89 elif not remove and not callback in self._submission_callbacks:
91 self._submission_callbacks.append(callback)
90 self._submission_callbacks.append(callback)
92
91
93
92
94 def _handle_submit(self):
93 def _handle_submit(self):
95 """Handles when a string widget view is submitted."""
94 """Handles when a string widget view is submitted."""
96 for handler in self._submission_callbacks:
95 for handler in self._submission_callbacks:
97 if callable(handler):
96 if callable(handler):
98 argspec = inspect.getargspec(handler)
97 argspec = inspect.getargspec(handler)
99 nargs = len(argspec[0])
98 nargs = len(argspec[0])
100
99
101 # Bound methods have an additional 'self' argument
100 # Bound methods have an additional 'self' argument
102 if isinstance(handler, types.MethodType):
101 if isinstance(handler, types.MethodType):
103 nargs -= 1
102 nargs -= 1
104
103
105 # Call the callback
104 # Call the callback
106 if nargs == 0:
105 if nargs == 0:
107 handler()
106 handler()
108 elif nargs == 1:
107 elif nargs == 1:
109 handler(self)
108 handler(self)
110 else:
109 else:
111 raise TypeError('StringWidget submit callback must ' \
110 raise TypeError('StringWidget submit callback must ' \
112 'accept 0 or 1 arguments.')
111 'accept 0 or 1 arguments.')
General Comments 0
You need to be logged in to leave comments. Login now