##// END OF EJS Templates
s/model_name/_model_name
Jonathan Frederic -
Show More
@@ -1,419 +1,419 b''
1 """Base Widget class. Allows user to create widgets in the back-end that render
1 """Base Widget class. Allows user to create widgets in the back-end that render
2 in the IPython notebook front-end.
2 in the IPython notebook front-end.
3 """
3 """
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (c) 2013, the IPython Development Team.
5 # Copyright (c) 2013, the IPython Development Team.
6 #
6 #
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8 #
8 #
9 # The full license is in the file COPYING.txt, distributed with this software.
9 # The full license is in the file COPYING.txt, distributed with this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 from contextlib import contextmanager
15 from contextlib import contextmanager
16
16
17 from IPython.kernel.comm import Comm
17 from IPython.kernel.comm import Comm
18 from IPython.config import LoggingConfigurable
18 from IPython.config import LoggingConfigurable
19 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, Tuple
19 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, Tuple
20 from IPython.utils.py3compat import string_types
20 from IPython.utils.py3compat import string_types
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Classes
23 # Classes
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 class CallbackDispatcher(LoggingConfigurable):
25 class CallbackDispatcher(LoggingConfigurable):
26 """A structure for registering and running callbacks"""
26 """A structure for registering and running callbacks"""
27 callbacks = List()
27 callbacks = List()
28
28
29 def __call__(self, *args, **kwargs):
29 def __call__(self, *args, **kwargs):
30 """Call all of the registered callbacks."""
30 """Call all of the registered callbacks."""
31 value = None
31 value = None
32 for callback in self.callbacks:
32 for callback in self.callbacks:
33 try:
33 try:
34 local_value = callback(*args, **kwargs)
34 local_value = callback(*args, **kwargs)
35 except Exception as e:
35 except Exception as e:
36 self.log.warn("Exception in callback %s: %s", callback, e)
36 self.log.warn("Exception in callback %s: %s", callback, e)
37 else:
37 else:
38 value = local_value if local_value is not None else value
38 value = local_value if local_value is not None else value
39 return value
39 return value
40
40
41 def register_callback(self, callback, remove=False):
41 def register_callback(self, callback, remove=False):
42 """(Un)Register a callback
42 """(Un)Register a callback
43
43
44 Parameters
44 Parameters
45 ----------
45 ----------
46 callback: method handle
46 callback: method handle
47 Method to be registered or unregistered.
47 Method to be registered or unregistered.
48 remove=False: bool
48 remove=False: bool
49 Whether to unregister the callback."""
49 Whether to unregister the callback."""
50
50
51 # (Un)Register the callback.
51 # (Un)Register the callback.
52 if remove and callback in self.callbacks:
52 if remove and callback in self.callbacks:
53 self.callbacks.remove(callback)
53 self.callbacks.remove(callback)
54 elif not remove and callback not in self.callbacks:
54 elif not remove and callback not in self.callbacks:
55 self.callbacks.append(callback)
55 self.callbacks.append(callback)
56
56
57
57
58 class Widget(LoggingConfigurable):
58 class Widget(LoggingConfigurable):
59 #-------------------------------------------------------------------------
59 #-------------------------------------------------------------------------
60 # Class attributes
60 # Class attributes
61 #-------------------------------------------------------------------------
61 #-------------------------------------------------------------------------
62 _widget_construction_callback = None
62 _widget_construction_callback = None
63 widgets = {}
63 widgets = {}
64
64
65 @staticmethod
65 @staticmethod
66 def on_widget_constructed(callback):
66 def on_widget_constructed(callback):
67 """Registers a callback to be called when a widget is constructed.
67 """Registers a callback to be called when a widget is constructed.
68
68
69 The callback must have the following signature:
69 The callback must have the following signature:
70 callback(widget)"""
70 callback(widget)"""
71 Widget._widget_construction_callback = callback
71 Widget._widget_construction_callback = callback
72
72
73 @staticmethod
73 @staticmethod
74 def _call_widget_constructed(widget):
74 def _call_widget_constructed(widget):
75 """Static method, called when a widget is constructed."""
75 """Static method, called when a widget is constructed."""
76 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
76 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
77 Widget._widget_construction_callback(widget)
77 Widget._widget_construction_callback(widget)
78
78
79 #-------------------------------------------------------------------------
79 #-------------------------------------------------------------------------
80 # Traits
80 # Traits
81 #-------------------------------------------------------------------------
81 #-------------------------------------------------------------------------
82 model_name = Unicode('WidgetModel', help="""Name of the backbone model
82 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
83 registered in the front-end to create and sync this widget with.""")
83 registered in the front-end to create and sync this widget with.""")
84 _view_name = Unicode(help="""Default view registered in the front-end
84 _view_name = Unicode(help="""Default view registered in the front-end
85 to use to represent the widget.""", sync=True)
85 to use to represent the widget.""", sync=True)
86 _comm = Instance('IPython.kernel.comm.Comm')
86 _comm = Instance('IPython.kernel.comm.Comm')
87
87
88 closed = Bool(False)
88 closed = Bool(False)
89
89
90 keys = List()
90 keys = List()
91 def _keys_default(self):
91 def _keys_default(self):
92 return [name for name in self.traits(sync=True)]
92 return [name for name in self.traits(sync=True)]
93
93
94 _property_lock = Tuple((None, None))
94 _property_lock = Tuple((None, None))
95
95
96 _display_callbacks = Instance(CallbackDispatcher, ())
96 _display_callbacks = Instance(CallbackDispatcher, ())
97 _msg_callbacks = Instance(CallbackDispatcher, ())
97 _msg_callbacks = Instance(CallbackDispatcher, ())
98
98
99 #-------------------------------------------------------------------------
99 #-------------------------------------------------------------------------
100 # (Con/de)structor
100 # (Con/de)structor
101 #-------------------------------------------------------------------------
101 #-------------------------------------------------------------------------
102 def __init__(self, **kwargs):
102 def __init__(self, **kwargs):
103 """Public constructor"""
103 """Public constructor"""
104 super(Widget, self).__init__(**kwargs)
104 super(Widget, self).__init__(**kwargs)
105
105
106 self.on_trait_change(self._handle_property_changed, self.keys)
106 self.on_trait_change(self._handle_property_changed, self.keys)
107 Widget._call_widget_constructed(self)
107 Widget._call_widget_constructed(self)
108
108
109 def __del__(self):
109 def __del__(self):
110 """Object disposal"""
110 """Object disposal"""
111 self.close()
111 self.close()
112
112
113 #-------------------------------------------------------------------------
113 #-------------------------------------------------------------------------
114 # Properties
114 # Properties
115 #-------------------------------------------------------------------------
115 #-------------------------------------------------------------------------
116
116
117 @property
117 @property
118 def comm(self):
118 def comm(self):
119 """Gets the Comm associated with this widget.
119 """Gets the Comm associated with this widget.
120
120
121 If a Comm doesn't exist yet, a Comm will be created automagically."""
121 If a Comm doesn't exist yet, a Comm will be created automagically."""
122 if self._comm is None:
122 if self._comm is None:
123 # Create a comm.
123 # Create a comm.
124 self._comm = Comm(target_name=self.model_name)
124 self._comm = Comm(target_name=self._model_name)
125 self._comm.on_msg(self._handle_msg)
125 self._comm.on_msg(self._handle_msg)
126 self._comm.on_close(self._close)
126 self._comm.on_close(self._close)
127 Widget.widgets[self.model_id] = self
127 Widget.widgets[self.model_id] = self
128
128
129 # first update
129 # first update
130 self.send_state()
130 self.send_state()
131 return self._comm
131 return self._comm
132
132
133 @property
133 @property
134 def model_id(self):
134 def model_id(self):
135 """Gets the model id of this widget.
135 """Gets the model id of this widget.
136
136
137 If a Comm doesn't exist yet, a Comm will be created automagically."""
137 If a Comm doesn't exist yet, a Comm will be created automagically."""
138 return self.comm.comm_id
138 return self.comm.comm_id
139
139
140 #-------------------------------------------------------------------------
140 #-------------------------------------------------------------------------
141 # Methods
141 # Methods
142 #-------------------------------------------------------------------------
142 #-------------------------------------------------------------------------
143 def _close(self):
143 def _close(self):
144 """Private close - cleanup objects, registry entries"""
144 """Private close - cleanup objects, registry entries"""
145 del Widget.widgets[self.model_id]
145 del Widget.widgets[self.model_id]
146 self._comm = None
146 self._comm = None
147 self.closed = True
147 self.closed = True
148
148
149 def close(self):
149 def close(self):
150 """Close method.
150 """Close method.
151
151
152 Closes the widget which closes the underlying comm.
152 Closes the widget which closes the underlying comm.
153 When the comm is closed, all of the widget views are automatically
153 When the comm is closed, all of the widget views are automatically
154 removed from the front-end."""
154 removed from the front-end."""
155 if not self.closed:
155 if not self.closed:
156 self._comm.close()
156 self._comm.close()
157 self._close()
157 self._close()
158
158
159 def send_state(self, key=None):
159 def send_state(self, key=None):
160 """Sends the widget state, or a piece of it, to the front-end.
160 """Sends the widget state, or a piece of it, to the front-end.
161
161
162 Parameters
162 Parameters
163 ----------
163 ----------
164 key : unicode (optional)
164 key : unicode (optional)
165 A single property's name to sync with the front-end.
165 A single property's name to sync with the front-end.
166 """
166 """
167 self._send({
167 self._send({
168 "method" : "update",
168 "method" : "update",
169 "state" : self.get_state()
169 "state" : self.get_state()
170 })
170 })
171
171
172 def get_state(self, key=None):
172 def get_state(self, key=None):
173 """Gets the widget state, or a piece of it.
173 """Gets the widget state, or a piece of it.
174
174
175 Parameters
175 Parameters
176 ----------
176 ----------
177 key : unicode (optional)
177 key : unicode (optional)
178 A single property's name to get.
178 A single property's name to get.
179 """
179 """
180 keys = self.keys if key is None else [key]
180 keys = self.keys if key is None else [key]
181 return {k: self._pack_widgets(getattr(self, k)) for k in keys}
181 return {k: self._pack_widgets(getattr(self, k)) for k in keys}
182
182
183 def send(self, content):
183 def send(self, content):
184 """Sends a custom msg to the widget model in the front-end.
184 """Sends a custom msg to the widget model in the front-end.
185
185
186 Parameters
186 Parameters
187 ----------
187 ----------
188 content : dict
188 content : dict
189 Content of the message to send.
189 Content of the message to send.
190 """
190 """
191 self._send({"method": "custom", "content": content})
191 self._send({"method": "custom", "content": content})
192
192
193 def on_msg(self, callback, remove=False):
193 def on_msg(self, callback, remove=False):
194 """(Un)Register a custom msg receive callback.
194 """(Un)Register a custom msg receive callback.
195
195
196 Parameters
196 Parameters
197 ----------
197 ----------
198 callback: callable
198 callback: callable
199 callback will be passed two arguments when a message arrives:
199 callback will be passed two arguments when a message arrives:
200 callback(widget, content)
200 callback(widget, content)
201 remove: bool
201 remove: bool
202 True if the callback should be unregistered."""
202 True if the callback should be unregistered."""
203 self._msg_callbacks.register_callback(callback, remove=remove)
203 self._msg_callbacks.register_callback(callback, remove=remove)
204
204
205 def on_displayed(self, callback, remove=False):
205 def on_displayed(self, callback, remove=False):
206 """(Un)Register a widget displayed callback.
206 """(Un)Register a widget displayed callback.
207
207
208 Parameters
208 Parameters
209 ----------
209 ----------
210 callback: method handler
210 callback: method handler
211 Must have a signature of:
211 Must have a signature of:
212 callback(widget, **kwargs)
212 callback(widget, **kwargs)
213 kwargs from display are passed through without modification.
213 kwargs from display are passed through without modification.
214 remove: bool
214 remove: bool
215 True if the callback should be unregistered."""
215 True if the callback should be unregistered."""
216 self._display_callbacks.register_callback(callback, remove=remove)
216 self._display_callbacks.register_callback(callback, remove=remove)
217
217
218 #-------------------------------------------------------------------------
218 #-------------------------------------------------------------------------
219 # Support methods
219 # Support methods
220 #-------------------------------------------------------------------------
220 #-------------------------------------------------------------------------
221 @contextmanager
221 @contextmanager
222 def _lock_property(self, key, value):
222 def _lock_property(self, key, value):
223 """Lock a property-value pair.
223 """Lock a property-value pair.
224
224
225 NOTE: This, in addition to the single lock for all state changes, is
225 NOTE: This, in addition to the single lock for all state changes, is
226 flawed. In the future we may want to look into buffering state changes
226 flawed. In the future we may want to look into buffering state changes
227 back to the front-end."""
227 back to the front-end."""
228 self._property_lock = (key, value)
228 self._property_lock = (key, value)
229 try:
229 try:
230 yield
230 yield
231 finally:
231 finally:
232 self._property_lock = (None, None)
232 self._property_lock = (None, None)
233
233
234 def _should_send_property(self, key, value):
234 def _should_send_property(self, key, value):
235 """Check the property lock (property_lock)"""
235 """Check the property lock (property_lock)"""
236 return key != self._property_lock[0] or \
236 return key != self._property_lock[0] or \
237 value != self._property_lock[1]
237 value != self._property_lock[1]
238
238
239 # Event handlers
239 # Event handlers
240 def _handle_msg(self, msg):
240 def _handle_msg(self, msg):
241 """Called when a msg is received from the front-end"""
241 """Called when a msg is received from the front-end"""
242 data = msg['content']['data']
242 data = msg['content']['data']
243 method = data['method']
243 method = data['method']
244 if not method in ['backbone', 'custom']:
244 if not method in ['backbone', 'custom']:
245 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
245 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
246
246
247 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
247 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
248 if method == 'backbone' and 'sync_data' in data:
248 if method == 'backbone' and 'sync_data' in data:
249 sync_data = data['sync_data']
249 sync_data = data['sync_data']
250 self._handle_receive_state(sync_data) # handles all methods
250 self._handle_receive_state(sync_data) # handles all methods
251
251
252 # Handle a custom msg from the front-end
252 # Handle a custom msg from the front-end
253 elif method == 'custom':
253 elif method == 'custom':
254 if 'content' in data:
254 if 'content' in data:
255 self._handle_custom_msg(data['content'])
255 self._handle_custom_msg(data['content'])
256
256
257 def _handle_receive_state(self, sync_data):
257 def _handle_receive_state(self, sync_data):
258 """Called when a state is received from the front-end."""
258 """Called when a state is received from the front-end."""
259 for name in self.keys:
259 for name in self.keys:
260 if name in sync_data:
260 if name in sync_data:
261 value = self._unpack_widgets(sync_data[name])
261 value = self._unpack_widgets(sync_data[name])
262 with self._lock_property(name, value):
262 with self._lock_property(name, value):
263 setattr(self, name, value)
263 setattr(self, name, value)
264
264
265 def _handle_custom_msg(self, content):
265 def _handle_custom_msg(self, content):
266 """Called when a custom msg is received."""
266 """Called when a custom msg is received."""
267 self._msg_callbacks(self, content)
267 self._msg_callbacks(self, content)
268
268
269 def _handle_property_changed(self, name, old, new):
269 def _handle_property_changed(self, name, old, new):
270 """Called when a property has been changed."""
270 """Called when a property has been changed."""
271 # Make sure this isn't information that the front-end just sent us.
271 # Make sure this isn't information that the front-end just sent us.
272 if self._should_send_property(name, new):
272 if self._should_send_property(name, new):
273 # Send new state to front-end
273 # Send new state to front-end
274 self.send_state(key=name)
274 self.send_state(key=name)
275
275
276 def _handle_displayed(self, **kwargs):
276 def _handle_displayed(self, **kwargs):
277 """Called when a view has been displayed for this widget instance"""
277 """Called when a view has been displayed for this widget instance"""
278 self._display_callbacks(self, **kwargs)
278 self._display_callbacks(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. Return value must be JSON-able."""
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 # Value must be JSON-able
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 the widget is visible.", sync=True)
321 visible = Bool(True, help="Whether 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, dict_or_key, value=None, selector=''):
342 def set_css(self, dict_or_key, value=None, selector=''):
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, kwarg only)
357 selector: unicode (optional, kwarg only)
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 if not selector in self._css:
365 if not selector in self._css:
366 self._css[selector] = {}
366 self._css[selector] = {}
367 my_css = self._css[selector]
367 my_css = self._css[selector]
368
368
369 if value is None:
369 if value is None:
370 css_dict = dict_or_key
370 css_dict = dict_or_key
371 else:
371 else:
372 css_dict = {dict_or_key: value}
372 css_dict = {dict_or_key: value}
373
373
374 for (key, value) in css_dict.items():
374 for (key, value) in css_dict.items():
375 if not (key in my_css and value == my_css[key]):
375 if not (key in my_css and value == my_css[key]):
376 my_css[key] = value
376 my_css[key] = value
377 self.send_state('_css')
377 self.send_state('_css')
378
378
379 def add_class(self, class_names, selector=""):
379 def add_class(self, class_names, selector=""):
380 """Add class[es] to a DOM element.
380 """Add class[es] to a DOM element.
381
381
382 Parameters
382 Parameters
383 ----------
383 ----------
384 class_names: unicode or list
384 class_names: unicode or list
385 Class name(s) to add to the DOM element(s).
385 Class name(s) to add to the DOM element(s).
386 selector: unicode (optional)
386 selector: unicode (optional)
387 JQuery selector to select the DOM element(s) that the class(es) will
387 JQuery selector to select the DOM element(s) that the class(es) will
388 be added to.
388 be added to.
389 """
389 """
390 class_list = class_names
390 class_list = class_names
391 if isinstance(class_list, list):
391 if isinstance(class_list, list):
392 class_list = ' '.join(class_list)
392 class_list = ' '.join(class_list)
393
393
394 self.send({
394 self.send({
395 "msg_type" : "add_class",
395 "msg_type" : "add_class",
396 "class_list" : class_list,
396 "class_list" : class_list,
397 "selector" : selector
397 "selector" : selector
398 })
398 })
399
399
400 def remove_class(self, class_names, selector=""):
400 def remove_class(self, class_names, selector=""):
401 """Remove class[es] from a DOM element.
401 """Remove class[es] from a DOM element.
402
402
403 Parameters
403 Parameters
404 ----------
404 ----------
405 class_names: unicode or list
405 class_names: unicode or list
406 Class name(s) to remove from the DOM element(s).
406 Class name(s) to remove from the DOM element(s).
407 selector: unicode (optional)
407 selector: unicode (optional)
408 JQuery selector to select the DOM element(s) that the class(es) will
408 JQuery selector to select the DOM element(s) that the class(es) will
409 be removed from.
409 be removed from.
410 """
410 """
411 class_list = class_names
411 class_list = class_names
412 if isinstance(class_list, list):
412 if isinstance(class_list, list):
413 class_list = ' '.join(class_list)
413 class_list = ' '.join(class_list)
414
414
415 self.send({
415 self.send({
416 "msg_type" : "remove_class",
416 "msg_type" : "remove_class",
417 "class_list" : class_list,
417 "class_list" : class_list,
418 "selector" : selector,
418 "selector" : selector,
419 })
419 })
General Comments 0
You need to be logged in to leave comments. Login now