##// END OF EJS Templates
Use relative paths for widget js jquery imports
Jonathan Frederic -
Show More
@@ -1,291 +1,291 b''
1 1 """Base Widget class. Allows user to create widgets in the backend that render
2 2 in the IPython notebook frontend.
3 3 """
4 4 #-----------------------------------------------------------------------------
5 5 # Copyright (c) 2013, the IPython Development Team.
6 6 #
7 7 # Distributed under the terms of the Modified BSD License.
8 8 #
9 9 # The full license is in the file COPYING.txt, distributed with this software.
10 10 #-----------------------------------------------------------------------------
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Imports
14 14 #-----------------------------------------------------------------------------
15 15 from copy import copy
16 16 from glob import glob
17 17 import uuid
18 18 import sys
19 19 import os
20 20
21 21 import IPython
22 22 from IPython.kernel.comm import Comm
23 23 from IPython.config import LoggingConfigurable
24 24 from IPython.utils.traitlets import Unicode, Dict, List, Instance, Bool
25 25 from IPython.display import Javascript, display
26 26 from IPython.utils.py3compat import string_types
27 27
28 28 #-----------------------------------------------------------------------------
29 29 # Shared
30 30 #-----------------------------------------------------------------------------
31 31 def init_widget_js():
32 32 path = os.path.split(os.path.abspath( __file__ ))[0]
33 33 for filepath in glob(os.path.join(path, "*.py")):
34 34 filename = os.path.split(filepath)[1]
35 35 name = filename.rsplit('.', 1)[0]
36 36 if not (name == 'widget' or name == '__init__') and name.startswith('widget_'):
37 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 39 display(Javascript(data='$.getScript("%s");' % js_path), exclude="text/plain")
40 40
41 41
42 42 #-----------------------------------------------------------------------------
43 43 # Classes
44 44 #-----------------------------------------------------------------------------
45 45 class Widget(LoggingConfigurable):
46 46
47 47 # Shared declarations
48 48 _keys = []
49 49
50 50 # Public declarations
51 51 target_name = Unicode('widget', help="""Name of the backbone model
52 52 registered in the frontend to create and sync this widget with.""")
53 53 default_view_name = Unicode(help="""Default view registered in the frontend
54 54 to use to represent the widget.""")
55 55 parent = Instance('IPython.html.widgets.widget.Widget')
56 56 visible = Bool(True, help="Whether or not the widget is visible.")
57 57
58 58 def _parent_changed(self, name, old, new):
59 59 if self._displayed:
60 60 raise Exception('Parent cannot be set because widget has been displayed.')
61 61 elif new == self:
62 62 raise Exception('Parent cannot be set to self.')
63 63 else:
64 64
65 65 # Parent/child association
66 66 if new is not None and not self in new._children:
67 67 new._children.append(self)
68 68 if old is not None and self in old._children:
69 69 old._children.remove(self)
70 70
71 71 # Private/protected declarations
72 72 _property_lock = False
73 73 _css = Dict() # Internal CSS property dict
74 74 _add_class = List() # Used to add a js class to a DOM element (call#, selector, class_name)
75 75 _remove_class = List() # Used to remove a js class from a DOM element (call#, selector, class_name)
76 76 _displayed = False
77 77 _comm = None
78 78
79 79
80 80 def __init__(self, **kwargs):
81 81 """Public constructor
82 82
83 83 Parameters
84 84 ----------
85 85 parent : Widget instance (optional)
86 86 Widget that this widget instance is child of. When the widget is
87 87 displayed in the frontend, it's corresponding view will be made
88 88 child of the parent's view if the parent's view exists already. If
89 89 the parent's view is displayed, it will automatically display this
90 90 widget's default view as it's child. The default view can be set
91 91 via the default_view_name property.
92 92 """
93 93 self._children = []
94 94 self._add_class = [0]
95 95 self._remove_class = [0]
96 96 super(Widget, self).__init__(**kwargs)
97 97
98 98 # Register after init to allow default values to be specified
99 99 self.on_trait_change(self._handle_property_changed, self.keys)
100 100
101 101
102 102 def __del__(self):
103 103 """Object disposal"""
104 104 self.close()
105 105
106 106
107 107 def close(self):
108 108 """Close method. Closes the widget which closes the underlying comm.
109 109 When the comm is closed, all of the widget views are automatically
110 110 removed from the frontend."""
111 111 self._comm.close()
112 112 del self._comm
113 113
114 114
115 115 # Properties
116 116 def _get_keys(self):
117 117 keys = ['visible', '_css', '_add_class', '_remove_class']
118 118 keys.extend(self._keys)
119 119 return keys
120 120 keys = property(_get_keys)
121 121
122 122
123 123 # Event handlers
124 124 def _handle_msg(self, msg):
125 125 """Called when a msg is recieved from the frontend"""
126 126 # Handle backbone sync methods CREATE, PATCH, and UPDATE
127 127 sync_method = msg['content']['data']['sync_method']
128 128 sync_data = msg['content']['data']['sync_data']
129 129 self._handle_recieve_state(sync_data) # handles all methods
130 130
131 131
132 132 def _handle_recieve_state(self, sync_data):
133 133 """Called when a state is recieved from the frontend."""
134 134 self._property_lock = True
135 135 try:
136 136
137 137 # Use _keys instead of keys - Don't get retrieve the css from the client side.
138 138 for name in self._keys:
139 139 if name in sync_data:
140 140 setattr(self, name, sync_data[name])
141 141 finally:
142 142 self._property_lock = False
143 143
144 144
145 145 def _handle_property_changed(self, name, old, new):
146 146 """Called when a proeprty has been changed."""
147 147 if not self._property_lock and self._comm is not None:
148 148 # TODO: Validate properties.
149 149 # Send new state to frontend
150 150 self.send_state(key=name)
151 151
152 152
153 153 def _handle_close(self):
154 154 """Called when the comm is closed by the frontend."""
155 155 self._comm = None
156 156
157 157
158 158 # Public methods
159 159 def send_state(self, key=None):
160 160 """Sends the widget state, or a piece of it, to the frontend.
161 161
162 162 Parameters
163 163 ----------
164 164 key : unicode (optional)
165 165 A single property's name to sync with the frontend.
166 166 """
167 167 if self._comm is not None:
168 168 state = {}
169 169
170 170 # If a key is provided, just send the state of that key.
171 171 keys = []
172 172 if key is None:
173 173 keys.extend(self.keys)
174 174 else:
175 175 keys.append(key)
176 176 for key in self.keys:
177 177 try:
178 178 state[key] = getattr(self, key)
179 179 except Exception as e:
180 180 pass # Eat errors, nom nom nom
181 181 self._comm.send({"method": "update",
182 182 "state": state})
183 183
184 184
185 185 def get_css(self, key, selector=""):
186 186 """Get a CSS property of the widget views (shared among all of the
187 187 views)
188 188
189 189 Parameters
190 190 ----------
191 191 key: unicode
192 192 CSS key
193 193 selector: unicode (optional)
194 194 JQuery selector used when the CSS key/value was set.
195 195 """
196 196 if selector in self._css and key in self._css[selector]:
197 197 return self._css[selector][key]
198 198 else:
199 199 return None
200 200
201 201
202 202 def set_css(self, key, value, selector=""):
203 203 """Set a CSS property of the widget views (shared among all of the
204 204 views)
205 205
206 206 Parameters
207 207 ----------
208 208 key: unicode
209 209 CSS key
210 210 value
211 211 CSS value
212 212 selector: unicode (optional)
213 213 JQuery selector to use to apply the CSS key/value.
214 214 """
215 215 if selector not in self._css:
216 216 self._css[selector] = {}
217 217
218 218 # Only update the property if it has changed.
219 219 if not (key in self._css[selector] and value in self._css[selector][key]):
220 220 self._css[selector][key] = value
221 221 self.send_state('_css') # Send new state to client.
222 222
223 223
224 224 def add_class(self, class_name, selector=""):
225 225 """Add class[es] to a DOM element
226 226
227 227 Parameters
228 228 ----------
229 229 class_name: unicode
230 230 Class name(s) to add to the DOM element(s). Multiple class names
231 231 must be space separated.
232 232 selector: unicode (optional)
233 233 JQuery selector to select the DOM element(s) that the class(es) will
234 234 be added to.
235 235 """
236 236 self._add_class = [self._add_class[0] + 1, selector, class_name]
237 237 self.send_state(key='_add_class')
238 238
239 239
240 240 def remove_class(self, class_name, selector=""):
241 241 """Remove class[es] from a DOM element
242 242
243 243 Parameters
244 244 ----------
245 245 class_name: unicode
246 246 Class name(s) to remove from the DOM element(s). Multiple class
247 247 names must be space separated.
248 248 selector: unicode (optional)
249 249 JQuery selector to select the DOM element(s) that the class(es) will
250 250 be removed from.
251 251 """
252 252 self._remove_class = [self._remove_class[0] + 1, selector, class_name]
253 253 self.send_state(key='_remove_class')
254 254
255 255
256 256 # Support methods
257 257 def _repr_widget_(self, view_name=None):
258 258 """Function that is called when `IPython.display.display` is called on
259 259 the widget.
260 260
261 261 Parameters
262 262 ----------
263 263 view_name: unicode (optional)
264 264 View to display in the frontend. Overrides default_view_name."""
265 265
266 266 if not view_name:
267 267 view_name = self.default_view_name
268 268
269 269 # Create a comm.
270 270 if self._comm is None:
271 271 self._comm = Comm(target_name=self.target_name)
272 272 self._comm.on_msg(self._handle_msg)
273 273 self._comm.on_close(self._handle_close)
274 274
275 275 # Make sure model is syncronized
276 276 self.send_state()
277 277
278 278 # Show view.
279 279 if self.parent is None or self.parent._comm is None:
280 280 self._comm.send({"method": "display", "view_name": view_name})
281 281 else:
282 282 self._comm.send({"method": "display",
283 283 "view_name": view_name,
284 284 "parent": self.parent._comm.comm_id})
285 285 self._displayed = True
286 286
287 287 # Now display children if any.
288 288 for child in self._children:
289 289 if child != self:
290 290 child._repr_widget_()
291 291 return None
General Comments 0
You need to be logged in to leave comments. Login now