##// END OF EJS Templates
Rebase fixes
Jonathan Frederic -
Show More
@@ -1,262 +1,280 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 "underscore",
6 6 "backbone",
7 7 "jquery",
8 8 "base/js/namespace"
9 9 ], function (_, Backbone, $, IPython) {
10 10 "use strict";
11 11 //--------------------------------------------------------------------
12 12 // WidgetManager class
13 13 //--------------------------------------------------------------------
14 14 var WidgetManager = function (comm_manager, notebook) {
15 15 // Public constructor
16 16 WidgetManager._managers.push(this);
17 17
18 18 // Attach a comm manager to the
19 19 this.keyboard_manager = notebook.keyboard_manager;
20 20 this.notebook = notebook;
21 21 this.comm_manager = comm_manager;
22 22 this._models = {}; /* Dictionary of model ids and model instances */
23 23
24 24 // Register with the comm manager.
25 25 this.comm_manager.register_target('ipython.widget', $.proxy(this._handle_comm_open, this));
26 26 };
27 27
28 28 //--------------------------------------------------------------------
29 29 // Class level
30 30 //--------------------------------------------------------------------
31 31 WidgetManager._model_types = {}; /* Dictionary of model type names (target_name) and model types. */
32 32 WidgetManager._view_types = {}; /* Dictionary of view names and view types. */
33 33 WidgetManager._managers = []; /* List of widget managers */
34 34
35 35 WidgetManager.register_widget_model = function (model_name, model_type) {
36 36 // Registers a widget model by name.
37 37 WidgetManager._model_types[model_name] = model_type;
38 38 };
39 39
40 40 WidgetManager.register_widget_view = function (view_name, view_type) {
41 41 // Registers a widget view by name.
42 42 WidgetManager._view_types[view_name] = view_type;
43 43 };
44 44
45 45 //--------------------------------------------------------------------
46 46 // Instance level
47 47 //--------------------------------------------------------------------
48 48 WidgetManager.prototype.display_view = function(msg, model) {
49 49 // Displays a view for a particular model.
50 50 var cell = this.get_msg_cell(msg.parent_header.msg_id);
51 51 if (cell === null) {
52 52 console.log("Could not determine where the display" +
53 53 " message was from. Widget will not be displayed");
54 54 } else {
55 55 var that = this;
56 this.create_view(model, {cell: cell, callback: function(view) {
56 this.create_view(model, {cell: cell, success: function(view) {
57 57 that._handle_display_view(view);
58 58 if (cell.widget_subarea) {
59 59 cell.widget_subarea.append(view.$el);
60 60 }
61 61 view.trigger('displayed');
62 62 }});
63 63 }
64 64 };
65 65
66 66 WidgetManager.prototype._handle_display_view = function (view) {
67 67 // Have the IPython keyboard manager disable its event
68 68 // handling so the widget can capture keyboard input.
69 69 // Note, this is only done on the outer most widgets.
70 70 if (this.keyboard_manager) {
71 71 this.keyboard_manager.register_events(view.$el);
72 72
73 73 if (view.additional_elements) {
74 74 for (var i = 0; i < view.additional_elements.length; i++) {
75 75 this.keyboard_manager.register_events(view.additional_elements[i]);
76 76 }
77 77 }
78 78 }
79 79 };
80 80
81 81
82 82 WidgetManager.prototype.create_view = function(model, options) {
83 83 // Creates a view for a particular model.
84 84
85 85 var view_name = model.get('_view_name');
86 86 var view_mod = model.get('_view_module');
87 var errback = options.errback || function(err) {console.log(err);};
87 var error = options.error || function(error) { console.log(error); };
88 88
89 89 var instantiate_view = function(ViewType) {
90 90 if (ViewType) {
91 91 // If a view is passed into the method, use that view's cell as
92 92 // the cell for the view that is created.
93 93 options = options || {};
94 94 if (options.parent !== undefined) {
95 95 options.cell = options.parent.options.cell;
96 96 }
97 97
98 98 // Create and render the view...
99 99 var parameters = {model: model, options: options};
100 100 var view = new ViewType(parameters);
101 101 view.render();
102 102 model.on('destroy', view.remove, view);
103 options.callback(view);
103 if (options.success) {
104 options.success(view);
105 }
104 106 } else {
105 errback({unknown_view: true, view_name: view_name,
107 error({unknown_view: true, view_name: view_name,
106 108 view_module: view_mod});
107 109 }
108 110 };
109 111
110 112 if (view_mod) {
111 113 require([view_mod], function(module) {
112 114 instantiate_view(module[view_name]);
113 }, errback);
115 }, error);
114 116 } else {
115 117 instantiate_view(WidgetManager._view_types[view_name]);
116 118 }
117 119 };
118 120
119 121 WidgetManager.prototype.get_msg_cell = function (msg_id) {
120 122 var cell = null;
121 123 // First, check to see if the msg was triggered by cell execution.
122 124 if (this.notebook) {
123 125 cell = this.notebook.get_msg_cell(msg_id);
124 126 }
125 127 if (cell !== null) {
126 128 return cell;
127 129 }
128 130 // Second, check to see if a get_cell callback was defined
129 131 // for the message. get_cell callbacks are registered for
130 132 // widget messages, so this block is actually checking to see if the
131 133 // message was triggered by a widget.
132 134 var kernel = this.comm_manager.kernel;
133 135 if (kernel) {
134 136 var callbacks = kernel.get_callbacks_for_msg(msg_id);
135 137 if (callbacks && callbacks.iopub &&
136 138 callbacks.iopub.get_cell !== undefined) {
137 139 return callbacks.iopub.get_cell();
138 140 }
139 141 }
140 142
141 143 // Not triggered by a cell or widget (no get_cell callback
142 144 // exists).
143 145 return null;
144 146 };
145 147
146 148 WidgetManager.prototype.callbacks = function (view) {
147 149 // callback handlers specific a view
148 150 var callbacks = {};
149 151 if (view && view.options.cell) {
150 152
151 153 // Try to get output handlers
152 154 var cell = view.options.cell;
153 155 var handle_output = null;
154 156 var handle_clear_output = null;
155 157 if (cell.output_area) {
156 158 handle_output = $.proxy(cell.output_area.handle_output, cell.output_area);
157 159 handle_clear_output = $.proxy(cell.output_area.handle_clear_output, cell.output_area);
158 160 }
159 161
160 // Create callback dict using what is known
162 // Create callback dictionary using what is known
161 163 var that = this;
162 164 callbacks = {
163 165 iopub : {
164 166 output : handle_output,
165 167 clear_output : handle_clear_output,
166 168
167 169 // Special function only registered by widget messages.
168 170 // Allows us to get the cell for a message so we know
169 171 // where to add widgets if the code requires it.
170 172 get_cell : function () {
171 173 return cell;
172 174 },
173 175 },
174 176 };
175 177 }
176 178 return callbacks;
177 179 };
178 180
179 181 WidgetManager.prototype.get_model = function (model_id) {
180 182 // Look-up a model instance by its id.
181 183 var model = this._models[model_id];
182 184 if (model !== undefined && model.id == model_id) {
183 185 return model;
184 186 }
185 187 return null;
186 188 };
187 189
188 190 WidgetManager.prototype._handle_comm_open = function (comm, msg) {
189 191 // Handle when a comm is opened.
190 return this.create_model({model_name: msg.content.data.model_name, comm: comm});
192 this.create_model({
193 model_name: msg.content.data.model_name,
194 model_module: msg.content.data.model_module,
195 comm: comm});
191 196 };
192 197
193 198 WidgetManager.prototype.create_model = function (options) {
194 199 // Create and return a new widget model.
195 200 //
196 201 // Minimally, one must provide the model_name and widget_class
197 202 // parameters to create a model from Javascript.
198 203 //
199 204 // Example
200 205 // --------
201 206 // JS:
202 // window.slider = IPython.notebook.kernel.widget_manager.create_model({
207 // IPython.notebook.kernel.widget_manager.create_model({
203 208 // model_name: 'WidgetModel',
204 209 // widget_class: 'IPython.html.widgets.widget_int.IntSlider',
205 210 // init_state_callback: function(model) { console.log('Create success!', model); }});
206 211 //
207 212 // Parameters
208 213 // ----------
209 214 // options: dictionary
210 215 // Dictionary of options with the following contents:
211 216 // model_name: string
212 217 // Target name of the widget model to create.
218 // model_module: (optional) string
219 // Module name of the widget model to create.
213 220 // widget_class: (optional) string
214 221 // Target name of the widget in the back-end.
215 222 // comm: (optional) Comm
223 // success: (optional) callback
224 // Callback for when the model was created successfully.
225 // error: (optional) callback
226 // Callback for when the model wasn't created.
216 227 // init_state_callback: (optional) callback
217 228 // Called when the first state push from the back-end is
218 229 // recieved. Allows you to modify the model after it's
219 230 // complete state is filled and synced.
220 231
232 // Make default callbacks if not specified.
233 var error = options.error || function(error) { console.log(error); };
234
221 235 // Create a comm if it wasn't provided.
222 236 var comm = options.comm;
223 237 if (!comm) {
224 238 comm = this.comm_manager.new_comm('ipython.widget', {'widget_class': options.widget_class});
225 239 }
226 240
227 // Create and return a new model that is connected to the comm.
241 // Create a new model that is connected to the comm.
228 242 var that = this;
229
230 243 var instantiate_model = function(ModelType) {
231 244 var model_id = comm.comm_id;
232 245 var widget_model = new ModelType(that, model_id, comm, options.init_state_callback);
233 246 widget_model.on('comm:close', function () {
234 247 delete that._models[model_id];
235 248 });
236 249 that._models[model_id] = widget_model;
250 if (options.success) {
251 options.success(widget_model);
252 }
237 253 };
238 254
239 var widget_type_name = msg.content.data.model_name;
240 var widget_module = msg.content.data.model_module;
241
255 // Get the model type using require or through the registry.
256 var widget_type_name = options.model_name;
257 var widget_module = options.model_module;
242 258 if (widget_module) {
259
243 260 // Load the module containing the widget model
244 261 require([widget_module], function(mod) {
245 262 if (mod[widget_type_name]) {
246 263 instantiate_model(mod[widget_type_name]);
247 264 } else {
248 console.log("Error creating widget model: " + widget_type_name
265 error("Error creating widget model: " + widget_type_name
249 266 + " not found in " + widget_module);
250 267 }
251 }, function(err) { console.log(err); });
268 }, error);
252 269 } else {
270
253 271 // No module specified, load from the global models registry
254 272 instantiate_model(WidgetManager._model_types[widget_type_name]);
255 273 }
256 274 };
257 275
258 276 // Backwards compatability.
259 277 IPython.WidgetManager = WidgetManager;
260 278
261 279 return {'WidgetManager': WidgetManager};
262 280 });
@@ -1,621 +1,621 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define(["widgets/js/manager",
5 5 "underscore",
6 6 "backbone",
7 7 "jquery",
8 8 "base/js/namespace",
9 9 ], function(widgetmanager, _, Backbone, $, IPython){
10 10
11 11 var WidgetModel = Backbone.Model.extend({
12 12 constructor: function (widget_manager, model_id, comm, init_state_callback) {
13 13 // Constructor
14 14 //
15 15 // Creates a WidgetModel instance.
16 16 //
17 17 // Parameters
18 18 // ----------
19 19 // widget_manager : WidgetManager instance
20 20 // model_id : string
21 21 // An ID unique to this model.
22 22 // comm : Comm instance (optional)
23 23 // init_state_callback : callback (optional)
24 24 // Called once when the first state message is recieved from
25 25 // the back-end.
26 26 this.widget_manager = widget_manager;
27 27 this.init_state_callback = init_state_callback;
28 28 this._buffered_state_diff = {};
29 29 this.pending_msgs = 0;
30 30 this.msg_buffer = null;
31 31 this.state_lock = null;
32 32 this.id = model_id;
33 33 this.views = {};
34 34
35 35 if (comm !== undefined) {
36 36 // Remember comm associated with the model.
37 37 this.comm = comm;
38 38 comm.model = this;
39 39
40 40 // Hook comm messages up to model.
41 41 comm.on_close($.proxy(this._handle_comm_closed, this));
42 42 comm.on_msg($.proxy(this._handle_comm_msg, this));
43 43 }
44 44 return Backbone.Model.apply(this);
45 45 },
46 46
47 47 send: function (content, callbacks) {
48 48 // Send a custom msg over the comm.
49 49 if (this.comm !== undefined) {
50 50 var data = {method: 'custom', content: content};
51 51 this.comm.send(data, callbacks);
52 52 this.pending_msgs++;
53 53 }
54 54 },
55 55
56 56 _handle_comm_closed: function (msg) {
57 57 // Handle when a widget is closed.
58 58 this.trigger('comm:close');
59 59 this.stopListening();
60 60 this.trigger('destroy', this);
61 61 delete this.comm.model; // Delete ref so GC will collect widget model.
62 62 delete this.comm;
63 63 delete this.model_id; // Delete id from model so widget manager cleans up.
64 64 for (var id in this.views) {
65 65 if (this.views.hasOwnProperty(id)) {
66 66 this.views[id].remove();
67 67 }
68 68 }
69 69 },
70 70
71 71 _handle_comm_msg: function (msg) {
72 72 // Handle incoming comm msg.
73 73 var method = msg.content.data.method;
74 74 switch (method) {
75 75 case 'update':
76 76 this.set_state(msg.content.data.state);
77 77 if (this.init_state_callback) {
78 78 this.init_state_callback.apply(this, [this]);
79 79 delete this.init_state_callback;
80 80 }
81 81 break;
82 82 case 'custom':
83 83 this.trigger('msg:custom', msg.content.data.content);
84 84 break;
85 85 case 'display':
86 86 this.widget_manager.display_view(msg, this);
87 87 break;
88 88 }
89 89 },
90 90
91 91 set_state: function (state) {
92 92 // Handle when a widget is updated via the python side.
93 93 this.state_lock = state;
94 94 try {
95 95 var that = this;
96 96 WidgetModel.__super__.set.apply(this, [Object.keys(state).reduce(function(obj, key) {
97 97 obj[key] = that._unpack_models(state[key]);
98 98 return obj;
99 99 }, {})]);
100 100 } finally {
101 101 this.state_lock = null;
102 102 }
103 103 },
104 104
105 105 _handle_status: function (msg, callbacks) {
106 106 // Handle status msgs.
107 107
108 108 // execution_state : ('busy', 'idle', 'starting')
109 109 if (this.comm !== undefined) {
110 110 if (msg.content.execution_state ==='idle') {
111 111 // Send buffer if this message caused another message to be
112 112 // throttled.
113 113 if (this.msg_buffer !== null &&
114 114 (this.get('msg_throttle') || 3) === this.pending_msgs) {
115 115 var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
116 116 this.comm.send(data, callbacks);
117 117 this.msg_buffer = null;
118 118 } else {
119 119 --this.pending_msgs;
120 120 }
121 121 }
122 122 }
123 123 },
124 124
125 125 callbacks: function(view) {
126 126 // Create msg callbacks for a comm msg.
127 127 var callbacks = this.widget_manager.callbacks(view);
128 128
129 129 if (callbacks.iopub === undefined) {
130 130 callbacks.iopub = {};
131 131 }
132 132
133 133 var that = this;
134 134 callbacks.iopub.status = function (msg) {
135 135 that._handle_status(msg, callbacks);
136 136 };
137 137 return callbacks;
138 138 },
139 139
140 140 set: function(key, val, options) {
141 141 // Set a value.
142 142 var return_value = WidgetModel.__super__.set.apply(this, arguments);
143 143
144 144 // Backbone only remembers the diff of the most recent set()
145 145 // operation. Calling set multiple times in a row results in a
146 146 // loss of diff information. Here we keep our own running diff.
147 147 this._buffered_state_diff = $.extend(this._buffered_state_diff, this.changedAttributes() || {});
148 148 return return_value;
149 149 },
150 150
151 151 sync: function (method, model, options) {
152 152 // Handle sync to the back-end. Called when a model.save() is called.
153 153
154 154 // Make sure a comm exists.
155 155 var error = options.error || function() {
156 156 console.error('Backbone sync error:', arguments);
157 157 };
158 158 if (this.comm === undefined) {
159 159 error();
160 160 return false;
161 161 }
162 162
163 163 // Delete any key value pairs that the back-end already knows about.
164 164 var attrs = (method === 'patch') ? options.attrs : model.toJSON(options);
165 165 if (this.state_lock !== null) {
166 166 var keys = Object.keys(this.state_lock);
167 167 for (var i=0; i<keys.length; i++) {
168 168 var key = keys[i];
169 169 if (attrs[key] === this.state_lock[key]) {
170 170 delete attrs[key];
171 171 }
172 172 }
173 173 }
174 174
175 175 // Only sync if there are attributes to send to the back-end.
176 176 attrs = this._pack_models(attrs);
177 177 if (_.size(attrs) > 0) {
178 178
179 179 // If this message was sent via backbone itself, it will not
180 180 // have any callbacks. It's important that we create callbacks
181 181 // so we can listen for status messages, etc...
182 182 var callbacks = options.callbacks || this.callbacks();
183 183
184 184 // Check throttle.
185 185 if (this.pending_msgs >= (this.get('msg_throttle') || 3)) {
186 186 // The throttle has been exceeded, buffer the current msg so
187 187 // it can be sent once the kernel has finished processing
188 188 // some of the existing messages.
189 189
190 190 // Combine updates if it is a 'patch' sync, otherwise replace updates
191 191 switch (method) {
192 192 case 'patch':
193 193 this.msg_buffer = $.extend(this.msg_buffer || {}, attrs);
194 194 break;
195 195 case 'update':
196 196 case 'create':
197 197 this.msg_buffer = attrs;
198 198 break;
199 199 default:
200 200 error();
201 201 return false;
202 202 }
203 203 this.msg_buffer_callbacks = callbacks;
204 204
205 205 } else {
206 206 // We haven't exceeded the throttle, send the message like
207 207 // normal.
208 208 var data = {method: 'backbone', sync_data: attrs};
209 209 this.comm.send(data, callbacks);
210 210 this.pending_msgs++;
211 211 }
212 212 }
213 213 // Since the comm is a one-way communication, assume the message
214 214 // arrived. Don't call success since we don't have a model back from the server
215 215 // this means we miss out on the 'sync' event.
216 216 this._buffered_state_diff = {};
217 217 },
218 218
219 219 save_changes: function(callbacks) {
220 220 // Push this model's state to the back-end
221 221 //
222 222 // This invokes a Backbone.Sync.
223 223 this.save(this._buffered_state_diff, {patch: true, callbacks: callbacks});
224 224 },
225 225
226 226 _pack_models: function(value) {
227 227 // Replace models with model ids recursively.
228 228 var that = this;
229 229 var packed;
230 230 if (value instanceof Backbone.Model) {
231 231 return "IPY_MODEL_" + value.id;
232 232
233 233 } else if ($.isArray(value)) {
234 234 packed = [];
235 235 _.each(value, function(sub_value, key) {
236 236 packed.push(that._pack_models(sub_value));
237 237 });
238 238 return packed;
239 239
240 240 } else if (value instanceof Object) {
241 241 packed = {};
242 242 _.each(value, function(sub_value, key) {
243 243 packed[key] = that._pack_models(sub_value);
244 244 });
245 245 return packed;
246 246
247 247 } else {
248 248 return value;
249 249 }
250 250 },
251 251
252 252 _unpack_models: function(value) {
253 253 // Replace model ids with models recursively.
254 254 var that = this;
255 255 var unpacked;
256 256 if ($.isArray(value)) {
257 257 unpacked = [];
258 258 _.each(value, function(sub_value, key) {
259 259 unpacked.push(that._unpack_models(sub_value));
260 260 });
261 261 return unpacked;
262 262
263 263 } else if (value instanceof Object) {
264 264 unpacked = {};
265 265 _.each(value, function(sub_value, key) {
266 266 unpacked[key] = that._unpack_models(sub_value);
267 267 });
268 268 return unpacked;
269 269
270 270 } else if (typeof value === 'string' && value.slice(0,10) === "IPY_MODEL_") {
271 271 var model = this.widget_manager.get_model(value.slice(10, value.length));
272 272 if (model) {
273 273 return model;
274 274 } else {
275 275 return value;
276 276 }
277 277 } else {
278 278 return value;
279 279 }
280 280 },
281 281
282 282 on_some_change: function(keys, callback, context) {
283 283 // on_some_change(["key1", "key2"], foo, context) differs from
284 284 // on("change:key1 change:key2", foo, context).
285 285 // If the widget attributes key1 and key2 are both modified,
286 286 // the second form will result in foo being called twice
287 287 // while the first will call foo only once.
288 288 this.on('change', function() {
289 289 if (keys.some(this.hasChanged, this)) {
290 290 callback.apply(context);
291 291 }
292 292 }, this);
293 293
294 294 },
295 295 });
296 296 widgetmanager.WidgetManager.register_widget_model('WidgetModel', WidgetModel);
297 297
298 298
299 299 var WidgetView = Backbone.View.extend({
300 300 initialize: function(parameters) {
301 301 // Public constructor.
302 302 this.model.on('change',this.update,this);
303 303 this.options = parameters.options;
304 304 this.child_model_views = {};
305 305 this.child_views = {};
306 306 this.id = this.id || IPython.utils.uuid();
307 307 this.model.views[this.id] = this;
308 308 this.on('displayed', function() {
309 309 this.is_displayed = true;
310 310 }, this);
311 311 },
312 312
313 313 update: function(){
314 314 // Triggered on model change.
315 315 //
316 316 // Update view to be consistent with this.model
317 317 },
318 318
319 319 create_child_view: function(child_model, options) {
320 320 // Create and return a child view.
321 321 //
322 322 // -given a model and (optionally) a view name if the view name is
323 323 // not given, it defaults to the model's default view attribute.
324 324
325 325 // TODO: this is hacky, and makes the view depend on this cell attribute and widget manager behavior
326 326 // it would be great to have the widget manager add the cell metadata
327 327 // to the subview without having to add it here.
328 328 var that = this;
329 329 var old_callback = options.callback || function(view) {};
330 options = $.extend({ parent: this, callback: function(child_view) {
330 options = $.extend({ parent: this, success: function(child_view) {
331 331 // Associate the view id with the model id.
332 332 if (that.child_model_views[child_model.id] === undefined) {
333 333 that.child_model_views[child_model.id] = [];
334 334 }
335 335 that.child_model_views[child_model.id].push(child_view.id);
336 336
337 337 // Remember the view by id.
338 338 that.child_views[child_view.id] = child_view;
339 339 old_callback(child_view);
340 340 }}, options || {});
341 341
342 342 this.model.widget_manager.create_view(child_model, options);
343 343 },
344 344
345 345 pop_child_view: function(child_model) {
346 346 // Delete a child view that was previously created using create_child_view.
347 347 var view_ids = this.child_model_views[child_model.id];
348 348 if (view_ids !== undefined) {
349 349
350 350 // Only delete the first view in the list.
351 351 var view_id = view_ids[0];
352 352 var view = this.child_views[view_id];
353 353 delete this.child_views[view_id];
354 354 view_ids.splice(0,1);
355 355 delete child_model.views[view_id];
356 356
357 357 // Remove the view list specific to this model if it is empty.
358 358 if (view_ids.length === 0) {
359 359 delete this.child_model_views[child_model.id];
360 360 }
361 361 return view;
362 362 }
363 363 return null;
364 364 },
365 365
366 366 do_diff: function(old_list, new_list, removed_callback, added_callback) {
367 367 // Difference a changed list and call remove and add callbacks for
368 368 // each removed and added item in the new list.
369 369 //
370 370 // Parameters
371 371 // ----------
372 372 // old_list : array
373 373 // new_list : array
374 374 // removed_callback : Callback(item)
375 375 // Callback that is called for each item removed.
376 376 // added_callback : Callback(item)
377 377 // Callback that is called for each item added.
378 378
379 379 // Walk the lists until an unequal entry is found.
380 380 var i;
381 381 for (i = 0; i < new_list.length; i++) {
382 382 if (i >= old_list.length || new_list[i] !== old_list[i]) {
383 383 break;
384 384 }
385 385 }
386 386
387 387 // Remove the non-matching items from the old list.
388 388 for (var j = i; j < old_list.length; j++) {
389 389 removed_callback(old_list[j]);
390 390 }
391 391
392 392 // Add the rest of the new list items.
393 393 for (; i < new_list.length; i++) {
394 394 added_callback(new_list[i]);
395 395 }
396 396 },
397 397
398 398 callbacks: function(){
399 399 // Create msg callbacks for a comm msg.
400 400 return this.model.callbacks(this);
401 401 },
402 402
403 403 render: function(){
404 404 // Render the view.
405 405 //
406 406 // By default, this is only called the first time the view is created
407 407 },
408 408
409 409 show: function(){
410 410 // Show the widget-area
411 411 if (this.options && this.options.cell &&
412 412 this.options.cell.widget_area !== undefined) {
413 413 this.options.cell.widget_area.show();
414 414 }
415 415 },
416 416
417 417 send: function (content) {
418 418 // Send a custom msg associated with this view.
419 419 this.model.send(content, this.callbacks());
420 420 },
421 421
422 422 touch: function () {
423 423 this.model.save_changes(this.callbacks());
424 424 },
425 425
426 426 after_displayed: function (callback, context) {
427 427 // Calls the callback right away is the view is already displayed
428 428 // otherwise, register the callback to the 'displayed' event.
429 429 if (this.is_displayed) {
430 430 callback.apply(context);
431 431 } else {
432 432 this.on('displayed', callback, context);
433 433 }
434 434 },
435 435 });
436 436
437 437
438 438 var DOMWidgetView = WidgetView.extend({
439 439 initialize: function (parameters) {
440 440 // Public constructor
441 441 DOMWidgetView.__super__.initialize.apply(this, [parameters]);
442 442 this.on('displayed', this.show, this);
443 443 this.model.on('change:visible', this.update_visible, this);
444 444 this.model.on('change:_css', this.update_css, this);
445 445
446 446 this.model.on('change:_dom_classes', function(model, new_classes) {
447 447 var old_classes = model.previous('_dom_classes');
448 448 this.update_classes(old_classes, new_classes);
449 449 }, this);
450 450
451 451 this.model.on('change:color', function (model, value) {
452 452 this.update_attr('color', value); }, this);
453 453
454 454 this.model.on('change:background_color', function (model, value) {
455 455 this.update_attr('background', value); }, this);
456 456
457 457 this.model.on('change:width', function (model, value) {
458 458 this.update_attr('width', value); }, this);
459 459
460 460 this.model.on('change:height', function (model, value) {
461 461 this.update_attr('height', value); }, this);
462 462
463 463 this.model.on('change:border_color', function (model, value) {
464 464 this.update_attr('border-color', value); }, this);
465 465
466 466 this.model.on('change:border_width', function (model, value) {
467 467 this.update_attr('border-width', value); }, this);
468 468
469 469 this.model.on('change:border_style', function (model, value) {
470 470 this.update_attr('border-style', value); }, this);
471 471
472 472 this.model.on('change:font_style', function (model, value) {
473 473 this.update_attr('font-style', value); }, this);
474 474
475 475 this.model.on('change:font_weight', function (model, value) {
476 476 this.update_attr('font-weight', value); }, this);
477 477
478 478 this.model.on('change:font_size', function (model, value) {
479 479 this.update_attr('font-size', this._default_px(value)); }, this);
480 480
481 481 this.model.on('change:font_family', function (model, value) {
482 482 this.update_attr('font-family', value); }, this);
483 483
484 484 this.model.on('change:padding', function (model, value) {
485 485 this.update_attr('padding', value); }, this);
486 486
487 487 this.model.on('change:margin', function (model, value) {
488 488 this.update_attr('margin', this._default_px(value)); }, this);
489 489
490 490 this.model.on('change:border_radius', function (model, value) {
491 491 this.update_attr('border-radius', this._default_px(value)); }, this);
492 492
493 493 this.after_displayed(function() {
494 494 this.update_visible(this.model, this.model.get("visible"));
495 495 this.update_classes([], this.model.get('_dom_classes'));
496 496
497 497 this.update_attr('color', this.model.get('color'));
498 498 this.update_attr('background', this.model.get('background_color'));
499 499 this.update_attr('width', this.model.get('width'));
500 500 this.update_attr('height', this.model.get('height'));
501 501 this.update_attr('border-color', this.model.get('border_color'));
502 502 this.update_attr('border-width', this.model.get('border_width'));
503 503 this.update_attr('border-style', this.model.get('border_style'));
504 504 this.update_attr('font-style', this.model.get('font_style'));
505 505 this.update_attr('font-weight', this.model.get('font_weight'));
506 506 this.update_attr('font-size', this.model.get('font_size'));
507 507 this.update_attr('font-family', this.model.get('font_family'));
508 508 this.update_attr('padding', this.model.get('padding'));
509 509 this.update_attr('margin', this.model.get('margin'));
510 510 this.update_attr('border-radius', this.model.get('border_radius'));
511 511
512 512 this.update_css(this.model, this.model.get("_css"));
513 513 }, this);
514 514 },
515 515
516 516 _default_px: function(value) {
517 517 // Makes browser interpret a numerical string as a pixel value.
518 518 if (/^\d+\.?(\d+)?$/.test(value.trim())) {
519 519 return value.trim() + 'px';
520 520 }
521 521 return value;
522 522 },
523 523
524 524 update_attr: function(name, value) {
525 525 // Set a css attr of the widget view.
526 526 this.$el.css(name, value);
527 527 },
528 528
529 529 update_visible: function(model, value) {
530 530 // Update visibility
531 531 this.$el.toggle(value);
532 532 },
533 533
534 534 update_css: function (model, css) {
535 535 // Update the css styling of this view.
536 536 var e = this.$el;
537 537 if (css === undefined) {return;}
538 538 for (var i = 0; i < css.length; i++) {
539 539 // Apply the css traits to all elements that match the selector.
540 540 var selector = css[i][0];
541 541 var elements = this._get_selector_element(selector);
542 542 if (elements.length > 0) {
543 543 var trait_key = css[i][1];
544 544 var trait_value = css[i][2];
545 545 elements.css(trait_key ,trait_value);
546 546 }
547 547 }
548 548 },
549 549
550 550 update_classes: function (old_classes, new_classes, $el) {
551 551 // Update the DOM classes applied to an element, default to this.$el.
552 552 if ($el===undefined) {
553 553 $el = this.$el;
554 554 }
555 555 this.do_diff(old_classes, new_classes, function(removed) {
556 556 $el.removeClass(removed);
557 557 }, function(added) {
558 558 $el.addClass(added);
559 559 });
560 560 },
561 561
562 562 update_mapped_classes: function(class_map, trait_name, previous_trait_value, $el) {
563 563 // Update the DOM classes applied to the widget based on a single
564 564 // trait's value.
565 565 //
566 566 // Given a trait value classes map, this function automatically
567 567 // handles applying the appropriate classes to the widget element
568 568 // and removing classes that are no longer valid.
569 569 //
570 570 // Parameters
571 571 // ----------
572 572 // class_map: dictionary
573 573 // Dictionary of trait values to class lists.
574 574 // Example:
575 575 // {
576 576 // success: ['alert', 'alert-success'],
577 577 // info: ['alert', 'alert-info'],
578 578 // warning: ['alert', 'alert-warning'],
579 579 // danger: ['alert', 'alert-danger']
580 580 // };
581 581 // trait_name: string
582 582 // Name of the trait to check the value of.
583 583 // previous_trait_value: optional string, default ''
584 584 // Last trait value
585 585 // $el: optional jQuery element handle, defaults to this.$el
586 586 // Element that the classes are applied to.
587 587 var key = previous_trait_value;
588 588 if (key === undefined) {
589 589 key = this.model.previous(trait_name);
590 590 }
591 591 var old_classes = class_map[key] ? class_map[key] : [];
592 592 key = this.model.get(trait_name);
593 593 var new_classes = class_map[key] ? class_map[key] : [];
594 594
595 595 this.update_classes(old_classes, new_classes, $el || this.$el);
596 596 },
597 597
598 598 _get_selector_element: function (selector) {
599 599 // Get the elements via the css selector.
600 600 var elements;
601 601 if (!selector) {
602 602 elements = this.$el;
603 603 } else {
604 604 elements = this.$el.find(selector).addBack(selector);
605 605 }
606 606 return elements;
607 607 },
608 608 });
609 609
610 610
611 611 var widget = {
612 612 'WidgetModel': WidgetModel,
613 613 'WidgetView': WidgetView,
614 614 'DOMWidgetView': DOMWidgetView,
615 615 };
616 616
617 617 // For backwards compatability.
618 618 $.extend(IPython, widget);
619 619
620 620 return widget;
621 621 });
@@ -1,36 +1,46 b''
1 1 // Test the widget manager.
2 2 casper.notebook_test(function () {
3 3 var index;
4 var slider = {};
5 4
6 5 this.then(function () {
7 6
8 7 // Check if the WidgetManager class is defined.
9 8 this.test.assert(this.evaluate(function() {
10 9 return IPython.WidgetManager !== undefined;
11 10 }), 'WidgetManager class is defined');
12 11
13 12 // Check if the widget manager has been instantiated.
14 13 this.test.assert(this.evaluate(function() {
15 14 return IPython.notebook.kernel.widget_manager !== undefined;
16 15 }), 'Notebook widget manager instantiated');
17 16
18 17 // Try creating a widget from Javascript.
19 slider.id = this.evaluate(function() {
20 var slider = IPython.notebook.kernel.widget_manager.create_model({
21 model_name: 'WidgetModel',
22 widget_class: 'IPython.html.widgets.widget_int.IntSlider',
23 init_state_callback: function(model) { console.log('Create success!', model); }});
24 return slider.id;
18 this.evaluate(function() {
19 IPython.notebook.kernel.widget_manager.create_model({
20 model_name: 'WidgetModel',
21 widget_class: 'IPython.html.widgets.widget_int.IntSlider',
22 init_state_callback: function(model) {
23 console.log('Create success!', model);
24 window.slider_id = model.id;
25 }
26 });
27 });
28 });
29
30 // Wait for the state to be recieved.
31 this.waitFor(function check() {
32 return this.evaluate(function() {
33 return window.slider_id !== undefined;
25 34 });
26 35 });
27 36
28 37 index = this.append_cell(
29 38 'from IPython.html.widgets import Widget\n' +
30 39 'widget = list(Widget.widgets.values())[0]\n' +
31 40 'print(widget.model_id)');
32 41 this.execute_cell_then(index, function(index) {
33 42 var output = this.get_output_cell(index).text.trim();
34 this.test.assertEquals(output, slider.id, "Widget created from the front-end.");
43 var slider_id = this.evaluate(function() { return window.slider_id; });
44 this.test.assertEquals(output, slider_id, "Widget created from the front-end.");
35 45 });
36 46 });
@@ -1,466 +1,466 b''
1 1 """Base Widget class. Allows user to create widgets in the back-end that render
2 2 in the IPython notebook front-end.
3 3 """
4 4 #-----------------------------------------------------------------------------
5 5 # Copyright (c) 2013, the IPython Development Team.
6 6 #
7 7 # Distributed under the terms of the Modified BSD License.
8 8 #
9 9 # The full license is in the file COPYING.txt, distributed with this software.
10 10 #-----------------------------------------------------------------------------
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Imports
14 14 #-----------------------------------------------------------------------------
15 15 from contextlib import contextmanager
16 16 import collections
17 17
18 18 from IPython.core.getipython import get_ipython
19 19 from IPython.kernel.comm import Comm
20 20 from IPython.config import LoggingConfigurable
21 21 from IPython.utils.importstring import import_item
22 22 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, \
23 23 CaselessStrEnum, Tuple, CUnicode, Int, Set
24 24 from IPython.utils.py3compat import string_types
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Classes
28 28 #-----------------------------------------------------------------------------
29 29 class CallbackDispatcher(LoggingConfigurable):
30 30 """A structure for registering and running callbacks"""
31 31 callbacks = List()
32 32
33 33 def __call__(self, *args, **kwargs):
34 34 """Call all of the registered callbacks."""
35 35 value = None
36 36 for callback in self.callbacks:
37 37 try:
38 38 local_value = callback(*args, **kwargs)
39 39 except Exception as e:
40 40 ip = get_ipython()
41 41 if ip is None:
42 42 self.log.warn("Exception in callback %s: %s", callback, e, exc_info=True)
43 43 else:
44 44 ip.showtraceback()
45 45 else:
46 46 value = local_value if local_value is not None else value
47 47 return value
48 48
49 49 def register_callback(self, callback, remove=False):
50 50 """(Un)Register a callback
51 51
52 52 Parameters
53 53 ----------
54 54 callback: method handle
55 55 Method to be registered or unregistered.
56 56 remove=False: bool
57 57 Whether to unregister the callback."""
58 58
59 59 # (Un)Register the callback.
60 60 if remove and callback in self.callbacks:
61 61 self.callbacks.remove(callback)
62 62 elif not remove and callback not in self.callbacks:
63 63 self.callbacks.append(callback)
64 64
65 65 def _show_traceback(method):
66 66 """decorator for showing tracebacks in IPython"""
67 67 def m(self, *args, **kwargs):
68 68 try:
69 69 return(method(self, *args, **kwargs))
70 70 except Exception as e:
71 71 ip = get_ipython()
72 72 if ip is None:
73 73 self.log.warn("Exception in widget method %s: %s", method, e, exc_info=True)
74 74 else:
75 75 ip.showtraceback()
76 76 return m
77 77
78 78 class Widget(LoggingConfigurable):
79 79 #-------------------------------------------------------------------------
80 80 # Class attributes
81 81 #-------------------------------------------------------------------------
82 82 _widget_construction_callback = None
83 83 widgets = {}
84 84
85 85 @staticmethod
86 86 def on_widget_constructed(callback):
87 87 """Registers a callback to be called when a widget is constructed.
88 88
89 89 The callback must have the following signature:
90 90 callback(widget)"""
91 91 Widget._widget_construction_callback = callback
92 92
93 93 @staticmethod
94 94 def _call_widget_constructed(widget):
95 95 """Static method, called when a widget is constructed."""
96 96 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
97 97 Widget._widget_construction_callback(widget)
98 98
99 99 @staticmethod
100 100 def handle_comm_opened(comm, msg):
101 101 """Static method, called when a widget is constructed."""
102 102 widget_class = import_item(msg['content']['data']['widget_class'])
103 103 widget = widget_class(comm=comm)
104 104
105 105
106 106 #-------------------------------------------------------------------------
107 107 # Traits
108 108 #-------------------------------------------------------------------------
109 109 _model_module = Unicode(None, allow_none=True, help="""A requirejs module name
110 110 in which to find _model_name. If empty, look in the global registry.""")
111 111 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
112 112 registered in the front-end to create and sync this widget with.""")
113 113 _view_module = Unicode(help="""A requirejs module in which to find _view_name.
114 114 If empty, look in the global registry.""", sync=True)
115 115 _view_name = Unicode(None, allow_none=True, help="""Default view registered in the front-end
116 116 to use to represent the widget.""", sync=True)
117 117 comm = Instance('IPython.kernel.comm.Comm')
118 118
119 119 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
120 120 front-end can send before receiving an idle msg from the back-end.""")
121 121
122 122 version = Int(0, sync=True, help="""Widget's version""")
123 123 keys = List()
124 124 def _keys_default(self):
125 125 return [name for name in self.traits(sync=True)]
126 126
127 127 _property_lock = Tuple((None, None))
128 128 _send_state_lock = Int(0)
129 129 _states_to_send = Set(allow_none=False)
130 130 _display_callbacks = Instance(CallbackDispatcher, ())
131 131 _msg_callbacks = Instance(CallbackDispatcher, ())
132 132
133 133 #-------------------------------------------------------------------------
134 134 # (Con/de)structor
135 135 #-------------------------------------------------------------------------
136 def __init__(self, open_comm=True, **kwargs):
136 def __init__(self, **kwargs):
137 137 """Public constructor"""
138 138 self._model_id = kwargs.pop('model_id', None)
139 139 super(Widget, self).__init__(**kwargs)
140 140
141 141 Widget._call_widget_constructed(self)
142 142 self.open()
143 143
144 144 def __del__(self):
145 145 """Object disposal"""
146 146 self.close()
147 147
148 148 #-------------------------------------------------------------------------
149 149 # Properties
150 150 #-------------------------------------------------------------------------
151 151
152 152 def open(self):
153 153 """Open a comm to the frontend if one isn't already open."""
154 154 if self.comm is None:
155 155 args = dict(target_name='ipython.widget',
156 156 data={'model_name': self._model_name,
157 157 'model_module': self._model_module})
158 158 if self._model_id is not None:
159 159 args['comm_id'] = self._model_id
160 160 self.comm = Comm(**args)
161 161
162 162 def _comm_changed(self, name, new):
163 163 """Called when the comm is changed."""
164 164 self.comm = new
165 165 self._model_id = self.model_id
166 166
167 167 self.comm.on_msg(self._handle_msg)
168 168 Widget.widgets[self.model_id] = self
169 169
170 170 # first update
171 171 self.send_state()
172 172
173 173 @property
174 174 def model_id(self):
175 175 """Gets the model id of this widget.
176 176
177 177 If a Comm doesn't exist yet, a Comm will be created automagically."""
178 178 return self.comm.comm_id
179 179
180 180 #-------------------------------------------------------------------------
181 181 # Methods
182 182 #-------------------------------------------------------------------------
183 183
184 184 def close(self):
185 185 """Close method.
186 186
187 187 Closes the underlying comm.
188 188 When the comm is closed, all of the widget views are automatically
189 189 removed from the front-end."""
190 190 if self.comm is not None:
191 191 Widget.widgets.pop(self.model_id, None)
192 192 self.comm.close()
193 193 self.comm = None
194 194
195 195 def send_state(self, key=None):
196 196 """Sends the widget state, or a piece of it, to the front-end.
197 197
198 198 Parameters
199 199 ----------
200 200 key : unicode, or iterable (optional)
201 201 A single property's name or iterable of property names to sync with the front-end.
202 202 """
203 203 self._send({
204 204 "method" : "update",
205 205 "state" : self.get_state(key=key)
206 206 })
207 207
208 208 def get_state(self, key=None):
209 209 """Gets the widget state, or a piece of it.
210 210
211 211 Parameters
212 212 ----------
213 213 key : unicode or iterable (optional)
214 214 A single property's name or iterable of property names to get.
215 215 """
216 216 if key is None:
217 217 keys = self.keys
218 218 elif isinstance(key, string_types):
219 219 keys = [key]
220 220 elif isinstance(key, collections.Iterable):
221 221 keys = key
222 222 else:
223 223 raise ValueError("key must be a string, an iterable of keys, or None")
224 224 state = {}
225 225 for k in keys:
226 226 f = self.trait_metadata(k, 'to_json', self._trait_to_json)
227 227 value = getattr(self, k)
228 228 state[k] = f(value)
229 229 return state
230 230
231 231 def set_state(self, sync_data):
232 232 """Called when a state is received from the front-end."""
233 233 for name in self.keys:
234 234 if name in sync_data:
235 235 json_value = sync_data[name]
236 236 from_json = self.trait_metadata(name, 'from_json', self._trait_from_json)
237 237 with self._lock_property(name, json_value):
238 238 setattr(self, name, from_json(json_value))
239 239
240 240 def send(self, content):
241 241 """Sends a custom msg to the widget model in the front-end.
242 242
243 243 Parameters
244 244 ----------
245 245 content : dict
246 246 Content of the message to send.
247 247 """
248 248 self._send({"method": "custom", "content": content})
249 249
250 250 def on_msg(self, callback, remove=False):
251 251 """(Un)Register a custom msg receive callback.
252 252
253 253 Parameters
254 254 ----------
255 255 callback: callable
256 256 callback will be passed two arguments when a message arrives::
257 257
258 258 callback(widget, content)
259 259
260 260 remove: bool
261 261 True if the callback should be unregistered."""
262 262 self._msg_callbacks.register_callback(callback, remove=remove)
263 263
264 264 def on_displayed(self, callback, remove=False):
265 265 """(Un)Register a widget displayed callback.
266 266
267 267 Parameters
268 268 ----------
269 269 callback: method handler
270 270 Must have a signature of::
271 271
272 272 callback(widget, **kwargs)
273 273
274 274 kwargs from display are passed through without modification.
275 275 remove: bool
276 276 True if the callback should be unregistered."""
277 277 self._display_callbacks.register_callback(callback, remove=remove)
278 278
279 279 #-------------------------------------------------------------------------
280 280 # Support methods
281 281 #-------------------------------------------------------------------------
282 282 @contextmanager
283 283 def _lock_property(self, key, value):
284 284 """Lock a property-value pair.
285 285
286 286 The value should be the JSON state of the property.
287 287
288 288 NOTE: This, in addition to the single lock for all state changes, is
289 289 flawed. In the future we may want to look into buffering state changes
290 290 back to the front-end."""
291 291 self._property_lock = (key, value)
292 292 try:
293 293 yield
294 294 finally:
295 295 self._property_lock = (None, None)
296 296
297 297 @contextmanager
298 298 def hold_sync(self):
299 299 """Hold syncing any state until the context manager is released"""
300 300 # We increment a value so that this can be nested. Syncing will happen when
301 301 # all levels have been released.
302 302 self._send_state_lock += 1
303 303 try:
304 304 yield
305 305 finally:
306 306 self._send_state_lock -=1
307 307 if self._send_state_lock == 0:
308 308 self.send_state(self._states_to_send)
309 309 self._states_to_send.clear()
310 310
311 311 def _should_send_property(self, key, value):
312 312 """Check the property lock (property_lock)"""
313 313 to_json = self.trait_metadata(key, 'to_json', self._trait_to_json)
314 314 if (key == self._property_lock[0]
315 315 and to_json(value) == self._property_lock[1]):
316 316 return False
317 317 elif self._send_state_lock > 0:
318 318 self._states_to_send.add(key)
319 319 return False
320 320 else:
321 321 return True
322 322
323 323 # Event handlers
324 324 @_show_traceback
325 325 def _handle_msg(self, msg):
326 326 """Called when a msg is received from the front-end"""
327 327 data = msg['content']['data']
328 328 method = data['method']
329 329 if not method in ['backbone', 'custom']:
330 330 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
331 331
332 332 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
333 333 if method == 'backbone' and 'sync_data' in data:
334 334 sync_data = data['sync_data']
335 335 self.set_state(sync_data) # handles all methods
336 336
337 337 # Handle a custom msg from the front-end
338 338 elif method == 'custom':
339 339 if 'content' in data:
340 340 self._handle_custom_msg(data['content'])
341 341
342 342 def _handle_custom_msg(self, content):
343 343 """Called when a custom msg is received."""
344 344 self._msg_callbacks(self, content)
345 345
346 346 def _notify_trait(self, name, old_value, new_value):
347 347 """Called when a property has been changed."""
348 348 # Trigger default traitlet callback machinery. This allows any user
349 349 # registered validation to be processed prior to allowing the widget
350 350 # machinery to handle the state.
351 351 LoggingConfigurable._notify_trait(self, name, old_value, new_value)
352 352
353 353 # Send the state after the user registered callbacks for trait changes
354 354 # have all fired (allows for user to validate values).
355 355 if self.comm is not None and name in self.keys:
356 356 # Make sure this isn't information that the front-end just sent us.
357 357 if self._should_send_property(name, new_value):
358 358 # Send new state to front-end
359 359 self.send_state(key=name)
360 360
361 361 def _handle_displayed(self, **kwargs):
362 362 """Called when a view has been displayed for this widget instance"""
363 363 self._display_callbacks(self, **kwargs)
364 364
365 365 def _trait_to_json(self, x):
366 366 """Convert a trait value to json
367 367
368 368 Traverse lists/tuples and dicts and serialize their values as well.
369 369 Replace any widgets with their model_id
370 370 """
371 371 if isinstance(x, dict):
372 372 return {k: self._trait_to_json(v) for k, v in x.items()}
373 373 elif isinstance(x, (list, tuple)):
374 374 return [self._trait_to_json(v) for v in x]
375 375 elif isinstance(x, Widget):
376 376 return "IPY_MODEL_" + x.model_id
377 377 else:
378 378 return x # Value must be JSON-able
379 379
380 380 def _trait_from_json(self, x):
381 381 """Convert json values to objects
382 382
383 383 Replace any strings representing valid model id values to Widget references.
384 384 """
385 385 if isinstance(x, dict):
386 386 return {k: self._trait_from_json(v) for k, v in x.items()}
387 387 elif isinstance(x, (list, tuple)):
388 388 return [self._trait_from_json(v) for v in x]
389 389 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
390 390 # we want to support having child widgets at any level in a hierarchy
391 391 # trusting that a widget UUID will not appear out in the wild
392 392 return Widget.widgets[x[10:]]
393 393 else:
394 394 return x
395 395
396 396 def _ipython_display_(self, **kwargs):
397 397 """Called when `IPython.display.display` is called on the widget."""
398 398 # Show view.
399 399 if self._view_name is not None:
400 400 self._send({"method": "display"})
401 401 self._handle_displayed(**kwargs)
402 402
403 403 def _send(self, msg):
404 404 """Sends a message to the model in the front-end."""
405 405 self.comm.send(msg)
406 406
407 407
408 408 class DOMWidget(Widget):
409 409 visible = Bool(True, help="Whether the widget is visible.", sync=True)
410 410 _css = Tuple(sync=True, help="CSS property list: (selector, key, value)")
411 411 _dom_classes = Tuple(sync=True, help="DOM classes applied to widget.$el.")
412 412
413 413 width = CUnicode(sync=True)
414 414 height = CUnicode(sync=True)
415 415 padding = CUnicode(sync=True)
416 416 margin = CUnicode(sync=True)
417 417
418 418 color = Unicode(sync=True)
419 419 background_color = Unicode(sync=True)
420 420 border_color = Unicode(sync=True)
421 421
422 422 border_width = CUnicode(sync=True)
423 423 border_radius = CUnicode(sync=True)
424 424 border_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_border-style.asp
425 425 'none',
426 426 'hidden',
427 427 'dotted',
428 428 'dashed',
429 429 'solid',
430 430 'double',
431 431 'groove',
432 432 'ridge',
433 433 'inset',
434 434 'outset',
435 435 'initial',
436 436 'inherit', ''],
437 437 default_value='', sync=True)
438 438
439 439 font_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_font-style.asp
440 440 'normal',
441 441 'italic',
442 442 'oblique',
443 443 'initial',
444 444 'inherit', ''],
445 445 default_value='', sync=True)
446 446 font_weight = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_weight.asp
447 447 'normal',
448 448 'bold',
449 449 'bolder',
450 450 'lighter',
451 451 'initial',
452 452 'inherit', ''] + [str(100 * (i+1)) for i in range(9)],
453 453 default_value='', sync=True)
454 454 font_size = CUnicode(sync=True)
455 455 font_family = Unicode(sync=True)
456 456
457 457 def __init__(self, *pargs, **kwargs):
458 458 super(DOMWidget, self).__init__(*pargs, **kwargs)
459 459
460 460 def _validate_border(name, old, new):
461 461 if new is not None and new != '':
462 462 if name != 'border_width' and not self.border_width:
463 463 self.border_width = 1
464 464 if name != 'border_style' and self.border_style == '':
465 465 self.border_style = 'solid'
466 466 self.on_trait_change(_validate_border, ['border_width', 'border_style', 'border_color'])
General Comments 0
You need to be logged in to leave comments. Login now