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