##// END OF EJS Templates
Use baseProjectUrl for widget import path
Jonathan Frederic -
Show More
@@ -1,316 +1,316 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("%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 super(Widget, self).__init__(**kwargs)
96 super(Widget, self).__init__(**kwargs)
97
97
98 # Register after init to allow default values to be specified
98 # Register after init to allow default values to be specified
99 self.on_trait_change(self._handle_property_changed, self.keys)
99 self.on_trait_change(self._handle_property_changed, self.keys)
100
100
101
101
102 def __del__(self):
102 def __del__(self):
103 """Object disposal"""
103 """Object disposal"""
104 self.close()
104 self.close()
105
105
106
106
107 def close(self):
107 def close(self):
108 """Close method. Closes the widget which closes the underlying comm.
108 """Close method. Closes the widget which closes the underlying comm.
109 When the comm is closed, all of the widget views are automatically
109 When the comm is closed, all of the widget views are automatically
110 removed from the frontend."""
110 removed from the frontend."""
111 self._comm.close()
111 self._comm.close()
112 del self._comm
112 del self._comm
113
113
114
114
115 # Properties
115 # Properties
116 def _get_keys(self):
116 def _get_keys(self):
117 keys = ['visible', '_css', '_add_class', '_remove_class']
117 keys = ['visible', '_css', '_add_class', '_remove_class']
118 keys.extend(self._keys)
118 keys.extend(self._keys)
119 return keys
119 return keys
120 keys = property(_get_keys)
120 keys = property(_get_keys)
121
121
122
122
123 # Event handlers
123 # Event handlers
124 def _handle_msg(self, msg):
124 def _handle_msg(self, msg):
125 """Called when a msg is recieved from the frontend"""
125 """Called when a msg is recieved from the frontend"""
126 # Handle backbone sync methods CREATE, PATCH, and UPDATE
126 # Handle backbone sync methods CREATE, PATCH, and UPDATE
127 sync_method = msg['content']['data']['sync_method']
127 sync_method = msg['content']['data']['sync_method']
128 sync_data = msg['content']['data']['sync_data']
128 sync_data = msg['content']['data']['sync_data']
129 self._handle_recieve_state(sync_data) # handles all methods
129 self._handle_recieve_state(sync_data) # handles all methods
130
130
131
131
132 def _handle_recieve_state(self, sync_data):
132 def _handle_recieve_state(self, sync_data):
133 """Called when a state is recieved from the frontend."""
133 """Called when a state is recieved from the frontend."""
134 self._property_lock = True
134 self._property_lock = True
135 try:
135 try:
136
136
137 # Use _keys instead of keys - Don't get retrieve the css from the client side.
137 # Use _keys instead of keys - Don't get retrieve the css from the client side.
138 for name in self._keys:
138 for name in self._keys:
139 if name in sync_data:
139 if name in sync_data:
140 setattr(self, name, sync_data[name])
140 setattr(self, name, sync_data[name])
141 finally:
141 finally:
142 self._property_lock = False
142 self._property_lock = False
143
143
144
144
145 def _handle_property_changed(self, name, old, new):
145 def _handle_property_changed(self, name, old, new):
146 """Called when a proeprty has been changed."""
146 """Called when a proeprty has been changed."""
147 if not self._property_lock and self._comm is not None:
147 if not self._property_lock and self._comm is not None:
148 # TODO: Validate properties.
148 # TODO: Validate properties.
149 # Send new state to frontend
149 # Send new state to frontend
150 self.send_state(key=name)
150 self.send_state(key=name)
151
151
152
152
153 def _handle_close(self):
153 def _handle_close(self):
154 """Called when the comm is closed by the frontend."""
154 """Called when the comm is closed by the frontend."""
155 self._comm = None
155 self._comm = None
156
156
157
157
158 # Public methods
158 # Public methods
159 def send_state(self, key=None):
159 def send_state(self, key=None):
160 """Sends the widget state, or a piece of it, to the frontend.
160 """Sends the widget state, or a piece of it, to the frontend.
161
161
162 Parameters
162 Parameters
163 ----------
163 ----------
164 key : unicode (optional)
164 key : unicode (optional)
165 A single property's name to sync with the frontend.
165 A single property's name to sync with the frontend.
166 """
166 """
167 if self._comm is not None:
167 if self._comm is not None:
168 state = {}
168 state = {}
169
169
170 # If a key is provided, just send the state of that key.
170 # If a key is provided, just send the state of that key.
171 keys = []
171 keys = []
172 if key is None:
172 if key is None:
173 keys.extend(self.keys)
173 keys.extend(self.keys)
174 else:
174 else:
175 keys.append(key)
175 keys.append(key)
176 for key in self.keys:
176 for key in self.keys:
177 try:
177 try:
178 state[key] = getattr(self, key)
178 state[key] = getattr(self, key)
179 except Exception as e:
179 except Exception as e:
180 pass # Eat errors, nom nom nom
180 pass # Eat errors, nom nom nom
181 self._comm.send({"method": "update",
181 self._comm.send({"method": "update",
182 "state": state})
182 "state": state})
183
183
184
184
185 def get_css(self, key, selector=""):
185 def get_css(self, key, selector=""):
186 """Get a CSS property of the widget. Note, this function does not
186 """Get a CSS property of the widget. Note, this function does not
187 actually request the CSS from the front-end; Only properties that have
187 actually request the CSS from the front-end; Only properties that have
188 been set with set_css can be read.
188 been set with set_css can be read.
189
189
190 Parameters
190 Parameters
191 ----------
191 ----------
192 key: unicode
192 key: unicode
193 CSS key
193 CSS key
194 selector: unicode (optional)
194 selector: unicode (optional)
195 JQuery selector used when the CSS key/value was set.
195 JQuery selector used when the CSS key/value was set.
196 """
196 """
197 if selector in self._css and key in self._css[selector]:
197 if selector in self._css and key in self._css[selector]:
198 return self._css[selector][key]
198 return self._css[selector][key]
199 else:
199 else:
200 return None
200 return None
201
201
202
202
203 def set_css(self, *args, **kwargs):
203 def set_css(self, *args, **kwargs):
204 """Set one or more CSS properties of the widget (shared among all of the
204 """Set one or more CSS properties of the widget (shared among all of the
205 views). This function has two signatures:
205 views). This function has two signatures:
206 - set_css(css_dict, [selector=''])
206 - set_css(css_dict, [selector=''])
207 - set_css(key, value, [selector=''])
207 - set_css(key, value, [selector=''])
208
208
209 Parameters
209 Parameters
210 ----------
210 ----------
211 css_dict : dict
211 css_dict : dict
212 CSS key/value pairs to apply
212 CSS key/value pairs to apply
213 key: unicode
213 key: unicode
214 CSS key
214 CSS key
215 value
215 value
216 CSS value
216 CSS value
217 selector: unicode (optional)
217 selector: unicode (optional)
218 JQuery selector to use to apply the CSS key/value.
218 JQuery selector to use to apply the CSS key/value.
219 """
219 """
220 selector = kwargs.get('selector', '')
220 selector = kwargs.get('selector', '')
221
221
222 # Signature 1: set_css(css_dict, [selector=''])
222 # Signature 1: set_css(css_dict, [selector=''])
223 if len(args) == 1:
223 if len(args) == 1:
224 if isinstance(args[0], dict):
224 if isinstance(args[0], dict):
225 for (key, value) in args[0].items():
225 for (key, value) in args[0].items():
226 self.set_css(key, value, selector=selector)
226 self.set_css(key, value, selector=selector)
227 else:
227 else:
228 raise Exception('css_dict must be a dict.')
228 raise Exception('css_dict must be a dict.')
229
229
230 # Signature 2: set_css(key, value, [selector=''])
230 # Signature 2: set_css(key, value, [selector=''])
231 elif len(args) == 2 or len(args) == 3:
231 elif len(args) == 2 or len(args) == 3:
232
232
233 # Selector can be a positional arg if it's the 3rd value
233 # Selector can be a positional arg if it's the 3rd value
234 if len(args) == 3:
234 if len(args) == 3:
235 selector = args[2]
235 selector = args[2]
236 if selector not in self._css:
236 if selector not in self._css:
237 self._css[selector] = {}
237 self._css[selector] = {}
238
238
239 # Only update the property if it has changed.
239 # Only update the property if it has changed.
240 key = args[0]
240 key = args[0]
241 value = args[1]
241 value = args[1]
242 if not (key in self._css[selector] and value in self._css[selector][key]):
242 if not (key in self._css[selector] and value in self._css[selector][key]):
243 self._css[selector][key] = value
243 self._css[selector][key] = value
244 self.send_state('_css') # Send new state to client.
244 self.send_state('_css') # Send new state to client.
245 else:
245 else:
246 raise Exception('set_css only accepts 1-3 arguments')
246 raise Exception('set_css only accepts 1-3 arguments')
247
247
248
248
249 def add_class(self, class_name, selector=""):
249 def add_class(self, class_name, selector=""):
250 """Add class[es] to a DOM element
250 """Add class[es] to a DOM element
251
251
252 Parameters
252 Parameters
253 ----------
253 ----------
254 class_name: unicode
254 class_name: unicode
255 Class name(s) to add to the DOM element(s). Multiple class names
255 Class name(s) to add to the DOM element(s). Multiple class names
256 must be space separated.
256 must be space separated.
257 selector: unicode (optional)
257 selector: unicode (optional)
258 JQuery selector to select the DOM element(s) that the class(es) will
258 JQuery selector to select the DOM element(s) that the class(es) will
259 be added to.
259 be added to.
260 """
260 """
261 self._add_class = [self._add_class[0] + 1, selector, class_name]
261 self._add_class = [self._add_class[0] + 1, selector, class_name]
262 self.send_state(key='_add_class')
262 self.send_state(key='_add_class')
263
263
264
264
265 def remove_class(self, class_name, selector=""):
265 def remove_class(self, class_name, selector=""):
266 """Remove class[es] from a DOM element
266 """Remove class[es] from a DOM element
267
267
268 Parameters
268 Parameters
269 ----------
269 ----------
270 class_name: unicode
270 class_name: unicode
271 Class name(s) to remove from the DOM element(s). Multiple class
271 Class name(s) to remove from the DOM element(s). Multiple class
272 names must be space separated.
272 names must be space separated.
273 selector: unicode (optional)
273 selector: unicode (optional)
274 JQuery selector to select the DOM element(s) that the class(es) will
274 JQuery selector to select the DOM element(s) that the class(es) will
275 be removed from.
275 be removed from.
276 """
276 """
277 self._remove_class = [self._remove_class[0] + 1, selector, class_name]
277 self._remove_class = [self._remove_class[0] + 1, selector, class_name]
278 self.send_state(key='_remove_class')
278 self.send_state(key='_remove_class')
279
279
280
280
281 # Support methods
281 # Support methods
282 def _repr_widget_(self, view_name=None):
282 def _repr_widget_(self, view_name=None):
283 """Function that is called when `IPython.display.display` is called on
283 """Function that is called when `IPython.display.display` is called on
284 the widget.
284 the widget.
285
285
286 Parameters
286 Parameters
287 ----------
287 ----------
288 view_name: unicode (optional)
288 view_name: unicode (optional)
289 View to display in the frontend. Overrides default_view_name."""
289 View to display in the frontend. Overrides default_view_name."""
290
290
291 if not view_name:
291 if not view_name:
292 view_name = self.default_view_name
292 view_name = self.default_view_name
293
293
294 # Create a comm.
294 # Create a comm.
295 if self._comm is None:
295 if self._comm is None:
296 self._comm = Comm(target_name=self.target_name)
296 self._comm = Comm(target_name=self.target_name)
297 self._comm.on_msg(self._handle_msg)
297 self._comm.on_msg(self._handle_msg)
298 self._comm.on_close(self._handle_close)
298 self._comm.on_close(self._handle_close)
299
299
300 # Make sure model is syncronized
300 # Make sure model is syncronized
301 self.send_state()
301 self.send_state()
302
302
303 # Show view.
303 # Show view.
304 if self.parent is None or self.parent._comm is None:
304 if self.parent is None or self.parent._comm is None:
305 self._comm.send({"method": "display", "view_name": view_name})
305 self._comm.send({"method": "display", "view_name": view_name})
306 else:
306 else:
307 self._comm.send({"method": "display",
307 self._comm.send({"method": "display",
308 "view_name": view_name,
308 "view_name": view_name,
309 "parent": self.parent._comm.comm_id})
309 "parent": self.parent._comm.comm_id})
310 self._displayed = True
310 self._displayed = True
311
311
312 # Now display children if any.
312 # Now display children if any.
313 for child in self._children:
313 for child in self._children:
314 if child != self:
314 if child != self:
315 child._repr_widget_()
315 child._repr_widget_()
316 return None
316 return None
General Comments 0
You need to be logged in to leave comments. Login now