##// END OF EJS Templates
Added Bootstrap specific classes,...
Jonathan Frederic -
Show More
@@ -1,537 +1,576 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define(["widgets/js/manager",
5 5 "underscore",
6 6 "backbone",
7 7 "jquery",
8 8 "base/js/namespace",
9 9 ], function(widgetmanager, _, Backbone, $, IPython){
10 10
11 11 var WidgetModel = Backbone.Model.extend({
12 12 constructor: function (widget_manager, model_id, comm) {
13 13 // Constructor
14 14 //
15 15 // Creates a WidgetModel instance.
16 16 //
17 17 // Parameters
18 18 // ----------
19 19 // widget_manager : WidgetManager instance
20 20 // model_id : string
21 21 // An ID unique to this model.
22 22 // comm : Comm instance (optional)
23 23 this.widget_manager = widget_manager;
24 24 this._buffered_state_diff = {};
25 25 this.pending_msgs = 0;
26 26 this.msg_buffer = null;
27 27 this.key_value_lock = null;
28 28 this.id = model_id;
29 29 this.views = [];
30 30
31 31 if (comm !== undefined) {
32 32 // Remember comm associated with the model.
33 33 this.comm = comm;
34 34 comm.model = this;
35 35
36 36 // Hook comm messages up to model.
37 37 comm.on_close($.proxy(this._handle_comm_closed, this));
38 38 comm.on_msg($.proxy(this._handle_comm_msg, this));
39 39 }
40 40 return Backbone.Model.apply(this);
41 41 },
42 42
43 43 send: function (content, callbacks) {
44 44 // Send a custom msg over the comm.
45 45 if (this.comm !== undefined) {
46 46 var data = {method: 'custom', content: content};
47 47 this.comm.send(data, callbacks);
48 48 this.pending_msgs++;
49 49 }
50 50 },
51 51
52 52 _handle_comm_closed: function (msg) {
53 53 // Handle when a widget is closed.
54 54 this.trigger('comm:close');
55 55 delete this.comm.model; // Delete ref so GC will collect widget model.
56 56 delete this.comm;
57 57 delete this.model_id; // Delete id from model so widget manager cleans up.
58 58 _.each(this.views, function(view, i) {
59 59 view.remove();
60 60 });
61 61 },
62 62
63 63 _handle_comm_msg: function (msg) {
64 64 // Handle incoming comm msg.
65 65 var method = msg.content.data.method;
66 66 switch (method) {
67 67 case 'update':
68 68 this.apply_update(msg.content.data.state);
69 69 break;
70 70 case 'custom':
71 71 this.trigger('msg:custom', msg.content.data.content);
72 72 break;
73 73 case 'display':
74 74 this.widget_manager.display_view(msg, this);
75 75 break;
76 76 }
77 77 },
78 78
79 79 apply_update: function (state) {
80 80 // Handle when a widget is updated via the python side.
81 81 var that = this;
82 82 _.each(state, function(value, key) {
83 83 that.key_value_lock = [key, value];
84 84 try {
85 85 WidgetModel.__super__.set.apply(that, [key, that._unpack_models(value)]);
86 86 } finally {
87 87 that.key_value_lock = null;
88 88 }
89 89 });
90 90 },
91 91
92 92 _handle_status: function (msg, callbacks) {
93 93 // Handle status msgs.
94 94
95 95 // execution_state : ('busy', 'idle', 'starting')
96 96 if (this.comm !== undefined) {
97 97 if (msg.content.execution_state ==='idle') {
98 98 // Send buffer if this message caused another message to be
99 99 // throttled.
100 100 if (this.msg_buffer !== null &&
101 101 (this.get('msg_throttle') || 3) === this.pending_msgs) {
102 102 var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
103 103 this.comm.send(data, callbacks);
104 104 this.msg_buffer = null;
105 105 } else {
106 106 --this.pending_msgs;
107 107 }
108 108 }
109 109 }
110 110 },
111 111
112 112 callbacks: function(view) {
113 113 // Create msg callbacks for a comm msg.
114 114 var callbacks = this.widget_manager.callbacks(view);
115 115
116 116 if (callbacks.iopub === undefined) {
117 117 callbacks.iopub = {};
118 118 }
119 119
120 120 var that = this;
121 121 callbacks.iopub.status = function (msg) {
122 122 that._handle_status(msg, callbacks);
123 123 };
124 124 return callbacks;
125 125 },
126 126
127 127 set: function(key, val, options) {
128 128 // Set a value.
129 129 var return_value = WidgetModel.__super__.set.apply(this, arguments);
130 130
131 131 // Backbone only remembers the diff of the most recent set()
132 132 // operation. Calling set multiple times in a row results in a
133 133 // loss of diff information. Here we keep our own running diff.
134 134 this._buffered_state_diff = $.extend(this._buffered_state_diff, this.changedAttributes() || {});
135 135 return return_value;
136 136 },
137 137
138 138 sync: function (method, model, options) {
139 139 // Handle sync to the back-end. Called when a model.save() is called.
140 140
141 141 // Make sure a comm exists.
142 142 var error = options.error || function() {
143 143 console.error('Backbone sync error:', arguments);
144 144 };
145 145 if (this.comm === undefined) {
146 146 error();
147 147 return false;
148 148 }
149 149
150 150 // Delete any key value pairs that the back-end already knows about.
151 151 var attrs = (method === 'patch') ? options.attrs : model.toJSON(options);
152 152 if (this.key_value_lock !== null) {
153 153 var key = this.key_value_lock[0];
154 154 var value = this.key_value_lock[1];
155 155 if (attrs[key] === value) {
156 156 delete attrs[key];
157 157 }
158 158 }
159 159
160 160 // Only sync if there are attributes to send to the back-end.
161 161 attrs = this._pack_models(attrs);
162 162 if (_.size(attrs) > 0) {
163 163
164 164 // If this message was sent via backbone itself, it will not
165 165 // have any callbacks. It's important that we create callbacks
166 166 // so we can listen for status messages, etc...
167 167 var callbacks = options.callbacks || this.callbacks();
168 168
169 169 // Check throttle.
170 170 if (this.pending_msgs >= (this.get('msg_throttle') || 3)) {
171 171 // The throttle has been exceeded, buffer the current msg so
172 172 // it can be sent once the kernel has finished processing
173 173 // some of the existing messages.
174 174
175 175 // Combine updates if it is a 'patch' sync, otherwise replace updates
176 176 switch (method) {
177 177 case 'patch':
178 178 this.msg_buffer = $.extend(this.msg_buffer || {}, attrs);
179 179 break;
180 180 case 'update':
181 181 case 'create':
182 182 this.msg_buffer = attrs;
183 183 break;
184 184 default:
185 185 error();
186 186 return false;
187 187 }
188 188 this.msg_buffer_callbacks = callbacks;
189 189
190 190 } else {
191 191 // We haven't exceeded the throttle, send the message like
192 192 // normal.
193 193 var data = {method: 'backbone', sync_data: attrs};
194 194 this.comm.send(data, callbacks);
195 195 this.pending_msgs++;
196 196 }
197 197 }
198 198 // Since the comm is a one-way communication, assume the message
199 199 // arrived. Don't call success since we don't have a model back from the server
200 200 // this means we miss out on the 'sync' event.
201 201 this._buffered_state_diff = {};
202 202 },
203 203
204 204 save_changes: function(callbacks) {
205 205 // Push this model's state to the back-end
206 206 //
207 207 // This invokes a Backbone.Sync.
208 208 this.save(this._buffered_state_diff, {patch: true, callbacks: callbacks});
209 209 },
210 210
211 211 _pack_models: function(value) {
212 212 // Replace models with model ids recursively.
213 213 var that = this;
214 214 var packed;
215 215 if (value instanceof Backbone.Model) {
216 216 return "IPY_MODEL_" + value.id;
217 217
218 218 } else if ($.isArray(value)) {
219 219 packed = [];
220 220 _.each(value, function(sub_value, key) {
221 221 packed.push(that._pack_models(sub_value));
222 222 });
223 223 return packed;
224 224
225 225 } else if (value instanceof Object) {
226 226 packed = {};
227 227 _.each(value, function(sub_value, key) {
228 228 packed[key] = that._pack_models(sub_value);
229 229 });
230 230 return packed;
231 231
232 232 } else {
233 233 return value;
234 234 }
235 235 },
236 236
237 237 _unpack_models: function(value) {
238 238 // Replace model ids with models recursively.
239 239 var that = this;
240 240 var unpacked;
241 241 if ($.isArray(value)) {
242 242 unpacked = [];
243 243 _.each(value, function(sub_value, key) {
244 244 unpacked.push(that._unpack_models(sub_value));
245 245 });
246 246 return unpacked;
247 247
248 248 } else if (value instanceof Object) {
249 249 unpacked = {};
250 250 _.each(value, function(sub_value, key) {
251 251 unpacked[key] = that._unpack_models(sub_value);
252 252 });
253 253 return unpacked;
254 254
255 255 } else if (typeof value === 'string' && value.slice(0,10) === "IPY_MODEL_") {
256 256 var model = this.widget_manager.get_model(value.slice(10, value.length));
257 257 if (model) {
258 258 return model;
259 259 } else {
260 260 return value;
261 261 }
262 262 } else {
263 263 return value;
264 264 }
265 265 },
266 266
267 267 });
268 268 widgetmanager.WidgetManager.register_widget_model('WidgetModel', WidgetModel);
269 269
270 270
271 271 var WidgetView = Backbone.View.extend({
272 272 initialize: function(parameters) {
273 273 // Public constructor.
274 274 this.model.on('change',this.update,this);
275 275 this.options = parameters.options;
276 276 this.child_model_views = {};
277 277 this.child_views = {};
278 278 this.model.views.push(this);
279 279 this.id = this.id || IPython.utils.uuid();
280 280 this.on('displayed', function() {
281 281 this.is_displayed = true;
282 282 }, this);
283 283 },
284 284
285 285 update: function(){
286 286 // Triggered on model change.
287 287 //
288 288 // Update view to be consistent with this.model
289 289 },
290 290
291 291 create_child_view: function(child_model, options) {
292 292 // Create and return a child view.
293 293 //
294 294 // -given a model and (optionally) a view name if the view name is
295 295 // not given, it defaults to the model's default view attribute.
296 296
297 297 // TODO: this is hacky, and makes the view depend on this cell attribute and widget manager behavior
298 298 // it would be great to have the widget manager add the cell metadata
299 299 // to the subview without having to add it here.
300 300 options = $.extend({ parent: this }, options || {});
301 301 var child_view = this.model.widget_manager.create_view(child_model, options, this);
302 302
303 303 // Associate the view id with the model id.
304 304 if (this.child_model_views[child_model.id] === undefined) {
305 305 this.child_model_views[child_model.id] = [];
306 306 }
307 307 this.child_model_views[child_model.id].push(child_view.id);
308 308
309 309 // Remember the view by id.
310 310 this.child_views[child_view.id] = child_view;
311 311 return child_view;
312 312 },
313 313
314 314 pop_child_view: function(child_model) {
315 315 // Delete a child view that was previously created using create_child_view.
316 316 var view_ids = this.child_model_views[child_model.id];
317 317 if (view_ids !== undefined) {
318 318
319 319 // Only delete the first view in the list.
320 320 var view_id = view_ids[0];
321 321 var view = this.child_views[view_id];
322 322 delete this.child_views[view_id];
323 323 view_ids.splice(0,1);
324 324 child_model.views.pop(view);
325 325
326 326 // Remove the view list specific to this model if it is empty.
327 327 if (view_ids.length === 0) {
328 328 delete this.child_model_views[child_model.id];
329 329 }
330 330 return view;
331 331 }
332 332 return null;
333 333 },
334 334
335 335 do_diff: function(old_list, new_list, removed_callback, added_callback) {
336 336 // Difference a changed list and call remove and add callbacks for
337 337 // each removed and added item in the new list.
338 338 //
339 339 // Parameters
340 340 // ----------
341 341 // old_list : array
342 342 // new_list : array
343 343 // removed_callback : Callback(item)
344 344 // Callback that is called for each item removed.
345 345 // added_callback : Callback(item)
346 346 // Callback that is called for each item added.
347 347
348 348 // Walk the lists until an unequal entry is found.
349 349 var i;
350 350 for (i = 0; i < new_list.length; i++) {
351 351 if (i >= old_list.length || new_list[i] !== old_list[i]) {
352 352 break;
353 353 }
354 354 }
355 355
356 356 // Remove the non-matching items from the old list.
357 357 for (var j = i; j < old_list.length; j++) {
358 358 removed_callback(old_list[j]);
359 359 }
360 360
361 361 // Add the rest of the new list items.
362 362 for (; i < new_list.length; i++) {
363 363 added_callback(new_list[i]);
364 364 }
365 365 },
366 366
367 367 callbacks: function(){
368 368 // Create msg callbacks for a comm msg.
369 369 return this.model.callbacks(this);
370 370 },
371 371
372 372 render: function(){
373 373 // Render the view.
374 374 //
375 375 // By default, this is only called the first time the view is created
376 376 },
377 377
378 378 show: function(){
379 379 // Show the widget-area
380 380 if (this.options && this.options.cell &&
381 381 this.options.cell.widget_area !== undefined) {
382 382 this.options.cell.widget_area.show();
383 383 }
384 384 },
385 385
386 386 send: function (content) {
387 387 // Send a custom msg associated with this view.
388 388 this.model.send(content, this.callbacks());
389 389 },
390 390
391 391 touch: function () {
392 392 this.model.save_changes(this.callbacks());
393 393 },
394 394
395 395 after_displayed: function (callback, context) {
396 396 // Calls the callback right away is the view is already displayed
397 397 // otherwise, register the callback to the 'displayed' event.
398 398 if (this.is_displayed) {
399 399 callback.apply(context);
400 400 } else {
401 401 this.on('displayed', callback, context);
402 402 }
403 403 },
404 404 });
405 405
406 406
407 407 var DOMWidgetView = WidgetView.extend({
408 408 initialize: function (parameters) {
409 409 // Public constructor
410 410 DOMWidgetView.__super__.initialize.apply(this, [parameters]);
411 411 this.on('displayed', this.show, this);
412 412 this.model.on('change:visible', this.update_visible, this);
413 413 this.model.on('change:_css', this.update_css, this);
414 414
415 415 this.model.on('change:_dom_classes', function(model, new_classes) {
416 416 var old_classes = model.previous('children');
417 417 this.update_classes(old_classes, new_classes);
418 418 }, this);
419 419
420 420 this.model.on('change:fore_color', function (model, value) {
421 421 this.update_attr('color', value); }, this);
422 422
423 423 this.model.on('change:back_color', function (model, value) {
424 424 this.update_attr('background', value); }, this);
425 425
426 426 this.model.on('change:width', function (model, value) {
427 427 this.update_attr('width', value); }, this);
428 428
429 429 this.model.on('change:height', function (model, value) {
430 430 this.update_attr('height', value); }, this);
431 431
432 432 this.model.on('change:border_color', function (model, value) {
433 433 this.update_attr('border-color', value); }, this);
434 434
435 435 this.model.on('change:border_width', function (model, value) {
436 436 this.update_attr('border-width', value); }, this);
437 437
438 438 this.model.on('change:border_style', function (model, value) {
439 439 this.update_attr('border-style', value); }, this);
440 440
441 441 this.model.on('change:font_style', function (model, value) {
442 442 this.update_attr('font-style', value); }, this);
443 443
444 444 this.model.on('change:font_weight', function (model, value) {
445 445 this.update_attr('font-weight', value); }, this);
446 446
447 447 this.model.on('change:font_size', function (model, value) {
448 448 this.update_attr('font-size', value); }, this);
449 449
450 450 this.model.on('change:font_family', function (model, value) {
451 451 this.update_attr('font-family', value); }, this);
452 452
453 453 this.model.on('change:padding', function (model, value) {
454 454 this.update_attr('padding', value); }, this);
455 455
456 456 this.model.on('change:margin', function (model, value) {
457 457 this.update_attr('margin', value); }, this);
458 458
459 459 this.after_displayed(function() {
460 460 this.update_visible(this.model, this.model.get("visible"));
461 461 this.update_css(this.model, this.model.get("_css"));
462 462
463 463 this.update_classes([], this.model.get('_dom_classes'));
464 464 this.update_attr('color', this.model.get('fore_color'));
465 465 this.update_attr('background', this.model.get('back_color'));
466 466 this.update_attr('width', this.model.get('width'));
467 467 this.update_attr('height', this.model.get('height'));
468 468 this.update_attr('border-color', this.model.get('border_color'));
469 469 this.update_attr('border-width', this.model.get('border_width'));
470 470 this.update_attr('border-style', this.model.get('border_style'));
471 471 this.update_attr('font-style', this.model.get('font_style'));
472 472 this.update_attr('font-weight', this.model.get('font_weight'));
473 473 this.update_attr('font-size', this.model.get('font_size'));
474 474 this.update_attr('font-family', this.model.get('font_family'));
475 475 this.update_attr('padding', this.model.get('padding'));
476 476 this.update_attr('margin', this.model.get('margin'));
477 477 }, this);
478 478 },
479 479
480 480 update_attr: function(name, value) {
481 481 // Set a css attr of the widget view.
482 482 this.$el.css(name, value);
483 483 },
484 484
485 485 update_visible: function(model, value) {
486 486 // Update visibility
487 487 this.$el.toggle(value);
488 488 },
489 489
490 490 update_css: function (model, css) {
491 491 // Update the css styling of this view.
492 492 var e = this.$el;
493 493 if (css === undefined) {return;}
494 494 for (var i = 0; i < css.length; i++) {
495 495 // Apply the css traits to all elements that match the selector.
496 496 var selector = css[i][0];
497 497 var elements = this._get_selector_element(selector);
498 498 if (elements.length > 0) {
499 499 var trait_key = css[i][1];
500 500 var trait_value = css[i][2];
501 501 elements.css(trait_key ,trait_value);
502 502 }
503 503 }
504 504 },
505 505
506 update_classes: function (old_classes, new_classes) {
507 // Update the DOM classes applied to the topmost element.
506 update_classes: function (old_classes, new_classes, $el) {
507 // Update the DOM classes applied to an element, default to this.$el.
508 if ($el===undefined) {
509 $el = this.$el;
510 }
508 511 this.do_diff(old_classes, new_classes, function(removed) {
509 this.$el.removeClass(removed);
512 $el.removeClass(removed);
510 513 }, function(added) {
511 this.$el.addClass(added);
514 $el.addClass(added);
512 515 });
513 516 },
514 517
518 update_mapped_classes: function(class_map, trait_name, previous_trait_value, $el) {
519 // Update the DOM classes applied to the widget based on a single
520 // trait's value.
521 //
522 // Given a trait value classes map, this function automatically
523 // handles applying the appropriate classes to the widget element
524 // and removing classes that are no longer valid.
525 //
526 // Parameters
527 // ----------
528 // class_map: dictionary
529 // Dictionary of trait values to class lists.
530 // Example:
531 // {
532 // success: ['alert', 'alert-success'],
533 // info: ['alert', 'alert-info'],
534 // warning: ['alert', 'alert-warning'],
535 // danger: ['alert', 'alert-danger']
536 // };
537 // trait_name: string
538 // Name of the trait to check the value of.
539 // previous_trait_value: optional string, default ''
540 // Last trait value
541 // $el: optional jQuery element handle, defaults to this.$el
542 // Element that the classes are applied to.
543 var key = previous_trait_value;
544 if (key === undefined) {
545 key = this.model.previous(trait_name);
546 }
547 var old_classes = class_map[key] ? class_map[key] : [];
548 key = this.model.get(trait_name);
549 var new_classes = class_map[key] ? class_map[key] : [];
550
551 this.update_classes(old_classes, new_classes, $el || this.$el);
552 },
553
515 554 _get_selector_element: function (selector) {
516 555 // Get the elements via the css selector.
517 556 var elements;
518 557 if (!selector) {
519 558 elements = this.$el;
520 559 } else {
521 560 elements = this.$el.find(selector).addBack(selector);
522 561 }
523 562 return elements;
524 563 },
525 564 });
526 565
527 566 var widget = {
528 567 'WidgetModel': WidgetModel,
529 568 'WidgetView': WidgetView,
530 569 'DOMWidgetView': DOMWidgetView,
531 570 };
532 571
533 572 // For backwards compatability.
534 573 $.extend(IPython, widget);
535 574
536 575 return widget;
537 576 });
@@ -1,124 +1,140 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 "widgets/js/widget",
6 6 "jquery",
7 7 "bootstrap",
8 8 ], function(widget, $){
9 9
10 10 var CheckboxView = widget.DOMWidgetView.extend({
11 11 render : function(){
12 12 // Called when view is rendered.
13 13 this.$el
14 14 .addClass('widget-hbox-single');
15 15 this.$label = $('<div />')
16 16 .addClass('widget-hlabel')
17 17 .appendTo(this.$el)
18 18 .hide();
19 19 this.$checkbox = $('<input />')
20 20 .attr('type', 'checkbox')
21 21 .appendTo(this.$el)
22 22 .click($.proxy(this.handle_click, this));
23 23
24 24 this.update(); // Set defaults.
25 25 },
26 26
27 27 update_attr: function(name, value) {
28 28 // Set a css attr of the widget view.
29 29 this.$checkbox.css(name, value);
30 30 },
31 31
32 32 handle_click: function() {
33 33 // Handles when the checkbox is clicked.
34 34
35 35 // Calling model.set will trigger all of the other views of the
36 36 // model to update.
37 37 var value = this.model.get('value');
38 38 this.model.set('value', ! value, {updated_view: this});
39 39 this.touch();
40 40 },
41 41
42 42 update : function(options){
43 43 // Update the contents of this view
44 44 //
45 45 // Called when the model is changed. The model may have been
46 46 // changed by another view or by a state update from the back-end.
47 47 this.$checkbox.prop('checked', this.model.get('value'));
48 48
49 49 if (options === undefined || options.updated_view != this) {
50 50 var disabled = this.model.get('disabled');
51 51 this.$checkbox.prop('disabled', disabled);
52 52
53 53 var description = this.model.get('description');
54 54 if (description.trim().length === 0) {
55 55 this.$label.hide();
56 56 } else {
57 57 this.$label.text(description);
58 58 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
59 59 this.$label.show();
60 60 }
61 61 }
62 62 return CheckboxView.__super__.update.apply(this);
63 63 },
64 64
65 65 });
66 66
67 67
68 68 var ToggleButtonView = widget.DOMWidgetView.extend({
69 69 render : function() {
70 70 // Called when view is rendered.
71 71 var that = this;
72 72 this.setElement($('<button />')
73 73 .addClass('btn btn-default')
74 74 .attr('type', 'button')
75 75 .on('click', function (e) {
76 76 e.preventDefault();
77 77 that.handle_click();
78 78 }));
79 79
80 this.model.on('change:button_style', function(model, value) {
81 this.update_button_style();
82 }, this);
83 this.update_button_style('');
84
80 85 this.update(); // Set defaults.
81 86 },
87
88 update_button_style: function(previous_trait_value) {
89 var class_map = {
90 primary: ['btn-primary'],
91 success: ['btn-success'],
92 info: ['btn-info'],
93 warning: ['btn-warning'],
94 danger: ['btn-danger']
95 };
96 this.update_mapped_classes(class_map, 'button_style', previous_trait_value);
97 },
82 98
83 99 update : function(options){
84 100 // Update the contents of this view
85 101 //
86 102 // Called when the model is changed. The model may have been
87 103 // changed by another view or by a state update from the back-end.
88 104 if (this.model.get('value')) {
89 105 this.$el.addClass('active');
90 106 } else {
91 107 this.$el.removeClass('active');
92 108 }
93 109
94 110 if (options === undefined || options.updated_view != this) {
95 111
96 112 var disabled = this.model.get('disabled');
97 113 this.$el.prop('disabled', disabled);
98 114
99 115 var description = this.model.get('description');
100 116 if (description.trim().length === 0) {
101 117 this.$el.html("&nbsp;"); // Preserve button height
102 118 } else {
103 119 this.$el.text(description);
104 120 }
105 121 }
106 122 return ToggleButtonView.__super__.update.apply(this);
107 123 },
108 124
109 125 handle_click: function(e) {
110 126 // Handles and validates user input.
111 127
112 128 // Calling model.set will trigger all of the other views of the
113 129 // model to update.
114 130 var value = this.model.get('value');
115 131 this.model.set('value', ! value, {updated_view: this});
116 132 this.touch();
117 133 },
118 134 });
119 135
120 136 return {
121 137 'CheckboxView': CheckboxView,
122 138 'ToggleButtonView': ToggleButtonView,
123 139 };
124 140 });
@@ -1,330 +1,344 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 "widgets/js/widget",
6 6 "jqueryui",
7 7 "bootstrap",
8 8 ], function(widget, $){
9 9
10 10 var BoxView = widget.DOMWidgetView.extend({
11 11 initialize: function(){
12 12 // Public constructor
13 13 BoxView.__super__.initialize.apply(this, arguments);
14 14 this.model.on('change:children', function(model, value) {
15 15 this.update_children(model.previous('children'), value);
16 16 }, this);
17 17 this.model.on('change:overflow_x', function(model, value) {
18 18 this.update_overflow_x();
19 19 }, this);
20 20 this.model.on('change:overflow_y', function(model, value) {
21 21 this.update_overflow_y();
22 22 }, this);
23 this.model.on('change:box_style', function(model, value) {
24 this.update_box_style();
25 }, this);
23 26 },
24 27
25 28 update_attr: function(name, value) {
26 29 // Set a css attr of the widget view.
27 30 this.$box.css(name, value);
28 31 },
29 32
30 33 render: function(){
31 34 // Called when view is rendered.
32 35 this.$box = this.$el;
33 36 this.$box.addClass('widget-box');
34 37 this.update_children([], this.model.get('children'));
35 38 this.update_overflow_x();
36 39 this.update_overflow_y();
40 this.update_box_style('');
37 41 },
38 42
39 43 update_overflow_x: function() {
40 44 // Called when the x-axis overflow setting is changed.
41 45 this.$box.css('overflow-x', this.model.get('overflow_x'));
42 46 },
43 47
44 48 update_overflow_y: function() {
45 49 // Called when the y-axis overflow setting is changed.
46 50 this.$box.css('overflow-y', this.model.get('overflow_y'));
47 51 },
52
53 update_box_style: function(previous_trait_value) {
54 var class_map = {
55 success: ['alert', 'alert-success'],
56 info: ['alert', 'alert-info'],
57 warning: ['alert', 'alert-warning'],
58 danger: ['alert', 'alert-danger']
59 };
60 this.update_mapped_classes(class_map, 'box_style', previous_trait_value, this.$box);
61 },
48 62
49 63 update_children: function(old_list, new_list) {
50 64 // Called when the children list changes.
51 65 this.do_diff(old_list, new_list,
52 66 $.proxy(this.remove_child_model, this),
53 67 $.proxy(this.add_child_model, this));
54 68 },
55 69
56 70 remove_child_model: function(model) {
57 71 // Called when a model is removed from the children list.
58 72 this.pop_child_view(model).remove();
59 73 },
60 74
61 75 add_child_model: function(model) {
62 76 // Called when a model is added to the children list.
63 77 var view = this.create_child_view(model);
64 78 this.$box.append(view.$el);
65 79
66 80 // Trigger the displayed event of the child view.
67 81 this.after_displayed(function() {
68 82 view.trigger('displayed');
69 83 });
70 84 },
71 85 });
72 86
73 87
74 88 var FlexBoxView = BoxView.extend({
75 89 render: function(){
76 90 FlexBoxView.__super__.render.apply(this);
77 91 this.model.on('change:orientation', this.update_orientation, this);
78 92 this.model.on('change:flex', this._flex_changed, this);
79 93 this.model.on('change:pack', this._pack_changed, this);
80 94 this.model.on('change:align', this._align_changed, this);
81 95 this._flex_changed();
82 96 this._pack_changed();
83 97 this._align_changed();
84 98 this.update_orientation();
85 99 },
86 100
87 101 update_orientation: function(){
88 102 var orientation = this.model.get("orientation");
89 103 if (orientation == "vertical") {
90 104 this.$box.removeClass("hbox").addClass("vbox");
91 105 } else {
92 106 this.$box.removeClass("vbox").addClass("hbox");
93 107 }
94 108 },
95 109
96 110 _flex_changed: function(){
97 111 if (this.model.previous('flex')) {
98 112 this.$box.removeClass('box-flex' + this.model.previous('flex'));
99 113 }
100 114 this.$box.addClass('box-flex' + this.model.get('flex'));
101 115 },
102 116
103 117 _pack_changed: function(){
104 118 if (this.model.previous('pack')) {
105 119 this.$box.removeClass(this.model.previous('pack'));
106 120 }
107 121 this.$box.addClass(this.model.get('pack'));
108 122 },
109 123
110 124 _align_changed: function(){
111 125 if (this.model.previous('align')) {
112 126 this.$box.removeClass('align-' + this.model.previous('align'));
113 127 }
114 128 this.$box.addClass('align-' + this.model.get('align'));
115 129 },
116 130 });
117 131
118 132 var PopupView = BoxView.extend({
119 133
120 134 render: function(){
121 135 // Called when view is rendered.
122 136 var that = this;
123 137
124 138 this.$el.on("remove", function(){
125 139 that.$backdrop.remove();
126 140 });
127 141 this.$backdrop = $('<div />')
128 142 .appendTo($('#notebook-container'))
129 143 .addClass('modal-dialog')
130 144 .css('position', 'absolute')
131 145 .css('left', '0px')
132 146 .css('top', '0px');
133 147 this.$window = $('<div />')
134 148 .appendTo(this.$backdrop)
135 149 .addClass('modal-content widget-modal')
136 150 .mousedown(function(){
137 151 that.bring_to_front();
138 152 });
139 153
140 154 // Set the elements array since the this.$window element is not child
141 155 // of this.$el and the parent widget manager or other widgets may
142 156 // need to know about all of the top-level widgets. The IPython
143 157 // widget manager uses this to register the elements with the
144 158 // keyboard manager.
145 159 this.additional_elements = [this.$window];
146 160
147 161 this.$title_bar = $('<div />')
148 162 .addClass('popover-title')
149 163 .appendTo(this.$window)
150 164 .mousedown(function(){
151 165 that.bring_to_front();
152 166 });
153 167 this.$close = $('<button />')
154 168 .addClass('close fa fa-remove')
155 169 .css('margin-left', '5px')
156 170 .appendTo(this.$title_bar)
157 171 .click(function(){
158 172 that.hide();
159 173 event.stopPropagation();
160 174 });
161 175 this.$minimize = $('<button />')
162 176 .addClass('close fa fa-arrow-down')
163 177 .appendTo(this.$title_bar)
164 178 .click(function(){
165 179 that.popped_out = !that.popped_out;
166 180 if (!that.popped_out) {
167 181 that.$minimize
168 182 .removeClass('fa fa-arrow-down')
169 183 .addClass('fa fa-arrow-up');
170 184
171 185 that.$window
172 186 .draggable('destroy')
173 187 .resizable('destroy')
174 188 .removeClass('widget-modal modal-content')
175 189 .addClass('docked-widget-modal')
176 190 .detach()
177 191 .insertBefore(that.$show_button);
178 192 that.$show_button.hide();
179 193 that.$close.hide();
180 194 } else {
181 195 that.$minimize
182 196 .addClass('fa fa-arrow-down')
183 197 .removeClass('fa fa-arrow-up');
184 198
185 199 that.$window
186 200 .removeClass('docked-widget-modal')
187 201 .addClass('widget-modal modal-content')
188 202 .detach()
189 203 .appendTo(that.$backdrop)
190 204 .draggable({handle: '.popover-title', snap: '#notebook, .modal', snapMode: 'both'})
191 205 .resizable()
192 206 .children('.ui-resizable-handle').show();
193 207 that.show();
194 208 that.$show_button.show();
195 209 that.$close.show();
196 210 }
197 211 event.stopPropagation();
198 212 });
199 213 this.$title = $('<div />')
200 214 .addClass('widget-modal-title')
201 215 .html("&nbsp;")
202 216 .appendTo(this.$title_bar);
203 217 this.$box = $('<div />')
204 218 .addClass('modal-body')
205 219 .addClass('widget-modal-body')
206 220 .addClass('widget-box')
207 221 .addClass('vbox')
208 222 .appendTo(this.$window);
209 223
210 224 this.$show_button = $('<button />')
211 225 .html("&nbsp;")
212 226 .addClass('btn btn-info widget-modal-show')
213 227 .appendTo(this.$el)
214 228 .click(function(){
215 229 that.show();
216 230 });
217 231
218 232 this.$window.draggable({handle: '.popover-title', snap: '#notebook, .modal', snapMode: 'both'});
219 233 this.$window.resizable();
220 234 this.$window.on('resize', function(){
221 235 that.$box.outerHeight(that.$window.innerHeight() - that.$title_bar.outerHeight());
222 236 });
223 237
224 238 this._shown_once = false;
225 239 this.popped_out = true;
226 240
227 241 this.update_children([], this.model.get('children'));
228 242 this.model.on('change:children', function(model, value) {
229 243 this.update_children(model.previous('children'), value);
230 244 }, this);
231 245 },
232 246
233 247 hide: function() {
234 248 // Called when the modal hide button is clicked.
235 249 this.$window.hide();
236 250 this.$show_button.removeClass('btn-info');
237 251 },
238 252
239 253 show: function() {
240 254 // Called when the modal show button is clicked.
241 255 this.$show_button.addClass('btn-info');
242 256 this.$window.show();
243 257 if (this.popped_out) {
244 258 this.$window.css("positon", "absolute");
245 259 this.$window.css("top", "0px");
246 260 this.$window.css("left", Math.max(0, (($('body').outerWidth() - this.$window.outerWidth()) / 2) +
247 261 $(window).scrollLeft()) + "px");
248 262 this.bring_to_front();
249 263 }
250 264 },
251 265
252 266 bring_to_front: function() {
253 267 // Make the modal top-most, z-ordered about the other modals.
254 268 var $widget_modals = $(".widget-modal");
255 269 var max_zindex = 0;
256 270 $widget_modals.each(function (index, el){
257 271 var zindex = parseInt($(el).css('z-index'));
258 272 if (!isNaN(zindex)) {
259 273 max_zindex = Math.max(max_zindex, zindex);
260 274 }
261 275 });
262 276
263 277 // Start z-index of widget modals at 2000
264 278 max_zindex = Math.max(max_zindex, 2000);
265 279
266 280 $widget_modals.each(function (index, el){
267 281 $el = $(el);
268 282 if (max_zindex == parseInt($el.css('z-index'))) {
269 283 $el.css('z-index', max_zindex - 1);
270 284 }
271 285 });
272 286 this.$window.css('z-index', max_zindex);
273 287 },
274 288
275 289 update: function(){
276 290 // Update the contents of this view
277 291 //
278 292 // Called when the model is changed. The model may have been
279 293 // changed by another view or by a state update from the back-end.
280 294 var description = this.model.get('description');
281 295 if (description.trim().length === 0) {
282 296 this.$title.html("&nbsp;"); // Preserve title height
283 297 } else {
284 298 this.$title.text(description);
285 299 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$title.get(0)]);
286 300 }
287 301
288 302 var button_text = this.model.get('button_text');
289 303 if (button_text.trim().length === 0) {
290 304 this.$show_button.html("&nbsp;"); // Preserve button height
291 305 } else {
292 306 this.$show_button.text(button_text);
293 307 }
294 308
295 309 if (!this._shown_once) {
296 310 this._shown_once = true;
297 311 this.show();
298 312 }
299 313
300 314 return PopupView.__super__.update.apply(this);
301 315 },
302 316
303 317 _get_selector_element: function(selector) {
304 318 // Get an element view a 'special' jquery selector. (see widget.js)
305 319 //
306 320 // Since the modal actually isn't within the $el in the DOM, we need to extend
307 321 // the selector logic to allow the user to set css on the modal if need be.
308 322 // The convention used is:
309 323 // "modal" - select the modal div
310 324 // "modal [selector]" - select element(s) within the modal div.
311 325 // "[selector]" - select elements within $el
312 326 // "" - select the $el
313 327 if (selector.substring(0, 5) == 'modal') {
314 328 if (selector == 'modal') {
315 329 return this.$window;
316 330 } else {
317 331 return this.$window.find(selector.substring(6));
318 332 }
319 333 } else {
320 334 return PopupView.__super__._get_selector_element.apply(this, [selector]);
321 335 }
322 336 },
323 337 });
324 338
325 339 return {
326 340 'BoxView': BoxView,
327 341 'PopupView': PopupView,
328 342 'FlexBoxView': FlexBoxView,
329 343 };
330 344 });
@@ -1,54 +1,70 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 "widgets/js/widget",
6 6 "jquery",
7 7 "bootstrap",
8 8 ], function(widget, $){
9 9
10 10 var ButtonView = widget.DOMWidgetView.extend({
11 11 render : function(){
12 12 // Called when view is rendered.
13 13 this.setElement($("<button />")
14 14 .addClass('btn btn-default'));
15 15
16 this.model.on('change:button_style', function(model, value) {
17 this.update_button_style();
18 }, this);
19 this.update_button_style('');
20
16 21 this.update(); // Set defaults.
17 22 },
18 23
19 24 update : function(){
20 25 // Update the contents of this view
21 26 //
22 27 // Called when the model is changed. The model may have been
23 28 // changed by another view or by a state update from the back-end.
24 29 var description = this.model.get('description');
25 30 if (description.length === 0) {
26 31 this.$el.html("&nbsp;"); // Preserve button height
27 32 } else {
28 33 this.$el.text(description);
29 34 }
30 35
31 36 if (this.model.get('disabled')) {
32 37 this.$el.attr('disabled','disabled');
33 38 } else {
34 39 this.$el.removeAttr('disabled');
35 40 }
36 41
37 42 return ButtonView.__super__.update.apply(this);
38 43 },
39 44
45 update_button_style: function(previous_trait_value) {
46 var class_map = {
47 primary: ['btn-primary'],
48 success: ['btn-success'],
49 info: ['btn-info'],
50 warning: ['btn-warning'],
51 danger: ['btn-danger']
52 };
53 this.update_mapped_classes(class_map, 'button_style', previous_trait_value);
54 },
55
40 56 events: {
41 57 // Dictionary of events and their handlers.
42 58 'click': '_handle_click',
43 59 },
44 60
45 61 _handle_click: function(){
46 62 // Handles when the button is clicked.
47 63 this.send({event: 'click'});
48 64 },
49 65 });
50 66
51 67 return {
52 68 'ButtonView': ButtonView,
53 69 };
54 70 });
@@ -1,364 +1,367 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 "widgets/js/widget",
6 6 "jqueryui",
7 7 "bootstrap",
8 8 ], function(widget, $){
9 9
10 10 var IntSliderView = widget.DOMWidgetView.extend({
11 11 render : function(){
12 12 // Called when view is rendered.
13 13 this.$el
14 14 .addClass('widget-hbox-single');
15 15 this.$label = $('<div />')
16 16 .appendTo(this.$el)
17 17 .addClass('widget-hlabel')
18 18 .hide();
19 19
20 20 this.$slider = $('<div />')
21 21 .slider({})
22 22 .addClass('slider');
23 23 // Put the slider in a container
24 24 this.$slider_container = $('<div />')
25 25 .addClass('widget-hslider')
26 26 .append(this.$slider);
27 27 this.$el.append(this.$slider_container);
28 28
29 29 this.$readout = $('<div/>')
30 30 .appendTo(this.$el)
31 31 .addClass('widget-hreadout')
32 32 .hide();
33 33
34 34 this.model.on('change:slider_color', function(sender, value) {
35 35 this.$slider.find('a').css('background', value);
36 36 }, this);
37 37
38 38 // Set defaults.
39 39 this.update();
40 40 },
41 41
42 42 update_attr: function(name, value) {
43 43 // Set a css attr of the widget view.
44 44 if (name == 'color') {
45 45 this.$readout.css(name, value);
46 46 } else if (name.substring(0, 4) == 'font') {
47 47 this.$readout.css(name, value);
48 48 } else if (name.substring(0, 6) == 'border') {
49 49 this.$slider.find('a').css(name, value);
50 50 this.$slider_container.css(name, value);
51 51 } else if (name == 'width' || name == 'height' || name == 'background') {
52 52 this.$slider_container.css(name, value);
53 53 } else {
54 54 this.$slider.css(name, value);
55 55 }
56 56 },
57 57
58 58 update : function(options){
59 59 // Update the contents of this view
60 60 //
61 61 // Called when the model is changed. The model may have been
62 62 // changed by another view or by a state update from the back-end.
63 63 if (options === undefined || options.updated_view != this) {
64 64 // JQuery slider option keys. These keys happen to have a
65 65 // one-to-one mapping with the corrosponding keys of the model.
66 66 var jquery_slider_keys = ['step', 'max', 'min', 'disabled'];
67 67 var that = this;
68 68 that.$slider.slider({});
69 69 _.each(jquery_slider_keys, function(key, i) {
70 70 var model_value = that.model.get(key);
71 71 if (model_value !== undefined) {
72 72 that.$slider.slider("option", key, model_value);
73 73 }
74 74 });
75 75 var range_value = this.model.get("_range");
76 76 if (range_value !== undefined) {
77 77 this.$slider.slider("option", "range", range_value);
78 78 }
79 79
80 80 // WORKAROUND FOR JQUERY SLIDER BUG.
81 81 // The horizontal position of the slider handle
82 82 // depends on the value of the slider at the time
83 83 // of orientation change. Before applying the new
84 84 // workaround, we set the value to the minimum to
85 85 // make sure that the horizontal placement of the
86 86 // handle in the vertical slider is always
87 87 // consistent.
88 88 var orientation = this.model.get('orientation');
89 89 var min = this.model.get('min');
90 90 var max = this.model.get('max');
91 91 if (this.model.get('_range')) {
92 92 this.$slider.slider('option', 'values', [min, min]);
93 93 } else {
94 94 this.$slider.slider('option', 'value', min);
95 95 }
96 96 this.$slider.slider('option', 'orientation', orientation);
97 97 var value = this.model.get('value');
98 98 if (this.model.get('_range')) {
99 99 // values for the range case are validated python-side in
100 100 // _Bounded{Int,Float}RangeWidget._validate
101 101 this.$slider.slider('option', 'values', value);
102 102 this.$readout.text(value.join("-"));
103 103 } else {
104 104 if(value > max) {
105 105 value = max;
106 106 }
107 107 else if(value < min){
108 108 value = min;
109 109 }
110 110 this.$slider.slider('option', 'value', value);
111 111 this.$readout.text(value);
112 112 }
113 113
114 114 if(this.model.get('value')!=value) {
115 115 this.model.set('value', value, {updated_view: this});
116 116 this.touch();
117 117 }
118 118
119 119 // Use the right CSS classes for vertical & horizontal sliders
120 120 if (orientation=='vertical') {
121 121 this.$slider_container
122 122 .removeClass('widget-hslider')
123 123 .addClass('widget-vslider');
124 124 this.$el
125 125 .removeClass('widget-hbox-single');
126 126 this.$label
127 127 .removeClass('widget-hlabel')
128 128 .addClass('widget-vlabel');
129 129 this.$readout
130 130 .removeClass('widget-hreadout')
131 131 .addClass('widget-vreadout');
132 132
133 133 } else {
134 134 this.$slider_container
135 135 .removeClass('widget-vslider')
136 136 .addClass('widget-hslider');
137 137 this.$el
138 138 .addClass('widget-hbox-single');
139 139 this.$label
140 140 .removeClass('widget-vlabel')
141 141 .addClass('widget-hlabel');
142 142 this.$readout
143 143 .removeClass('widget-vreadout')
144 144 .addClass('widget-hreadout');
145 145 }
146 146
147 147 var description = this.model.get('description');
148 148 if (description.length === 0) {
149 149 this.$label.hide();
150 150 } else {
151 151 this.$label.text(description);
152 152 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
153 153 this.$label.show();
154 154 }
155 155
156 156 var readout = this.model.get('readout');
157 157 if (readout) {
158 158 this.$readout.show();
159 159 } else {
160 160 this.$readout.hide();
161 161 }
162 162 }
163 163 return IntSliderView.__super__.update.apply(this);
164 164 },
165 165
166 166 events: {
167 167 // Dictionary of events and their handlers.
168 168 "slide" : "handleSliderChange"
169 169 },
170 170
171 171 handleSliderChange: function(e, ui) {
172 172 // Called when the slider value is changed.
173 173
174 174 // Calling model.set will trigger all of the other views of the
175 175 // model to update.
176 176 if (this.model.get("_range")) {
177 177 var actual_value = ui.values.map(this._validate_slide_value);
178 178 this.$readout.text(actual_value.join("-"));
179 179 } else {
180 180 var actual_value = this._validate_slide_value(ui.value);
181 181 this.$readout.text(actual_value);
182 182 }
183 183 this.model.set('value', actual_value, {updated_view: this});
184 184 this.touch();
185 185 },
186 186
187 187 _validate_slide_value: function(x) {
188 188 // Validate the value of the slider before sending it to the back-end
189 189 // and applying it to the other views on the page.
190 190
191 191 // Double bit-wise not truncates the decimel (int cast).
192 192 return ~~x;
193 193 },
194 194 });
195 195
196 196
197 197 var IntTextView = widget.DOMWidgetView.extend({
198 198 render : function(){
199 199 // Called when view is rendered.
200 200 this.$el
201 201 .addClass('widget-hbox-single');
202 202 this.$label = $('<div />')
203 203 .appendTo(this.$el)
204 204 .addClass('widget-hlabel')
205 205 .hide();
206 206 this.$textbox = $('<input type="text" />')
207 207 .addClass('form-control')
208 208 .addClass('widget-numeric-text')
209 209 .appendTo(this.$el);
210 210 this.update(); // Set defaults.
211 211 },
212 212
213 213 update : function(options){
214 214 // Update the contents of this view
215 215 //
216 216 // Called when the model is changed. The model may have been
217 217 // changed by another view or by a state update from the back-end.
218 218 if (options === undefined || options.updated_view != this) {
219 219 var value = this.model.get('value');
220 220 if (this._parse_value(this.$textbox.val()) != value) {
221 221 this.$textbox.val(value);
222 222 }
223 223
224 224 if (this.model.get('disabled')) {
225 225 this.$textbox.attr('disabled','disabled');
226 226 } else {
227 227 this.$textbox.removeAttr('disabled');
228 228 }
229 229
230 230 var description = this.model.get('description');
231 231 if (description.length === 0) {
232 232 this.$label.hide();
233 233 } else {
234 234 this.$label.text(description);
235 235 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
236 236 this.$label.show();
237 237 }
238 238 }
239 239 return IntTextView.__super__.update.apply(this);
240 240 },
241 241
242 242 update_attr: function(name, value) {
243 243 // Set a css attr of the widget view.
244 244 this.$textbox.css(name, value);
245 245 },
246 246
247 247 events: {
248 248 // Dictionary of events and their handlers.
249 249 "keyup input" : "handleChanging",
250 250 "paste input" : "handleChanging",
251 251 "cut input" : "handleChanging",
252 252
253 253 // Fires only when control is validated or looses focus.
254 254 "change input" : "handleChanged"
255 255 },
256 256
257 257 handleChanging: function(e) {
258 258 // Handles and validates user input.
259 259
260 260 // Try to parse value as a int.
261 261 var numericalValue = 0;
262 262 if (e.target.value !== '') {
263 263 var trimmed = e.target.value.trim();
264 264 if (!(['-', '-.', '.', '+.', '+'].indexOf(trimmed) >= 0)) {
265 265 numericalValue = this._parse_value(e.target.value);
266 266 }
267 267 }
268 268
269 269 // If parse failed, reset value to value stored in model.
270 270 if (isNaN(numericalValue)) {
271 271 e.target.value = this.model.get('value');
272 272 } else if (!isNaN(numericalValue)) {
273 273 if (this.model.get('max') !== undefined) {
274 274 numericalValue = Math.min(this.model.get('max'), numericalValue);
275 275 }
276 276 if (this.model.get('min') !== undefined) {
277 277 numericalValue = Math.max(this.model.get('min'), numericalValue);
278 278 }
279 279
280 280 // Apply the value if it has changed.
281 281 if (numericalValue != this.model.get('value')) {
282 282
283 283 // Calling model.set will trigger all of the other views of the
284 284 // model to update.
285 285 this.model.set('value', numericalValue, {updated_view: this});
286 286 this.touch();
287 287 }
288 288 }
289 289 },
290 290
291 291 handleChanged: function(e) {
292 292 // Applies validated input.
293 293 if (this.model.get('value') != e.target.value) {
294 294 e.target.value = this.model.get('value');
295 295 }
296 296 },
297 297
298 298 _parse_value: function(value) {
299 299 // Parse the value stored in a string.
300 300 return parseInt(value);
301 301 },
302 302 });
303 303
304 304
305 305 var ProgressView = widget.DOMWidgetView.extend({
306 306 render : function(){
307 307 // Called when view is rendered.
308 308 this.$el
309 309 .addClass('widget-hbox-single');
310 310 this.$label = $('<div />')
311 311 .appendTo(this.$el)
312 312 .addClass('widget-hlabel')
313 313 .hide();
314 314 this.$progress = $('<div />')
315 315 .addClass('progress')
316 316 .addClass('widget-progress')
317 317 .appendTo(this.$el);
318 318 this.$bar = $('<div />')
319 319 .addClass('progress-bar')
320 320 .css('width', '50%')
321 321 .appendTo(this.$progress);
322 322 this.update(); // Set defaults.
323 323 },
324 324
325 325 update : function(){
326 326 // Update the contents of this view
327 327 //
328 328 // Called when the model is changed. The model may have been
329 329 // changed by another view or by a state update from the back-end.
330 330 var value = this.model.get('value');
331 331 var max = this.model.get('max');
332 332 var min = this.model.get('min');
333 333 var percent = 100.0 * (value - min) / (max - min);
334 334 this.$bar.css('width', percent + '%');
335 335
336 336 var description = this.model.get('description');
337 337 if (description.length === 0) {
338 338 this.$label.hide();
339 339 } else {
340 340 this.$label.text(description);
341 341 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
342 342 this.$label.show();
343 343 }
344 344 return ProgressView.__super__.update.apply(this);
345 345 },
346 346
347 347 update_attr: function(name, value) {
348 348 // Set a css attr of the widget view.
349 if (name.substring(0, 6) == 'border' || name == 'width' || name == 'height' || name == 'background') {
349 if (name.substring(0, 6) == 'border' || name == 'width' ||
350 name == 'height' || name == 'background' || name == 'margin' ||
351 name == 'padding') {
352
350 353 this.$progress.css(name, value);
351 354 } else if (name == 'color') {
352 355 this.$bar.css('background', value);
353 356 } else {
354 357 this.$bar.css(name, value);
355 358 }
356 359 },
357 360 });
358 361
359 362 return {
360 363 'IntSliderView': IntSliderView,
361 364 'IntTextView': IntTextView,
362 365 'ProgressView': ProgressView,
363 366 };
364 367 });
@@ -1,433 +1,472 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 "widgets/js/widget",
6 6 "base/js/utils",
7 7 "jquery",
8 8 "bootstrap",
9 9 ], function(widget, utils, $){
10 10
11 11 var DropdownView = widget.DOMWidgetView.extend({
12 12 render : function(){
13 13 // Called when view is rendered.
14 14 this.$el
15 15 .addClass('widget-hbox-single');
16 16 this.$label = $('<div />')
17 17 .appendTo(this.$el)
18 18 .addClass('widget-hlabel')
19 19 .hide();
20 20 this.$buttongroup = $('<div />')
21 21 .addClass('widget_item')
22 22 .addClass('btn-group')
23 23 .appendTo(this.$el);
24 24 this.$droplabel = $('<button />')
25 25 .addClass('btn btn-default')
26 26 .addClass('widget-combo-btn')
27 27 .html("&nbsp;")
28 28 .appendTo(this.$buttongroup);
29 29 this.$dropbutton = $('<button />')
30 30 .addClass('btn btn-default')
31 31 .addClass('dropdown-toggle')
32 32 .addClass('widget-combo-carrot-btn')
33 33 .attr('data-toggle', 'dropdown')
34 34 .append($('<span />').addClass("caret"))
35 35 .appendTo(this.$buttongroup);
36 36 this.$droplist = $('<ul />')
37 37 .addClass('dropdown-menu')
38 38 .appendTo(this.$buttongroup);
39
40 this.model.on('change:button_style', function(model, value) {
41 this.update_button_style();
42 }, this);
43 this.update_button_style('');
39 44
40 45 // Set defaults.
41 46 this.update();
42 47 },
43 48
44 49 update : function(options){
45 50 // Update the contents of this view
46 51 //
47 52 // Called when the model is changed. The model may have been
48 53 // changed by another view or by a state update from the back-end.
49 54
50 55 if (options === undefined || options.updated_view != this) {
51 56 var selected_item_text = this.model.get('value_name');
52 57 if (selected_item_text.trim().length === 0) {
53 58 this.$droplabel.html("&nbsp;");
54 59 } else {
55 60 this.$droplabel.text(selected_item_text);
56 61 }
57 62
58 63 var items = this.model.get('value_names');
59 64 var $replace_droplist = $('<ul />')
60 65 .addClass('dropdown-menu');
61 66 // Copy the style
62 67 $replace_droplist.attr('style', this.$droplist.attr('style'));
63 68 var that = this;
64 69 _.each(items, function(item, i) {
65 70 var item_button = $('<a href="#"/>')
66 71 .text(item)
67 72 .on('click', $.proxy(that.handle_click, that));
68 73 $replace_droplist.append($('<li />').append(item_button));
69 74 });
70 75
71 76 this.$droplist.replaceWith($replace_droplist);
72 77 this.$droplist.remove();
73 78 this.$droplist = $replace_droplist;
74 79
75 80 if (this.model.get('disabled')) {
76 81 this.$buttongroup.attr('disabled','disabled');
77 82 this.$droplabel.attr('disabled','disabled');
78 83 this.$dropbutton.attr('disabled','disabled');
79 84 this.$droplist.attr('disabled','disabled');
80 85 } else {
81 86 this.$buttongroup.removeAttr('disabled');
82 87 this.$droplabel.removeAttr('disabled');
83 88 this.$dropbutton.removeAttr('disabled');
84 89 this.$droplist.removeAttr('disabled');
85 90 }
86 91
87 92 var description = this.model.get('description');
88 93 if (description.length === 0) {
89 94 this.$label.hide();
90 95 } else {
91 96 this.$label.text(description);
92 97 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
93 98 this.$label.show();
94 99 }
95 100 }
96 101 return DropdownView.__super__.update.apply(this);
97 102 },
98 103
104 update_button_style: function(previous_trait_value) {
105 var class_map = {
106 primary: ['btn-primary'],
107 success: ['btn-success'],
108 info: ['btn-info'],
109 warning: ['btn-warning'],
110 danger: ['btn-danger']
111 };
112 this.update_mapped_classes(class_map, 'button_style', previous_trait_value, this.$droplabel);
113 this.update_mapped_classes(class_map, 'button_style', previous_trait_value, this.$dropbutton);
114 },
115
99 116 update_attr: function(name, value) {
100 117 // Set a css attr of the widget view.
101 118 if (name.substring(0, 6) == 'border' || name == 'background' || name == 'color') {
102 119 this.$droplabel.css(name, value);
103 120 this.$dropbutton.css(name, value);
104 121 this.$droplist.css(name, value);
105 122 } else if (name == 'width') {
106 var width = value - this.$dropbutton.width();
107 this.$droplist.css(name, width);
108 this.$droplabel.css(name, width);
123 this.$droplist.css(name, value);
124 this.$droplabel.css(name, value);
125 } else if (name == 'padding') {
126 this.$droplist.css(name, value);
127 this.$buttongroup.css(name, value);
128 } else if (name == 'margin') {
129 this.$buttongroup.css(name, value);
109 130 } else if (name == 'height') {
110 131 this.$droplabel.css(name, value);
111 132 this.$dropbutton.css(name, value);
112 133 } else {
113 134 this.$droplist.css(name, value);
114 135 this.$droplabel.css(name, value);
115 136 }
116 137 },
117 138
118 139 handle_click: function (e) {
119 140 // Handle when a value is clicked.
120 141
121 142 // Calling model.set will trigger all of the other views of the
122 143 // model to update.
123 144 this.model.set('value_name', $(e.target).text(), {updated_view: this});
124 145 this.touch();
125 146 },
126 147
127 148 });
128 149
129 150
130 151 var RadioButtonsView = widget.DOMWidgetView.extend({
131 152 render : function(){
132 153 // Called when view is rendered.
133 154 this.$el
134 155 .addClass('widget-hbox');
135 156 this.$label = $('<div />')
136 157 .appendTo(this.$el)
137 158 .addClass('widget-hlabel')
138 159 .hide();
139 160 this.$container = $('<div />')
140 161 .appendTo(this.$el)
141 162 .addClass('widget-radio-box');
142 163 this.update();
143 164 },
144 165
145 166 update : function(options){
146 167 // Update the contents of this view
147 168 //
148 169 // Called when the model is changed. The model may have been
149 170 // changed by another view or by a state update from the back-end.
150 171 if (options === undefined || options.updated_view != this) {
151 172 // Add missing items to the DOM.
152 173 var items = this.model.get('value_names');
153 174 var disabled = this.model.get('disabled');
154 175 var that = this;
155 176 _.each(items, function(item, index) {
156 177 var item_query = ' :input[value="' + item + '"]';
157 178 if (that.$el.find(item_query).length === 0) {
158 179 var $label = $('<label />')
159 180 .addClass('radio')
160 181 .text(item)
161 182 .appendTo(that.$container);
162 183
163 184 $('<input />')
164 185 .attr('type', 'radio')
165 186 .addClass(that.model)
166 187 .val(item)
167 188 .prependTo($label)
168 189 .on('click', $.proxy(that.handle_click, that));
169 190 }
170 191
171 192 var $item_element = that.$container.find(item_query);
172 193 if (that.model.get('value_name') == item) {
173 194 $item_element.prop('checked', true);
174 195 } else {
175 196 $item_element.prop('checked', false);
176 197 }
177 198 $item_element.prop('disabled', disabled);
178 199 });
179 200
180 201 // Remove items that no longer exist.
181 202 this.$container.find('input').each(function(i, obj) {
182 203 var value = $(obj).val();
183 204 var found = false;
184 205 _.each(items, function(item, index) {
185 206 if (item == value) {
186 207 found = true;
187 208 return false;
188 209 }
189 210 });
190 211
191 212 if (!found) {
192 213 $(obj).parent().remove();
193 214 }
194 215 });
195 216
196 217 var description = this.model.get('description');
197 218 if (description.length === 0) {
198 219 this.$label.hide();
199 220 } else {
200 221 this.$label.text(description);
201 222 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
202 223 this.$label.show();
203 224 }
204 225 }
205 226 return RadioButtonsView.__super__.update.apply(this);
206 227 },
207 228
208 229 update_attr: function(name, value) {
209 230 // Set a css attr of the widget view.
210 231 this.$container.css(name, value);
211 232 },
212 233
213 234 handle_click: function (e) {
214 235 // Handle when a value is clicked.
215 236
216 237 // Calling model.set will trigger all of the other views of the
217 238 // model to update.
218 239 this.model.set('value_name', $(e.target).val(), {updated_view: this});
219 240 this.touch();
220 241 },
221 242 });
222 243
223 244
224 245 var ToggleButtonsView = widget.DOMWidgetView.extend({
225 246 initialize: function() {
226 247 this._css_state = {};
227 248 ToggleButtonsView.__super__.initialize.apply(this, arguments);
228 249 },
229 250
230 251 render: function() {
231 252 // Called when view is rendered.
232 253 this.$el
233 254 .addClass('widget-hbox-single');
234 255 this.$label = $('<div />')
235 256 .appendTo(this.$el)
236 257 .addClass('widget-hlabel')
237 258 .hide();
238 259 this.$buttongroup = $('<div />')
239 260 .addClass('btn-group')
240 261 .attr('data-toggle', 'buttons-radio')
241 262 .appendTo(this.$el);
263
264 this.model.on('change:button_style', function(model, value) {
265 this.update_button_style();
266 }, this);
267 this.update_button_style('');
242 268 this.update();
243 269 },
244 270
245 271 update : function(options){
246 272 // Update the contents of this view
247 273 //
248 274 // Called when the model is changed. The model may have been
249 275 // changed by another view or by a state update from the back-end.
250 276 if (options === undefined || options.updated_view != this) {
251 277 // Add missing items to the DOM.
252 278 var items = this.model.get('value_names');
253 279 var disabled = this.model.get('disabled');
254 280 var that = this;
255 281 var item_html;
256 282 _.each(items, function(item, index) {
257 283 if (item.trim().length == 0) {
258 284 item_html = "&nbsp;";
259 285 } else {
260 286 item_html = utils.escape_html(item);
261 287 }
262 288 var item_query = '[data-value="' + item + '"]';
263 289 var $item_element = that.$buttongroup.find(item_query);
264 290 if (!$item_element.length) {
265 291 $item_element = $('<button/>')
266 292 .attr('type', 'button')
267 293 .addClass('btn btn-default')
268 294 .html(item_html)
269 295 .appendTo(that.$buttongroup)
270 296 .attr('data-value', item)
271 297 .on('click', $.proxy(that.handle_click, that));
272 that._update_button_style($item_element);
298 that.update_style_traits($item_element);
273 299 }
274 300 if (that.model.get('value_name') == item) {
275 301 $item_element.addClass('active');
276 302 } else {
277 303 $item_element.removeClass('active');
278 304 }
279 305 $item_element.prop('disabled', disabled);
280 306 });
281 307
282 308 // Remove items that no longer exist.
283 309 this.$buttongroup.find('button').each(function(i, obj) {
284 310 var value = $(obj).data('value');
285 311 var found = false;
286 312 _.each(items, function(item, index) {
287 313 if (item == value) {
288 314 found = true;
289 315 return false;
290 316 }
291 317 });
292 318
293 319 if (!found) {
294 320 $(obj).remove();
295 321 }
296 322 });
297 323
298 324 var description = this.model.get('description');
299 325 if (description.length === 0) {
300 326 this.$label.hide();
301 327 } else {
302 328 this.$label.text(description);
303 329 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
304 330 this.$label.show();
305 331 }
306 332 }
307 333 return ToggleButtonsView.__super__.update.apply(this);
308 334 },
309 335
310 336 update_attr: function(name, value) {
311 337 // Set a css attr of the widget view.
312 338 this._css_state[name] = value;
313 this._update_button_style();
339 this.update_style_traits();
314 340 },
315 341
316 _update_button_style: function(button) {
342 update_style_traits: function(button) {
317 343 for (var name in this._css_state) {
318 if (this._css_state.hasOwnProperty(name) && name != 'width') {
319 if (button) {
320 button.css(name, this._css_state[name]);
321 } else {
322 this.$buttongroup.find('button').each(function(i, obj) {
323 $(obj).css(name, this._css_state[name]);
324 });
325 }
344 if (this._css_state.hasOwnProperty(name)) {
345 if (name == 'margin') {
346 this.$buttongroup.css(name, this._css_state[name]);
347 } else if (name != 'width') {
348 if (button) {
349 button.css(name, this._css_state[name]);
350 } else {
351 this.$buttongroup.find('button').css(name, this._css_state[name]);
352 }
353 }
326 354 }
327 355 }
328 356 },
329 357
358 update_button_style: function(previous_trait_value) {
359 var class_map = {
360 primary: ['btn-primary'],
361 success: ['btn-success'],
362 info: ['btn-info'],
363 warning: ['btn-warning'],
364 danger: ['btn-danger']
365 };
366 this.update_mapped_classes(class_map, 'button_style', previous_trait_value, this.$buttongroup.find('button'));
367 },
368
330 369 handle_click: function (e) {
331 370 // Handle when a value is clicked.
332 371
333 372 // Calling model.set will trigger all of the other views of the
334 373 // model to update.
335 374 this.model.set('value_name', $(e.target).data('value'), {updated_view: this});
336 375 this.touch();
337 376 },
338 377 });
339 378
340 379
341 380 var SelectView = widget.DOMWidgetView.extend({
342 381 render : function(){
343 382 // Called when view is rendered.
344 383 this.$el
345 384 .addClass('widget-hbox');
346 385 this.$label = $('<div />')
347 386 .appendTo(this.$el)
348 387 .addClass('widget-hlabel')
349 388 .hide();
350 389 this.$listbox = $('<select />')
351 390 .addClass('widget-listbox form-control')
352 391 .attr('size', 6)
353 392 .appendTo(this.$el);
354 393 this.update();
355 394 },
356 395
357 396 update : function(options){
358 397 // Update the contents of this view
359 398 //
360 399 // Called when the model is changed. The model may have been
361 400 // changed by another view or by a state update from the back-end.
362 401 if (options === undefined || options.updated_view != this) {
363 402 // Add missing items to the DOM.
364 403 var items = this.model.get('value_names');
365 404 var that = this;
366 405 _.each(items, function(item, index) {
367 406 var item_query = ' :contains("' + item + '")';
368 407 if (that.$listbox.find(item_query).length === 0) {
369 408 $('<option />')
370 409 .text(item)
371 410 .attr('value_name', item)
372 411 .appendTo(that.$listbox)
373 412 .on('click', $.proxy(that.handle_click, that));
374 413 }
375 414 });
376 415
377 416 // Select the correct element
378 417 this.$listbox.val(this.model.get('value_name'));
379 418
380 419 // Disable listbox if needed
381 420 var disabled = this.model.get('disabled');
382 421 this.$listbox.prop('disabled', disabled);
383 422
384 423 // Remove items that no longer exist.
385 424 this.$listbox.find('option').each(function(i, obj) {
386 425 var value = $(obj).text();
387 426 var found = false;
388 427 _.each(items, function(item, index) {
389 428 if (item == value) {
390 429 found = true;
391 430 return false;
392 431 }
393 432 });
394 433
395 434 if (!found) {
396 435 $(obj).remove();
397 436 }
398 437 });
399 438
400 439 var description = this.model.get('description');
401 440 if (description.length === 0) {
402 441 this.$label.hide();
403 442 } else {
404 443 this.$label.text(description);
405 444 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
406 445 this.$label.show();
407 446 }
408 447 }
409 448 return SelectView.__super__.update.apply(this);
410 449 },
411 450
412 451 update_attr: function(name, value) {
413 452 // Set a css attr of the widget view.
414 453 this.$listbox.css(name, value);
415 454 },
416 455
417 456 handle_click: function (e) {
418 457 // Handle when a value is clicked.
419 458
420 459 // Calling model.set will trigger all of the other views of the
421 460 // model to update.
422 461 this.model.set('value_name', $(e.target).text(), {updated_view: this});
423 462 this.touch();
424 463 },
425 464 });
426 465
427 466 return {
428 467 'DropdownView': DropdownView,
429 468 'RadioButtonsView': RadioButtonsView,
430 469 'ToggleButtonsView': ToggleButtonsView,
431 470 'SelectView': SelectView,
432 471 };
433 472 });
@@ -1,43 +1,48 b''
1 1 """Bool class.
2 2
3 3 Represents a boolean using a widget.
4 4 """
5 5 #-----------------------------------------------------------------------------
6 6 # Copyright (c) 2013, the IPython Development Team.
7 7 #
8 8 # Distributed under the terms of the Modified BSD License.
9 9 #
10 10 # The full license is in the file COPYING.txt, distributed with this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from .widget import DOMWidget
17 17 from IPython.utils.traitlets import Unicode, Bool
18 18 from IPython.utils.warn import DeprecatedClass
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Classes
22 22 #-----------------------------------------------------------------------------
23 23 class _Bool(DOMWidget):
24 24 """A base class for creating widgets that represent booleans."""
25 25 value = Bool(False, help="Bool value", sync=True)
26 26 description = Unicode('', help="Description of the boolean (label).", sync=True)
27 27 disabled = Bool(False, help="Enable or disable user changes.", sync=True)
28 28
29 29
30 30 class Checkbox(_Bool):
31 31 """Displays a boolean `value`."""
32 32 _view_name = Unicode('CheckboxView', sync=True)
33 33
34 34
35 35 class ToggleButton(_Bool):
36 36 """Displays a boolean `value`."""
37 37
38 38 _view_name = Unicode('ToggleButtonView', sync=True)
39 39
40 button_style = CaselessStrEnum(
41 values=['primary', 'success', 'info', 'warning', 'danger', ''],
42 default_value='', allow_none=True, sync=True, help="""Use a
43 predefined styling for the button.""")
44
40 45
41 46 # Remove in IPython 4.0
42 47 CheckboxWidget = DeprecatedClass(Checkbox, 'CheckboxWidget')
43 48 ToggleButtonWidget = DeprecatedClass(ToggleButton, 'ToggleButtonWidget')
@@ -1,83 +1,88 b''
1 1 """Box class.
2 2
3 3 Represents a container that can be used to group other widgets.
4 4 """
5 5
6 6 # Copyright (c) IPython Development Team.
7 7 # Distributed under the terms of the Modified BSD License.
8 8
9 9 from .widget import DOMWidget
10 10 from IPython.utils.traitlets import Unicode, Tuple, TraitError, Int, CaselessStrEnum
11 11 from IPython.utils.warn import DeprecatedClass
12 12
13 13 class Box(DOMWidget):
14 14 """Displays multiple widgets in a group."""
15 15 _view_name = Unicode('BoxView', sync=True)
16 16
17 17 # Child widgets in the container.
18 18 # Using a tuple here to force reassignment to update the list.
19 19 # When a proper notifying-list trait exists, that is what should be used here.
20 20 children = Tuple(sync=True, allow_none=False)
21 21
22 22 _overflow_values = ['visible', 'hidden', 'scroll', 'auto', 'initial', 'inherit', '']
23 23 overflow_x = CaselessStrEnum(
24 24 values=_overflow_values,
25 25 default_value='', allow_none=False, sync=True, help="""Specifies what
26 26 happens to content that is too large for the rendered region.""")
27 27 overflow_y = CaselessStrEnum(
28 28 values=_overflow_values,
29 29 default_value='', allow_none=False, sync=True, help="""Specifies what
30 30 happens to content that is too large for the rendered region.""")
31 31
32 box_style = CaselessStrEnum(
33 values=['success', 'info', 'warning', 'danger', ''],
34 default_value='', allow_none=True, sync=True, help="""Use a
35 predefined styling for the box.""")
36
32 37 def __init__(self, children = (), **kwargs):
33 38 kwargs['children'] = children
34 39 super(Box, self).__init__(**kwargs)
35 40 self.on_displayed(Box._fire_children_displayed)
36 41
37 42 def _fire_children_displayed(self):
38 43 for child in self.children:
39 44 child._handle_displayed()
40 45
41 46
42 47 class Popup(Box):
43 48 """Displays multiple widgets in an in page popup div."""
44 49 _view_name = Unicode('PopupView', sync=True)
45 50
46 51 description = Unicode(sync=True)
47 52 button_text = Unicode(sync=True)
48 53
49 54
50 55 class FlexBox(Box):
51 56 """Displays multiple widgets using the flexible box model."""
52 57 _view_name = Unicode('FlexBoxView', sync=True)
53 58 orientation = CaselessStrEnum(values=['vertical', 'horizontal'], default_value='vertical', sync=True)
54 59 flex = Int(0, sync=True, help="""Specify the flexible-ness of the model.""")
55 60 def _flex_changed(self, name, old, new):
56 61 new = min(max(0, new), 2)
57 62 if self.flex != new:
58 63 self.flex = new
59 64
60 65 _locations = ['start', 'center', 'end', 'baseline', 'stretch']
61 66 pack = CaselessStrEnum(
62 67 values=_locations,
63 68 default_value='start', allow_none=False, sync=True)
64 69 align = CaselessStrEnum(
65 70 values=_locations,
66 71 default_value='start', allow_none=False, sync=True)
67 72
68 73
69 74 def VBox(*pargs, **kwargs):
70 75 """Displays multiple widgets vertically using the flexible box model."""
71 76 kwargs['orientation'] = 'vertical'
72 77 return FlexBox(*pargs, **kwargs)
73 78
74 79 def HBox(*pargs, **kwargs):
75 80 """Displays multiple widgets horizontally using the flexible box model."""
76 81 kwargs['orientation'] = 'horizontal'
77 82 return FlexBox(*pargs, **kwargs)
78 83
79 84
80 85 # Remove in IPython 4.0
81 86 ContainerWidget = DeprecatedClass(Box, 'ContainerWidget')
82 87 PopupWidget = DeprecatedClass(Popup, 'PopupWidget')
83 88
@@ -1,65 +1,70 b''
1 1 """Button class.
2 2
3 3 Represents a button in the frontend using a widget. Allows user to listen for
4 4 click events on the button and trigger backend code when the clicks are fired.
5 5 """
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (c) 2013, the IPython Development Team.
8 8 #
9 9 # Distributed under the terms of the Modified BSD License.
10 10 #
11 11 # The full license is in the file COPYING.txt, distributed with this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17 from .widget import DOMWidget, CallbackDispatcher
18 18 from IPython.utils.traitlets import Unicode, Bool
19 19 from IPython.utils.warn import DeprecatedClass
20 20
21 21 #-----------------------------------------------------------------------------
22 22 # Classes
23 23 #-----------------------------------------------------------------------------
24 24 class Button(DOMWidget):
25 25 """Button widget.
26 26
27 27 This widget has an `on_click` method that allows you to listen for the
28 28 user clicking on the button. The click event itself is stateless."""
29 29 _view_name = Unicode('ButtonView', sync=True)
30 30
31 31 # Keys
32 32 description = Unicode('', help="Description of the button (label).", sync=True)
33 33 disabled = Bool(False, help="Enable or disable user changes.", sync=True)
34
35 button_style = CaselessStrEnum(
36 values=['primary', 'success', 'info', 'warning', 'danger', ''],
37 default_value='', allow_none=True, sync=True, help="""Use a
38 predefined styling for the button.""")
34 39
35 40 def __init__(self, **kwargs):
36 41 """Constructor"""
37 42 super(Button, self).__init__(**kwargs)
38 43 self._click_handlers = CallbackDispatcher()
39 44 self.on_msg(self._handle_button_msg)
40 45
41 46 def on_click(self, callback, remove=False):
42 47 """Register a callback to execute when the button is clicked.
43 48
44 49 The callback will be called with one argument,
45 50 the clicked button widget instance.
46 51
47 52 Parameters
48 53 ----------
49 54 remove : bool (optional)
50 55 Set to true to remove the callback from the list of callbacks."""
51 56 self._click_handlers.register_callback(callback, remove=remove)
52 57
53 58 def _handle_button_msg(self, _, content):
54 59 """Handle a msg from the front-end.
55 60
56 61 Parameters
57 62 ----------
58 63 content: dict
59 64 Content of the msg."""
60 65 if content.get('event', '') == 'click':
61 66 self._click_handlers(self)
62 67
63 68
64 69 # Remove in IPython 4.0
65 70 ButtonWidget = DeprecatedClass(Button, 'ButtonWidget')
@@ -1,139 +1,144 b''
1 1 """Selection classes.
2 2
3 3 Represents an enumeration using a widget.
4 4 """
5 5 #-----------------------------------------------------------------------------
6 6 # Copyright (c) 2013, the IPython Development Team.
7 7 #
8 8 # Distributed under the terms of the Modified BSD License.
9 9 #
10 10 # The full license is in the file COPYING.txt, distributed with this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16
17 17 from collections import OrderedDict
18 18 from threading import Lock
19 19
20 20 from .widget import DOMWidget
21 21 from IPython.utils.traitlets import Unicode, List, Bool, Any, Dict, TraitError
22 22 from IPython.utils.py3compat import unicode_type
23 23 from IPython.utils.warn import DeprecatedClass
24 24
25 25 #-----------------------------------------------------------------------------
26 26 # SelectionWidget
27 27 #-----------------------------------------------------------------------------
28 28 class _Selection(DOMWidget):
29 29 """Base class for Selection widgets
30 30
31 31 ``values`` can be specified as a list or dict. If given as a list,
32 32 it will be transformed to a dict of the form ``{str(value):value}``.
33 33 """
34 34
35 35 value = Any(help="Selected value")
36 36 values = Dict(help="""Dictionary of {name: value} the user can select.
37 37
38 38 The keys of this dictionary are the strings that will be displayed in the UI,
39 39 representing the actual Python choices.
40 40
41 41 The keys of this dictionary are also available as value_names.
42 42 """)
43 43 value_name = Unicode(help="The name of the selected value", sync=True)
44 44 value_names = List(Unicode, help="""Read-only list of names for each value.
45 45
46 46 If values is specified as a list, this is the string representation of each element.
47 47 Otherwise, it is the keys of the values dictionary.
48 48
49 49 These strings are used to display the choices in the front-end.""", sync=True)
50 50 disabled = Bool(False, help="Enable or disable user changes", sync=True)
51 51 description = Unicode(help="Description of the value this widget represents", sync=True)
52 52
53 53
54 54 def __init__(self, *args, **kwargs):
55 55 self.value_lock = Lock()
56 56 self._in_values_changed = False
57 57 if 'values' in kwargs:
58 58 values = kwargs['values']
59 59 # convert list values to an dict of {str(v):v}
60 60 if isinstance(values, list):
61 61 # preserve list order with an OrderedDict
62 62 kwargs['values'] = OrderedDict((unicode_type(v), v) for v in values)
63 63 # python3.3 turned on hash randomization by default - this means that sometimes, randomly
64 64 # we try to set value before setting values, due to dictionary ordering. To fix this, force
65 65 # the setting of self.values right now, before anything else runs
66 66 self.values = kwargs.pop('values')
67 67 DOMWidget.__init__(self, *args, **kwargs)
68 68
69 69 def _values_changed(self, name, old, new):
70 70 """Handles when the values dict has been changed.
71 71
72 72 Setting values implies setting value names from the keys of the dict.
73 73 """
74 74 self._in_values_changed = True
75 75 try:
76 76 self.value_names = list(new.keys())
77 77 finally:
78 78 self._in_values_changed = False
79 79
80 80 # ensure that the chosen value is one of the choices
81 81 if self.value not in new.values():
82 82 self.value = next(iter(new.values()))
83 83
84 84 def _value_names_changed(self, name, old, new):
85 85 if not self._in_values_changed:
86 86 raise TraitError("value_names is a read-only proxy to values.keys(). Use the values dict instead.")
87 87
88 88 def _value_changed(self, name, old, new):
89 89 """Called when value has been changed"""
90 90 if self.value_lock.acquire(False):
91 91 try:
92 92 # Reverse dictionary lookup for the value name
93 93 for k,v in self.values.items():
94 94 if new == v:
95 95 # set the selected value name
96 96 self.value_name = k
97 97 return
98 98 # undo the change, and raise KeyError
99 99 self.value = old
100 100 raise KeyError(new)
101 101 finally:
102 102 self.value_lock.release()
103 103
104 104 def _value_name_changed(self, name, old, new):
105 105 """Called when the value name has been changed (typically by the frontend)."""
106 106 if self.value_lock.acquire(False):
107 107 try:
108 108 self.value = self.values[new]
109 109 finally:
110 110 self.value_lock.release()
111 111
112 112
113 113 class ToggleButtons(_Selection):
114 114 """Group of toggle buttons that represent an enumeration. Only one toggle
115 115 button can be toggled at any point in time."""
116 116 _view_name = Unicode('ToggleButtonsView', sync=True)
117 117
118 button_style = CaselessStrEnum(
119 values=['primary', 'success', 'info', 'warning', 'danger', ''],
120 default_value='', allow_none=True, sync=True, help="""Use a
121 predefined styling for the buttons.""")
122
118 123
119 124 class Dropdown(_Selection):
120 125 """Allows you to select a single item from a dropdown."""
121 126 _view_name = Unicode('DropdownView', sync=True)
122 127
123 128
124 129 class RadioButtons(_Selection):
125 130 """Group of radio buttons that represent an enumeration. Only one radio
126 131 button can be toggled at any point in time."""
127 132 _view_name = Unicode('RadioButtonsView', sync=True)
128 133
129 134
130 135 class Select(_Selection):
131 136 """Listbox that only allows one item to be selected at any given time."""
132 137 _view_name = Unicode('SelectView', sync=True)
133 138
134 139
135 140 # Remove in IPython 4.0
136 141 ToggleButtonsWidget = DeprecatedClass(ToggleButtons, 'ToggleButtonsWidget')
137 142 DropdownWidget = DeprecatedClass(Dropdown, 'DropdownWidget')
138 143 RadioButtonsWidget = DeprecatedClass(RadioButtons, 'RadioButtonsWidget')
139 144 SelectWidget = DeprecatedClass(Select, 'SelectWidget')
General Comments 0
You need to be logged in to leave comments. Login now