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