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