##// END OF EJS Templates
Actual absolute paths...
Jonathan Frederic -
Show More
@@ -1,251 +1,251 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
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))
40 40
41 41
42 42 #-----------------------------------------------------------------------------
43 43 # Classes
44 44 #-----------------------------------------------------------------------------
45 45 class Widget(LoggingConfigurable):
46 46
47 47 # Public declarations
48 48 target_name = Unicode('widget', help="""Name of the backbone model
49 49 registered in the frontend to create and sync this widget with.""")
50 50 default_view_name = Unicode(help="""Default view registered in the frontend
51 51 to use to represent the widget.""")
52 52
53 53
54 54 # Private/protected declarations
55 55 _keys = []
56 56 _property_lock = False
57 57 _parent = None
58 58 _children = []
59 59 _css = Dict()
60 60
61 61
62 62 def __init__(self, parent=None, **kwargs):
63 63 """Public constructor
64 64
65 65 Parameters
66 66 ----------
67 67 parent : Widget instance (optional)
68 68 Widget that this widget instance is child of. When the widget is
69 69 displayed in the frontend, it's corresponding view will be made
70 70 child of the parent's view if the parent's view exists already. If
71 71 the parent's view is displayed, it will automatically display this
72 72 widget's default view as it's child. The default view can be set
73 73 via the default_view_name property.
74 74 """
75 75 super(Widget, self).__init__(**kwargs)
76 76
77 77 # Parent/child association
78 78 self._children = []
79 79 if parent is not None:
80 80 parent._children.append(self)
81 81 self._parent = parent
82 82 self.comm = None
83 83
84 84 # Register after init to allow default values to be specified
85 85 self.on_trait_change(self._handle_property_changed, self.keys)
86 86
87 87
88 88 def __del__(self):
89 89 """Object disposal"""
90 90 self.close()
91 91
92 92
93 93 def close(self):
94 94 """Close method. Closes the widget which closes the underlying comm.
95 95 When the comm is closed, all of the widget views are automatically
96 96 removed from the frontend."""
97 97 self.comm.close()
98 98 del self.comm
99 99
100 100
101 101 # Properties
102 102 def _get_parent(self):
103 103 return self._parent
104 104 parent = property(_get_parent)
105 105
106 106
107 107 def _get_children(self):
108 108 return copy(self._children)
109 109 children = property(_get_children)
110 110
111 111
112 112 def _get_keys(self):
113 113 keys = ['_css']
114 114 keys.extend(self._keys)
115 115 return keys
116 116 keys = property(_get_keys)
117 117
118 118
119 119 # Event handlers
120 120 def _handle_msg(self, msg):
121 121 """Called when a msg is recieved from the frontend"""
122 122 # Handle backbone sync methods CREATE, PATCH, and UPDATE
123 123 sync_method = msg['content']['data']['sync_method']
124 124 sync_data = msg['content']['data']['sync_data']
125 125 self._handle_recieve_state(sync_data) # handles all methods
126 126
127 127
128 128 def _handle_recieve_state(self, sync_data):
129 129 """Called when a state is recieved from the frontend."""
130 130 self._property_lock = True
131 131 try:
132 132
133 133 # Use _keys instead of keys - Don't get retrieve the css from the client side.
134 134 for name in self._keys:
135 135 if name in sync_data:
136 136 setattr(self, name, sync_data[name])
137 137 finally:
138 138 self._property_lock = False
139 139
140 140
141 141 def _handle_property_changed(self, name, old, new):
142 142 """Called when a proeprty has been changed."""
143 143 if not self._property_lock and self.comm is not None:
144 144 # TODO: Validate properties.
145 145 # Send new state to frontend
146 146 self.send_state(key=name)
147 147
148 148
149 149 def _handle_close(self):
150 150 """Called when the comm is closed by the frontend."""
151 151 self.comm = None
152 152
153 153
154 154 # Public methods
155 155 def send_state(self, key=None):
156 156 """Sends the widget state, or a piece of it, to the frontend.
157 157
158 158 Parameters
159 159 ----------
160 160 key : unicode (optional)
161 161 A single property's name to sync with the frontend.
162 162 """
163 163 state = {}
164 164
165 165 # If a key is provided, just send the state of that key.
166 166 keys = []
167 167 if key is None:
168 168 keys.extend(self.keys)
169 169 else:
170 170 keys.append(key)
171 171 for key in self.keys:
172 172 try:
173 173 state[key] = getattr(self, key)
174 174 except Exception as e:
175 175 pass # Eat errors, nom nom nom
176 176 self.comm.send({"method": "update",
177 177 "state": state})
178 178
179 179
180 180 def get_css(self, key, selector=""):
181 181 """Get a CSS property of the widget views (shared among all of the
182 182 views)
183 183
184 184 Parameters
185 185 ----------
186 186 key: unicode
187 187 CSS key
188 188 selector: unicode (optional)
189 189 JQuery selector used when the CSS key/value was set.
190 190 """
191 191 if selector in self._css and key in self._css[selector]:
192 192 return self._css[selector][key]
193 193 else:
194 194 return None
195 195
196 196
197 197 def set_css(self, key, value, selector=""):
198 198 """Set a CSS property of the widget views (shared among all of the
199 199 views)
200 200
201 201 Parameters
202 202 ----------
203 203 key: unicode
204 204 CSS key
205 205 value
206 206 CSS value
207 207 selector: unicode (optional)
208 208 JQuery selector to use to apply the CSS key/value.
209 209 """
210 210 if selector not in self._css:
211 211 self._css[selector] = {}
212 212
213 213 # Only update the property if it has changed.
214 214 if not (key in self._css[selector] and value in self._css[selector][key]):
215 215 self._css[selector][key] = value
216 216 self.send_state() # Send new state to client.
217 217
218 218
219 219 # Support methods
220 220 def _repr_widget_(self, view_name=None):
221 221 """Function that is called when `IPython.display.display` is called on
222 222 the widget.
223 223
224 224 Parameters
225 225 ----------
226 226 view_name: unicode (optional)
227 227 View to display in the frontend. Overrides default_view_name."""
228 228 if not view_name:
229 229 view_name = self.default_view_name
230 230
231 231 # Create a comm.
232 232 if self.comm is None:
233 233 self.comm = Comm(target_name=self.target_name)
234 234 self.comm.on_msg(self._handle_msg)
235 235 self.comm.on_close(self._handle_close)
236 236
237 237 # Make sure model is syncronized
238 238 self.send_state()
239 239
240 240 # Show view.
241 241 if self.parent is None:
242 242 self.comm.send({"method": "display", "view_name": view_name})
243 243 else:
244 244 self.comm.send({"method": "display",
245 245 "view_name": view_name,
246 246 "parent": self.parent.comm.comm_id})
247 247
248 248 # Now display children if any.
249 249 for child in self.children:
250 250 child._repr_widget_()
251 251 return None
General Comments 0
You need to be logged in to leave comments. Login now