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