##// END OF EJS Templates
Separate the display from the models on the python side, creating a BaseWidget class....
Jason Grout -
Show More
@@ -1,441 +1,445 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 import inspect
20 import inspect
21 import types
21 import types
22
22
23 import IPython
23 import IPython
24 from IPython.kernel.comm import Comm
24 from IPython.kernel.comm import Comm
25 from IPython.config import LoggingConfigurable
25 from IPython.config import LoggingConfigurable
26 from IPython.utils.traitlets import Unicode, Dict, List, Instance, Bool
26 from IPython.utils.traitlets import Unicode, Dict, List, Instance, Bool
27 from IPython.display import Javascript, display
27 from IPython.display import Javascript, display
28 from IPython.utils.py3compat import string_types
28 from IPython.utils.py3compat import string_types
29
29
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 # Classes
31 # Classes
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 class Widget(LoggingConfigurable):
33
34 class BaseWidget(LoggingConfigurable):
34
35
35 # Shared declarations (Class level)
36 # Shared declarations (Class level)
36 _keys = []
37 _keys = List(Unicode, help="List of keys comprising the state of the model.")
38 _children_attr = List(Unicode, help="List of keys of children objects of the model.")
39 _children_lists_attr = List(Unicode, help="List of keys containing lists of children objects of the model.")
37 widget_construction_callback = None
40 widget_construction_callback = None
38
41
39 def on_widget_constructed(callback):
42 def on_widget_constructed(callback):
40 """Class method, registers a callback to be called when a widget is
43 """Class method, registers a callback to be called when a widget is
41 constructed. The callback must have the following signature:
44 constructed. The callback must have the following signature:
42 callback(widget)"""
45 callback(widget)"""
43 Widget.widget_construction_callback = callback
46 Widget.widget_construction_callback = callback
44
47
45 def _handle_widget_constructed(widget):
48 def _handle_widget_constructed(widget):
46 """Class method, called when a widget is constructed."""
49 """Class method, called when a widget is constructed."""
47 if Widget.widget_construction_callback is not None and callable(Widget.widget_construction_callback):
50 if Widget.widget_construction_callback is not None and callable(Widget.widget_construction_callback):
48 Widget.widget_construction_callback(widget)
51 Widget.widget_construction_callback(widget)
49
52
50
53
51
54
52 # Public declarations (Instance level)
55 # Public declarations (Instance level)
53 target_name = Unicode('widget', help="""Name of the backbone model
56 target_name = Unicode('widget', help="""Name of the backbone model
54 registered in the frontend to create and sync this widget with.""")
57 registered in the frontend to create and sync this widget with.""")
55 default_view_name = Unicode(help="""Default view registered in the frontend
58 default_view_name = Unicode(help="""Default view registered in the frontend
56 to use to represent the widget.""")
59 to use to represent the widget.""")
57 parent = Instance('IPython.html.widgets.widget.Widget')
58 visible = Bool(True, help="Whether or not the widget is visible.")
59
60 def _parent_changed(self, name, old, new):
61 if self._displayed:
62 raise Exception('Parent cannot be set because widget has been displayed.')
63 elif new == self:
64 raise Exception('Parent cannot be set to self.')
65 else:
66
67 # Parent/child association
68 if new is not None and not self in new._children:
69 new._children.append(self)
70 if old is not None and self in old._children:
71 old._children.remove(self)
72
60
73 # Private/protected declarations
61 # Private/protected declarations
74 _property_lock = (None, None) # Last updated (key, value) from the front-end. Prevents echo.
62 _property_lock = (None, None) # Last updated (key, value) from the front-end. Prevents echo.
75 _css = Dict() # Internal CSS property dict
76 _displayed = False
63 _displayed = False
77
64 _comm = None
78
65
79 def __init__(self, **kwargs):
66 def __init__(self, **kwargs):
80 """Public constructor
67 """Public constructor
81
82 Parameters
83 ----------
84 parent : Widget instance (optional)
85 Widget that this widget instance is child of. When the widget is
86 displayed in the frontend, it's corresponding view will be made
87 child of the parent's view if the parent's view exists already. If
88 the parent's view is displayed, it will automatically display this
89 widget's default view as it's child. The default view can be set
90 via the default_view_name property.
91 """
68 """
92 self._children = []
93 self._display_callbacks = []
69 self._display_callbacks = []
94 self._msg_callbacks = []
70 self._msg_callbacks = []
95 super(Widget, self).__init__(**kwargs)
71 super(BaseWidget, self).__init__(**kwargs)
96
72
97 # Register after init to allow default values to be specified
73 # Register after init to allow default values to be specified
98 self.on_trait_change(self._handle_property_changed, self.keys)
74 # TODO: register three different handlers, one for each list, and abstract out the common parts
99
75 self.on_trait_change(self._handle_property_changed, self.keys+self._children_attr+self._children_lists_attr)
100 Widget._handle_widget_constructed(self)
76 Widget._handle_widget_constructed(self)
101
77
102
103 def __del__(self):
78 def __del__(self):
104 """Object disposal"""
79 """Object disposal"""
105 self.close()
80 self.close()
106
81
107
82
108 def close(self):
83 def close(self):
109 """Close method. Closes the widget which closes the underlying comm.
84 """Close method. Closes the widget which closes the underlying comm.
110 When the comm is closed, all of the widget views are automatically
85 When the comm is closed, all of the widget views are automatically
111 removed from the frontend."""
86 removed from the frontend."""
112 self._close_communication()
87 self._close_communication()
113
88
114
89
115 # Properties
90 # Properties
116 def _get_keys(self):
91 @property
117 keys = ['visible', '_css']
92 def keys(self):
93 keys = ['_children_attr', '_children_lists_attr']
118 keys.extend(self._keys)
94 keys.extend(self._keys)
119 return keys
95 return keys
120 keys = property(_get_keys)
121
96
97 @property
98 def comm(self):
99 if self._comm is None:
100 self._open_communication()
101 return self._comm
122
102
123 # Event handlers
103 # Event handlers
124 def _handle_msg(self, msg):
104 def _handle_msg(self, msg):
125 """Called when a msg is recieved from the frontend"""
105 """Called when a msg is recieved from the frontend"""
126 data = msg['content']['data']
106 data = msg['content']['data']
127 method = data['method']
107 method = data['method']
128
108
129 # Handle backbone sync methods CREATE, PATCH, and UPDATE
109 # Handle backbone sync methods CREATE, PATCH, and UPDATE
130 if method == 'backbone':
110 if method == 'backbone':
131 if 'sync_method' in data and 'sync_data' in data:
111 if 'sync_method' in data and 'sync_data' in data:
132 sync_method = data['sync_method']
112 sync_method = data['sync_method']
133 sync_data = data['sync_data']
113 sync_data = data['sync_data']
134 self._handle_recieve_state(sync_data) # handles all methods
114 self._handle_recieve_state(sync_data) # handles all methods
135
115
136 # Handle a custom msg from the front-end
116 # Handle a custom msg from the front-end
137 elif method == 'custom':
117 elif method == 'custom':
138 if 'custom_content' in data:
118 if 'custom_content' in data:
139 self._handle_custom_msg(data['custom_content'])
119 self._handle_custom_msg(data['custom_content'])
140
120
141
121
142 def _handle_custom_msg(self, content):
122 def _handle_custom_msg(self, content):
143 """Called when a custom msg is recieved."""
123 """Called when a custom msg is recieved."""
144 for handler in self._msg_callbacks:
124 for handler in self._msg_callbacks:
145 if callable(handler):
125 if callable(handler):
146 argspec = inspect.getargspec(handler)
126 argspec = inspect.getargspec(handler)
147 nargs = len(argspec[0])
127 nargs = len(argspec[0])
148
128
149 # Bound methods have an additional 'self' argument
129 # Bound methods have an additional 'self' argument
150 if isinstance(handler, types.MethodType):
130 if isinstance(handler, types.MethodType):
151 nargs -= 1
131 nargs -= 1
152
132
153 # Call the callback
133 # Call the callback
154 if nargs == 1:
134 if nargs == 1:
155 handler(content)
135 handler(content)
156 elif nargs == 2:
136 elif nargs == 2:
157 handler(self, content)
137 handler(self, content)
158 else:
138 else:
159 raise TypeError('Widget msg callback must ' \
139 raise TypeError('Widget msg callback must ' \
160 'accept 1 or 2 arguments, not %d.' % nargs)
140 'accept 1 or 2 arguments, not %d.' % nargs)
161
141
162
142
163 def _handle_recieve_state(self, sync_data):
143 def _handle_recieve_state(self, sync_data):
164 """Called when a state is recieved from the frontend."""
144 """Called when a state is recieved from the frontend."""
165 # Use _keys instead of keys - Don't get retrieve the css from the client side.
145 # Use _keys instead of keys - Don't get retrieve the css from the client side.
166 for name in self._keys:
146 for name in self._keys:
167 if name in sync_data:
147 if name in sync_data:
168 try:
148 try:
169 self._property_lock = (name, sync_data[name])
149 self._property_lock = (name, sync_data[name])
170 setattr(self, name, sync_data[name])
150 setattr(self, name, sync_data[name])
171 finally:
151 finally:
172 self._property_lock = (None, None)
152 self._property_lock = (None, None)
173
153
174
154
175 def _handle_property_changed(self, name, old, new):
155 def _handle_property_changed(self, name, old, new):
176 """Called when a proeprty has been changed."""
156 """Called when a proeprty has been changed."""
177 # Make sure this isn't information that the front-end just sent us.
157 # Make sure this isn't information that the front-end just sent us.
178 if self._property_lock[0] != name and self._property_lock[1] != new:
158 if self._property_lock[0] != name and self._property_lock[1] != new:
179 # Send new state to frontend
159 # Send new state to frontend
180 self.send_state(key=name)
160 self.send_state(key=name)
181
161
182
183 def _handle_displayed(self, **kwargs):
162 def _handle_displayed(self, **kwargs):
184 """Called when a view has been displayed for this widget instance
163 """Called when a view has been displayed for this widget instance
185
164
186 Parameters
165 Parameters
187 ----------
166 ----------
188 [view_name]: unicode (optional kwarg)
167 [view_name]: unicode (optional kwarg)
189 Name of the view that was displayed."""
168 Name of the view that was displayed."""
190 for handler in self._display_callbacks:
169 for handler in self._display_callbacks:
191 if callable(handler):
170 if callable(handler):
192 argspec = inspect.getargspec(handler)
171 argspec = inspect.getargspec(handler)
193 nargs = len(argspec[0])
172 nargs = len(argspec[0])
194
173
195 # Bound methods have an additional 'self' argument
174 # Bound methods have an additional 'self' argument
196 if isinstance(handler, types.MethodType):
175 if isinstance(handler, types.MethodType):
197 nargs -= 1
176 nargs -= 1
198
177
199 # Call the callback
178 # Call the callback
200 if nargs == 0:
179 if nargs == 0:
201 handler()
180 handler()
202 elif nargs == 1:
181 elif nargs == 1:
203 handler(self)
182 handler(self)
204 elif nargs == 2:
183 elif nargs == 2:
205 handler(self, kwargs.get('view_name', None))
184 handler(self, kwargs.get('view_name', None))
206 else:
185 else:
207 handler(self, **kwargs)
186 handler(self, **kwargs)
208
187
209
210 # Public methods
188 # Public methods
211 def send_state(self, key=None):
189 def send_state(self, key=None):
212 """Sends the widget state, or a piece of it, to the frontend.
190 """Sends the widget state, or a piece of it, to the frontend.
213
191
214 Parameters
192 Parameters
215 ----------
193 ----------
216 key : unicode (optional)
194 key : unicode (optional)
217 A single property's name to sync with the frontend.
195 A single property's name to sync with the frontend.
218 """
196 """
219 state = {}
220
221 # If a key is provided, just send the state of that key.
222 keys = []
223 if key is None:
224 keys.extend(self.keys)
225 else:
226 keys.append(key)
227 for key in self.keys:
228 try:
229 state[key] = getattr(self, key)
230 except Exception as e:
231 pass # Eat errors, nom nom nom
232 self._send({"method": "update",
197 self._send({"method": "update",
233 "state": state})
198 "state": self.get_state()})
234
199
235
200 def get_state(self, key=None)
236 def get_css(self, key, selector=""):
201 """Gets the widget state, or a piece of it.
237 """Get a CSS property of the widget. Note, this function does not
238 actually request the CSS from the front-end; Only properties that have
239 been set with set_css can be read.
240
202
241 Parameters
203 Parameters
242 ----------
204 ----------
243 key: unicode
205 key : unicode (optional)
244 CSS key
206 A single property's name to get.
245 selector: unicode (optional)
246 JQuery selector used when the CSS key/value was set.
247 """
248 if selector in self._css and key in self._css[selector]:
249 return self._css[selector][key]
250 else:
251 return None
252
253
254 def set_css(self, *args, **kwargs):
255 """Set one or more CSS properties of the widget (shared among all of the
256 views). This function has two signatures:
257 - set_css(css_dict, [selector=''])
258 - set_css(key, value, [selector=''])
259
260 Parameters
261 ----------
262 css_dict : dict
263 CSS key/value pairs to apply
264 key: unicode
265 CSS key
266 value
267 CSS value
268 selector: unicode (optional)
269 JQuery selector to use to apply the CSS key/value.
270 """
207 """
271 selector = kwargs.get('selector', '')
208 state = {}
272
209
273 # Signature 1: set_css(css_dict, [selector=''])
210 # If a key is provided, just send the state of that key.
274 if len(args) == 1:
211 if key is None:
275 if isinstance(args[0], dict):
212 keys = self.keys[:]
276 for (key, value) in args[0].items():
213 children_attr = self._children_attr[:]
277 self.set_css(key, value, selector=selector)
214 children_lists_attr = self._children_lists_attr[:]
278 else:
215 else:
279 raise Exception('css_dict must be a dict.')
216 keys = []
280
217 children_attr = []
281 # Signature 2: set_css(key, value, [selector=''])
218 children_lists_attr = []
282 elif len(args) == 2 or len(args) == 3:
219 if key in self._children_attr:
283
220 children_attr.append(key)
284 # Selector can be a positional arg if it's the 3rd value
221 elif key in self._children_lists_attr:
285 if len(args) == 3:
222 children_lists_attr.append(key)
286 selector = args[2]
287 if selector not in self._css:
288 self._css[selector] = {}
289
290 # Only update the property if it has changed.
291 key = args[0]
292 value = args[1]
293 if not (key in self._css[selector] and value in self._css[selector][key]):
294 self._css[selector][key] = value
295 self.send_state('_css') # Send new state to client.
296 else:
223 else:
297 raise Exception('set_css only accepts 1-3 arguments')
224 keys.append(key)
298
225 for k in keys:
299
226 state[k] = getattr(self, k)
300 def add_class(self, class_name, selector=""):
227 for k in children_attr:
301 """Add class[es] to a DOM element
228 # automatically create models on the browser side if they aren't already created
302
229 state[k] = getattr(self, k).comm.comm_id
303 Parameters
230 for k in children_lists_attr:
304 ----------
231 # automatically create models on the browser side if they aren't already created
305 class_name: unicode
232 state[k] = [i.comm.comm_id for i in getattr(self, k)]
306 Class name(s) to add to the DOM element(s). Multiple class names
233 return state
307 must be space separated.
308 selector: unicode (optional)
309 JQuery selector to select the DOM element(s) that the class(es) will
310 be added to.
311 """
312 self._send({"method": "add_class",
313 "class_list": class_name,
314 "selector": selector})
315
316
317 def remove_class(self, class_name, selector=""):
318 """Remove class[es] from a DOM element
319
320 Parameters
321 ----------
322 class_name: unicode
323 Class name(s) to remove from the DOM element(s). Multiple class
324 names must be space separated.
325 selector: unicode (optional)
326 JQuery selector to select the DOM element(s) that the class(es) will
327 be removed from.
328 """
329 self._send({"method": "remove_class",
330 "class_list": class_name,
331 "selector": selector})
332
234
333
235
334 def send(self, content):
236 def send(self, content):
335 """Sends a custom msg to the widget model in the front-end.
237 """Sends a custom msg to the widget model in the front-end.
336
238
337 Parameters
239 Parameters
338 ----------
240 ----------
339 content : dict
241 content : dict
340 Content of the message to send.
242 Content of the message to send.
341 """
243 """
342 self._send({"method": "custom",
244 self._send({"method": "custom",
343 "custom_content": content})
245 "custom_content": content})
344
246
345
247
346 def on_msg(self, callback, remove=False):
248 def on_msg(self, callback, remove=False):
347 """Register a callback for when a custom msg is recieved from the front-end
249 """Register a callback for when a custom msg is recieved from the front-end
348
250
349 Parameters
251 Parameters
350 ----------
252 ----------
351 callback: method handler
253 callback: method handler
352 Can have a signature of:
254 Can have a signature of:
353 - callback(content)
255 - callback(content)
354 - callback(sender, content)
256 - callback(sender, content)
355 remove: bool
257 remove: bool
356 True if the callback should be unregistered."""
258 True if the callback should be unregistered."""
357 if remove and callback in self._msg_callbacks:
259 if remove and callback in self._msg_callbacks:
358 self._msg_callbacks.remove(callback)
260 self._msg_callbacks.remove(callback)
359 elif not remove and not callback in self._msg_callbacks:
261 elif not remove and not callback in self._msg_callbacks:
360 self._msg_callbacks.append(callback)
262 self._msg_callbacks.append(callback)
361
263
362
264
363 def on_displayed(self, callback, remove=False):
265 def on_displayed(self, callback, remove=False):
364 """Register a callback to be called when the widget has been displayed
266 """Register a callback to be called when the widget has been displayed
365
267
366 Parameters
268 Parameters
367 ----------
269 ----------
368 callback: method handler
270 callback: method handler
369 Can have a signature of:
271 Can have a signature of:
370 - callback()
272 - callback()
371 - callback(sender)
273 - callback(sender)
372 - callback(sender, view_name)
274 - callback(sender, view_name)
373 - callback(sender, **kwargs)
275 - callback(sender, **kwargs)
374 kwargs from display call passed through without modification.
276 kwargs from display call passed through without modification.
375 remove: bool
277 remove: bool
376 True if the callback should be unregistered."""
278 True if the callback should be unregistered."""
377 if remove and callback in self._display_callbacks:
279 if remove and callback in self._display_callbacks:
378 self._display_callbacks.remove(callback)
280 self._display_callbacks.remove(callback)
379 elif not remove and not callback in self._display_callbacks:
281 elif not remove and not callback in self._display_callbacks:
380 self._display_callbacks.append(callback)
282 self._display_callbacks.append(callback)
381
283
382
284
383 # Support methods
285 # Support methods
384 def _repr_widget_(self, **kwargs):
286 def _repr_widget_(self, **kwargs):
385 """Function that is called when `IPython.display.display` is called on
287 """Function that is called when `IPython.display.display` is called on
386 the widget.
288 the widget.
387
289
388 Parameters
290 Parameters
389 ----------
291 ----------
390 view_name: unicode (optional)
292 view_name: unicode (optional)
391 View to display in the frontend. Overrides default_view_name."""
293 View to display in the frontend. Overrides default_view_name."""
392 view_name = kwargs.get('view_name', self.default_view_name)
294 view_name = kwargs.get('view_name', self.default_view_name)
393
295
394 # Create a communication.
296 # Create a communication.
395 self._open_communication()
297 self._open_communication()
396
298
397 # Make sure model is syncronized
299 # Make sure model is syncronized
398 self.send_state()
300 self.send_state()
399
301
400 # Show view.
302 # Show view.
401 if self.parent is None or self.parent._comm is None:
402 self._send({"method": "display", "view_name": view_name})
303 self._send({"method": "display", "view_name": view_name})
403 else:
404 self._send({"method": "display",
405 "view_name": view_name,
406 "parent": self.parent._comm.comm_id})
407 self._handle_displayed(**kwargs)
408 self._displayed = True
304 self._displayed = True
409
305 self._handle_displayed(**kwargs)
410 # Now display children if any.
411 for child in self._children:
412 if child != self:
413 child._repr_widget_()
414 return None
415
306
416
307
417 def _open_communication(self):
308 def _open_communication(self):
418 """Opens a communication with the front-end."""
309 """Opens a communication with the front-end."""
419 # Create a comm.
310 # Create a comm.
420 if not hasattr(self, '_comm') or self._comm is None:
311 if not hasattr(self, '_comm') or self._comm is None:
421 self._comm = Comm(target_name=self.target_name)
312 self._comm = Comm(target_name=self.target_name)
422 self._comm.on_msg(self._handle_msg)
313 self._comm.on_msg(self._handle_msg)
423 self._comm.on_close(self._close_communication)
314 self._comm.on_close(self._close_communication)
424
315
425
316
426 def _close_communication(self):
317 def _close_communication(self):
427 """Closes a communication with the front-end."""
318 """Closes a communication with the front-end."""
428 if hasattr(self, '_comm') and self._comm is not None:
319 if hasattr(self, '_comm') and self._comm is not None:
429 try:
320 try:
430 self._comm.close()
321 self._comm.close()
431 finally:
322 finally:
432 self._comm = None
323 self._comm = None
433
324
434
325
435 def _send(self, msg):
326 def _send(self, msg):
436 """Sends a message to the model in the front-end"""
327 """Sends a message to the model in the front-end"""
437 if hasattr(self, '_comm') and self._comm is not None:
328 if self._comm is not None:
438 self._comm.send(msg)
329 self._comm.send(msg)
439 return True
330 return True
440 else:
331 else:
441 return False
332 return False
333
334 class Widget(BaseWidget):
335
336 _children = List(Instance('IPython.html.widgets.widget.Widget'))
337 _children_lists_attr = List(Unicode, ['_children'])
338 visible = Bool(True, help="Whether or not the widget is visible.")
339
340 # Private/protected declarations
341 _css = Dict() # Internal CSS property dict
342
343 # Properties
344 @property
345 def keys(self):
346 keys = ['visible', '_css']
347 keys.extend(super(Widget, self).keys)
348 return keys
349
350 def get_css(self, key, selector=""):
351 """Get a CSS property of the widget. Note, this function does not
352 actually request the CSS from the front-end; Only properties that have
353 been set with set_css can be read.
354
355 Parameters
356 ----------
357 key: unicode
358 CSS key
359 selector: unicode (optional)
360 JQuery selector used when the CSS key/value was set.
361 """
362 if selector in self._css and key in self._css[selector]:
363 return self._css[selector][key]
364 else:
365 return None
366
367
368 def set_css(self, *args, **kwargs):
369 """Set one or more CSS properties of the widget (shared among all of the
370 views). This function has two signatures:
371 - set_css(css_dict, [selector=''])
372 - set_css(key, value, [selector=''])
373
374 Parameters
375 ----------
376 css_dict : dict
377 CSS key/value pairs to apply
378 key: unicode
379 CSS key
380 value
381 CSS value
382 selector: unicode (optional)
383 JQuery selector to use to apply the CSS key/value.
384 """
385 selector = kwargs.get('selector', '')
386
387 # Signature 1: set_css(css_dict, [selector=''])
388 if len(args) == 1:
389 if isinstance(args[0], dict):
390 for (key, value) in args[0].items():
391 self.set_css(key, value, selector=selector)
392 else:
393 raise Exception('css_dict must be a dict.')
394
395 # Signature 2: set_css(key, value, [selector=''])
396 elif len(args) == 2 or len(args) == 3:
397
398 # Selector can be a positional arg if it's the 3rd value
399 if len(args) == 3:
400 selector = args[2]
401 if selector not in self._css:
402 self._css[selector] = {}
403
404 # Only update the property if it has changed.
405 key = args[0]
406 value = args[1]
407 if not (key in self._css[selector] and value in self._css[selector][key]):
408 self._css[selector][key] = value
409 self.send_state('_css') # Send new state to client.
410 else:
411 raise Exception('set_css only accepts 1-3 arguments')
412
413
414 def add_class(self, class_name, selector=""):
415 """Add class[es] to a DOM element
416
417 Parameters
418 ----------
419 class_name: unicode
420 Class name(s) to add to the DOM element(s). Multiple class names
421 must be space separated.
422 selector: unicode (optional)
423 JQuery selector to select the DOM element(s) that the class(es) will
424 be added to.
425 """
426 self._send({"method": "add_class",
427 "class_list": class_name,
428 "selector": selector})
429
430
431 def remove_class(self, class_name, selector=""):
432 """Remove class[es] from a DOM element
433
434 Parameters
435 ----------
436 class_name: unicode
437 Class name(s) to remove from the DOM element(s). Multiple class
438 names must be space separated.
439 selector: unicode (optional)
440 JQuery selector to select the DOM element(s) that the class(es) will
441 be removed from.
442 """
443 self._send({"method": "remove_class",
444 "class_list": class_name,
445 "selector": selector})
General Comments 0
You need to be logged in to leave comments. Login now