##// END OF EJS Templates
Change serialization terminology to serialize/deserialize
Jason Grout -
Show More
@@ -1,455 +1,455 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
16
17 from IPython.core.getipython import get_ipython
17 from IPython.core.getipython import get_ipython
18 from IPython.kernel.comm import Comm
18 from IPython.kernel.comm import Comm
19 from IPython.config import LoggingConfigurable
19 from IPython.config import LoggingConfigurable
20 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, Tuple, Int
20 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, Tuple, Int
21 from IPython.utils.py3compat import string_types
21 from IPython.utils.py3compat import string_types
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Classes
24 # Classes
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 class CallbackDispatcher(LoggingConfigurable):
26 class CallbackDispatcher(LoggingConfigurable):
27 """A structure for registering and running callbacks"""
27 """A structure for registering and running callbacks"""
28 callbacks = List()
28 callbacks = List()
29
29
30 def __call__(self, *args, **kwargs):
30 def __call__(self, *args, **kwargs):
31 """Call all of the registered callbacks."""
31 """Call all of the registered callbacks."""
32 value = None
32 value = None
33 for callback in self.callbacks:
33 for callback in self.callbacks:
34 try:
34 try:
35 local_value = callback(*args, **kwargs)
35 local_value = callback(*args, **kwargs)
36 except Exception as e:
36 except Exception as e:
37 ip = get_ipython()
37 ip = get_ipython()
38 if ip is None:
38 if ip is None:
39 self.log.warn("Exception in callback %s: %s", callback, e, exc_info=True)
39 self.log.warn("Exception in callback %s: %s", callback, e, exc_info=True)
40 else:
40 else:
41 ip.showtraceback()
41 ip.showtraceback()
42 else:
42 else:
43 value = local_value if local_value is not None else value
43 value = local_value if local_value is not None else value
44 return value
44 return value
45
45
46 def register_callback(self, callback, remove=False):
46 def register_callback(self, callback, remove=False):
47 """(Un)Register a callback
47 """(Un)Register a callback
48
48
49 Parameters
49 Parameters
50 ----------
50 ----------
51 callback: method handle
51 callback: method handle
52 Method to be registered or unregistered.
52 Method to be registered or unregistered.
53 remove=False: bool
53 remove=False: bool
54 Whether to unregister the callback."""
54 Whether to unregister the callback."""
55
55
56 # (Un)Register the callback.
56 # (Un)Register the callback.
57 if remove and callback in self.callbacks:
57 if remove and callback in self.callbacks:
58 self.callbacks.remove(callback)
58 self.callbacks.remove(callback)
59 elif not remove and callback not in self.callbacks:
59 elif not remove and callback not in self.callbacks:
60 self.callbacks.append(callback)
60 self.callbacks.append(callback)
61
61
62 def _show_traceback(method):
62 def _show_traceback(method):
63 """decorator for showing tracebacks in IPython"""
63 """decorator for showing tracebacks in IPython"""
64 def m(self, *args, **kwargs):
64 def m(self, *args, **kwargs):
65 try:
65 try:
66 return(method(self, *args, **kwargs))
66 return(method(self, *args, **kwargs))
67 except Exception as e:
67 except Exception as e:
68 ip = get_ipython()
68 ip = get_ipython()
69 if ip is None:
69 if ip is None:
70 self.log.warn("Exception in widget method %s: %s", method, e, exc_info=True)
70 self.log.warn("Exception in widget method %s: %s", method, e, exc_info=True)
71 else:
71 else:
72 ip.showtraceback()
72 ip.showtraceback()
73 return m
73 return m
74
74
75 class Widget(LoggingConfigurable):
75 class Widget(LoggingConfigurable):
76 #-------------------------------------------------------------------------
76 #-------------------------------------------------------------------------
77 # Class attributes
77 # Class attributes
78 #-------------------------------------------------------------------------
78 #-------------------------------------------------------------------------
79 _widget_construction_callback = None
79 _widget_construction_callback = None
80 widgets = {}
80 widgets = {}
81
81
82 @staticmethod
82 @staticmethod
83 def on_widget_constructed(callback):
83 def on_widget_constructed(callback):
84 """Registers a callback to be called when a widget is constructed.
84 """Registers a callback to be called when a widget is constructed.
85
85
86 The callback must have the following signature:
86 The callback must have the following signature:
87 callback(widget)"""
87 callback(widget)"""
88 Widget._widget_construction_callback = callback
88 Widget._widget_construction_callback = callback
89
89
90 @staticmethod
90 @staticmethod
91 def _call_widget_constructed(widget):
91 def _call_widget_constructed(widget):
92 """Static method, called when a widget is constructed."""
92 """Static method, called when a widget is constructed."""
93 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
93 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
94 Widget._widget_construction_callback(widget)
94 Widget._widget_construction_callback(widget)
95
95
96 #-------------------------------------------------------------------------
96 #-------------------------------------------------------------------------
97 # Traits
97 # Traits
98 #-------------------------------------------------------------------------
98 #-------------------------------------------------------------------------
99 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
99 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
100 registered in the front-end to create and sync this widget with.""")
100 registered in the front-end to create and sync this widget with.""")
101 _view_name = Unicode(help="""Default view registered in the front-end
101 _view_name = Unicode(help="""Default view registered in the front-end
102 to use to represent the widget.""", sync=True)
102 to use to represent the widget.""", sync=True)
103 _comm = Instance('IPython.kernel.comm.Comm')
103 _comm = Instance('IPython.kernel.comm.Comm')
104
104
105 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
105 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
106 front-end can send before receiving an idle msg from the back-end.""")
106 front-end can send before receiving an idle msg from the back-end.""")
107
107
108 keys = List()
108 keys = List()
109 def _keys_default(self):
109 def _keys_default(self):
110 return [name for name in self.traits(sync=True)]
110 return [name for name in self.traits(sync=True)]
111
111
112 _property_lock = Tuple((None, None))
112 _property_lock = Tuple((None, None))
113
113
114 _display_callbacks = Instance(CallbackDispatcher, ())
114 _display_callbacks = Instance(CallbackDispatcher, ())
115 _msg_callbacks = Instance(CallbackDispatcher, ())
115 _msg_callbacks = Instance(CallbackDispatcher, ())
116
116
117 #-------------------------------------------------------------------------
117 #-------------------------------------------------------------------------
118 # (Con/de)structor
118 # (Con/de)structor
119 #-------------------------------------------------------------------------
119 #-------------------------------------------------------------------------
120 def __init__(self, **kwargs):
120 def __init__(self, **kwargs):
121 """Public constructor"""
121 """Public constructor"""
122 super(Widget, self).__init__(**kwargs)
122 super(Widget, self).__init__(**kwargs)
123
123
124 self.on_trait_change(self._handle_property_changed, self.keys)
124 self.on_trait_change(self._handle_property_changed, self.keys)
125 Widget._call_widget_constructed(self)
125 Widget._call_widget_constructed(self)
126
126
127 def __del__(self):
127 def __del__(self):
128 """Object disposal"""
128 """Object disposal"""
129 self.close()
129 self.close()
130
130
131 #-------------------------------------------------------------------------
131 #-------------------------------------------------------------------------
132 # Properties
132 # Properties
133 #-------------------------------------------------------------------------
133 #-------------------------------------------------------------------------
134
134
135 @property
135 @property
136 def comm(self):
136 def comm(self):
137 """Gets the Comm associated with this widget.
137 """Gets the Comm associated with this widget.
138
138
139 If a Comm doesn't exist yet, a Comm will be created automagically."""
139 If a Comm doesn't exist yet, a Comm will be created automagically."""
140 if self._comm is None:
140 if self._comm is None:
141 # Create a comm.
141 # Create a comm.
142 self._comm = Comm(target_name=self._model_name)
142 self._comm = Comm(target_name=self._model_name)
143 self._comm.on_msg(self._handle_msg)
143 self._comm.on_msg(self._handle_msg)
144 self._comm.on_close(self._close)
144 self._comm.on_close(self._close)
145 Widget.widgets[self.model_id] = self
145 Widget.widgets[self.model_id] = self
146
146
147 # first update
147 # first update
148 self.send_state()
148 self.send_state()
149 return self._comm
149 return self._comm
150
150
151 @property
151 @property
152 def model_id(self):
152 def model_id(self):
153 """Gets the model id of this widget.
153 """Gets the model id of this widget.
154
154
155 If a Comm doesn't exist yet, a Comm will be created automagically."""
155 If a Comm doesn't exist yet, a Comm will be created automagically."""
156 return self.comm.comm_id
156 return self.comm.comm_id
157
157
158 #-------------------------------------------------------------------------
158 #-------------------------------------------------------------------------
159 # Methods
159 # Methods
160 #-------------------------------------------------------------------------
160 #-------------------------------------------------------------------------
161 def _close(self):
161 def _close(self):
162 """Private close - cleanup objects, registry entries"""
162 """Private close - cleanup objects, registry entries"""
163 del Widget.widgets[self.model_id]
163 del Widget.widgets[self.model_id]
164 self._comm = None
164 self._comm = None
165
165
166 def close(self):
166 def close(self):
167 """Close method.
167 """Close method.
168
168
169 Closes the widget which closes the underlying comm.
169 Closes the widget which closes the underlying comm.
170 When the comm is closed, all of the widget views are automatically
170 When the comm is closed, all of the widget views are automatically
171 removed from the front-end."""
171 removed from the front-end."""
172 if self._comm is not None:
172 if self._comm is not None:
173 self._comm.close()
173 self._comm.close()
174 self._close()
174 self._close()
175
175
176 def send_state(self, key=None):
176 def send_state(self, key=None):
177 """Sends the widget state, or a piece of it, to the front-end.
177 """Sends the widget state, or a piece of it, to the front-end.
178
178
179 Parameters
179 Parameters
180 ----------
180 ----------
181 key : unicode (optional)
181 key : unicode (optional)
182 A single property's name to sync with the front-end.
182 A single property's name to sync with the front-end.
183 """
183 """
184 self._send({
184 self._send({
185 "method" : "update",
185 "method" : "update",
186 "state" : self.get_state()
186 "state" : self.get_state()
187 })
187 })
188
188
189 def get_state(self, key=None):
189 def get_state(self, key=None):
190 """Gets the widget state, or a piece of it.
190 """Gets the widget state, or a piece of it.
191
191
192 Parameters
192 Parameters
193 ----------
193 ----------
194 key : unicode (optional)
194 key : unicode (optional)
195 A single property's name to get.
195 A single property's name to get.
196 """
196 """
197 keys = self.keys if key is None else [key]
197 keys = self.keys if key is None else [key]
198 state = {}
198 state = {}
199 for k in keys:
199 for k in keys:
200 f = self.trait_metadata(k, 'to_json')
200 f = self.trait_metadata(k, 'serialize')
201 value = getattr(self, k)
201 value = getattr(self, k)
202 if f is not None:
202 if f is not None:
203 state[k] = f(value)
203 state[k] = f(value)
204 else:
204 else:
205 state[k] = self._serialize_trait(value)
205 state[k] = self._serialize_trait(value)
206 return state
206 return state
207
207
208 def send(self, content):
208 def send(self, content):
209 """Sends a custom msg to the widget model in the front-end.
209 """Sends a custom msg to the widget model in the front-end.
210
210
211 Parameters
211 Parameters
212 ----------
212 ----------
213 content : dict
213 content : dict
214 Content of the message to send.
214 Content of the message to send.
215 """
215 """
216 self._send({"method": "custom", "content": content})
216 self._send({"method": "custom", "content": content})
217
217
218 def on_msg(self, callback, remove=False):
218 def on_msg(self, callback, remove=False):
219 """(Un)Register a custom msg receive callback.
219 """(Un)Register a custom msg receive callback.
220
220
221 Parameters
221 Parameters
222 ----------
222 ----------
223 callback: callable
223 callback: callable
224 callback will be passed two arguments when a message arrives::
224 callback will be passed two arguments when a message arrives::
225
225
226 callback(widget, content)
226 callback(widget, content)
227
227
228 remove: bool
228 remove: bool
229 True if the callback should be unregistered."""
229 True if the callback should be unregistered."""
230 self._msg_callbacks.register_callback(callback, remove=remove)
230 self._msg_callbacks.register_callback(callback, remove=remove)
231
231
232 def on_displayed(self, callback, remove=False):
232 def on_displayed(self, callback, remove=False):
233 """(Un)Register a widget displayed callback.
233 """(Un)Register a widget displayed callback.
234
234
235 Parameters
235 Parameters
236 ----------
236 ----------
237 callback: method handler
237 callback: method handler
238 Must have a signature of::
238 Must have a signature of::
239
239
240 callback(widget, **kwargs)
240 callback(widget, **kwargs)
241
241
242 kwargs from display are passed through without modification.
242 kwargs from display are passed through without modification.
243 remove: bool
243 remove: bool
244 True if the callback should be unregistered."""
244 True if the callback should be unregistered."""
245 self._display_callbacks.register_callback(callback, remove=remove)
245 self._display_callbacks.register_callback(callback, remove=remove)
246
246
247 #-------------------------------------------------------------------------
247 #-------------------------------------------------------------------------
248 # Support methods
248 # Support methods
249 #-------------------------------------------------------------------------
249 #-------------------------------------------------------------------------
250 @contextmanager
250 @contextmanager
251 def _lock_property(self, key, value):
251 def _lock_property(self, key, value):
252 """Lock a property-value pair.
252 """Lock a property-value pair.
253
253
254 NOTE: This, in addition to the single lock for all state changes, is
254 NOTE: This, in addition to the single lock for all state changes, is
255 flawed. In the future we may want to look into buffering state changes
255 flawed. In the future we may want to look into buffering state changes
256 back to the front-end."""
256 back to the front-end."""
257 self._property_lock = (key, value)
257 self._property_lock = (key, value)
258 try:
258 try:
259 yield
259 yield
260 finally:
260 finally:
261 self._property_lock = (None, None)
261 self._property_lock = (None, None)
262
262
263 def _should_send_property(self, key, value):
263 def _should_send_property(self, key, value):
264 """Check the property lock (property_lock)"""
264 """Check the property lock (property_lock)"""
265 return key != self._property_lock[0] or \
265 return key != self._property_lock[0] or \
266 value != self._property_lock[1]
266 value != self._property_lock[1]
267
267
268 # Event handlers
268 # Event handlers
269 @_show_traceback
269 @_show_traceback
270 def _handle_msg(self, msg):
270 def _handle_msg(self, msg):
271 """Called when a msg is received from the front-end"""
271 """Called when a msg is received from the front-end"""
272 data = msg['content']['data']
272 data = msg['content']['data']
273 method = data['method']
273 method = data['method']
274 if not method in ['backbone', 'custom']:
274 if not method in ['backbone', 'custom']:
275 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
275 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
276
276
277 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
277 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
278 if method == 'backbone' and 'sync_data' in data:
278 if method == 'backbone' and 'sync_data' in data:
279 sync_data = data['sync_data']
279 sync_data = data['sync_data']
280 self._handle_receive_state(sync_data) # handles all methods
280 self._handle_receive_state(sync_data) # handles all methods
281
281
282 # Handle a custom msg from the front-end
282 # Handle a custom msg from the front-end
283 elif method == 'custom':
283 elif method == 'custom':
284 if 'content' in data:
284 if 'content' in data:
285 self._handle_custom_msg(data['content'])
285 self._handle_custom_msg(data['content'])
286
286
287 def _handle_receive_state(self, sync_data):
287 def _handle_receive_state(self, sync_data):
288 """Called when a state is received from the front-end."""
288 """Called when a state is received from the front-end."""
289 for name in self.keys:
289 for name in self.keys:
290 if name in sync_data:
290 if name in sync_data:
291 f = self.trait_metadata(name, 'from_json')
291 f = self.trait_metadata(name, 'deserialize')
292 if f is not None:
292 if f is not None:
293 value = f(sync_data[name])
293 value = f(sync_data[name])
294 else:
294 else:
295 value = self._unserialize_trait(sync_data[name])
295 value = self._deserialize_trait(sync_data[name])
296 with self._lock_property(name, value):
296 with self._lock_property(name, value):
297 setattr(self, name, value)
297 setattr(self, name, value)
298
298
299 def _handle_custom_msg(self, content):
299 def _handle_custom_msg(self, content):
300 """Called when a custom msg is received."""
300 """Called when a custom msg is received."""
301 self._msg_callbacks(self, content)
301 self._msg_callbacks(self, content)
302
302
303 def _handle_property_changed(self, name, old, new):
303 def _handle_property_changed(self, name, old, new):
304 """Called when a property has been changed."""
304 """Called when a property has been changed."""
305 # Make sure this isn't information that the front-end just sent us.
305 # Make sure this isn't information that the front-end just sent us.
306 if self._should_send_property(name, new):
306 if self._should_send_property(name, new):
307 # Send new state to front-end
307 # Send new state to front-end
308 self.send_state(key=name)
308 self.send_state(key=name)
309
309
310 def _handle_displayed(self, **kwargs):
310 def _handle_displayed(self, **kwargs):
311 """Called when a view has been displayed for this widget instance"""
311 """Called when a view has been displayed for this widget instance"""
312 self._display_callbacks(self, **kwargs)
312 self._display_callbacks(self, **kwargs)
313
313
314 def _serialize_trait(self, x):
314 def _serialize_trait(self, x):
315 """Serialize a trait value to json
315 """Serialize a trait value to json
316
316
317 Traverse lists/tuples and dicts and serialize their values as well.
317 Traverse lists/tuples and dicts and serialize their values as well.
318 Replace any widgets with their model_id
318 Replace any widgets with their model_id
319 """
319 """
320 if isinstance(x, dict):
320 if isinstance(x, dict):
321 return {k: self._serialize_trait(v) for k, v in x.items()}
321 return {k: self._serialize_trait(v) for k, v in x.items()}
322 elif isinstance(x, (list, tuple)):
322 elif isinstance(x, (list, tuple)):
323 return [self._serialize_trait(v) for v in x]
323 return [self._serialize_trait(v) for v in x]
324 elif isinstance(x, Widget):
324 elif isinstance(x, Widget):
325 return "IPY_MODEL_" + x.model_id
325 return "IPY_MODEL_" + x.model_id
326 else:
326 else:
327 return x # Value must be JSON-able
327 return x # Value must be JSON-able
328
328
329 def _unserialize_trait(self, x):
329 def _deserialize_trait(self, x):
330 """Convert json values to objects
330 """Convert json values to objects
331
331
332 We explicitly support converting valid string widget UUIDs to Widget references.
332 We explicitly support converting valid string widget UUIDs to Widget references.
333 """
333 """
334 if isinstance(x, dict):
334 if isinstance(x, dict):
335 return {k: self._unserialize_trait(v) for k, v in x.items()}
335 return {k: self._deserialize_trait(v) for k, v in x.items()}
336 elif isinstance(x, (list, tuple)):
336 elif isinstance(x, (list, tuple)):
337 return [self._unserialize_trait(v) for v in x]
337 return [self._deserialize_trait(v) for v in x]
338 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
338 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
339 # we want to support having child widgets at any level in a hierarchy
339 # we want to support having child widgets at any level in a hierarchy
340 # trusting that a widget UUID will not appear out in the wild
340 # trusting that a widget UUID will not appear out in the wild
341 return Widget.widgets[x]
341 return Widget.widgets[x]
342 else:
342 else:
343 return x
343 return x
344
344
345 def _ipython_display_(self, **kwargs):
345 def _ipython_display_(self, **kwargs):
346 """Called when `IPython.display.display` is called on the widget."""
346 """Called when `IPython.display.display` is called on the widget."""
347 # Show view. By sending a display message, the comm is opened and the
347 # Show view. By sending a display message, the comm is opened and the
348 # initial state is sent.
348 # initial state is sent.
349 self._send({"method": "display"})
349 self._send({"method": "display"})
350 self._handle_displayed(**kwargs)
350 self._handle_displayed(**kwargs)
351
351
352 def _send(self, msg):
352 def _send(self, msg):
353 """Sends a message to the model in the front-end."""
353 """Sends a message to the model in the front-end."""
354 self.comm.send(msg)
354 self.comm.send(msg)
355
355
356
356
357 class DOMWidget(Widget):
357 class DOMWidget(Widget):
358 visible = Bool(True, help="Whether the widget is visible.", sync=True)
358 visible = Bool(True, help="Whether the widget is visible.", sync=True)
359 _css = List(sync=True) # Internal CSS property list: (selector, key, value)
359 _css = List(sync=True) # Internal CSS property list: (selector, key, value)
360
360
361 def get_css(self, key, selector=""):
361 def get_css(self, key, selector=""):
362 """Get a CSS property of the widget.
362 """Get a CSS property of the widget.
363
363
364 Note: This function does not actually request the CSS from the
364 Note: This function does not actually request the CSS from the
365 front-end; Only properties that have been set with set_css can be read.
365 front-end; Only properties that have been set with set_css can be read.
366
366
367 Parameters
367 Parameters
368 ----------
368 ----------
369 key: unicode
369 key: unicode
370 CSS key
370 CSS key
371 selector: unicode (optional)
371 selector: unicode (optional)
372 JQuery selector used when the CSS key/value was set.
372 JQuery selector used when the CSS key/value was set.
373 """
373 """
374 if selector in self._css and key in self._css[selector]:
374 if selector in self._css and key in self._css[selector]:
375 return self._css[selector][key]
375 return self._css[selector][key]
376 else:
376 else:
377 return None
377 return None
378
378
379 def set_css(self, dict_or_key, value=None, selector=''):
379 def set_css(self, dict_or_key, value=None, selector=''):
380 """Set one or more CSS properties of the widget.
380 """Set one or more CSS properties of the widget.
381
381
382 This function has two signatures:
382 This function has two signatures:
383 - set_css(css_dict, selector='')
383 - set_css(css_dict, selector='')
384 - set_css(key, value, selector='')
384 - set_css(key, value, selector='')
385
385
386 Parameters
386 Parameters
387 ----------
387 ----------
388 css_dict : dict
388 css_dict : dict
389 CSS key/value pairs to apply
389 CSS key/value pairs to apply
390 key: unicode
390 key: unicode
391 CSS key
391 CSS key
392 value:
392 value:
393 CSS value
393 CSS value
394 selector: unicode (optional, kwarg only)
394 selector: unicode (optional, kwarg only)
395 JQuery selector to use to apply the CSS key/value. If no selector
395 JQuery selector to use to apply the CSS key/value. If no selector
396 is provided, an empty selector is used. An empty selector makes the
396 is provided, an empty selector is used. An empty selector makes the
397 front-end try to apply the css to a default element. The default
397 front-end try to apply the css to a default element. The default
398 element is an attribute unique to each view, which is a DOM element
398 element is an attribute unique to each view, which is a DOM element
399 of the view that should be styled with common CSS (see
399 of the view that should be styled with common CSS (see
400 `$el_to_style` in the Javascript code).
400 `$el_to_style` in the Javascript code).
401 """
401 """
402 if value is None:
402 if value is None:
403 css_dict = dict_or_key
403 css_dict = dict_or_key
404 else:
404 else:
405 css_dict = {dict_or_key: value}
405 css_dict = {dict_or_key: value}
406
406
407 for (key, value) in css_dict.items():
407 for (key, value) in css_dict.items():
408 # First remove the selector/key pair from the css list if it exists.
408 # First remove the selector/key pair from the css list if it exists.
409 # Then add the selector/key pair and new value to the bottom of the
409 # Then add the selector/key pair and new value to the bottom of the
410 # list.
410 # list.
411 self._css = [x for x in self._css if not (x[0]==selector and x[1]==key)]
411 self._css = [x for x in self._css if not (x[0]==selector and x[1]==key)]
412 self._css += [(selector, key, value)]
412 self._css += [(selector, key, value)]
413 self.send_state('_css')
413 self.send_state('_css')
414
414
415 def add_class(self, class_names, selector=""):
415 def add_class(self, class_names, selector=""):
416 """Add class[es] to a DOM element.
416 """Add class[es] to a DOM element.
417
417
418 Parameters
418 Parameters
419 ----------
419 ----------
420 class_names: unicode or list
420 class_names: unicode or list
421 Class name(s) to add to the DOM element(s).
421 Class name(s) to add to the DOM element(s).
422 selector: unicode (optional)
422 selector: unicode (optional)
423 JQuery selector to select the DOM element(s) that the class(es) will
423 JQuery selector to select the DOM element(s) that the class(es) will
424 be added to.
424 be added to.
425 """
425 """
426 class_list = class_names
426 class_list = class_names
427 if isinstance(class_list, (list, tuple)):
427 if isinstance(class_list, (list, tuple)):
428 class_list = ' '.join(class_list)
428 class_list = ' '.join(class_list)
429
429
430 self.send({
430 self.send({
431 "msg_type" : "add_class",
431 "msg_type" : "add_class",
432 "class_list" : class_list,
432 "class_list" : class_list,
433 "selector" : selector
433 "selector" : selector
434 })
434 })
435
435
436 def remove_class(self, class_names, selector=""):
436 def remove_class(self, class_names, selector=""):
437 """Remove class[es] from a DOM element.
437 """Remove class[es] from a DOM element.
438
438
439 Parameters
439 Parameters
440 ----------
440 ----------
441 class_names: unicode or list
441 class_names: unicode or list
442 Class name(s) to remove from the DOM element(s).
442 Class name(s) to remove from the DOM element(s).
443 selector: unicode (optional)
443 selector: unicode (optional)
444 JQuery selector to select the DOM element(s) that the class(es) will
444 JQuery selector to select the DOM element(s) that the class(es) will
445 be removed from.
445 be removed from.
446 """
446 """
447 class_list = class_names
447 class_list = class_names
448 if isinstance(class_list, (list, tuple)):
448 if isinstance(class_list, (list, tuple)):
449 class_list = ' '.join(class_list)
449 class_list = ' '.join(class_list)
450
450
451 self.send({
451 self.send({
452 "msg_type" : "remove_class",
452 "msg_type" : "remove_class",
453 "class_list" : class_list,
453 "class_list" : class_list,
454 "selector" : selector,
454 "selector" : selector,
455 })
455 })
General Comments 0
You need to be logged in to leave comments. Login now