##// END OF EJS Templates
Fixed doc string comments, removed extra space
Jonathan Frederic -
Show More
@@ -1,361 +1,364 b''
1 """Base Widget class. Allows user to create widgets in the backend that render
1 """Base Widget class. Allows user to create widgets in the backend that render
2 in the IPython notebook frontend.
2 in the IPython notebook frontend.
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 copy import copy
15 from copy import copy
16 from glob import glob
16 from glob import glob
17 import uuid
17 import uuid
18 import sys
18 import sys
19 import os
19 import os
20
20
21 import IPython
21 import IPython
22 from IPython.kernel.comm import Comm
22 from IPython.kernel.comm import Comm
23 from IPython.config import LoggingConfigurable
23 from IPython.config import LoggingConfigurable
24 from IPython.utils.traitlets import Unicode, Dict, List, Instance, Bool
24 from IPython.utils.traitlets import Unicode, Dict, List, Instance, Bool
25 from IPython.display import Javascript, display
25 from IPython.display import Javascript, display
26 from IPython.utils.py3compat import string_types
26 from IPython.utils.py3compat import string_types
27
27
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 # Shared
29 # Shared
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 def init_widget_js():
31 def init_widget_js():
32 path = os.path.split(os.path.abspath( __file__ ))[0]
32 path = os.path.split(os.path.abspath( __file__ ))[0]
33 for filepath in glob(os.path.join(path, "*.py")):
33 for filepath in glob(os.path.join(path, "*.py")):
34 filename = os.path.split(filepath)[1]
34 filename = os.path.split(filepath)[1]
35 name = filename.rsplit('.', 1)[0]
35 name = filename.rsplit('.', 1)[0]
36 if not (name == 'widget' or name == '__init__') and name.startswith('widget_'):
36 if not (name == 'widget' or name == '__init__') and name.startswith('widget_'):
37 # Remove 'widget_' from the start of the name before compiling the path.
37 # Remove 'widget_' from the start of the name before compiling the path.
38 js_path = 'static/notebook/js/widgets/%s.js' % name[7:]
38 js_path = 'static/notebook/js/widgets/%s.js' % name[7:]
39 display(Javascript(data='$.getScript($("body").data("baseProjectUrl") + "%s");' % js_path), exclude="text/plain")
39 display(Javascript(data='$.getScript($("body").data("baseProjectUrl") + "%s");' % js_path), exclude="text/plain")
40
40
41
41
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43 # Classes
43 # Classes
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45 class Widget(LoggingConfigurable):
45 class Widget(LoggingConfigurable):
46
46
47 # Shared declarations
47 # Shared declarations
48 _keys = []
48 _keys = []
49
49
50 # Public declarations
50 # Public declarations
51 target_name = Unicode('widget', help="""Name of the backbone model
51 target_name = Unicode('widget', help="""Name of the backbone model
52 registered in the frontend to create and sync this widget with.""")
52 registered in the frontend to create and sync this widget with.""")
53 default_view_name = Unicode(help="""Default view registered in the frontend
53 default_view_name = Unicode(help="""Default view registered in the frontend
54 to use to represent the widget.""")
54 to use to represent the widget.""")
55 parent = Instance('IPython.html.widgets.widget.Widget')
55 parent = Instance('IPython.html.widgets.widget.Widget')
56 visible = Bool(True, help="Whether or not the widget is visible.")
56 visible = Bool(True, help="Whether or not the widget is visible.")
57
57
58 def _parent_changed(self, name, old, new):
58 def _parent_changed(self, name, old, new):
59 if self._displayed:
59 if self._displayed:
60 raise Exception('Parent cannot be set because widget has been displayed.')
60 raise Exception('Parent cannot be set because widget has been displayed.')
61 elif new == self:
61 elif new == self:
62 raise Exception('Parent cannot be set to self.')
62 raise Exception('Parent cannot be set to self.')
63 else:
63 else:
64
64
65 # Parent/child association
65 # Parent/child association
66 if new is not None and not self in new._children:
66 if new is not None and not self in new._children:
67 new._children.append(self)
67 new._children.append(self)
68 if old is not None and self in old._children:
68 if old is not None and self in old._children:
69 old._children.remove(self)
69 old._children.remove(self)
70
70
71 # Private/protected declarations
71 # Private/protected declarations
72 _property_lock = False
72 _property_lock = False
73 _css = Dict() # Internal CSS property dict
73 _css = Dict() # Internal CSS property dict
74 _add_class = List() # Used to add a js class to a DOM element (call#, selector, class_name)
74 _add_class = List() # Used to add a js class to a DOM element (call#, selector, class_name)
75 _remove_class = List() # Used to remove a js class from a DOM element (call#, selector, class_name)
75 _remove_class = List() # Used to remove a js class from a DOM element (call#, selector, class_name)
76 _displayed = False
76 _displayed = False
77 _comm = None
77 _comm = None
78
78
79
79
80 def __init__(self, **kwargs):
80 def __init__(self, **kwargs):
81 """Public constructor
81 """Public constructor
82
82
83 Parameters
83 Parameters
84 ----------
84 ----------
85 parent : Widget instance (optional)
85 parent : Widget instance (optional)
86 Widget that this widget instance is child of. When the widget is
86 Widget that this widget instance is child of. When the widget is
87 displayed in the frontend, it's corresponding view will be made
87 displayed in the frontend, it's corresponding view will be made
88 child of the parent's view if the parent's view exists already. If
88 child of the parent's view if the parent's view exists already. If
89 the parent's view is displayed, it will automatically display this
89 the parent's view is displayed, it will automatically display this
90 widget's default view as it's child. The default view can be set
90 widget's default view as it's child. The default view can be set
91 via the default_view_name property.
91 via the default_view_name property.
92 """
92 """
93 self._children = []
93 self._children = []
94 self._add_class = [0]
94 self._add_class = [0]
95 self._remove_class = [0]
95 self._remove_class = [0]
96 self._display_callbacks = []
96 self._display_callbacks = []
97 super(Widget, self).__init__(**kwargs)
97 super(Widget, self).__init__(**kwargs)
98
98
99 # Register after init to allow default values to be specified
99 # Register after init to allow default values to be specified
100 self.on_trait_change(self._handle_property_changed, self.keys)
100 self.on_trait_change(self._handle_property_changed, self.keys)
101
101
102
102
103 def __del__(self):
103 def __del__(self):
104 """Object disposal"""
104 """Object disposal"""
105 self.close()
105 self.close()
106
106
107
107
108 def close(self):
108 def close(self):
109 """Close method. Closes the widget which closes the underlying comm.
109 """Close method. Closes the widget which closes the underlying comm.
110 When the comm is closed, all of the widget views are automatically
110 When the comm is closed, all of the widget views are automatically
111 removed from the frontend."""
111 removed from the frontend."""
112 self._comm.close()
112 self._comm.close()
113 del self._comm
113 del self._comm
114
114
115
115
116 # Properties
116 # Properties
117 def _get_keys(self):
117 def _get_keys(self):
118 keys = ['visible', '_css', '_add_class', '_remove_class']
118 keys = ['visible', '_css', '_add_class', '_remove_class']
119 keys.extend(self._keys)
119 keys.extend(self._keys)
120 return keys
120 return keys
121 keys = property(_get_keys)
121 keys = property(_get_keys)
122
122
123
123
124 # Event handlers
124 # Event handlers
125 def _handle_msg(self, msg):
125 def _handle_msg(self, msg):
126 """Called when a msg is recieved from the frontend"""
126 """Called when a msg is recieved from the frontend"""
127 # Handle backbone sync methods CREATE, PATCH, and UPDATE
127 # Handle backbone sync methods CREATE, PATCH, and UPDATE
128 sync_method = msg['content']['data']['sync_method']
128 sync_method = msg['content']['data']['sync_method']
129 sync_data = msg['content']['data']['sync_data']
129 sync_data = msg['content']['data']['sync_data']
130 self._handle_recieve_state(sync_data) # handles all methods
130 self._handle_recieve_state(sync_data) # handles all methods
131
131
132
132
133 def _handle_recieve_state(self, sync_data):
133 def _handle_recieve_state(self, sync_data):
134 """Called when a state is recieved from the frontend."""
134 """Called when a state is recieved from the frontend."""
135 self._property_lock = True
135 self._property_lock = True
136 try:
136 try:
137
137
138 # Use _keys instead of keys - Don't get retrieve the css from the client side.
138 # Use _keys instead of keys - Don't get retrieve the css from the client side.
139 for name in self._keys:
139 for name in self._keys:
140 if name in sync_data:
140 if name in sync_data:
141 setattr(self, name, sync_data[name])
141 setattr(self, name, sync_data[name])
142 finally:
142 finally:
143 self._property_lock = False
143 self._property_lock = False
144
144
145
145
146 def _handle_property_changed(self, name, old, new):
146 def _handle_property_changed(self, name, old, new):
147 """Called when a proeprty has been changed."""
147 """Called when a proeprty has been changed."""
148 if not self._property_lock and self._comm is not None:
148 if not self._property_lock and self._comm is not None:
149 # TODO: Validate properties.
149 # TODO: Validate properties.
150 # Send new state to frontend
150 # Send new state to frontend
151 self.send_state(key=name)
151 self.send_state(key=name)
152
152
153
153
154 def _handle_close(self):
154 def _handle_close(self):
155 """Called when the comm is closed by the frontend."""
155 """Called when the comm is closed by the frontend."""
156 self._comm = None
156 self._comm = None
157
157
158
158
159 # Public methods
159 # Public methods
160 def send_state(self, key=None):
160 def send_state(self, key=None):
161 """Sends the widget state, or a piece of it, to the frontend.
161 """Sends the widget state, or a piece of it, to the frontend.
162
162
163 Parameters
163 Parameters
164 ----------
164 ----------
165 key : unicode (optional)
165 key : unicode (optional)
166 A single property's name to sync with the frontend.
166 A single property's name to sync with the frontend.
167 """
167 """
168 if self._comm is not None:
168 if self._comm is not None:
169 state = {}
169 state = {}
170
170
171 # If a key is provided, just send the state of that key.
171 # If a key is provided, just send the state of that key.
172 keys = []
172 keys = []
173 if key is None:
173 if key is None:
174 keys.extend(self.keys)
174 keys.extend(self.keys)
175 else:
175 else:
176 keys.append(key)
176 keys.append(key)
177 for key in self.keys:
177 for key in self.keys:
178 try:
178 try:
179 state[key] = getattr(self, key)
179 state[key] = getattr(self, key)
180 except Exception as e:
180 except Exception as e:
181 pass # Eat errors, nom nom nom
181 pass # Eat errors, nom nom nom
182 self._comm.send({"method": "update",
182 self._comm.send({"method": "update",
183 "state": state})
183 "state": state})
184
184
185
185
186 def get_css(self, key, selector=""):
186 def get_css(self, key, selector=""):
187 """Get a CSS property of the widget. Note, this function does not
187 """Get a CSS property of the widget. Note, this function does not
188 actually request the CSS from the front-end; Only properties that have
188 actually request the CSS from the front-end; Only properties that have
189 been set with set_css can be read.
189 been set with set_css can be read.
190
190
191 Parameters
191 Parameters
192 ----------
192 ----------
193 key: unicode
193 key: unicode
194 CSS key
194 CSS key
195 selector: unicode (optional)
195 selector: unicode (optional)
196 JQuery selector used when the CSS key/value was set.
196 JQuery selector used when the CSS key/value was set.
197 """
197 """
198 if selector in self._css and key in self._css[selector]:
198 if selector in self._css and key in self._css[selector]:
199 return self._css[selector][key]
199 return self._css[selector][key]
200 else:
200 else:
201 return None
201 return None
202
202
203
203
204 def set_css(self, *args, **kwargs):
204 def set_css(self, *args, **kwargs):
205 """Set one or more CSS properties of the widget (shared among all of the
205 """Set one or more CSS properties of the widget (shared among all of the
206 views). This function has two signatures:
206 views). This function has two signatures:
207 - set_css(css_dict, [selector=''])
207 - set_css(css_dict, [selector=''])
208 - set_css(key, value, [selector=''])
208 - set_css(key, value, [selector=''])
209
209
210 Parameters
210 Parameters
211 ----------
211 ----------
212 css_dict : dict
212 css_dict : dict
213 CSS key/value pairs to apply
213 CSS key/value pairs to apply
214 key: unicode
214 key: unicode
215 CSS key
215 CSS key
216 value
216 value
217 CSS value
217 CSS value
218 selector: unicode (optional)
218 selector: unicode (optional)
219 JQuery selector to use to apply the CSS key/value.
219 JQuery selector to use to apply the CSS key/value.
220 """
220 """
221 selector = kwargs.get('selector', '')
221 selector = kwargs.get('selector', '')
222
222
223 # Signature 1: set_css(css_dict, [selector=''])
223 # Signature 1: set_css(css_dict, [selector=''])
224 if len(args) == 1:
224 if len(args) == 1:
225 if isinstance(args[0], dict):
225 if isinstance(args[0], dict):
226 for (key, value) in args[0].items():
226 for (key, value) in args[0].items():
227 self.set_css(key, value, selector=selector)
227 self.set_css(key, value, selector=selector)
228 else:
228 else:
229 raise Exception('css_dict must be a dict.')
229 raise Exception('css_dict must be a dict.')
230
230
231 # Signature 2: set_css(key, value, [selector=''])
231 # Signature 2: set_css(key, value, [selector=''])
232 elif len(args) == 2 or len(args) == 3:
232 elif len(args) == 2 or len(args) == 3:
233
233
234 # Selector can be a positional arg if it's the 3rd value
234 # Selector can be a positional arg if it's the 3rd value
235 if len(args) == 3:
235 if len(args) == 3:
236 selector = args[2]
236 selector = args[2]
237 if selector not in self._css:
237 if selector not in self._css:
238 self._css[selector] = {}
238 self._css[selector] = {}
239
239
240 # Only update the property if it has changed.
240 # Only update the property if it has changed.
241 key = args[0]
241 key = args[0]
242 value = args[1]
242 value = args[1]
243 if not (key in self._css[selector] and value in self._css[selector][key]):
243 if not (key in self._css[selector] and value in self._css[selector][key]):
244 self._css[selector][key] = value
244 self._css[selector][key] = value
245 self.send_state('_css') # Send new state to client.
245 self.send_state('_css') # Send new state to client.
246 else:
246 else:
247 raise Exception('set_css only accepts 1-3 arguments')
247 raise Exception('set_css only accepts 1-3 arguments')
248
248
249
249
250 def add_class(self, class_name, selector=""):
250 def add_class(self, class_name, selector=""):
251 """Add class[es] to a DOM element
251 """Add class[es] to a DOM element
252
252
253 Parameters
253 Parameters
254 ----------
254 ----------
255 class_name: unicode
255 class_name: unicode
256 Class name(s) to add to the DOM element(s). Multiple class names
256 Class name(s) to add to the DOM element(s). Multiple class names
257 must be space separated.
257 must be space separated.
258 selector: unicode (optional)
258 selector: unicode (optional)
259 JQuery selector to select the DOM element(s) that the class(es) will
259 JQuery selector to select the DOM element(s) that the class(es) will
260 be added to.
260 be added to.
261 """
261 """
262 self._add_class = [self._add_class[0] + 1, selector, class_name]
262 self._add_class = [self._add_class[0] + 1, selector, class_name]
263 self.send_state(key='_add_class')
263 self.send_state(key='_add_class')
264
264
265
265
266 def remove_class(self, class_name, selector=""):
266 def remove_class(self, class_name, selector=""):
267 """Remove class[es] from a DOM element
267 """Remove class[es] from a DOM element
268
268
269 Parameters
269 Parameters
270 ----------
270 ----------
271 class_name: unicode
271 class_name: unicode
272 Class name(s) to remove from the DOM element(s). Multiple class
272 Class name(s) to remove from the DOM element(s). Multiple class
273 names must be space separated.
273 names must be space separated.
274 selector: unicode (optional)
274 selector: unicode (optional)
275 JQuery selector to select the DOM element(s) that the class(es) will
275 JQuery selector to select the DOM element(s) that the class(es) will
276 be removed from.
276 be removed from.
277 """
277 """
278 self._remove_class = [self._remove_class[0] + 1, selector, class_name]
278 self._remove_class = [self._remove_class[0] + 1, selector, class_name]
279 self.send_state(key='_remove_class')
279 self.send_state(key='_remove_class')
280
280
281
281
282 def on_displayed(self, callback, remove=False):
282 def on_displayed(self, callback, remove=False):
283 """Register a callback to be called when the widget has been displayed
283 """Register a callback to be called when the widget has been displayed
284
284
285 Parameters
286 ----------
285 callback: method handler
287 callback: method handler
286 Can have a signature of:
288 Can have a signature of:
287 - callback()
289 - callback()
288 - callback(sender)
290 - callback(sender)
289 - callback(sender, view_name)
291 - callback(sender, view_name)
290 remove: bool
292 remove: bool
291 True if the callback should be unregistered."""
293 True if the callback should be unregistered."""
292 if remove:
294 if remove:
293 self._display_callbacks.remove(callback)
295 self._display_callbacks.remove(callback)
294 elif not callback in self._display_callbacks:
296 elif not callback in self._display_callbacks:
295 self._display_callbacks.append(callback)
297 self._display_callbacks.append(callback)
296
298
297
299
298 def handle_displayed(self, view_name):
300 def handle_displayed(self, view_name):
299 """Called when a view has been displayed for this widget instance
301 """Called when a view has been displayed for this widget instance
300
302
303 Parameters
304 ----------
301 view_name: unicode
305 view_name: unicode
302 Name of the view that was displayed."""
306 Name of the view that was displayed."""
303 for handler in self._display_callbacks:
307 for handler in self._display_callbacks:
304 if callable(handler):
308 if callable(handler):
305 argspec = inspect.getargspec(handler)
309 argspec = inspect.getargspec(handler)
306 nargs = len(argspec[0])
310 nargs = len(argspec[0])
307
311
308 # Bound methods have an additional 'self' argument
312 # Bound methods have an additional 'self' argument
309 if isinstance(handler, types.MethodType):
313 if isinstance(handler, types.MethodType):
310 nargs -= 1
314 nargs -= 1
311
315
312 # Call the callback
316 # Call the callback
313 if nargs == 0:
317 if nargs == 0:
314 handler()
318 handler()
315 elif nargs == 1:
319 elif nargs == 1:
316 handler(self)
320 handler(self)
317 elif nargs == 2:
321 elif nargs == 2:
318 handler(self, view_name)
322 handler(self, view_name)
319 else:
323 else:
320 raise TypeError('Widget display callback must ' \
324 raise TypeError('Widget display callback must ' \
321 'accept 0-2 arguments, not %d.' % nargs)
325 'accept 0-2 arguments, not %d.' % nargs)
322
326
323
327
324
325 # Support methods
328 # Support methods
326 def _repr_widget_(self, view_name=None):
329 def _repr_widget_(self, view_name=None):
327 """Function that is called when `IPython.display.display` is called on
330 """Function that is called when `IPython.display.display` is called on
328 the widget.
331 the widget.
329
332
330 Parameters
333 Parameters
331 ----------
334 ----------
332 view_name: unicode (optional)
335 view_name: unicode (optional)
333 View to display in the frontend. Overrides default_view_name."""
336 View to display in the frontend. Overrides default_view_name."""
334
337
335 if not view_name:
338 if not view_name:
336 view_name = self.default_view_name
339 view_name = self.default_view_name
337
340
338 # Create a comm.
341 # Create a comm.
339 if self._comm is None:
342 if self._comm is None:
340 self._comm = Comm(target_name=self.target_name)
343 self._comm = Comm(target_name=self.target_name)
341 self._comm.on_msg(self._handle_msg)
344 self._comm.on_msg(self._handle_msg)
342 self._comm.on_close(self._handle_close)
345 self._comm.on_close(self._handle_close)
343
346
344 # Make sure model is syncronized
347 # Make sure model is syncronized
345 self.send_state()
348 self.send_state()
346
349
347 # Show view.
350 # Show view.
348 if self.parent is None or self.parent._comm is None:
351 if self.parent is None or self.parent._comm is None:
349 self._comm.send({"method": "display", "view_name": view_name})
352 self._comm.send({"method": "display", "view_name": view_name})
350 else:
353 else:
351 self._comm.send({"method": "display",
354 self._comm.send({"method": "display",
352 "view_name": view_name,
355 "view_name": view_name,
353 "parent": self.parent._comm.comm_id})
356 "parent": self.parent._comm.comm_id})
354 self._displayed = True
357 self._displayed = True
355 self.handle_displayed(view_name)
358 self.handle_displayed(view_name)
356
359
357 # Now display children if any.
360 # Now display children if any.
358 for child in self._children:
361 for child in self._children:
359 if child != self:
362 if child != self:
360 child._repr_widget_()
363 child._repr_widget_()
361 return None
364 return None
General Comments 0
You need to be logged in to leave comments. Login now