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