##// END OF EJS Templates
Moved touch logic out of model into view....
Jonathan Frederic -
Show More
@@ -1,562 +1,555
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 // Base Widget Model and View classes
10 10 //============================================================================
11 11
12 12 /**
13 13 * @module IPython
14 14 * @namespace IPython
15 15 **/
16 16
17 17 define(["notebook/js/widgetmanager",
18 18 "components/underscore/underscore-min",
19 19 "components/backbone/backbone-min"],
20 20 function(widget_manager, underscore, backbone){
21 21
22 22 //--------------------------------------------------------------------
23 23 // WidgetModel class
24 24 //--------------------------------------------------------------------
25 25 var WidgetModel = Backbone.Model.extend({
26 26 constructor: function (widget_manager, widget_id, comm) {
27 27 this.widget_manager = widget_manager;
28 28 this.pending_msgs = 0;
29 29 this.msg_throttle = 3;
30 30 this.msg_buffer = null;
31 31 this.views = [];
32 32 this.id = widget_id;
33 33 this._custom_msg_callbacks = [];
34 34
35 35 if (comm !== undefined) {
36 36
37 37 // Remember comm associated with the model.
38 38 this.comm = comm;
39 39 comm.model = this;
40 40
41 41 // Hook comm messages up to model.
42 42 comm.on_close($.proxy(this._handle_comm_closed, this));
43 43 comm.on_msg($.proxy(this._handle_comm_msg, this));
44 44 }
45 45
46 46 return Backbone.Model.apply(this);
47 47 },
48
49
50 update_other_views: function (caller) {
51 this.last_modified_view = caller;
52 this.save(this.changedAttributes(), {patch: true});
53
54 for (var view_index in this.views) {
55 var view = this.views[view_index];
56 if (view !== caller) {
57 view.update();
58 }
59 }
60 },
61 48
62 49
63 50 send: function (content, cell) {
64 51 if (this._has_comm()) {
65 52 // Used the last modified view as the sender of the message. This
66 53 // will insure that any python code triggered by the sent message
67 54 // can create and display widgets and output.
68 55 if (cell === undefined) {
69 56 if (this.last_modified_view !== undefined &&
70 57 this.last_modified_view.cell !== undefined) {
71 58 cell = this.last_modified_view.cell;
72 59 }
73 60 }
74 61 var callbacks = this._make_callbacks(cell);
75 62 var data = {method: 'custom', custom_content: content};
76 63
77 64 this.comm.send(data, callbacks);
78 65 }
79 66 },
80 67
81 68
82 69 on_view_created: function (callback) {
83 70 this._view_created_callback = callback;
84 71 },
85 72
86 73
87 74 on_close: function (callback) {
88 75 this._close_callback = callback;
89 76 },
90 77
91 78
92 79 on_msg: function (callback, remove) {
93 80 if (remove) {
94 81 var found_index = -1;
95 82 for (var index in this._custom_msg_callbacks) {
96 83 if (callback === this._custom_msg_callbacks[index]) {
97 84 found_index = index;
98 85 break;
99 86 }
100 87 }
101 88
102 89 if (found_index >= 0) {
103 90 this._custom_msg_callbacks.splice(found_index, 1);
104 91 }
105 92 } else {
106 93 this._custom_msg_callbacks.push(callback);
107 94 }
108 95 },
109 96
110 97
111 98 _handle_custom_msg: function (content) {
112 99 for (var index in this._custom_msg_callbacks) {
113 100 try {
114 101 this._custom_msg_callbacks[index](content);
115 102 } catch (e) {
116 103 console.log("Exception in widget model msg callback", e, content);
117 104 }
118 105 }
119 106 },
120 107
121 108
122 109 // Handle when a widget is closed.
123 110 _handle_comm_closed: function (msg) {
124 111 this._execute_views_method('remove');
125 112 if (this._has_comm()) {
126 113 delete this.comm.model; // Delete ref so GC will collect widget model.
127 114 delete this.comm;
128 115 }
129 116 delete this.widget_id; // Delete id from model so widget manager cleans up.
130 117 },
131 118
132 119
133 120 // Handle incomming comm msg.
134 121 _handle_comm_msg: function (msg) {
135 122 var method = msg.content.data.method;
136 123 switch (method) {
137 124 case 'display':
138 125
139 126 // Try to get the cell.
140 127 var cell = this._get_msg_cell(msg.parent_header.msg_id);
141 128 if (cell === null) {
142 129 console.log("Could not determine where the display" +
143 130 " message was from. Widget will not be displayed");
144 131 } else {
145 132 this.create_views(msg.content.data.view_name,
146 133 msg.content.data.parent,
147 134 cell);
148 135 }
149 136 break;
150 137 case 'update':
151 138 this.apply_update(msg.content.data.state);
152 139 break;
153 140 case 'add_class':
154 141 case 'remove_class':
155 142 var selector = msg.content.data.selector;
156 143 if (selector === undefined) {
157 144 selector = '';
158 145 }
159 146
160 147 var class_list = msg.content.data.class_list;
161 148 this._execute_views_method(method, selector, class_list);
162 149 break;
163 150 case 'set_snapshot':
164 151 var cell = this._get_msg_cell(msg.parent_header.msg_id);
165 152 cell.metadata.snapshot = msg.content.data.snapshot;
166 153 break;
167 154 case 'custom':
168 155 this._handle_custom_msg(msg.content.data.custom_content);
169 156 break;
170 157 }
171 158 },
172 159
173 160
174 161 // Handle when a widget is updated via the python side.
175 162 apply_update: function (state) {
176 163 this.updating = true;
177 164 try {
178 165 for (var key in state) {
179 166 if (state.hasOwnProperty(key)) {
180 167 if (key == "_css") {
181 168
182 169 // Set the css value of the model as an attribute
183 170 // instead of a backbone trait because we are only
184 171 // interested in backend css -> frontend css. In
185 172 // other words, if the css dict changes in the
186 173 // frontend, we don't need to push the changes to
187 174 // the backend.
188 175 this.css = state[key];
189 176 } else {
190 177 this.set(key, state[key]);
191 178 }
192 179 }
193 180 }
194 181 this.save();
195 182 } finally {
196 183 this.updating = false;
197 184 }
198 185 },
199 186
200 187
201 188 _handle_status: function (cell, msg) {
202 189 //execution_state : ('busy', 'idle', 'starting')
203 190 if (this._has_comm()) {
204 191 if (msg.content.execution_state=='idle') {
205 192
206 193 // Send buffer if this message caused another message to be
207 194 // throttled.
208 195 if (this.msg_buffer !== null &&
209 196 this.msg_throttle == this.pending_msgs) {
210 197
211 198 var callbacks = this._make_callbacks(cell);
212 199 var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
213 200 this.comm.send(data, callbacks);
214 201 this.msg_buffer = null;
215 202 } else {
216 203
217 204 // Only decrease the pending message count if the buffer
218 205 // doesn't get flushed (sent).
219 206 --this.pending_msgs;
220 207 }
221 208 }
222 209 }
223 210 },
224 211
225 212
226 213 // Custom syncronization logic.
227 214 _handle_sync: function (method, options) {
228 215 var model_json = this.toJSON();
229 216 var attr;
230 217
231 218 // Only send updated state if the state hasn't been changed
232 219 // during an update.
233 220 if (this._has_comm()) {
234 221 if (!this.updating) {
235 222 if (this.pending_msgs >= this.msg_throttle) {
236 223 // The throttle has been exceeded, buffer the current msg so
237 224 // it can be sent once the kernel has finished processing
238 225 // some of the existing messages.
239 226 if (method=='patch') {
240 227 if (this.msg_buffer === null) {
241 228 this.msg_buffer = $.extend({}, model_json); // Copy
242 229 }
243 230 for (attr in options.attrs) {
244 231 this.msg_buffer[attr] = options.attrs[attr];
245 232 }
246 233 } else {
247 234 this.msg_buffer = $.extend({}, model_json); // Copy
248 235 }
249 236
250 237 } else {
251 238 // We haven't exceeded the throttle, send the message like
252 239 // normal. If this is a patch operation, just send the
253 240 // changes.
254 241 var send_json = model_json;
255 242 if (method =='patch') {
256 243 send_json = {};
257 244 for (attr in options.attrs) {
258 245 send_json[attr] = options.attrs[attr];
259 246 }
260 247 }
261 248
262 249 var data = {method: 'backbone', sync_method: method, sync_data: send_json};
263 250
264 251 var cell = null;
265 252 if (this.last_modified_view !== undefined && this.last_modified_view !== null) {
266 253 cell = this.last_modified_view.cell;
267 254 }
268 255
269 256 var callbacks = this._make_callbacks(cell);
270 257 this.comm.send(data, callbacks);
271 258 this.pending_msgs++;
272 259 }
273 260 }
274 261 }
275 262
276 263 // Since the comm is a one-way communication, assume the message
277 264 // arrived.
278 265 return model_json;
279 266 },
280 267
281 268
282 269 _handle_view_created: function (view) {
283 270 if (this._view_created_callback) {
284 271 try {
285 272 this._view_created_callback(view);
286 273 } catch (e) {
287 274 console.log("Exception in widget model view displayed callback", e, view, this);
288 275 }
289 276 }
290 277 },
291 278
292 279
293 280 _execute_views_method: function (/* method_name, [argument0], [argument1], [...] */) {
294 281 var method_name = arguments[0];
295 282 var args = null;
296 283 if (arguments.length > 1) {
297 284 args = [].splice.call(arguments,1);
298 285 }
299 286
300 287 for (var view_index in this.views) {
301 288 var view = this.views[view_index];
302 289 var method = view[method_name];
303 290 if (args === null) {
304 291 method.apply(view);
305 292 } else {
306 293 method.apply(view, args);
307 294 }
308 295 }
309 296 },
310 297
311 298
312 299 // Create view that represents the model.
313 300 create_views: function (view_name, parent_id, cell) {
314 301 var new_views = [];
315 302 var view;
316 303
317 304 // Try creating and adding the view to it's parent.
318 305 var displayed = false;
319 306 if (parent_id !== undefined) {
320 307 var parent_model = this.widget_manager.get_model(parent_id);
321 308 if (parent_model !== null) {
322 309 var parent_views = parent_model.views;
323 310 for (var parent_view_index in parent_views) {
324 311 var parent_view = parent_views[parent_view_index];
325 312 if (parent_view.cell === cell) {
326 313 if (parent_view.display_child !== undefined) {
327 314 view = this._create_view(view_name, cell);
328 315 if (view !== null) {
329 316 new_views.push(view);
330 317 parent_view.display_child(view);
331 318 displayed = true;
332 319 this._handle_view_created(view);
333 320 }
334 321 }
335 322 }
336 323 }
337 324 }
338 325 }
339 326
340 327 // If no parent view is defined or exists. Add the view's
341 328 // element to cell's widget div.
342 329 if (!displayed) {
343 330 view = this._create_view(view_name, cell);
344 331 if (view !== null) {
345 332 new_views.push(view);
346 333
347 334 if (cell.widget_subarea !== undefined && cell.widget_subarea !== null) {
348 335 cell.widget_area.show();
349 336 cell.widget_subarea.append(view.$el);
350 337 this._handle_view_created(view);
351 338 }
352 339 }
353 340 }
354 341
355 342 // Force the new view(s) to update their selves
356 343 for (var view_index in new_views) {
357 344 view = new_views[view_index];
358 345 view.update();
359 346 }
360 347 },
361 348
362 349
363 350 // Create a view
364 351 _create_view: function (view_name, cell) {
365 352 var ViewType = this.widget_manager.widget_view_types[view_name];
366 353 if (ViewType !== undefined && ViewType !== null) {
367 354 var view = new ViewType({model: this});
368 355 view.render();
369 356 this.views.push(view);
370 357 view.cell = cell;
371 358
372 359 // Handle when the view element is remove from the page.
373 360 var that = this;
374 361 view.$el.on("remove", function () {
375 362 var index = that.views.indexOf(view);
376 363 if (index > -1) {
377 364 that.views.splice(index, 1);
378 365 }
379 366 view.remove(); // Clean-up view
380 367
381 368 // Close the comm if there are no views left.
382 369 if (that.views.length() === 0) {
383 370 if (that._close_callback) {
384 371 try {
385 372 that._close_callback(that);
386 373 } catch (e) {
387 374 console.log("Exception in widget model close callback", e, that);
388 375 }
389 376 }
390 377
391 378 if (that._has_comm()) {
392 379 that.comm.close();
393 380 delete that.comm.model; // Delete ref so GC will collect widget model.
394 381 delete that.comm;
395 382 }
396 383 delete that.widget_id; // Delete id from model so widget manager cleans up.
397 384 }
398 385 });
399 386 return view;
400 387 }
401 388 return null;
402 389 },
403 390
404 391
405 392 // Build a callback dict.
406 393 _make_callbacks: function (cell) {
407 394 var callbacks = {};
408 395 if (cell !== null) {
409 396
410 397 // Try to get output handlers
411 398 var handle_output = null;
412 399 var handle_clear_output = null;
413 400 if (cell.output_area !== undefined && cell.output_area !== null) {
414 401 handle_output = $.proxy(cell.output_area.handle_output, cell.output_area);
415 402 handle_clear_output = $.proxy(cell.output_area.handle_clear_output, cell.output_area);
416 403 }
417 404
418 405 // Create callback dict usign what is known
419 406 var that = this;
420 407 callbacks = {
421 408 iopub : {
422 409 output : handle_output,
423 410 clear_output : handle_clear_output,
424 411
425 412 status : function (msg) {
426 413 that._handle_status(cell, msg);
427 414 },
428 415
429 416 // Special function only registered by widget messages.
430 417 // Allows us to get the cell for a message so we know
431 418 // where to add widgets if the code requires it.
432 419 get_cell : function () {
433 420 return cell;
434 421 },
435 422 },
436 423 };
437 424 }
438 425 return callbacks;
439 426 },
440 427
441 428
442 429 // Get the output area corresponding to the msg_id.
443 430 // cell is an instance of IPython.Cell
444 431 _get_msg_cell: function (msg_id) {
445 432
446 433 // First, check to see if the msg was triggered by cell execution.
447 434 var cell = this.widget_manager.get_msg_cell(msg_id);
448 435 if (cell !== null) {
449 436 return cell;
450 437 }
451 438
452 439 // Second, check to see if a get_cell callback was defined
453 440 // for the message. get_cell callbacks are registered for
454 441 // widget messages, so this block is actually checking to see if the
455 442 // message was triggered by a widget.
456 443 var kernel = this.widget_manager.get_kernel();
457 444 if (kernel !== undefined && kernel !== null) {
458 445 var callbacks = kernel.get_callbacks_for_msg(msg_id);
459 446 if (callbacks !== undefined &&
460 447 callbacks.iopub !== undefined &&
461 448 callbacks.iopub.get_cell !== undefined) {
462 449
463 450 return callbacks.iopub.get_cell();
464 451 }
465 452 }
466 453
467 454 // Not triggered by a cell or widget (no get_cell callback
468 455 // exists).
469 456 return null;
470 457 },
471 458
472 459
473 460 // Function that checks if a comm has been attached to this widget
474 461 // model. Returns True if a valid comm is attached.
475 462 _has_comm: function() {
476 463 return this.comm !== undefined && this.comm !== null;
477 464 },
478 465 });
479 466
480 467
481 468 //--------------------------------------------------------------------
482 469 // WidgetView class
483 470 //--------------------------------------------------------------------
484 471 var WidgetView = Backbone.View.extend({
485 472
486 473 initialize: function () {
487 474 this.visible = true;
488 475 this.model.on('sync',this.update,this);
489 476 },
490 477
491 478 add_class: function (selector, class_list) {
492 479 var elements = this._get_selector_element(selector);
493 480 if (elements.length > 0) {
494 481 elements.addClass(class_list);
495 482 }
496 483 },
497 484
498 485 remove_class: function (selector, class_list) {
499 486 var elements = this._get_selector_element(selector);
500 487 if (elements.length > 0) {
501 488 elements.removeClass(class_list);
502 489 }
503 490 },
504 491
505 492
506 493 send: function (content) {
507 494 this.model.send(content, this.cell);
508 495 },
509 496
497
498 touch: function () {
499 this.model.last_modified_view = this;
500 this.model.save(this.model.changedAttributes(), {patch: true});
501 },
502
510 503 update: function () {
511 504 if (this.model.get('visible') !== undefined) {
512 505 if (this.visible != this.model.get('visible')) {
513 506 this.visible = this.model.get('visible');
514 507 if (this.visible) {
515 508 this.$el.show();
516 509 } else {
517 510 this.$el.hide();
518 511 }
519 512 }
520 513 }
521 514
522 515 if (this.model.css !== undefined) {
523 516 for (var selector in this.model.css) {
524 517 if (this.model.css.hasOwnProperty(selector)) {
525 518
526 519 // Apply the css traits to all elements that match the selector.
527 520 var elements = this._get_selector_element(selector);
528 521 if (elements.length > 0) {
529 522 var css_traits = this.model.css[selector];
530 523 for (var css_key in css_traits) {
531 524 if (css_traits.hasOwnProperty(css_key)) {
532 525 elements.css(css_key, css_traits[css_key]);
533 526 }
534 527 }
535 528 }
536 529 }
537 530 }
538 531 }
539 532 },
540 533
541 534 _get_selector_element: function (selector) {
542 535 // Get the elements via the css selector. If the selector is
543 536 // blank, apply the style to the $el_to_style element. If
544 537 // the $el_to_style element is not defined, use apply the
545 538 // style to the view's element.
546 539 var elements = this.$el.find(selector);
547 540 if (selector === undefined || selector === null || selector === '') {
548 541 if (this.$el_to_style === undefined) {
549 542 elements = this.$el;
550 543 } else {
551 544 elements = this.$el_to_style;
552 545 }
553 546 }
554 547 return elements;
555 548 },
556 549 });
557 550
558 551 IPython.WidgetModel = WidgetModel;
559 552 IPython.WidgetView = WidgetView;
560 553
561 554 return widget_manager;
562 555 }); No newline at end of file
@@ -1,123 +1,123
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 // BoolWidget
10 10 //============================================================================
11 11
12 12 /**
13 13 * @module IPython
14 14 * @namespace IPython
15 15 **/
16 16
17 17 define(["notebook/js/widgets/base"], function(widget_manager){
18 18
19 19 var BoolWidgetModel = IPython.WidgetModel.extend({});
20 20 widget_manager.register_widget_model('BoolWidgetModel', BoolWidgetModel);
21 21
22 22 var CheckboxView = IPython.WidgetView.extend({
23 23
24 24 // Called when view is rendered.
25 25 render : function(){
26 26 this.$el
27 27 .addClass('widget-hbox-single');
28 28 this.$label = $('<div />')
29 29 .addClass('widget-hlabel')
30 30 .appendTo(this.$el)
31 31 .hide();
32 32 var that = this;
33 33 this.$checkbox = $('<input />')
34 34 .attr('type', 'checkbox')
35 35 .click(function(el) {
36 36 that.user_invoked_update = true;
37 37 that.model.set('value', that.$checkbox.prop('checked'));
38 that.model.update_other_views(that);
38 that.touch();
39 39 that.user_invoked_update = false;
40 40 })
41 41 .appendTo(this.$el);
42 42
43 43 this.$el_to_style = this.$checkbox; // Set default element to style
44 44 this.update(); // Set defaults.
45 45 },
46 46
47 47 // Handles: Backend -> Frontend Sync
48 48 // Frontent -> Frontend Sync
49 49 update : function(){
50 50 if (!this.user_invoked_update) {
51 51 this.$checkbox.prop('checked', this.model.get('value'));
52 52
53 53 var disabled = this.model.get('disabled');
54 54 this.$checkbox.prop('disabled', disabled);
55 55
56 56 var description = this.model.get('description');
57 57 if (description.length === 0) {
58 58 this.$label.hide();
59 59 } else {
60 60 this.$label.html(description);
61 61 this.$label.show();
62 62 }
63 63 }
64 64 return IPython.WidgetView.prototype.update.call(this);
65 65 },
66 66
67 67 });
68 68
69 69 widget_manager.register_widget_view('CheckboxView', CheckboxView);
70 70
71 71 var ToggleButtonView = IPython.WidgetView.extend({
72 72
73 73 // Called when view is rendered.
74 74 render : function(){
75 75 this.$el
76 76 .html('');
77 77 this.$button = $('<button />')
78 78 .addClass('btn')
79 79 .attr('type', 'button')
80 80 .attr('data-toggle', 'button')
81 81 .appendTo(this.$el);
82 82 this.$el_to_style = this.$button; // Set default element to style
83 83
84 84 this.update(); // Set defaults.
85 85 },
86 86
87 87 // Handles: Backend -> Frontend Sync
88 88 // Frontent -> Frontend Sync
89 89 update : function(){
90 90 if (!this.user_invoked_update) {
91 91 if (this.model.get('value')) {
92 92 this.$button.addClass('active');
93 93 } else {
94 94 this.$button.removeClass('active');
95 95 }
96 96
97 97 var disabled = this.model.get('disabled');
98 98 this.$button.prop('disabled', disabled);
99 99
100 100 var description = this.model.get('description');
101 101 if (description.length === 0) {
102 102 this.$button.html(' '); // Preserve button height
103 103 } else {
104 104 this.$button.html(description);
105 105 }
106 106 }
107 107 return IPython.WidgetView.prototype.update.call(this);
108 108 },
109 109
110 110 events: {"click button" : "handleClick"},
111 111
112 112 // Handles and validates user input.
113 113 handleClick: function(e) {
114 114 this.user_invoked_update = true;
115 115 this.model.set('value', ! $(e.target).hasClass('active'));
116 this.model.update_other_views(this);
116 this.touch();
117 117 this.user_invoked_update = false;
118 118 },
119 119 });
120 120
121 121 widget_manager.register_widget_view('ToggleButtonView', ToggleButtonView);
122 122
123 123 });
@@ -1,255 +1,255
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 // FloatRangeWidget
10 10 //============================================================================
11 11
12 12 /**
13 13 * @module IPython
14 14 * @namespace IPython
15 15 **/
16 16
17 17 define(["notebook/js/widgets/base"], function(widget_manager){
18 18 var FloatRangeWidgetModel = IPython.WidgetModel.extend({});
19 19 widget_manager.register_widget_model('FloatRangeWidgetModel', FloatRangeWidgetModel);
20 20
21 21 var FloatSliderView = IPython.WidgetView.extend({
22 22
23 23 // Called when view is rendered.
24 24 render : function(){
25 25 this.$el
26 26 .addClass('widget-hbox-single')
27 27 .html('');
28 28 this.$label = $('<div />')
29 29 .appendTo(this.$el)
30 30 .addClass('widget-hlabel')
31 31 .hide();
32 32 this.$slider = $('<div />')
33 33 .slider({})
34 34 .addClass('slider');
35 35
36 36 // Put the slider in a container
37 37 this.$slider_container = $('<div />')
38 38 .addClass('widget-hslider')
39 39 .append(this.$slider);
40 40 this.$el_to_style = this.$slider_container; // Set default element to style
41 41 this.$el.append(this.$slider_container);
42 42
43 43 // Set defaults.
44 44 this.update();
45 45 },
46 46
47 47 // Handles: Backend -> Frontend Sync
48 48 // Frontent -> Frontend Sync
49 49 update : function(){
50 50 // Slider related keys.
51 51 var _keys = ['step', 'max', 'min', 'disabled'];
52 52 for (var index in _keys) {
53 53 var key = _keys[index];
54 54 if (this.model.get(key) !== undefined) {
55 55 this.$slider.slider("option", key, this.model.get(key));
56 56 }
57 57 }
58 58
59 59 // WORKAROUND FOR JQUERY SLIDER BUG.
60 60 // The horizontal position of the slider handle
61 61 // depends on the value of the slider at the time
62 62 // of orientation change. Before applying the new
63 63 // workaround, we set the value to the minimum to
64 64 // make sure that the horizontal placement of the
65 65 // handle in the vertical slider is always
66 66 // consistent.
67 67 var orientation = this.model.get('orientation');
68 68 var value = this.model.get('min');
69 69 this.$slider.slider('option', 'value', value);
70 70 this.$slider.slider('option', 'orientation', orientation);
71 71 value = this.model.get('value');
72 72 this.$slider.slider('option', 'value', value);
73 73
74 74 // Use the right CSS classes for vertical & horizontal sliders
75 75 if (orientation=='vertical') {
76 76 this.$slider_container
77 77 .removeClass('widget-hslider')
78 78 .addClass('widget-vslider');
79 79 this.$el
80 80 .removeClass('widget-hbox-single')
81 81 .addClass('widget-vbox-single');
82 82 this.$label
83 83 .removeClass('widget-hlabel')
84 84 .addClass('widget-vlabel');
85 85
86 86 } else {
87 87 this.$slider_container
88 88 .removeClass('widget-vslider')
89 89 .addClass('widget-hslider');
90 90 this.$el
91 91 .removeClass('widget-vbox-single')
92 92 .addClass('widget-hbox-single');
93 93 this.$label
94 94 .removeClass('widget-vlabel')
95 95 .addClass('widget-hlabel');
96 96 }
97 97
98 98 var description = this.model.get('description');
99 99 if (description.length === 0) {
100 100 this.$label.hide();
101 101 } else {
102 102 this.$label.html(description);
103 103 this.$label.show();
104 104 }
105 105 return IPython.WidgetView.prototype.update.call(this);
106 106 },
107 107
108 108 // Handles: User input
109 109 events: { "slide" : "handleSliderChange" },
110 110 handleSliderChange: function(e, ui) {
111 111 this.model.set('value', ui.value);
112 this.model.update_other_views(this);
112 this.touch();
113 113 },
114 114 });
115 115
116 116 widget_manager.register_widget_view('FloatSliderView', FloatSliderView);
117 117
118 118
119 119 var FloatTextView = IPython.WidgetView.extend({
120 120
121 121 // Called when view is rendered.
122 122 render : function(){
123 123 this.$el
124 124 .addClass('widget-hbox-single')
125 125 .html('');
126 126 this.$label = $('<div />')
127 127 .appendTo(this.$el)
128 128 .addClass('widget-hlabel')
129 129 .hide();
130 130 this.$textbox = $('<input type="text" />')
131 131 .addClass('input')
132 132 .addClass('widget-numeric-text')
133 133 .appendTo(this.$el);
134 134 this.$el_to_style = this.$textbox; // Set default element to style
135 135 this.update(); // Set defaults.
136 136 },
137 137
138 138 // Handles: Backend -> Frontend Sync
139 139 // Frontent -> Frontend Sync
140 140 update : function(){
141 141 var value = this.model.get('value');
142 142 if (!this.changing && parseFloat(this.$textbox.val()) != value) {
143 143 this.$textbox.val(value);
144 144 }
145 145
146 146 if (this.model.get('disabled')) {
147 147 this.$textbox.attr('disabled','disabled');
148 148 } else {
149 149 this.$textbox.removeAttr('disabled');
150 150 }
151 151
152 152 var description = this.model.get('description');
153 153 if (description.length === 0) {
154 154 this.$label.hide();
155 155 } else {
156 156 this.$label.html(description);
157 157 this.$label.show();
158 158 }
159 159 return IPython.WidgetView.prototype.update.call(this);
160 160 },
161 161
162 162
163 163 events: {"keyup input" : "handleChanging",
164 164 "paste input" : "handleChanging",
165 165 "cut input" : "handleChanging",
166 166 "change input" : "handleChanged"}, // Fires only when control is validated or looses focus.
167 167
168 168 // Handles and validates user input.
169 169 handleChanging: function(e) {
170 170
171 171 // Try to parse value as a float.
172 172 var numericalValue = 0.0;
173 173 if (e.target.value !== '') {
174 174 numericalValue = parseFloat(e.target.value);
175 175 }
176 176
177 177 // If parse failed, reset value to value stored in model.
178 178 if (isNaN(numericalValue)) {
179 179 e.target.value = this.model.get('value');
180 180 } else if (!isNaN(numericalValue)) {
181 181 if (this.model.get('max') !== undefined) {
182 182 numericalValue = Math.min(this.model.get('max'), numericalValue);
183 183 }
184 184 if (this.model.get('min') !== undefined) {
185 185 numericalValue = Math.max(this.model.get('min'), numericalValue);
186 186 }
187 187
188 188 // Apply the value if it has changed.
189 189 if (numericalValue != this.model.get('value')) {
190 190 this.changing = true;
191 191 this.model.set('value', numericalValue);
192 this.model.update_other_views(this);
192 this.touch();
193 193 this.changing = false;
194 194 }
195 195 }
196 196 },
197 197
198 198 // Applies validated input.
199 199 handleChanged: function(e) {
200 200 // Update the textbox
201 201 if (this.model.get('value') != e.target.value) {
202 202 e.target.value = this.model.get('value');
203 203 }
204 204 }
205 205 });
206 206
207 207 widget_manager.register_widget_view('FloatTextView', FloatTextView);
208 208
209 209
210 210 var ProgressView = IPython.WidgetView.extend({
211 211
212 212 // Called when view is rendered.
213 213 render : function(){
214 214 this.$el
215 215 .addClass('widget-hbox-single')
216 216 .html('');
217 217 this.$label = $('<div />')
218 218 .appendTo(this.$el)
219 219 .addClass('widget-hlabel')
220 220 .hide();
221 221 this.$progress = $('<div />')
222 222 .addClass('progress')
223 223 .addClass('widget-progress')
224 224 .appendTo(this.$el);
225 225 this.$el_to_style = this.$progress; // Set default element to style
226 226 this.$bar = $('<div />')
227 227 .addClass('bar')
228 228 .css('width', '50%')
229 229 .appendTo(this.$progress);
230 230 this.update(); // Set defaults.
231 231 },
232 232
233 233 // Handles: Backend -> Frontend Sync
234 234 // Frontent -> Frontend Sync
235 235 update : function(){
236 236 var value = this.model.get('value');
237 237 var max = this.model.get('max');
238 238 var min = this.model.get('min');
239 239 var percent = 100.0 * (value - min) / (max - min);
240 240 this.$bar.css('width', percent + '%');
241 241
242 242 var description = this.model.get('description');
243 243 if (description.length === 0) {
244 244 this.$label.hide();
245 245 } else {
246 246 this.$label.html(description);
247 247 this.$label.show();
248 248 }
249 249 return IPython.WidgetView.prototype.update.call(this);
250 250 },
251 251
252 252 });
253 253
254 254 widget_manager.register_widget_view('ProgressView', ProgressView);
255 255 });
@@ -1,207 +1,207
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 // IntRangeWidget
10 10 //============================================================================
11 11
12 12 /**
13 13 * @module IPython
14 14 * @namespace IPython
15 15 **/
16 16
17 17 define(["notebook/js/widgets/base"], function(widget_manager){
18 18 var IntRangeWidgetModel = IPython.WidgetModel.extend({});
19 19 widget_manager.register_widget_model('IntRangeWidgetModel', IntRangeWidgetModel);
20 20
21 21 var IntSliderView = IPython.WidgetView.extend({
22 22
23 23 // Called when view is rendered.
24 24 render : function(){
25 25 this.$el
26 26 .addClass('widget-hbox-single')
27 27 .html('');
28 28 this.$label = $('<div />')
29 29 .appendTo(this.$el)
30 30 .addClass('widget-hlabel')
31 31 .hide();
32 32 this.$slider = $('<div />')
33 33 .slider({})
34 34 .addClass('slider');
35 35
36 36 // Put the slider in a container
37 37 this.$slider_container = $('<div />')
38 38 .addClass('widget-hslider')
39 39 .append(this.$slider);
40 40 this.$el_to_style = this.$slider_container; // Set default element to style
41 41 this.$el.append(this.$slider_container);
42 42
43 43 // Set defaults.
44 44 this.update();
45 45 },
46 46
47 47 // Handles: Backend -> Frontend Sync
48 48 // Frontent -> Frontend Sync
49 49 update : function(){
50 50 // Slider related keys.
51 51 var _keys = ['step', 'max', 'min', 'disabled'];
52 52 for (var index in _keys) {
53 53 var key = _keys[index];
54 54 if (this.model.get(key) !== undefined) {
55 55 this.$slider.slider("option", key, this.model.get(key));
56 56 }
57 57 }
58 58
59 59 // WORKAROUND FOR JQUERY SLIDER BUG.
60 60 // The horizontal position of the slider handle
61 61 // depends on the value of the slider at the time
62 62 // of orientation change. Before applying the new
63 63 // workaround, we set the value to the minimum to
64 64 // make sure that the horizontal placement of the
65 65 // handle in the vertical slider is always
66 66 // consistent.
67 67 var orientation = this.model.get('orientation');
68 68 var value = this.model.get('min');
69 69 this.$slider.slider('option', 'value', value);
70 70 this.$slider.slider('option', 'orientation', orientation);
71 71 value = this.model.get('value');
72 72 this.$slider.slider('option', 'value', value);
73 73
74 74 // Use the right CSS classes for vertical & horizontal sliders
75 75 if (orientation=='vertical') {
76 76 this.$slider_container
77 77 .removeClass('widget-hslider')
78 78 .addClass('widget-vslider');
79 79 this.$el
80 80 .removeClass('widget-hbox-single')
81 81 .addClass('widget-vbox-single');
82 82 this.$label
83 83 .removeClass('widget-hlabel')
84 84 .addClass('widget-vlabel');
85 85
86 86 } else {
87 87 this.$slider_container
88 88 .removeClass('widget-vslider')
89 89 .addClass('widget-hslider');
90 90 this.$el
91 91 .removeClass('widget-vbox-single')
92 92 .addClass('widget-hbox-single');
93 93 this.$label
94 94 .removeClass('widget-vlabel')
95 95 .addClass('widget-hlabel');
96 96 }
97 97
98 98 var description = this.model.get('description');
99 99 if (description.length === 0) {
100 100 this.$label.hide();
101 101 } else {
102 102 this.$label.html(description);
103 103 this.$label.show();
104 104 }
105 105 return IPython.WidgetView.prototype.update.call(this);
106 106 },
107 107
108 108 // Handles: User input
109 109 events: { "slide" : "handleSliderChange" },
110 110 handleSliderChange: function(e, ui) {
111 111 this.model.set('value', ~~ui.value); // Double bit-wise not to truncate decimel
112 this.model.update_other_views(this);
112 this.touch();
113 113 },
114 114 });
115 115
116 116 widget_manager.register_widget_view('IntSliderView', IntSliderView);
117 117
118 118 var IntTextView = IPython.WidgetView.extend({
119 119
120 120 // Called when view is rendered.
121 121 render : function(){
122 122 this.$el
123 123 .addClass('widget-hbox-single')
124 124 .html('');
125 125 this.$label = $('<div />')
126 126 .appendTo(this.$el)
127 127 .addClass('widget-hlabel')
128 128 .hide();
129 129 this.$textbox = $('<input type="text" />')
130 130 .addClass('input')
131 131 .addClass('widget-numeric-text')
132 132 .appendTo(this.$el);
133 133 this.$el_to_style = this.$textbox; // Set default element to style
134 134 this.update(); // Set defaults.
135 135 },
136 136
137 137 // Handles: Backend -> Frontend Sync
138 138 // Frontent -> Frontend Sync
139 139 update : function(){
140 140 var value = this.model.get('value');
141 141 if (!this.changing && parseInt(this.$textbox.val()) != value) {
142 142 this.$textbox.val(value);
143 143 }
144 144
145 145 if (this.model.get('disabled')) {
146 146 this.$textbox.attr('disabled','disabled');
147 147 } else {
148 148 this.$textbox.removeAttr('disabled');
149 149 }
150 150
151 151 var description = this.model.get('description');
152 152 if (description.length === 0) {
153 153 this.$label.hide();
154 154 } else {
155 155 this.$label.html(description);
156 156 this.$label.show();
157 157 }
158 158 return IPython.WidgetView.prototype.update.call(this);
159 159 },
160 160
161 161
162 162 events: {"keyup input" : "handleChanging",
163 163 "paste input" : "handleChanging",
164 164 "cut input" : "handleChanging",
165 165 "change input" : "handleChanged"}, // Fires only when control is validated or looses focus.
166 166
167 167 // Handles and validates user input.
168 168 handleChanging: function(e) {
169 169
170 170 // Try to parse value as a float.
171 171 var numericalValue = 0;
172 172 if (e.target.value !== '') {
173 173 numericalValue = parseInt(e.target.value);
174 174 }
175 175
176 176 // If parse failed, reset value to value stored in model.
177 177 if (isNaN(numericalValue)) {
178 178 e.target.value = this.model.get('value');
179 179 } else if (!isNaN(numericalValue)) {
180 180 if (this.model.get('max') !== undefined) {
181 181 numericalValue = Math.min(this.model.get('max'), numericalValue);
182 182 }
183 183 if (this.model.get('min') !== undefined) {
184 184 numericalValue = Math.max(this.model.get('min'), numericalValue);
185 185 }
186 186
187 187 // Apply the value if it has changed.
188 188 if (numericalValue != this.model.get('value')) {
189 189 this.changing = true;
190 190 this.model.set('value', numericalValue);
191 this.model.update_other_views(this);
191 this.touch();
192 192 this.changing = false;
193 193 }
194 194 }
195 195 },
196 196
197 197 // Applies validated input.
198 198 handleChanged: function(e) {
199 199 // Update the textbox
200 200 if (this.model.get('value') != e.target.value) {
201 201 e.target.value = this.model.get('value');
202 202 }
203 203 }
204 204 });
205 205
206 206 widget_manager.register_widget_view('IntTextView', IntTextView);
207 207 });
@@ -1,179 +1,179
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 // MultiContainerWidget
10 10 //============================================================================
11 11
12 12 /**
13 13 * @module IPython
14 14 * @namespace IPython
15 15 **/
16 16
17 17 define(["notebook/js/widgets/base"], function(widget_manager){
18 18 var MulticontainerModel = IPython.WidgetModel.extend({});
19 19 widget_manager.register_widget_model('MulticontainerWidgetModel', MulticontainerModel);
20 20
21 21 var AccordionView = IPython.WidgetView.extend({
22 22
23 23 render: function(){
24 24 var guid = 'accordion' + IPython.utils.uuid();
25 25 this.$el
26 26 .attr('id', guid)
27 27 .addClass('accordion');
28 28 this.containers = [];
29 29 },
30 30
31 31 update: function() {
32 32 // Set tab titles
33 33 var titles = this.model.get('_titles');
34 34 for (var page_index in titles) {
35 35
36 36 var accordian = this.containers[page_index];
37 37 if (accordian !== undefined) {
38 38 accordian
39 39 .find('.accordion-heading')
40 40 .find('.accordion-toggle')
41 41 .html(titles[page_index]);
42 42 }
43 43 }
44 44
45 45 // Set selected page
46 46 var selected_index = this.model.get("selected_index");
47 47 if (0 <= selected_index && selected_index < this.containers.length) {
48 48 for (var index in this.containers) {
49 49 if (index==selected_index) {
50 50 this.containers[index].find('.accordion-body').collapse('show');
51 51 } else {
52 52 this.containers[index].find('.accordion-body').collapse('hide');
53 53 }
54 54
55 55 }
56 56 }
57 57
58 58 return IPython.WidgetView.prototype.update.call(this);
59 59 },
60 60
61 61 display_child: function(view) {
62 62
63 63 var index = this.containers.length;
64 64 var uuid = IPython.utils.uuid();
65 65 var accordion_group = $('<div />')
66 66 .addClass('accordion-group')
67 67 .appendTo(this.$el);
68 68 var accordion_heading = $('<div />')
69 69 .addClass('accordion-heading')
70 70 .appendTo(accordion_group);
71 71 var that = this;
72 72 var accordion_toggle = $('<a />')
73 73 .addClass('accordion-toggle')
74 74 .attr('data-toggle', 'collapse')
75 75 .attr('data-parent', '#' + this.$el.attr('id'))
76 76 .attr('href', '#' + uuid)
77 77 .click(function(evt){
78 78 that.model.set("selected_index", index);
79 that.model.update_other_views(that);
79 that.touch();
80 80 })
81 81 .html('Page ' + index)
82 82 .appendTo(accordion_heading);
83 83 var accordion_body = $('<div />', {id: uuid})
84 84 .addClass('accordion-body collapse')
85 85 .appendTo(accordion_group);
86 86 var accordion_inner = $('<div />')
87 87 .addClass('accordion-inner')
88 88 .appendTo(accordion_body);
89 89 this.containers.push(accordion_group);
90 90 accordion_inner.append(view.$el);
91 91
92 92 this.update();
93 93
94 94 // Stupid workaround to close the bootstrap accordion tabs which
95 95 // open by default even though they don't have the `in` class
96 96 // attached to them. For some reason a delay is required.
97 97 // TODO: Better fix.
98 98 setTimeout(function(){ that.update(); }, 500);
99 99 },
100 100 });
101 101
102 102 widget_manager.register_widget_view('AccordionView', AccordionView);
103 103
104 104 var TabView = IPython.WidgetView.extend({
105 105
106 106 render: function(){
107 107 var uuid = 'tabs'+IPython.utils.uuid();
108 108 var that = this;
109 109 this.$tabs = $('<div />', {id: uuid})
110 110 .addClass('nav')
111 111 .addClass('nav-tabs')
112 112 .appendTo(this.$el);
113 113 this.$tab_contents = $('<div />', {id: uuid + 'Content'})
114 114 .addClass('tab-content')
115 115 .appendTo(this.$el);
116 116
117 117 this.containers = [];
118 118 },
119 119
120 120 update: function() {
121 121 // Set tab titles
122 122 var titles = this.model.get('_titles');
123 123 for (var page_index in titles) {
124 124 var tab_text = this.containers[page_index];
125 125 if (tab_text !== undefined) {
126 126 tab_text.html(titles[page_index]);
127 127 }
128 128 }
129 129
130 130 var selected_index = this.model.get('selected_index');
131 131 if (0 <= selected_index && selected_index < this.containers.length) {
132 132 this.select_page(selected_index);
133 133 }
134 134
135 135 return IPython.WidgetView.prototype.update.call(this);
136 136 },
137 137
138 138 display_child: function(view) {
139 139
140 140 var index = this.containers.length;
141 141 var uuid = IPython.utils.uuid();
142 142
143 143 var that = this;
144 144 var tab = $('<li />')
145 145 .css('list-style-type', 'none')
146 146 .appendTo(this.$tabs);
147 147 var tab_text = $('<a />')
148 148 .attr('href', '#' + uuid)
149 149 .attr('data-toggle', 'tab')
150 150 .html('Page ' + index)
151 151 .appendTo(tab)
152 152 .click(function (e) {
153 153 that.model.set("selected_index", index);
154 that.model.update_other_views(that);
154 that.touch();
155 155 that.select_page(index);
156 156 });
157 157 this.containers.push(tab_text);
158 158
159 159 var contents_div = $('<div />', {id: uuid})
160 160 .addClass('tab-pane')
161 161 .addClass('fade')
162 162 .append(view.$el)
163 163 .appendTo(this.$tab_contents);
164 164
165 165 if (index === 0) {
166 166 tab_text.tab('show');
167 167 }
168 168 this.update();
169 169 },
170 170
171 171 select_page: function(index) {
172 172 this.$tabs.find('li')
173 173 .removeClass('active');
174 174 this.containers[index].tab('show');
175 175 },
176 176 });
177 177
178 178 widget_manager.register_widget_view('TabView', TabView);
179 179 });
@@ -1,360 +1,360
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 // SelectionWidget
10 10 //============================================================================
11 11
12 12 /**
13 13 * @module IPython
14 14 * @namespace IPython
15 15 **/
16 16
17 17 define(["notebook/js/widgets/base"], function(widget_manager){
18 18 var SelectionWidgetModel = IPython.WidgetModel.extend({});
19 19 widget_manager.register_widget_model('SelectionWidgetModel', SelectionWidgetModel);
20 20
21 21 var DropdownView = IPython.WidgetView.extend({
22 22
23 23 // Called when view is rendered.
24 24 render : function(){
25 25
26 26 this.$el
27 27 .addClass('widget-hbox-single')
28 28 .html('');
29 29 this.$label = $('<div />')
30 30 .appendTo(this.$el)
31 31 .addClass('widget-hlabel')
32 32 .hide();
33 33 this.$buttongroup = $('<div />')
34 34 .addClass('widget_item')
35 35 .addClass('btn-group')
36 36 .appendTo(this.$el);
37 37 this.$el_to_style = this.$buttongroup; // Set default element to style
38 38 this.$droplabel = $('<button />')
39 39 .addClass('btn')
40 40 .addClass('widget-combo-btn')
41 41 .html('&nbsp;')
42 42 .appendTo(this.$buttongroup);
43 43 this.$dropbutton = $('<button />')
44 44 .addClass('btn')
45 45 .addClass('dropdown-toggle')
46 46 .addClass('widget-combo-carrot-btn')
47 47 .attr('data-toggle', 'dropdown')
48 48 .html('<span class="caret"></span>')
49 49 .appendTo(this.$buttongroup);
50 50 this.$droplist = $('<ul />')
51 51 .addClass('dropdown-menu')
52 52 .appendTo(this.$buttongroup);
53 53
54 54 // Set defaults.
55 55 this.update();
56 56 },
57 57
58 58 // Handles: Backend -> Frontend Sync
59 59 // Frontent -> Frontend Sync
60 60 update : function(){
61 61
62 62 var selected_item_text = this.model.get('value');
63 63 selected_item_text = selected_item_text.replace(/ /g, '&nbsp;');
64 64 selected_item_text = selected_item_text.replace(/\n/g, '<br>\n');
65 65 if (selected_item_text.length === 0) {
66 66 this.$droplabel.html('&nbsp;');
67 67 } else {
68 68 this.$droplabel.html(selected_item_text);
69 69 }
70 70
71 71 var items = this.model.get('values');
72 72 this.$droplist.html('');
73 73 for (var index in items) {
74 74 var that = this;
75 75 var item_button = $('<a href="#"/>')
76 76 .html(items[index])
77 77 .on('click', $.proxy(this.handle_click, this));
78 78 this.$droplist.append($('<li />').append(item_button));
79 79 }
80 80
81 81 if (this.model.get('disabled')) {
82 82 this.$buttongroup.attr('disabled','disabled');
83 83 this.$droplabel.attr('disabled','disabled');
84 84 this.$dropbutton.attr('disabled','disabled');
85 85 this.$droplist.attr('disabled','disabled');
86 86 } else {
87 87 this.$buttongroup.removeAttr('disabled');
88 88 this.$droplabel.removeAttr('disabled');
89 89 this.$dropbutton.removeAttr('disabled');
90 90 this.$droplist.removeAttr('disabled');
91 91 }
92 92
93 93 var description = this.model.get('description');
94 94 if (description.length === 0) {
95 95 this.$label.hide();
96 96 } else {
97 97 this.$label.html(description);
98 98 this.$label.show();
99 99 }
100 100 return IPython.WidgetView.prototype.update.call(this);
101 101 },
102 102
103 103 // Handle when a value is clicked.
104 104 handle_click: function (e) {
105 105 this.model.set('value', $(e.target).html(), this);
106 this.model.update_other_views(this);
106 this.touch();
107 107 },
108 108
109 109 });
110 110
111 111 widget_manager.register_widget_view('DropdownView', DropdownView);
112 112
113 113 var RadioButtonsView = IPython.WidgetView.extend({
114 114
115 115 // Called when view is rendered.
116 116 render : function(){
117 117 this.$el
118 118 .addClass('widget-hbox')
119 119 .html('');
120 120 this.$label = $('<div />')
121 121 .appendTo(this.$el)
122 122 .addClass('widget-hlabel')
123 123 .hide();
124 124 this.$container = $('<div />')
125 125 .appendTo(this.$el)
126 126 .addClass('widget-container')
127 127 .addClass('vbox');
128 128 this.$el_to_style = this.$container; // Set default element to style
129 129 this.update();
130 130 },
131 131
132 132 // Handles: Backend -> Frontend Sync
133 133 // Frontent -> Frontend Sync
134 134 update : function(){
135 135
136 136 // Add missing items to the DOM.
137 137 var items = this.model.get('values');
138 138 var disabled = this.model.get('disabled');
139 139 for (var index in items) {
140 140 var item_query = ' :input[value="' + items[index] + '"]';
141 141 if (this.$el.find(item_query).length === 0) {
142 142 var $label = $('<label />')
143 143 .addClass('radio')
144 144 .html(items[index])
145 145 .appendTo(this.$container);
146 146
147 147 $('<input />')
148 148 .attr('type', 'radio')
149 149 .addClass(this.model)
150 150 .val(items[index])
151 151 .prependTo($label)
152 152 .on('click', $.proxy(this.handle_click, this));
153 153 }
154 154
155 155 var $item_element = this.$container.find(item_query);
156 156 if (this.model.get('value') == items[index]) {
157 157 $item_element.prop('checked', true);
158 158 } else {
159 159 $item_element.prop('checked', false);
160 160 }
161 161 $item_element.prop('disabled', disabled);
162 162 }
163 163
164 164 // Remove items that no longer exist.
165 165 this.$container.find('input').each(function(i, obj) {
166 166 var value = $(obj).val();
167 167 var found = false;
168 168 for (var index in items) {
169 169 if (items[index] == value) {
170 170 found = true;
171 171 break;
172 172 }
173 173 }
174 174
175 175 if (!found) {
176 176 $(obj).parent().remove();
177 177 }
178 178 });
179 179
180 180 var description = this.model.get('description');
181 181 if (description.length === 0) {
182 182 this.$label.hide();
183 183 } else {
184 184 this.$label.html(description);
185 185 this.$label.show();
186 186 }
187 187 return IPython.WidgetView.prototype.update.call(this);
188 188 },
189 189
190 190 // Handle when a value is clicked.
191 191 handle_click: function (e) {
192 192 this.model.set('value', $(e.target).val(), this);
193 this.model.update_other_views(this);
193 this.touch();
194 194 },
195 195 });
196 196
197 197 widget_manager.register_widget_view('RadioButtonsView', RadioButtonsView);
198 198
199 199
200 200 var ToggleButtonsView = IPython.WidgetView.extend({
201 201
202 202 // Called when view is rendered.
203 203 render : function(){
204 204 this.$el
205 205 .addClass('widget-hbox-single')
206 206 .html('');
207 207 this.$label = $('<div />')
208 208 .appendTo(this.$el)
209 209 .addClass('widget-hlabel')
210 210 .hide();
211 211 this.$buttongroup = $('<div />')
212 212 .addClass('btn-group')
213 213 .attr('data-toggle', 'buttons-radio')
214 214 .appendTo(this.$el);
215 215 this.$el_to_style = this.$buttongroup; // Set default element to style
216 216 this.update();
217 217 },
218 218
219 219 // Handles: Backend -> Frontend Sync
220 220 // Frontent -> Frontend Sync
221 221 update : function(){
222 222
223 223 // Add missing items to the DOM.
224 224 var items = this.model.get('values');
225 225 var disabled = this.model.get('disabled');
226 226 for (var index in items) {
227 227 var item_query = ' :contains("' + items[index] + '")';
228 228 if (this.$buttongroup.find(item_query).length === 0) {
229 229 $('<button />')
230 230 .attr('type', 'button')
231 231 .addClass('btn')
232 232 .html(items[index])
233 233 .appendTo(this.$buttongroup)
234 234 .on('click', $.proxy(this.handle_click, this));
235 235 }
236 236
237 237 var $item_element = this.$buttongroup.find(item_query);
238 238 if (this.model.get('value') == items[index]) {
239 239 $item_element.addClass('active');
240 240 } else {
241 241 $item_element.removeClass('active');
242 242 }
243 243 $item_element.prop('disabled', disabled);
244 244 }
245 245
246 246 // Remove items that no longer exist.
247 247 this.$buttongroup.find('button').each(function(i, obj) {
248 248 var value = $(obj).html();
249 249 var found = false;
250 250 for (var index in items) {
251 251 if (items[index] == value) {
252 252 found = true;
253 253 break;
254 254 }
255 255 }
256 256
257 257 if (!found) {
258 258 $(obj).remove();
259 259 }
260 260 });
261 261
262 262 var description = this.model.get('description');
263 263 if (description.length === 0) {
264 264 this.$label.hide();
265 265 } else {
266 266 this.$label.html(description);
267 267 this.$label.show();
268 268 }
269 269 return IPython.WidgetView.prototype.update.call(this);
270 270 },
271 271
272 272 // Handle when a value is clicked.
273 273 handle_click: function (e) {
274 274 this.model.set('value', $(e.target).html(), this);
275 this.model.update_other_views(this);
275 this.touch();
276 276 },
277 277
278 278 });
279 279
280 280 widget_manager.register_widget_view('ToggleButtonsView', ToggleButtonsView);
281 281
282 282 var ListBoxView = IPython.WidgetView.extend({
283 283
284 284 // Called when view is rendered.
285 285 render : function(){
286 286 this.$el
287 287 .addClass('widget-hbox')
288 288 .html('');
289 289 this.$label = $('<div />')
290 290 .appendTo(this.$el)
291 291 .addClass('widget-hlabel')
292 292 .hide();
293 293 this.$listbox = $('<select />')
294 294 .addClass('widget-listbox')
295 295 .attr('size', 6)
296 296 .appendTo(this.$el);
297 297 this.$el_to_style = this.$listbox; // Set default element to style
298 298 this.update();
299 299 },
300 300
301 301 // Handles: Backend -> Frontend Sync
302 302 // Frontent -> Frontend Sync
303 303 update : function(){
304 304
305 305 // Add missing items to the DOM.
306 306 var items = this.model.get('values');
307 307 for (var index in items) {
308 308 var item_query = ' :contains("' + items[index] + '")';
309 309 if (this.$listbox.find(item_query).length === 0) {
310 310 $('<option />')
311 311 .html(items[index])
312 312 .attr('value', items[index])
313 313 .appendTo(this.$listbox)
314 314 .on('click', $.proxy(this.handle_click, this));
315 315 }
316 316 }
317 317
318 318 // Select the correct element
319 319 this.$listbox.val(this.model.get('value'));
320 320
321 321 // Disable listbox if needed
322 322 var disabled = this.model.get('disabled');
323 323 this.$listbox.prop('disabled', disabled);
324 324
325 325 // Remove items that no longer exist.
326 326 this.$listbox.find('option').each(function(i, obj) {
327 327 var value = $(obj).html();
328 328 var found = false;
329 329 for (var index in items) {
330 330 if (items[index] == value) {
331 331 found = true;
332 332 break;
333 333 }
334 334 }
335 335
336 336 if (!found) {
337 337 $(obj).remove();
338 338 }
339 339 });
340 340
341 341 var description = this.model.get('description');
342 342 if (description.length === 0) {
343 343 this.$label.hide();
344 344 } else {
345 345 this.$label.html(description);
346 346 this.$label.show();
347 347 }
348 348 return IPython.WidgetView.prototype.update.call(this);
349 349 },
350 350
351 351 // Handle when a value is clicked.
352 352 handle_click: function (e) {
353 353 this.model.set('value', $(e.target).html(), this);
354 this.model.update_other_views(this);
354 this.touch();
355 355 },
356 356
357 357 });
358 358
359 359 widget_manager.register_widget_view('ListBoxView', ListBoxView);
360 360 });
@@ -1,188 +1,188
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 // StringWidget
10 10 //============================================================================
11 11
12 12 /**
13 13 * @module IPython
14 14 * @namespace IPython
15 15 **/
16 16
17 17 define(["notebook/js/widgets/base"], function(widget_manager){
18 18 var StringWidgetModel = IPython.WidgetModel.extend({});
19 19 widget_manager.register_widget_model('StringWidgetModel', StringWidgetModel);
20 20
21 21 var HTMLView = IPython.WidgetView.extend({
22 22
23 23 // Called when view is rendered.
24 24 render : function(){
25 25 this.update(); // Set defaults.
26 26 },
27 27
28 28 // Handles: Backend -> Frontend Sync
29 29 // Frontent -> Frontend Sync
30 30 update : function(){
31 31 this.$el.html(this.model.get('value'));
32 32 return IPython.WidgetView.prototype.update.call(this);
33 33 },
34 34
35 35 });
36 36
37 37 widget_manager.register_widget_view('HTMLView', HTMLView);
38 38
39 39
40 40 var LatexView = IPython.WidgetView.extend({
41 41
42 42 // Called when view is rendered.
43 43 render : function(){
44 44 this.update(); // Set defaults.
45 45 },
46 46
47 47 // Handles: Backend -> Frontend Sync
48 48 // Frontent -> Frontend Sync
49 49 update : function(){
50 50 this.$el.html(this.model.get('value'));
51 51 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$el.get(0)]);
52 52
53 53 return IPython.WidgetView.prototype.update.call(this);
54 54 },
55 55
56 56 });
57 57
58 58 widget_manager.register_widget_view('LatexView', LatexView);
59 59
60 60 var TextAreaView = IPython.WidgetView.extend({
61 61
62 62 // Called when view is rendered.
63 63 render: function(){
64 64 this.$el
65 65 .addClass('widget-hbox')
66 66 .html('');
67 67 this.$label = $('<div />')
68 68 .appendTo(this.$el)
69 69 .addClass('widget-hlabel')
70 70 .hide();
71 71 this.$textbox = $('<textarea />')
72 72 .attr('rows', 5)
73 73 .addClass('widget-text')
74 74 .appendTo(this.$el);
75 75 this.$el_to_style = this.$textbox; // Set default element to style
76 76 this.update(); // Set defaults.
77 77
78 78 this.model.on_msg($.proxy(this._handle_textarea_msg, this));
79 79 },
80 80
81 81
82 82 _handle_textarea_msg: function (content){
83 83 if (content.method == "scroll_to_bottom") {
84 84 this.scroll_to_bottom();
85 85 }
86 86 },
87 87
88 88
89 89 scroll_to_bottom: function (){
90 90 this.$textbox.scrollTop(this.$textbox[0].scrollHeight);
91 91 },
92 92
93 93
94 94 // Handles: Backend -> Frontend Sync
95 95 // Frontent -> Frontend Sync
96 96 update: function(){
97 97 if (!this.user_invoked_update) {
98 98 this.$textbox.val(this.model.get('value'));
99 99 }
100 100
101 101 var disabled = this.model.get('disabled');
102 102 this.$textbox.prop('disabled', disabled);
103 103
104 104 var description = this.model.get('description');
105 105 if (description.length === 0) {
106 106 this.$label.hide();
107 107 } else {
108 108 this.$label.html(description);
109 109 this.$label.show();
110 110 }
111 111 return IPython.WidgetView.prototype.update.call(this);
112 112 },
113 113
114 114 events: {"keyup textarea": "handleChanging",
115 115 "paste textarea": "handleChanging",
116 116 "cut textarea": "handleChanging"},
117 117
118 118 // Handles and validates user input.
119 119 handleChanging: function(e) {
120 120 this.user_invoked_update = true;
121 121 this.model.set('value', e.target.value);
122 this.model.update_other_views(this);
122 this.touch();
123 123 this.user_invoked_update = false;
124 124 },
125 125 });
126 126
127 127 widget_manager.register_widget_view('TextAreaView', TextAreaView);
128 128
129 129 var TextBoxView = IPython.WidgetView.extend({
130 130
131 131 // Called when view is rendered.
132 132 render: function(){
133 133 this.$el
134 134 .addClass('widget-hbox-single')
135 135 .html('');
136 136 this.$label = $('<div />')
137 137 .addClass('widget-hlabel')
138 138 .appendTo(this.$el)
139 139 .hide();
140 140 this.$textbox = $('<input type="text" />')
141 141 .addClass('input')
142 142 .addClass('widget-text')
143 143 .appendTo(this.$el);
144 144 this.$el_to_style = this.$textbox; // Set default element to style
145 145 this.update(); // Set defaults.
146 146 },
147 147
148 148 // Handles: Backend -> Frontend Sync
149 149 // Frontent -> Frontend Sync
150 150 update: function(){
151 151 if (this.$textbox.val() != this.model.get('value')) {
152 152 this.$textbox.val(this.model.get('value'));
153 153 }
154 154
155 155 var disabled = this.model.get('disabled');
156 156 this.$textbox.prop('disabled', disabled);
157 157
158 158 var description = this.model.get('description');
159 159 if (description.length === 0) {
160 160 this.$label.hide();
161 161 } else {
162 162 this.$label.html(description);
163 163 this.$label.show();
164 164 }
165 165 return IPython.WidgetView.prototype.update.call(this);
166 166 },
167 167
168 168 events: {"keyup input": "handleChanging",
169 169 "paste input": "handleChanging",
170 170 "cut input": "handleChanging",
171 171 "keypress input": "handleKeypress"},
172 172
173 173 // Handles and validates user input.
174 174 handleChanging: function(e) {
175 175 this.model.set('value', e.target.value);
176 this.model.update_other_views(this);
176 this.touch();
177 177 },
178 178
179 179 // Handles text submition
180 180 handleKeypress: function(e) {
181 181 if (e.keyCode == 13) { // Return key
182 182 this.send({event: 'submit'});
183 183 }
184 184 },
185 185 });
186 186
187 187 widget_manager.register_widget_view('TextBoxView', TextBoxView);
188 188 });
General Comments 0
You need to be logged in to leave comments. Login now