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