##// END OF EJS Templates
Support specifying requirejs modules for widget models
Thomas Kluyver -
Show More
@@ -1,204 +1,224 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, callback: 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 errback = options.errback || function(err) {console.log(err);};
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 options.callback(view);
104 } else {
104 } else {
105 errback({unknown_view: true, view_name: view_name,
105 errback({unknown_view: true, view_name: view_name,
106 view_module: view_mod});
106 view_module: view_mod});
107 }
107 }
108 };
108 };
109
109
110 if (view_mod) {
110 if (view_mod) {
111 require([view_mod], function(module) {
111 require([view_mod], function(module) {
112 instantiate_view(module[view_name]);
112 instantiate_view(module[view_name]);
113 }, errback);
113 }, errback);
114 } else {
114 } else {
115 instantiate_view(WidgetManager._view_types[view_name]);
115 instantiate_view(WidgetManager._view_types[view_name]);
116 }
116 }
117 };
117 };
118
118
119 WidgetManager.prototype.get_msg_cell = function (msg_id) {
119 WidgetManager.prototype.get_msg_cell = function (msg_id) {
120 var cell = null;
120 var cell = null;
121 // First, check to see if the msg was triggered by cell execution.
121 // First, check to see if the msg was triggered by cell execution.
122 if (this.notebook) {
122 if (this.notebook) {
123 cell = this.notebook.get_msg_cell(msg_id);
123 cell = this.notebook.get_msg_cell(msg_id);
124 }
124 }
125 if (cell !== null) {
125 if (cell !== null) {
126 return cell;
126 return cell;
127 }
127 }
128 // Second, check to see if a get_cell callback was defined
128 // Second, check to see if a get_cell callback was defined
129 // for the message. get_cell callbacks are registered for
129 // for the message. get_cell callbacks are registered for
130 // widget messages, so this block is actually checking to see if the
130 // widget messages, so this block is actually checking to see if the
131 // message was triggered by a widget.
131 // message was triggered by a widget.
132 var kernel = this.comm_manager.kernel;
132 var kernel = this.comm_manager.kernel;
133 if (kernel) {
133 if (kernel) {
134 var callbacks = kernel.get_callbacks_for_msg(msg_id);
134 var callbacks = kernel.get_callbacks_for_msg(msg_id);
135 if (callbacks && callbacks.iopub &&
135 if (callbacks && callbacks.iopub &&
136 callbacks.iopub.get_cell !== undefined) {
136 callbacks.iopub.get_cell !== undefined) {
137 return callbacks.iopub.get_cell();
137 return callbacks.iopub.get_cell();
138 }
138 }
139 }
139 }
140
140
141 // Not triggered by a cell or widget (no get_cell callback
141 // Not triggered by a cell or widget (no get_cell callback
142 // exists).
142 // exists).
143 return null;
143 return null;
144 };
144 };
145
145
146 WidgetManager.prototype.callbacks = function (view) {
146 WidgetManager.prototype.callbacks = function (view) {
147 // callback handlers specific a view
147 // callback handlers specific a view
148 var callbacks = {};
148 var callbacks = {};
149 if (view && view.options.cell) {
149 if (view && view.options.cell) {
150
150
151 // Try to get output handlers
151 // Try to get output handlers
152 var cell = view.options.cell;
152 var cell = view.options.cell;
153 var handle_output = null;
153 var handle_output = null;
154 var handle_clear_output = null;
154 var handle_clear_output = null;
155 if (cell.output_area) {
155 if (cell.output_area) {
156 handle_output = $.proxy(cell.output_area.handle_output, cell.output_area);
156 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);
157 handle_clear_output = $.proxy(cell.output_area.handle_clear_output, cell.output_area);
158 }
158 }
159
159
160 // Create callback dict using what is known
160 // Create callback dict using what is known
161 var that = this;
161 var that = this;
162 callbacks = {
162 callbacks = {
163 iopub : {
163 iopub : {
164 output : handle_output,
164 output : handle_output,
165 clear_output : handle_clear_output,
165 clear_output : handle_clear_output,
166
166
167 // Special function only registered by widget messages.
167 // Special function only registered by widget messages.
168 // Allows us to get the cell for a message so we know
168 // Allows us to get the cell for a message so we know
169 // where to add widgets if the code requires it.
169 // where to add widgets if the code requires it.
170 get_cell : function () {
170 get_cell : function () {
171 return cell;
171 return cell;
172 },
172 },
173 },
173 },
174 };
174 };
175 }
175 }
176 return callbacks;
176 return callbacks;
177 };
177 };
178
178
179 WidgetManager.prototype.get_model = function (model_id) {
179 WidgetManager.prototype.get_model = function (model_id) {
180 // Look-up a model instance by its id.
180 // Look-up a model instance by its id.
181 var model = this._models[model_id];
181 var model = this._models[model_id];
182 if (model !== undefined && model.id == model_id) {
182 if (model !== undefined && model.id == model_id) {
183 return model;
183 return model;
184 }
184 }
185 return null;
185 return null;
186 };
186 };
187
187
188 WidgetManager.prototype._handle_comm_open = function (comm, msg) {
188 WidgetManager.prototype._handle_comm_open = function (comm, msg) {
189 // Handle when a comm is opened.
189 // Handle when a comm is opened.
190 var that = this;
190 var that = this;
191 var model_id = comm.comm_id;
191
192 var instantiate_model = function(ModelType) {
193 var model_id = comm.comm_id;
194 var widget_model = new ModelType(that, model_id, comm);
195 widget_model.on('comm:close', function () {
196 delete that._models[model_id];
197 });
198 that._models[model_id] = widget_model;
199 };
200
192 var widget_type_name = msg.content.data.model_name;
201 var widget_type_name = msg.content.data.model_name;
193 var widget_model = new WidgetManager._model_types[widget_type_name](this, model_id, comm);
202 var widget_module = msg.content.data.model_module;
194 widget_model.on('comm:close', function () {
203
195 delete that._models[model_id];
204 if (widget_module) {
196 });
205 // Load the module containing the widget model
197 this._models[model_id] = widget_model;
206 require([widget_module], function(mod) {
207 if (mod[widget_type_name]) {
208 instantiate_model(mod[widget_type_name]);
209 } else {
210 console.log("Error creating widget model: " + widget_type_name
211 + " not found in " + widget_module);
212 }
213 }, function(err) { console.log(err); });
214 } else {
215 // No module specified, load from the global models registry
216 instantiate_model(WidgetManager._model_types[widget_type_name]);
217 }
198 };
218 };
199
219
200 // Backwards compatability.
220 // Backwards compatability.
201 IPython.WidgetManager = WidgetManager;
221 IPython.WidgetManager = WidgetManager;
202
222
203 return {'WidgetManager': WidgetManager};
223 return {'WidgetManager': WidgetManager};
204 });
224 });
@@ -1,450 +1,454 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.traitlets import Unicode, Dict, Instance, Bool, List, \
21 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, \
22 CaselessStrEnum, Tuple, CUnicode, Int, Set
22 CaselessStrEnum, Tuple, CUnicode, Int, Set
23 from IPython.utils.py3compat import string_types
23 from IPython.utils.py3compat import string_types
24
24
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 # Classes
26 # Classes
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 class CallbackDispatcher(LoggingConfigurable):
28 class CallbackDispatcher(LoggingConfigurable):
29 """A structure for registering and running callbacks"""
29 """A structure for registering and running callbacks"""
30 callbacks = List()
30 callbacks = List()
31
31
32 def __call__(self, *args, **kwargs):
32 def __call__(self, *args, **kwargs):
33 """Call all of the registered callbacks."""
33 """Call all of the registered callbacks."""
34 value = None
34 value = None
35 for callback in self.callbacks:
35 for callback in self.callbacks:
36 try:
36 try:
37 local_value = callback(*args, **kwargs)
37 local_value = callback(*args, **kwargs)
38 except Exception as e:
38 except Exception as e:
39 ip = get_ipython()
39 ip = get_ipython()
40 if ip is None:
40 if ip is None:
41 self.log.warn("Exception in callback %s: %s", callback, e, exc_info=True)
41 self.log.warn("Exception in callback %s: %s", callback, e, exc_info=True)
42 else:
42 else:
43 ip.showtraceback()
43 ip.showtraceback()
44 else:
44 else:
45 value = local_value if local_value is not None else value
45 value = local_value if local_value is not None else value
46 return value
46 return value
47
47
48 def register_callback(self, callback, remove=False):
48 def register_callback(self, callback, remove=False):
49 """(Un)Register a callback
49 """(Un)Register a callback
50
50
51 Parameters
51 Parameters
52 ----------
52 ----------
53 callback: method handle
53 callback: method handle
54 Method to be registered or unregistered.
54 Method to be registered or unregistered.
55 remove=False: bool
55 remove=False: bool
56 Whether to unregister the callback."""
56 Whether to unregister the callback."""
57
57
58 # (Un)Register the callback.
58 # (Un)Register the callback.
59 if remove and callback in self.callbacks:
59 if remove and callback in self.callbacks:
60 self.callbacks.remove(callback)
60 self.callbacks.remove(callback)
61 elif not remove and callback not in self.callbacks:
61 elif not remove and callback not in self.callbacks:
62 self.callbacks.append(callback)
62 self.callbacks.append(callback)
63
63
64 def _show_traceback(method):
64 def _show_traceback(method):
65 """decorator for showing tracebacks in IPython"""
65 """decorator for showing tracebacks in IPython"""
66 def m(self, *args, **kwargs):
66 def m(self, *args, **kwargs):
67 try:
67 try:
68 return(method(self, *args, **kwargs))
68 return(method(self, *args, **kwargs))
69 except Exception as e:
69 except Exception as e:
70 ip = get_ipython()
70 ip = get_ipython()
71 if ip is None:
71 if ip is None:
72 self.log.warn("Exception in widget method %s: %s", method, e, exc_info=True)
72 self.log.warn("Exception in widget method %s: %s", method, e, exc_info=True)
73 else:
73 else:
74 ip.showtraceback()
74 ip.showtraceback()
75 return m
75 return m
76
76
77 class Widget(LoggingConfigurable):
77 class Widget(LoggingConfigurable):
78 #-------------------------------------------------------------------------
78 #-------------------------------------------------------------------------
79 # Class attributes
79 # Class attributes
80 #-------------------------------------------------------------------------
80 #-------------------------------------------------------------------------
81 _widget_construction_callback = None
81 _widget_construction_callback = None
82 widgets = {}
82 widgets = {}
83
83
84 @staticmethod
84 @staticmethod
85 def on_widget_constructed(callback):
85 def on_widget_constructed(callback):
86 """Registers a callback to be called when a widget is constructed.
86 """Registers a callback to be called when a widget is constructed.
87
87
88 The callback must have the following signature:
88 The callback must have the following signature:
89 callback(widget)"""
89 callback(widget)"""
90 Widget._widget_construction_callback = callback
90 Widget._widget_construction_callback = callback
91
91
92 @staticmethod
92 @staticmethod
93 def _call_widget_constructed(widget):
93 def _call_widget_constructed(widget):
94 """Static method, called when a widget is constructed."""
94 """Static method, called when a widget is constructed."""
95 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
95 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
96 Widget._widget_construction_callback(widget)
96 Widget._widget_construction_callback(widget)
97
97
98 #-------------------------------------------------------------------------
98 #-------------------------------------------------------------------------
99 # Traits
99 # Traits
100 #-------------------------------------------------------------------------
100 #-------------------------------------------------------------------------
101 _model_module = Unicode(None, allow_none=True, help="""A requirejs module name
102 in which to find _model_name. If empty, look in the global registry.""")
101 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
103 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
102 registered in the front-end to create and sync this widget with.""")
104 registered in the front-end to create and sync this widget with.""")
103 _view_module = Unicode(help="""A requirejs module in which to find _view_name.
105 _view_module = Unicode(help="""A requirejs module in which to find _view_name.
104 If empty, look in the global registry.""", sync=True)
106 If empty, look in the global registry.""", sync=True)
105 _view_name = Unicode(None, allow_none=True, help="""Default view registered in the front-end
107 _view_name = Unicode(None, allow_none=True, help="""Default view registered in the front-end
106 to use to represent the widget.""", sync=True)
108 to use to represent the widget.""", sync=True)
107 comm = Instance('IPython.kernel.comm.Comm')
109 comm = Instance('IPython.kernel.comm.Comm')
108
110
109 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
111 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
110 front-end can send before receiving an idle msg from the back-end.""")
112 front-end can send before receiving an idle msg from the back-end.""")
111
113
112 version = Int(0, sync=True, help="""Widget's version""")
114 version = Int(0, sync=True, help="""Widget's version""")
113 keys = List()
115 keys = List()
114 def _keys_default(self):
116 def _keys_default(self):
115 return [name for name in self.traits(sync=True)]
117 return [name for name in self.traits(sync=True)]
116
118
117 _property_lock = Tuple((None, None))
119 _property_lock = Tuple((None, None))
118 _send_state_lock = Int(0)
120 _send_state_lock = Int(0)
119 _states_to_send = Set(allow_none=False)
121 _states_to_send = Set(allow_none=False)
120 _display_callbacks = Instance(CallbackDispatcher, ())
122 _display_callbacks = Instance(CallbackDispatcher, ())
121 _msg_callbacks = Instance(CallbackDispatcher, ())
123 _msg_callbacks = Instance(CallbackDispatcher, ())
122
124
123 #-------------------------------------------------------------------------
125 #-------------------------------------------------------------------------
124 # (Con/de)structor
126 # (Con/de)structor
125 #-------------------------------------------------------------------------
127 #-------------------------------------------------------------------------
126 def __init__(self, **kwargs):
128 def __init__(self, **kwargs):
127 """Public constructor"""
129 """Public constructor"""
128 self._model_id = kwargs.pop('model_id', None)
130 self._model_id = kwargs.pop('model_id', None)
129 super(Widget, self).__init__(**kwargs)
131 super(Widget, self).__init__(**kwargs)
130
132
131 Widget._call_widget_constructed(self)
133 Widget._call_widget_constructed(self)
132 self.open()
134 self.open()
133
135
134 def __del__(self):
136 def __del__(self):
135 """Object disposal"""
137 """Object disposal"""
136 self.close()
138 self.close()
137
139
138 #-------------------------------------------------------------------------
140 #-------------------------------------------------------------------------
139 # Properties
141 # Properties
140 #-------------------------------------------------------------------------
142 #-------------------------------------------------------------------------
141
143
142 def open(self):
144 def open(self):
143 """Open a comm to the frontend if one isn't already open."""
145 """Open a comm to the frontend if one isn't already open."""
144 if self.comm is None:
146 if self.comm is None:
145 args = dict(target_name='ipython.widget', data={ 'model_name': self._model_name })
147 args = dict(target_name='ipython.widget',
148 data={'model_name': self._model_name,
149 'model_module': self._model_module})
146 if self._model_id is not None:
150 if self._model_id is not None:
147 args['comm_id'] = self._model_id
151 args['comm_id'] = self._model_id
148 self.comm = Comm(**args)
152 self.comm = Comm(**args)
149 self._model_id = self.model_id
153 self._model_id = self.model_id
150
154
151 self.comm.on_msg(self._handle_msg)
155 self.comm.on_msg(self._handle_msg)
152 Widget.widgets[self.model_id] = self
156 Widget.widgets[self.model_id] = self
153
157
154 # first update
158 # first update
155 self.send_state()
159 self.send_state()
156
160
157 @property
161 @property
158 def model_id(self):
162 def model_id(self):
159 """Gets the model id of this widget.
163 """Gets the model id of this widget.
160
164
161 If a Comm doesn't exist yet, a Comm will be created automagically."""
165 If a Comm doesn't exist yet, a Comm will be created automagically."""
162 return self.comm.comm_id
166 return self.comm.comm_id
163
167
164 #-------------------------------------------------------------------------
168 #-------------------------------------------------------------------------
165 # Methods
169 # Methods
166 #-------------------------------------------------------------------------
170 #-------------------------------------------------------------------------
167
171
168 def close(self):
172 def close(self):
169 """Close method.
173 """Close method.
170
174
171 Closes the underlying comm.
175 Closes the underlying comm.
172 When the comm is closed, all of the widget views are automatically
176 When the comm is closed, all of the widget views are automatically
173 removed from the front-end."""
177 removed from the front-end."""
174 if self.comm is not None:
178 if self.comm is not None:
175 Widget.widgets.pop(self.model_id, None)
179 Widget.widgets.pop(self.model_id, None)
176 self.comm.close()
180 self.comm.close()
177 self.comm = None
181 self.comm = None
178
182
179 def send_state(self, key=None):
183 def send_state(self, key=None):
180 """Sends the widget state, or a piece of it, to the front-end.
184 """Sends the widget state, or a piece of it, to the front-end.
181
185
182 Parameters
186 Parameters
183 ----------
187 ----------
184 key : unicode, or iterable (optional)
188 key : unicode, or iterable (optional)
185 A single property's name or iterable of property names to sync with the front-end.
189 A single property's name or iterable of property names to sync with the front-end.
186 """
190 """
187 self._send({
191 self._send({
188 "method" : "update",
192 "method" : "update",
189 "state" : self.get_state(key=key)
193 "state" : self.get_state(key=key)
190 })
194 })
191
195
192 def get_state(self, key=None):
196 def get_state(self, key=None):
193 """Gets the widget state, or a piece of it.
197 """Gets the widget state, or a piece of it.
194
198
195 Parameters
199 Parameters
196 ----------
200 ----------
197 key : unicode or iterable (optional)
201 key : unicode or iterable (optional)
198 A single property's name or iterable of property names to get.
202 A single property's name or iterable of property names to get.
199 """
203 """
200 if key is None:
204 if key is None:
201 keys = self.keys
205 keys = self.keys
202 elif isinstance(key, string_types):
206 elif isinstance(key, string_types):
203 keys = [key]
207 keys = [key]
204 elif isinstance(key, collections.Iterable):
208 elif isinstance(key, collections.Iterable):
205 keys = key
209 keys = key
206 else:
210 else:
207 raise ValueError("key must be a string, an iterable of keys, or None")
211 raise ValueError("key must be a string, an iterable of keys, or None")
208 state = {}
212 state = {}
209 for k in keys:
213 for k in keys:
210 f = self.trait_metadata(k, 'to_json', self._trait_to_json)
214 f = self.trait_metadata(k, 'to_json', self._trait_to_json)
211 value = getattr(self, k)
215 value = getattr(self, k)
212 state[k] = f(value)
216 state[k] = f(value)
213 return state
217 return state
214
218
215 def set_state(self, sync_data):
219 def set_state(self, sync_data):
216 """Called when a state is received from the front-end."""
220 """Called when a state is received from the front-end."""
217 for name in self.keys:
221 for name in self.keys:
218 if name in sync_data:
222 if name in sync_data:
219 json_value = sync_data[name]
223 json_value = sync_data[name]
220 from_json = self.trait_metadata(name, 'from_json', self._trait_from_json)
224 from_json = self.trait_metadata(name, 'from_json', self._trait_from_json)
221 with self._lock_property(name, json_value):
225 with self._lock_property(name, json_value):
222 setattr(self, name, from_json(json_value))
226 setattr(self, name, from_json(json_value))
223
227
224 def send(self, content):
228 def send(self, content):
225 """Sends a custom msg to the widget model in the front-end.
229 """Sends a custom msg to the widget model in the front-end.
226
230
227 Parameters
231 Parameters
228 ----------
232 ----------
229 content : dict
233 content : dict
230 Content of the message to send.
234 Content of the message to send.
231 """
235 """
232 self._send({"method": "custom", "content": content})
236 self._send({"method": "custom", "content": content})
233
237
234 def on_msg(self, callback, remove=False):
238 def on_msg(self, callback, remove=False):
235 """(Un)Register a custom msg receive callback.
239 """(Un)Register a custom msg receive callback.
236
240
237 Parameters
241 Parameters
238 ----------
242 ----------
239 callback: callable
243 callback: callable
240 callback will be passed two arguments when a message arrives::
244 callback will be passed two arguments when a message arrives::
241
245
242 callback(widget, content)
246 callback(widget, content)
243
247
244 remove: bool
248 remove: bool
245 True if the callback should be unregistered."""
249 True if the callback should be unregistered."""
246 self._msg_callbacks.register_callback(callback, remove=remove)
250 self._msg_callbacks.register_callback(callback, remove=remove)
247
251
248 def on_displayed(self, callback, remove=False):
252 def on_displayed(self, callback, remove=False):
249 """(Un)Register a widget displayed callback.
253 """(Un)Register a widget displayed callback.
250
254
251 Parameters
255 Parameters
252 ----------
256 ----------
253 callback: method handler
257 callback: method handler
254 Must have a signature of::
258 Must have a signature of::
255
259
256 callback(widget, **kwargs)
260 callback(widget, **kwargs)
257
261
258 kwargs from display are passed through without modification.
262 kwargs from display are passed through without modification.
259 remove: bool
263 remove: bool
260 True if the callback should be unregistered."""
264 True if the callback should be unregistered."""
261 self._display_callbacks.register_callback(callback, remove=remove)
265 self._display_callbacks.register_callback(callback, remove=remove)
262
266
263 #-------------------------------------------------------------------------
267 #-------------------------------------------------------------------------
264 # Support methods
268 # Support methods
265 #-------------------------------------------------------------------------
269 #-------------------------------------------------------------------------
266 @contextmanager
270 @contextmanager
267 def _lock_property(self, key, value):
271 def _lock_property(self, key, value):
268 """Lock a property-value pair.
272 """Lock a property-value pair.
269
273
270 The value should be the JSON state of the property.
274 The value should be the JSON state of the property.
271
275
272 NOTE: This, in addition to the single lock for all state changes, is
276 NOTE: This, in addition to the single lock for all state changes, is
273 flawed. In the future we may want to look into buffering state changes
277 flawed. In the future we may want to look into buffering state changes
274 back to the front-end."""
278 back to the front-end."""
275 self._property_lock = (key, value)
279 self._property_lock = (key, value)
276 try:
280 try:
277 yield
281 yield
278 finally:
282 finally:
279 self._property_lock = (None, None)
283 self._property_lock = (None, None)
280
284
281 @contextmanager
285 @contextmanager
282 def hold_sync(self):
286 def hold_sync(self):
283 """Hold syncing any state until the context manager is released"""
287 """Hold syncing any state until the context manager is released"""
284 # We increment a value so that this can be nested. Syncing will happen when
288 # We increment a value so that this can be nested. Syncing will happen when
285 # all levels have been released.
289 # all levels have been released.
286 self._send_state_lock += 1
290 self._send_state_lock += 1
287 try:
291 try:
288 yield
292 yield
289 finally:
293 finally:
290 self._send_state_lock -=1
294 self._send_state_lock -=1
291 if self._send_state_lock == 0:
295 if self._send_state_lock == 0:
292 self.send_state(self._states_to_send)
296 self.send_state(self._states_to_send)
293 self._states_to_send.clear()
297 self._states_to_send.clear()
294
298
295 def _should_send_property(self, key, value):
299 def _should_send_property(self, key, value):
296 """Check the property lock (property_lock)"""
300 """Check the property lock (property_lock)"""
297 to_json = self.trait_metadata(key, 'to_json', self._trait_to_json)
301 to_json = self.trait_metadata(key, 'to_json', self._trait_to_json)
298 if (key == self._property_lock[0]
302 if (key == self._property_lock[0]
299 and to_json(value) == self._property_lock[1]):
303 and to_json(value) == self._property_lock[1]):
300 return False
304 return False
301 elif self._send_state_lock > 0:
305 elif self._send_state_lock > 0:
302 self._states_to_send.add(key)
306 self._states_to_send.add(key)
303 return False
307 return False
304 else:
308 else:
305 return True
309 return True
306
310
307 # Event handlers
311 # Event handlers
308 @_show_traceback
312 @_show_traceback
309 def _handle_msg(self, msg):
313 def _handle_msg(self, msg):
310 """Called when a msg is received from the front-end"""
314 """Called when a msg is received from the front-end"""
311 data = msg['content']['data']
315 data = msg['content']['data']
312 method = data['method']
316 method = data['method']
313 if not method in ['backbone', 'custom']:
317 if not method in ['backbone', 'custom']:
314 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
318 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
315
319
316 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
320 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
317 if method == 'backbone' and 'sync_data' in data:
321 if method == 'backbone' and 'sync_data' in data:
318 sync_data = data['sync_data']
322 sync_data = data['sync_data']
319 self.set_state(sync_data) # handles all methods
323 self.set_state(sync_data) # handles all methods
320
324
321 # Handle a custom msg from the front-end
325 # Handle a custom msg from the front-end
322 elif method == 'custom':
326 elif method == 'custom':
323 if 'content' in data:
327 if 'content' in data:
324 self._handle_custom_msg(data['content'])
328 self._handle_custom_msg(data['content'])
325
329
326 def _handle_custom_msg(self, content):
330 def _handle_custom_msg(self, content):
327 """Called when a custom msg is received."""
331 """Called when a custom msg is received."""
328 self._msg_callbacks(self, content)
332 self._msg_callbacks(self, content)
329
333
330 def _notify_trait(self, name, old_value, new_value):
334 def _notify_trait(self, name, old_value, new_value):
331 """Called when a property has been changed."""
335 """Called when a property has been changed."""
332 # Trigger default traitlet callback machinery. This allows any user
336 # Trigger default traitlet callback machinery. This allows any user
333 # registered validation to be processed prior to allowing the widget
337 # registered validation to be processed prior to allowing the widget
334 # machinery to handle the state.
338 # machinery to handle the state.
335 LoggingConfigurable._notify_trait(self, name, old_value, new_value)
339 LoggingConfigurable._notify_trait(self, name, old_value, new_value)
336
340
337 # Send the state after the user registered callbacks for trait changes
341 # Send the state after the user registered callbacks for trait changes
338 # have all fired (allows for user to validate values).
342 # have all fired (allows for user to validate values).
339 if self.comm is not None and name in self.keys:
343 if self.comm is not None and name in self.keys:
340 # Make sure this isn't information that the front-end just sent us.
344 # Make sure this isn't information that the front-end just sent us.
341 if self._should_send_property(name, new_value):
345 if self._should_send_property(name, new_value):
342 # Send new state to front-end
346 # Send new state to front-end
343 self.send_state(key=name)
347 self.send_state(key=name)
344
348
345 def _handle_displayed(self, **kwargs):
349 def _handle_displayed(self, **kwargs):
346 """Called when a view has been displayed for this widget instance"""
350 """Called when a view has been displayed for this widget instance"""
347 self._display_callbacks(self, **kwargs)
351 self._display_callbacks(self, **kwargs)
348
352
349 def _trait_to_json(self, x):
353 def _trait_to_json(self, x):
350 """Convert a trait value to json
354 """Convert a trait value to json
351
355
352 Traverse lists/tuples and dicts and serialize their values as well.
356 Traverse lists/tuples and dicts and serialize their values as well.
353 Replace any widgets with their model_id
357 Replace any widgets with their model_id
354 """
358 """
355 if isinstance(x, dict):
359 if isinstance(x, dict):
356 return {k: self._trait_to_json(v) for k, v in x.items()}
360 return {k: self._trait_to_json(v) for k, v in x.items()}
357 elif isinstance(x, (list, tuple)):
361 elif isinstance(x, (list, tuple)):
358 return [self._trait_to_json(v) for v in x]
362 return [self._trait_to_json(v) for v in x]
359 elif isinstance(x, Widget):
363 elif isinstance(x, Widget):
360 return "IPY_MODEL_" + x.model_id
364 return "IPY_MODEL_" + x.model_id
361 else:
365 else:
362 return x # Value must be JSON-able
366 return x # Value must be JSON-able
363
367
364 def _trait_from_json(self, x):
368 def _trait_from_json(self, x):
365 """Convert json values to objects
369 """Convert json values to objects
366
370
367 Replace any strings representing valid model id values to Widget references.
371 Replace any strings representing valid model id values to Widget references.
368 """
372 """
369 if isinstance(x, dict):
373 if isinstance(x, dict):
370 return {k: self._trait_from_json(v) for k, v in x.items()}
374 return {k: self._trait_from_json(v) for k, v in x.items()}
371 elif isinstance(x, (list, tuple)):
375 elif isinstance(x, (list, tuple)):
372 return [self._trait_from_json(v) for v in x]
376 return [self._trait_from_json(v) for v in x]
373 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
377 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
374 # we want to support having child widgets at any level in a hierarchy
378 # we want to support having child widgets at any level in a hierarchy
375 # trusting that a widget UUID will not appear out in the wild
379 # trusting that a widget UUID will not appear out in the wild
376 return Widget.widgets[x[10:]]
380 return Widget.widgets[x[10:]]
377 else:
381 else:
378 return x
382 return x
379
383
380 def _ipython_display_(self, **kwargs):
384 def _ipython_display_(self, **kwargs):
381 """Called when `IPython.display.display` is called on the widget."""
385 """Called when `IPython.display.display` is called on the widget."""
382 # Show view.
386 # Show view.
383 if self._view_name is not None:
387 if self._view_name is not None:
384 self._send({"method": "display"})
388 self._send({"method": "display"})
385 self._handle_displayed(**kwargs)
389 self._handle_displayed(**kwargs)
386
390
387 def _send(self, msg):
391 def _send(self, msg):
388 """Sends a message to the model in the front-end."""
392 """Sends a message to the model in the front-end."""
389 self.comm.send(msg)
393 self.comm.send(msg)
390
394
391
395
392 class DOMWidget(Widget):
396 class DOMWidget(Widget):
393 visible = Bool(True, help="Whether the widget is visible.", sync=True)
397 visible = Bool(True, help="Whether the widget is visible.", sync=True)
394 _css = Tuple(sync=True, help="CSS property list: (selector, key, value)")
398 _css = Tuple(sync=True, help="CSS property list: (selector, key, value)")
395 _dom_classes = Tuple(sync=True, help="DOM classes applied to widget.$el.")
399 _dom_classes = Tuple(sync=True, help="DOM classes applied to widget.$el.")
396
400
397 width = CUnicode(sync=True)
401 width = CUnicode(sync=True)
398 height = CUnicode(sync=True)
402 height = CUnicode(sync=True)
399 padding = CUnicode(sync=True)
403 padding = CUnicode(sync=True)
400 margin = CUnicode(sync=True)
404 margin = CUnicode(sync=True)
401
405
402 color = Unicode(sync=True)
406 color = Unicode(sync=True)
403 background_color = Unicode(sync=True)
407 background_color = Unicode(sync=True)
404 border_color = Unicode(sync=True)
408 border_color = Unicode(sync=True)
405
409
406 border_width = CUnicode(sync=True)
410 border_width = CUnicode(sync=True)
407 border_radius = CUnicode(sync=True)
411 border_radius = CUnicode(sync=True)
408 border_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_border-style.asp
412 border_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_border-style.asp
409 'none',
413 'none',
410 'hidden',
414 'hidden',
411 'dotted',
415 'dotted',
412 'dashed',
416 'dashed',
413 'solid',
417 'solid',
414 'double',
418 'double',
415 'groove',
419 'groove',
416 'ridge',
420 'ridge',
417 'inset',
421 'inset',
418 'outset',
422 'outset',
419 'initial',
423 'initial',
420 'inherit', ''],
424 'inherit', ''],
421 default_value='', sync=True)
425 default_value='', sync=True)
422
426
423 font_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_font-style.asp
427 font_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_font-style.asp
424 'normal',
428 'normal',
425 'italic',
429 'italic',
426 'oblique',
430 'oblique',
427 'initial',
431 'initial',
428 'inherit', ''],
432 'inherit', ''],
429 default_value='', sync=True)
433 default_value='', sync=True)
430 font_weight = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_weight.asp
434 font_weight = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_weight.asp
431 'normal',
435 'normal',
432 'bold',
436 'bold',
433 'bolder',
437 'bolder',
434 'lighter',
438 'lighter',
435 'initial',
439 'initial',
436 'inherit', ''] + [str(100 * (i+1)) for i in range(9)],
440 'inherit', ''] + [str(100 * (i+1)) for i in range(9)],
437 default_value='', sync=True)
441 default_value='', sync=True)
438 font_size = CUnicode(sync=True)
442 font_size = CUnicode(sync=True)
439 font_family = Unicode(sync=True)
443 font_family = Unicode(sync=True)
440
444
441 def __init__(self, *pargs, **kwargs):
445 def __init__(self, *pargs, **kwargs):
442 super(DOMWidget, self).__init__(*pargs, **kwargs)
446 super(DOMWidget, self).__init__(*pargs, **kwargs)
443
447
444 def _validate_border(name, old, new):
448 def _validate_border(name, old, new):
445 if new is not None and new != '':
449 if new is not None and new != '':
446 if name != 'border_width' and not self.border_width:
450 if name != 'border_width' and not self.border_width:
447 self.border_width = 1
451 self.border_width = 1
448 if name != 'border_style' and self.border_style == '':
452 if name != 'border_style' and self.border_style == '':
449 self.border_style = 'solid'
453 self.border_style = 'solid'
450 self.on_trait_change(_validate_border, ['border_width', 'border_style', 'border_color'])
454 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