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