##// END OF EJS Templates
Fixed a bug that didn't allow callbacks to set a property...
Jonathan Frederic -
Show More
@@ -1,158 +1,154 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
2 // Copyright (C) 2013 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // StringWidget
9 // StringWidget
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 **/
15 **/
16
16
17 define(["notebook/js/widget"], function(widget_manager){
17 define(["notebook/js/widget"], function(widget_manager){
18 var StringWidgetModel = IPython.WidgetModel.extend({});
18 var StringWidgetModel = IPython.WidgetModel.extend({});
19 widget_manager.register_widget_model('StringWidgetModel', StringWidgetModel);
19 widget_manager.register_widget_model('StringWidgetModel', StringWidgetModel);
20
20
21 var LabelView = IPython.WidgetView.extend({
21 var LabelView = IPython.WidgetView.extend({
22
22
23 // Called when view is rendered.
23 // Called when view is rendered.
24 render : function(){
24 render : function(){
25 this.$el = $('<div />');
25 this.$el = $('<div />');
26 this.update(); // Set defaults.
26 this.update(); // Set defaults.
27 },
27 },
28
28
29 // Handles: Backend -> Frontend Sync
29 // Handles: Backend -> Frontend Sync
30 // Frontent -> Frontend Sync
30 // Frontent -> Frontend Sync
31 update : function(){
31 update : function(){
32 this.$el.html(this.model.get('value'));
32 this.$el.html(this.model.get('value'));
33 return IPython.WidgetView.prototype.update.call(this);
33 return IPython.WidgetView.prototype.update.call(this);
34 },
34 },
35
35
36 });
36 });
37
37
38 widget_manager.register_widget_view('LabelView', LabelView);
38 widget_manager.register_widget_view('LabelView', LabelView);
39
39
40 var TextAreaView = IPython.WidgetView.extend({
40 var TextAreaView = IPython.WidgetView.extend({
41
41
42 // Called when view is rendered.
42 // Called when view is rendered.
43 render : function(){
43 render : function(){
44 this.$el
44 this.$el
45 .addClass('widget-hbox')
45 .addClass('widget-hbox')
46 .html('');
46 .html('');
47 this.$label = $('<div />')
47 this.$label = $('<div />')
48 .appendTo(this.$el)
48 .appendTo(this.$el)
49 .addClass('widget-hlabel')
49 .addClass('widget-hlabel')
50 .hide();
50 .hide();
51 this.$textbox = $('<textarea />')
51 this.$textbox = $('<textarea />')
52 .attr('rows', 5)
52 .attr('rows', 5)
53 .addClass('widget-text')
53 .addClass('widget-text')
54 .appendTo(this.$el);
54 .appendTo(this.$el);
55 this.$el_to_style = this.$textbox; // Set default element to style
55 this.$el_to_style = this.$textbox; // Set default element to style
56 this.update(); // Set defaults.
56 this.update(); // Set defaults.
57 },
57 },
58
58
59 // Handles: Backend -> Frontend Sync
59 // Handles: Backend -> Frontend Sync
60 // Frontent -> Frontend Sync
60 // Frontent -> Frontend Sync
61 update : function(){
61 update : function(){
62 if (!this.user_invoked_update) {
62 if (!this.user_invoked_update) {
63 this.$textbox.val(this.model.get('value'));
63 this.$textbox.val(this.model.get('value'));
64 }
64 }
65
65
66 var disabled = this.model.get('disabled');
66 var disabled = this.model.get('disabled');
67 this.$textbox.prop('disabled', disabled);
67 this.$textbox.prop('disabled', disabled);
68
68
69 var description = this.model.get('description');
69 var description = this.model.get('description');
70 if (description.length == 0) {
70 if (description.length == 0) {
71 this.$label.hide();
71 this.$label.hide();
72 } else {
72 } else {
73 this.$label.html(description);
73 this.$label.html(description);
74 this.$label.show();
74 this.$label.show();
75 }
75 }
76 return IPython.WidgetView.prototype.update.call(this);
76 return IPython.WidgetView.prototype.update.call(this);
77 },
77 },
78
78
79 events: {"keyup textarea" : "handleChanging",
79 events: {"keyup textarea" : "handleChanging",
80 "paste textarea" : "handleChanging",
80 "paste textarea" : "handleChanging",
81 "cut textarea" : "handleChanging"},
81 "cut textarea" : "handleChanging"},
82
82
83 // Handles and validates user input.
83 // Handles and validates user input.
84 handleChanging: function(e) {
84 handleChanging: function(e) {
85 this.user_invoked_update = true;
85 this.user_invoked_update = true;
86 this.model.set('value', e.target.value);
86 this.model.set('value', e.target.value);
87 this.model.update_other_views(this);
87 this.model.update_other_views(this);
88 this.user_invoked_update = false;
88 this.user_invoked_update = false;
89 },
89 },
90 });
90 });
91
91
92 widget_manager.register_widget_view('TextAreaView', TextAreaView);
92 widget_manager.register_widget_view('TextAreaView', TextAreaView);
93
93
94 var TextBoxView = IPython.WidgetView.extend({
94 var TextBoxView = IPython.WidgetView.extend({
95
95
96 // Called when view is rendered.
96 // Called when view is rendered.
97 render : function(){
97 render : function(){
98 this.$el
98 this.$el
99 .addClass('widget-hbox-single')
99 .addClass('widget-hbox-single')
100 .html('');
100 .html('');
101 this.$label = $('<div />')
101 this.$label = $('<div />')
102 .addClass('widget-hlabel')
102 .addClass('widget-hlabel')
103 .appendTo(this.$el)
103 .appendTo(this.$el)
104 .hide();
104 .hide();
105 this.$textbox = $('<input type="text" />')
105 this.$textbox = $('<input type="text" />')
106 .addClass('input')
106 .addClass('input')
107 .addClass('widget-text')
107 .addClass('widget-text')
108 .appendTo(this.$el);
108 .appendTo(this.$el);
109 this.$el_to_style = this.$textbox; // Set default element to style
109 this.$el_to_style = this.$textbox; // Set default element to style
110 this.update(); // Set defaults.
110 this.update(); // Set defaults.
111 },
111 },
112
112
113 // Handles: Backend -> Frontend Sync
113 // Handles: Backend -> Frontend Sync
114 // Frontent -> Frontend Sync
114 // Frontent -> Frontend Sync
115 update : function(){
115 update : function(){
116 if (!this.user_invoked_update) {
116 if (this.$textbox.val() != this.model.get('value')) {
117 this.$textbox.val(this.model.get('value'));
117 this.$textbox.val(this.model.get('value'));
118 }
118 }
119
119
120 var disabled = this.model.get('disabled');
120 var disabled = this.model.get('disabled');
121 this.$textbox.prop('disabled', disabled);
121 this.$textbox.prop('disabled', disabled);
122
122
123 var description = this.model.get('description');
123 var description = this.model.get('description');
124 if (description.length == 0) {
124 if (description.length == 0) {
125 this.$label.hide();
125 this.$label.hide();
126 } else {
126 } else {
127 this.$label.html(description);
127 this.$label.html(description);
128 this.$label.show();
128 this.$label.show();
129 }
129 }
130 return IPython.WidgetView.prototype.update.call(this);
130 return IPython.WidgetView.prototype.update.call(this);
131 },
131 },
132
132
133 events: {"keyup input" : "handleChanging",
133 events: {"keyup input" : "handleChanging",
134 "paste input" : "handleChanging",
134 "paste input" : "handleChanging",
135 "cut input" : "handleChanging",
135 "cut input" : "handleChanging",
136 "keypress input" : "handleKeypress"},
136 "keypress input" : "handleKeypress"},
137
137
138 // Handles and validates user input.
138 // Handles and validates user input.
139 handleChanging: function(e) {
139 handleChanging: function(e) {
140 this.user_invoked_update = true;
141 this.model.set('value', e.target.value);
140 this.model.set('value', e.target.value);
142 this.model.update_other_views(this);
141 this.model.update_other_views(this);
143 this.user_invoked_update = false;
144 },
142 },
145
143
146 // Handles text submition
144 // Handles text submition
147 handleKeypress: function(e) {
145 handleKeypress: function(e) {
148 if (e.keyCode == 13) { // Return key
146 if (e.keyCode == 13) { // Return key
149 this.user_invoked_update = true;
150 this.model.set('submits', this.model.get('submits') + 1);
147 this.model.set('submits', this.model.get('submits') + 1);
151 this.model.update_other_views(this);
148 this.model.update_other_views(this);
152 this.user_invoked_update = false;
153 }
149 }
154 },
150 },
155 });
151 });
156
152
157 widget_manager.register_widget_view('TextBoxView', TextBoxView);
153 widget_manager.register_widget_view('TextBoxView', TextBoxView);
158 });
154 });
@@ -1,415 +1,416 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 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32 # Classes
32 # Classes
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34 class Widget(LoggingConfigurable):
34 class Widget(LoggingConfigurable):
35
35
36 # Shared declarations
36 # Shared declarations
37 _keys = []
37 _keys = []
38
38
39 # Public declarations
39 # Public declarations
40 target_name = Unicode('widget', help="""Name of the backbone model
40 target_name = Unicode('widget', help="""Name of the backbone model
41 registered in the frontend to create and sync this widget with.""")
41 registered in the frontend to create and sync this widget with.""")
42 default_view_name = Unicode(help="""Default view registered in the frontend
42 default_view_name = Unicode(help="""Default view registered in the frontend
43 to use to represent the widget.""")
43 to use to represent the widget.""")
44 parent = Instance('IPython.html.widgets.widget.Widget')
44 parent = Instance('IPython.html.widgets.widget.Widget')
45 visible = Bool(True, help="Whether or not the widget is visible.")
45 visible = Bool(True, help="Whether or not the widget is visible.")
46
46
47 def _parent_changed(self, name, old, new):
47 def _parent_changed(self, name, old, new):
48 if self._displayed:
48 if self._displayed:
49 raise Exception('Parent cannot be set because widget has been displayed.')
49 raise Exception('Parent cannot be set because widget has been displayed.')
50 elif new == self:
50 elif new == self:
51 raise Exception('Parent cannot be set to self.')
51 raise Exception('Parent cannot be set to self.')
52 else:
52 else:
53
53
54 # Parent/child association
54 # Parent/child association
55 if new is not None and not self in new._children:
55 if new is not None and not self in new._children:
56 new._children.append(self)
56 new._children.append(self)
57 if old is not None and self in old._children:
57 if old is not None and self in old._children:
58 old._children.remove(self)
58 old._children.remove(self)
59
59
60 # Private/protected declarations
60 # Private/protected declarations
61 _property_lock = False
61 _property_lock = (None, None) # Last updated (key, value) from the front-end. Prevents echo.
62 _css = Dict() # Internal CSS property dict
62 _css = Dict() # Internal CSS property dict
63 _add_class = List() # Used to add a js class to a DOM element (call#, selector, class_name)
63 _add_class = List() # Used to add a js class to a DOM element (call#, selector, class_name)
64 _remove_class = List() # Used to remove a js class from a DOM element (call#, selector, class_name)
64 _remove_class = List() # Used to remove a js class from a DOM element (call#, selector, class_name)
65 _displayed = False
65 _displayed = False
66 _comm = None
66 _comm = None
67
67
68
68
69 def __init__(self, **kwargs):
69 def __init__(self, **kwargs):
70 """Public constructor
70 """Public constructor
71
71
72 Parameters
72 Parameters
73 ----------
73 ----------
74 parent : Widget instance (optional)
74 parent : Widget instance (optional)
75 Widget that this widget instance is child of. When the widget is
75 Widget that this widget instance is child of. When the widget is
76 displayed in the frontend, it's corresponding view will be made
76 displayed in the frontend, it's corresponding view will be made
77 child of the parent's view if the parent's view exists already. If
77 child of the parent's view if the parent's view exists already. If
78 the parent's view is displayed, it will automatically display this
78 the parent's view is displayed, it will automatically display this
79 widget's default view as it's child. The default view can be set
79 widget's default view as it's child. The default view can be set
80 via the default_view_name property.
80 via the default_view_name property.
81 """
81 """
82 self._children = []
82 self._children = []
83 self._add_class = [0]
83 self._add_class = [0]
84 self._remove_class = [0]
84 self._remove_class = [0]
85 self._display_callbacks = []
85 self._display_callbacks = []
86 self._msg_callbacks = []
86 self._msg_callbacks = []
87 super(Widget, self).__init__(**kwargs)
87 super(Widget, self).__init__(**kwargs)
88
88
89 # Register after init to allow default values to be specified
89 # Register after init to allow default values to be specified
90 self.on_trait_change(self._handle_property_changed, self.keys)
90 self.on_trait_change(self._handle_property_changed, self.keys)
91
91
92
92
93 def __del__(self):
93 def __del__(self):
94 """Object disposal"""
94 """Object disposal"""
95 self.close()
95 self.close()
96
96
97
97
98 def close(self):
98 def close(self):
99 """Close method. Closes the widget which closes the underlying comm.
99 """Close method. Closes the widget which closes the underlying comm.
100 When the comm is closed, all of the widget views are automatically
100 When the comm is closed, all of the widget views are automatically
101 removed from the frontend."""
101 removed from the frontend."""
102 try:
102 try:
103 self._comm.close()
103 self._comm.close()
104 del self._comm
104 del self._comm
105 except:
105 except:
106 pass # Comm doesn't exist and/or is already closed.
106 pass # Comm doesn't exist and/or is already closed.
107
107
108
108
109 # Properties
109 # Properties
110 def _get_keys(self):
110 def _get_keys(self):
111 keys = ['visible', '_css', '_add_class', '_remove_class']
111 keys = ['visible', '_css', '_add_class', '_remove_class']
112 keys.extend(self._keys)
112 keys.extend(self._keys)
113 return keys
113 return keys
114 keys = property(_get_keys)
114 keys = property(_get_keys)
115
115
116
116
117 # Event handlers
117 # Event handlers
118 def _handle_msg(self, msg):
118 def _handle_msg(self, msg):
119 """Called when a msg is recieved from the frontend"""
119 """Called when a msg is recieved from the frontend"""
120 data = msg['content']['data']
120 data = msg['content']['data']
121
121
122 # Handle backbone sync methods CREATE, PATCH, and UPDATE
122 # Handle backbone sync methods CREATE, PATCH, and UPDATE
123 if 'sync_method' in data and 'sync_data' in data:
123 if 'sync_method' in data and 'sync_data' in data:
124 sync_method = data['sync_method']
124 sync_method = data['sync_method']
125 sync_data = data['sync_data']
125 sync_data = data['sync_data']
126 self._handle_recieve_state(sync_data) # handles all methods
126 self._handle_recieve_state(sync_data) # handles all methods
127
127
128 # Handle a custom msg from the front-end
128 # Handle a custom msg from the front-end
129 if 'custom_content' in data:
129 if 'custom_content' in data:
130 self._handle_custom_msg(data['custom_content'])
130 self._handle_custom_msg(data['custom_content'])
131
131
132
132
133 def _handle_custom_msg(self, content):
133 def _handle_custom_msg(self, content):
134 """Called when a custom msg is recieved."""
134 """Called when a custom msg is recieved."""
135 for handler in self._msg_callbacks:
135 for handler in self._msg_callbacks:
136 if callable(handler):
136 if callable(handler):
137 argspec = inspect.getargspec(handler)
137 argspec = inspect.getargspec(handler)
138 nargs = len(argspec[0])
138 nargs = len(argspec[0])
139
139
140 # Bound methods have an additional 'self' argument
140 # Bound methods have an additional 'self' argument
141 if isinstance(handler, types.MethodType):
141 if isinstance(handler, types.MethodType):
142 nargs -= 1
142 nargs -= 1
143
143
144 # Call the callback
144 # Call the callback
145 if nargs == 1:
145 if nargs == 1:
146 handler(content)
146 handler(content)
147 elif nargs == 2:
147 elif nargs == 2:
148 handler(self, content)
148 handler(self, content)
149 else:
149 else:
150 raise TypeError('Widget msg callback must ' \
150 raise TypeError('Widget msg callback must ' \
151 'accept 1 or 2 arguments, not %d.' % nargs)
151 'accept 1 or 2 arguments, not %d.' % nargs)
152
152
153
153
154 def _handle_recieve_state(self, sync_data):
154 def _handle_recieve_state(self, sync_data):
155 """Called when a state is recieved from the frontend."""
155 """Called when a state is recieved from the frontend."""
156 self._property_lock = True
156 # Use _keys instead of keys - Don't get retrieve the css from the client side.
157 try:
157 for name in self._keys:
158
158 if name in sync_data:
159 # Use _keys instead of keys - Don't get retrieve the css from the client side.
159 try:
160 for name in self._keys:
160 self._property_lock = (name, sync_data[name])
161 if name in sync_data:
162 setattr(self, name, sync_data[name])
161 setattr(self, name, sync_data[name])
163 finally:
162 finally:
164 self._property_lock = False
163 self._property_lock = (None, None)
165
164
166
165
167 def _handle_property_changed(self, name, old, new):
166 def _handle_property_changed(self, name, old, new):
168 """Called when a proeprty has been changed."""
167 """Called when a proeprty has been changed."""
169 if not self._property_lock and self._comm is not None:
168 # Make sure this isn't information that the front-end just sent us.
169 if self._property_lock[0] != name and self._property_lock[1] != new \
170 and self._comm is not None:
170 # TODO: Validate properties.
171 # TODO: Validate properties.
171 # Send new state to frontend
172 # Send new state to frontend
172 self.send_state(key=name)
173 self.send_state(key=name)
173
174
174
175
175 def _handle_close(self):
176 def _handle_close(self):
176 """Called when the comm is closed by the frontend."""
177 """Called when the comm is closed by the frontend."""
177 self._comm = None
178 self._comm = None
178
179
179
180
180 def _handle_displayed(self, view_name):
181 def _handle_displayed(self, view_name):
181 """Called when a view has been displayed for this widget instance
182 """Called when a view has been displayed for this widget instance
182
183
183 Parameters
184 Parameters
184 ----------
185 ----------
185 view_name: unicode
186 view_name: unicode
186 Name of the view that was displayed."""
187 Name of the view that was displayed."""
187 for handler in self._display_callbacks:
188 for handler in self._display_callbacks:
188 if callable(handler):
189 if callable(handler):
189 argspec = inspect.getargspec(handler)
190 argspec = inspect.getargspec(handler)
190 nargs = len(argspec[0])
191 nargs = len(argspec[0])
191
192
192 # Bound methods have an additional 'self' argument
193 # Bound methods have an additional 'self' argument
193 if isinstance(handler, types.MethodType):
194 if isinstance(handler, types.MethodType):
194 nargs -= 1
195 nargs -= 1
195
196
196 # Call the callback
197 # Call the callback
197 if nargs == 0:
198 if nargs == 0:
198 handler()
199 handler()
199 elif nargs == 1:
200 elif nargs == 1:
200 handler(self)
201 handler(self)
201 elif nargs == 2:
202 elif nargs == 2:
202 handler(self, view_name)
203 handler(self, view_name)
203 else:
204 else:
204 raise TypeError('Widget display callback must ' \
205 raise TypeError('Widget display callback must ' \
205 'accept 0-2 arguments, not %d.' % nargs)
206 'accept 0-2 arguments, not %d.' % nargs)
206
207
207
208
208 # Public methods
209 # Public methods
209 def send_state(self, key=None):
210 def send_state(self, key=None):
210 """Sends the widget state, or a piece of it, to the frontend.
211 """Sends the widget state, or a piece of it, to the frontend.
211
212
212 Parameters
213 Parameters
213 ----------
214 ----------
214 key : unicode (optional)
215 key : unicode (optional)
215 A single property's name to sync with the frontend.
216 A single property's name to sync with the frontend.
216 """
217 """
217 if self._comm is not None:
218 if self._comm is not None:
218 state = {}
219 state = {}
219
220
220 # If a key is provided, just send the state of that key.
221 # If a key is provided, just send the state of that key.
221 keys = []
222 keys = []
222 if key is None:
223 if key is None:
223 keys.extend(self.keys)
224 keys.extend(self.keys)
224 else:
225 else:
225 keys.append(key)
226 keys.append(key)
226 for key in self.keys:
227 for key in self.keys:
227 try:
228 try:
228 state[key] = getattr(self, key)
229 state[key] = getattr(self, key)
229 except Exception as e:
230 except Exception as e:
230 pass # Eat errors, nom nom nom
231 pass # Eat errors, nom nom nom
231 self._comm.send({"method": "update",
232 self._comm.send({"method": "update",
232 "state": state})
233 "state": state})
233
234
234
235
235 def get_css(self, key, selector=""):
236 def get_css(self, key, selector=""):
236 """Get a CSS property of the widget. Note, this function does not
237 """Get a CSS property of the widget. Note, this function does not
237 actually request the CSS from the front-end; Only properties that have
238 actually request the CSS from the front-end; Only properties that have
238 been set with set_css can be read.
239 been set with set_css can be read.
239
240
240 Parameters
241 Parameters
241 ----------
242 ----------
242 key: unicode
243 key: unicode
243 CSS key
244 CSS key
244 selector: unicode (optional)
245 selector: unicode (optional)
245 JQuery selector used when the CSS key/value was set.
246 JQuery selector used when the CSS key/value was set.
246 """
247 """
247 if selector in self._css and key in self._css[selector]:
248 if selector in self._css and key in self._css[selector]:
248 return self._css[selector][key]
249 return self._css[selector][key]
249 else:
250 else:
250 return None
251 return None
251
252
252
253
253 def set_css(self, *args, **kwargs):
254 def set_css(self, *args, **kwargs):
254 """Set one or more CSS properties of the widget (shared among all of the
255 """Set one or more CSS properties of the widget (shared among all of the
255 views). This function has two signatures:
256 views). This function has two signatures:
256 - set_css(css_dict, [selector=''])
257 - set_css(css_dict, [selector=''])
257 - set_css(key, value, [selector=''])
258 - set_css(key, value, [selector=''])
258
259
259 Parameters
260 Parameters
260 ----------
261 ----------
261 css_dict : dict
262 css_dict : dict
262 CSS key/value pairs to apply
263 CSS key/value pairs to apply
263 key: unicode
264 key: unicode
264 CSS key
265 CSS key
265 value
266 value
266 CSS value
267 CSS value
267 selector: unicode (optional)
268 selector: unicode (optional)
268 JQuery selector to use to apply the CSS key/value.
269 JQuery selector to use to apply the CSS key/value.
269 """
270 """
270 selector = kwargs.get('selector', '')
271 selector = kwargs.get('selector', '')
271
272
272 # Signature 1: set_css(css_dict, [selector=''])
273 # Signature 1: set_css(css_dict, [selector=''])
273 if len(args) == 1:
274 if len(args) == 1:
274 if isinstance(args[0], dict):
275 if isinstance(args[0], dict):
275 for (key, value) in args[0].items():
276 for (key, value) in args[0].items():
276 self.set_css(key, value, selector=selector)
277 self.set_css(key, value, selector=selector)
277 else:
278 else:
278 raise Exception('css_dict must be a dict.')
279 raise Exception('css_dict must be a dict.')
279
280
280 # Signature 2: set_css(key, value, [selector=''])
281 # Signature 2: set_css(key, value, [selector=''])
281 elif len(args) == 2 or len(args) == 3:
282 elif len(args) == 2 or len(args) == 3:
282
283
283 # Selector can be a positional arg if it's the 3rd value
284 # Selector can be a positional arg if it's the 3rd value
284 if len(args) == 3:
285 if len(args) == 3:
285 selector = args[2]
286 selector = args[2]
286 if selector not in self._css:
287 if selector not in self._css:
287 self._css[selector] = {}
288 self._css[selector] = {}
288
289
289 # Only update the property if it has changed.
290 # Only update the property if it has changed.
290 key = args[0]
291 key = args[0]
291 value = args[1]
292 value = args[1]
292 if not (key in self._css[selector] and value in self._css[selector][key]):
293 if not (key in self._css[selector] and value in self._css[selector][key]):
293 self._css[selector][key] = value
294 self._css[selector][key] = value
294 self.send_state('_css') # Send new state to client.
295 self.send_state('_css') # Send new state to client.
295 else:
296 else:
296 raise Exception('set_css only accepts 1-3 arguments')
297 raise Exception('set_css only accepts 1-3 arguments')
297
298
298
299
299 def add_class(self, class_name, selector=""):
300 def add_class(self, class_name, selector=""):
300 """Add class[es] to a DOM element
301 """Add class[es] to a DOM element
301
302
302 Parameters
303 Parameters
303 ----------
304 ----------
304 class_name: unicode
305 class_name: unicode
305 Class name(s) to add to the DOM element(s). Multiple class names
306 Class name(s) to add to the DOM element(s). Multiple class names
306 must be space separated.
307 must be space separated.
307 selector: unicode (optional)
308 selector: unicode (optional)
308 JQuery selector to select the DOM element(s) that the class(es) will
309 JQuery selector to select the DOM element(s) that the class(es) will
309 be added to.
310 be added to.
310 """
311 """
311 self._add_class = [self._add_class[0] + 1, selector, class_name]
312 self._add_class = [self._add_class[0] + 1, selector, class_name]
312 self.send_state(key='_add_class')
313 self.send_state(key='_add_class')
313
314
314
315
315 def remove_class(self, class_name, selector=""):
316 def remove_class(self, class_name, selector=""):
316 """Remove class[es] from a DOM element
317 """Remove class[es] from a DOM element
317
318
318 Parameters
319 Parameters
319 ----------
320 ----------
320 class_name: unicode
321 class_name: unicode
321 Class name(s) to remove from the DOM element(s). Multiple class
322 Class name(s) to remove from the DOM element(s). Multiple class
322 names must be space separated.
323 names must be space separated.
323 selector: unicode (optional)
324 selector: unicode (optional)
324 JQuery selector to select the DOM element(s) that the class(es) will
325 JQuery selector to select the DOM element(s) that the class(es) will
325 be removed from.
326 be removed from.
326 """
327 """
327 self._remove_class = [self._remove_class[0] + 1, selector, class_name]
328 self._remove_class = [self._remove_class[0] + 1, selector, class_name]
328 self.send_state(key='_remove_class')
329 self.send_state(key='_remove_class')
329
330
330
331
331 def send(self, content):
332 def send(self, content):
332 """Sends a custom msg to the widget model in the front-end.
333 """Sends a custom msg to the widget model in the front-end.
333
334
334 Parameters
335 Parameters
335 ----------
336 ----------
336 content : dict
337 content : dict
337 Content of the message to send.
338 Content of the message to send.
338 """
339 """
339 if self._comm is not None:
340 if self._comm is not None:
340 self._comm.send({"method": "custom",
341 self._comm.send({"method": "custom",
341 "custom_content": content})
342 "custom_content": content})
342
343
343
344
344 def on_msg(self, callback, remove=False):
345 def on_msg(self, callback, remove=False):
345 """Register a callback for when a custom msg is recieved from the front-end
346 """Register a callback for when a custom msg is recieved from the front-end
346
347
347 Parameters
348 Parameters
348 ----------
349 ----------
349 callback: method handler
350 callback: method handler
350 Can have a signature of:
351 Can have a signature of:
351 - callback(content)
352 - callback(content)
352 - callback(sender, content)
353 - callback(sender, content)
353 remove: bool
354 remove: bool
354 True if the callback should be unregistered."""
355 True if the callback should be unregistered."""
355 if remove and callback in self._msg_callbacks:
356 if remove and callback in self._msg_callbacks:
356 self._msg_callbacks.remove(callback)
357 self._msg_callbacks.remove(callback)
357 elif not remove and not callback in self._msg_callbacks:
358 elif not remove and not callback in self._msg_callbacks:
358 self._msg_callbacks.append(callback)
359 self._msg_callbacks.append(callback)
359
360
360
361
361 def on_displayed(self, callback, remove=False):
362 def on_displayed(self, callback, remove=False):
362 """Register a callback to be called when the widget has been displayed
363 """Register a callback to be called when the widget has been displayed
363
364
364 Parameters
365 Parameters
365 ----------
366 ----------
366 callback: method handler
367 callback: method handler
367 Can have a signature of:
368 Can have a signature of:
368 - callback()
369 - callback()
369 - callback(sender)
370 - callback(sender)
370 - callback(sender, view_name)
371 - callback(sender, view_name)
371 remove: bool
372 remove: bool
372 True if the callback should be unregistered."""
373 True if the callback should be unregistered."""
373 if remove and callback in self._display_callbacks:
374 if remove and callback in self._display_callbacks:
374 self._display_callbacks.remove(callback)
375 self._display_callbacks.remove(callback)
375 elif not remove and not callback in self._display_callbacks:
376 elif not remove and not callback in self._display_callbacks:
376 self._display_callbacks.append(callback)
377 self._display_callbacks.append(callback)
377
378
378
379
379 # Support methods
380 # Support methods
380 def _repr_widget_(self, view_name=None):
381 def _repr_widget_(self, view_name=None):
381 """Function that is called when `IPython.display.display` is called on
382 """Function that is called when `IPython.display.display` is called on
382 the widget.
383 the widget.
383
384
384 Parameters
385 Parameters
385 ----------
386 ----------
386 view_name: unicode (optional)
387 view_name: unicode (optional)
387 View to display in the frontend. Overrides default_view_name."""
388 View to display in the frontend. Overrides default_view_name."""
388
389
389 if not view_name:
390 if not view_name:
390 view_name = self.default_view_name
391 view_name = self.default_view_name
391
392
392 # Create a comm.
393 # Create a comm.
393 if self._comm is None:
394 if self._comm is None:
394 self._comm = Comm(target_name=self.target_name)
395 self._comm = Comm(target_name=self.target_name)
395 self._comm.on_msg(self._handle_msg)
396 self._comm.on_msg(self._handle_msg)
396 self._comm.on_close(self._handle_close)
397 self._comm.on_close(self._handle_close)
397
398
398 # Make sure model is syncronized
399 # Make sure model is syncronized
399 self.send_state()
400 self.send_state()
400
401
401 # Show view.
402 # Show view.
402 if self.parent is None or self.parent._comm is None:
403 if self.parent is None or self.parent._comm is None:
403 self._comm.send({"method": "display", "view_name": view_name})
404 self._comm.send({"method": "display", "view_name": view_name})
404 else:
405 else:
405 self._comm.send({"method": "display",
406 self._comm.send({"method": "display",
406 "view_name": view_name,
407 "view_name": view_name,
407 "parent": self.parent._comm.comm_id})
408 "parent": self.parent._comm.comm_id})
408 self._displayed = True
409 self._displayed = True
409 self._handle_displayed(view_name)
410 self._handle_displayed(view_name)
410
411
411 # Now display children if any.
412 # Now display children if any.
412 for child in self._children:
413 for child in self._children:
413 if child != self:
414 if child != self:
414 child._repr_widget_()
415 child._repr_widget_()
415 return None
416 return None
General Comments 0
You need to be logged in to leave comments. Login now