##// END OF EJS Templates
Many checks off the todo list, test fixes
Jonathan Frederic -
Show More
@@ -95,7 +95,7 b' function(widget_manager, underscore, backbone){'
95 var value = state[key];
95 var value = state[key];
96 this.key_value_lock = [key, value];
96 this.key_value_lock = [key, value];
97 try {
97 try {
98 this.set(key, state[key]);
98 this.set(key, this._unpack_models(value));
99 } finally {
99 } finally {
100 this.key_value_lock = null;
100 this.key_value_lock = null;
101 }
101 }
@@ -137,32 +137,26 b' function(widget_manager, underscore, backbone){'
137 // The throttle has been exceeded, buffer the current msg so
137 // The throttle has been exceeded, buffer the current msg so
138 // it can be sent once the kernel has finished processing
138 // it can be sent once the kernel has finished processing
139 // some of the existing messages.
139 // some of the existing messages.
140 if (method=='patch') {
140 if (this.msg_buffer === null) {
141 if (this.msg_buffer === null) {
142 this.msg_buffer = $.extend({}, model_json); // Copy
143 }
144 for (attr in options.attrs) {
145 var value = options.attrs[attr];
146 if (this.key_value_lock === null || attr != this.key_value_lock[0] || value != this.key_value_lock[1]) {
147 this.msg_buffer[attr] = value;
148 }
149 }
150 } else {
151 this.msg_buffer = $.extend({}, model_json); // Copy
141 this.msg_buffer = $.extend({}, model_json); // Copy
152 }
142 }
143 for (attr in options.attrs) {
144 var value = this._pack_models(options.attrs[attr]);
145 if (this.key_value_lock === null || attr != this.key_value_lock[0] || value != this.key_value_lock[1]) {
146 this.msg_buffer[attr] = value;
147 }
148 }
153
149
154 } else {
150 } else {
155 // We haven't exceeded the throttle, send the message like
151 // We haven't exceeded the throttle, send the message like
156 // normal. If this is a patch operation, just send the
152 // normal. If this is a patch operation, just send the
157 // changes.
153 // changes.
158 var send_json = model_json;
154 var send_json = model_json;
159 if (method =='patch') {
155 send_json = {};
160 send_json = {};
156 for (attr in options.attrs) {
161 for (attr in options.attrs) {
157 var value = this._pack_models(options.attrs[attr]);
162 var value = options.attrs[attr];
158 if (this.key_value_lock === null || attr != this.key_value_lock[0] || value != this.key_value_lock[1]) {
163 if (this.key_value_lock === null || attr != this.key_value_lock[0] || value != this.key_value_lock[1]) {
159 send_json[attr] = value;
164 send_json[attr] = value;
165 }
166 }
160 }
167 }
161 }
168
162
@@ -177,6 +171,37 b' function(widget_manager, underscore, backbone){'
177 return model_json;
171 return model_json;
178 },
172 },
179
173
174 _pack_models: function(value) {
175 if (value instanceof Backbone.Model) {
176 return value.id;
177 } else if (value instanceof Object) {
178 var packed = {};
179 for (var key in value) {
180 packed[key] = this._pack_models(value[key]);
181 }
182 return packed;
183 } else {
184 return value;
185 }
186 },
187
188 _unpack_models: function(value) {
189 if (value instanceof Object) {
190 var unpacked = {};
191 for (var key in value) {
192 unpacked[key] = this._unpack_models(value[key]);
193 }
194 return unpacked;
195 } else {
196 var model = this.widget_manager.get_model(value);
197 if (model !== null) {
198 return model;
199 } else {
200 return value;
201 }
202 }
203 },
204
180 });
205 });
181
206
182
207
@@ -196,24 +221,23 b' function(widget_manager, underscore, backbone){'
196 // triggered on model change
221 // triggered on model change
197 },
222 },
198
223
199 child_view: function(model_id, options) {
224 child_view: function(child_model, options) {
200 // create and return a child view, given a model id for a model and (optionally) a view name
225 // create and return a child view, given a model and (optionally) a view name
201 // if the view name is not given, it defaults to the model's default view attribute
226 // if the view name is not given, it defaults to the model's default view attribute
202 var child_model = this.widget_manager.get_model(model_id);
227 var child_view = this.model.widget_manager.create_view(child_model, options);
203 var child_view = this.widget_manager.create_view(child_model, options);
228 this.child_views[child_model.id] = child_view;
204 this.child_views[model_id] = child_view;
205 return child_view;
229 return child_view;
206 },
230 },
207
231
208 update_child_views: function(old_list, new_list) {
232 update_child_views: function(old_list, new_list) {
209 // this function takes an old list and new list of model ids
233 // this function takes an old list and new list of models
210 // views in child_views that correspond to deleted ids are deleted
234 // views in child_views that correspond to deleted ids are deleted
211 // views corresponding to added ids are added child_views
235 // views corresponding to added ids are added child_views
212
236
213 // delete old views
237 // delete old views
214 _.each(_.difference(old_list, new_list), function(element, index, list) {
238 _.each(_.difference(old_list, new_list), function(element, index, list) {
215 var view = this.child_views[element];
239 var view = this.child_views[element.id];
216 delete this.child_views[element];
240 delete this.child_views[element.id];
217 view.remove();
241 view.remove();
218 }, this);
242 }, this);
219
243
@@ -247,10 +271,10 b' function(widget_manager, underscore, backbone){'
247 _.each(_.difference(new_list, old_list), function(item, index, list) {
271 _.each(_.difference(new_list, old_list), function(item, index, list) {
248 added_callback(item);
272 added_callback(item);
249 }, this);
273 }, this);
250 }
274 },
251
275
252 callbacks: function(){
276 callbacks: function(){
253 return this.widget_manager.callbacks(this);
277 return this.model.widget_manager.callbacks(this);
254 },
278 },
255
279
256 render: function(){
280 render: function(){
@@ -271,7 +295,7 b' function(widget_manager, underscore, backbone){'
271 // TODO: make changes more granular (e.g., trigger on visible:change)
295 // TODO: make changes more granular (e.g., trigger on visible:change)
272 this.model.on('change', this.update, this);
296 this.model.on('change', this.update, this);
273 this.model.on('msg:custom', this.on_msg, this);
297 this.model.on('msg:custom', this.on_msg, this);
274 WidgetView.initialize.apply(this, arguments);
298 DOMWidgetView.__super__.initialize.apply(this, arguments);
275 },
299 },
276
300
277 on_msg: function(msg) {
301 on_msg: function(msg) {
@@ -64,7 +64,7 b' define(["notebook/js/widgets/widget"], function(widget_manager){'
64 this.$label.show();
64 this.$label.show();
65 }
65 }
66 }
66 }
67 return IPython.DOMWidgetView.update.apply(this);
67 return CheckboxView.__super__.update.apply(this);
68 },
68 },
69
69
70 });
70 });
@@ -88,13 +88,13 b' define(["notebook/js/widgets/widget"], function(widget_manager){'
88 //
88 //
89 // Called when the model is changed. The model may have been
89 // Called when the model is changed. The model may have been
90 // changed by another view or by a state update from the back-end.
90 // changed by another view or by a state update from the back-end.
91 if (options === undefined || options.updated_view != this) {
91 if (this.model.get('value')) {
92 if (this.model.get('value')) {
92 this.$el.addClass('active');
93 this.$el.addClass('active');
93 } else {
94 } else {
94 this.$el.removeClass('active');
95 this.$el.removeClass('active');
95 }
96 }
97
96
97 if (options === undefined || options.updated_view != this) {
98 var disabled = this.model.get('disabled');
98 var disabled = this.model.get('disabled');
99 this.$el.prop('disabled', disabled);
99 this.$el.prop('disabled', disabled);
100
100
@@ -105,17 +105,17 b' define(["notebook/js/widgets/widget"], function(widget_manager){'
105 this.$el.html(description);
105 this.$el.html(description);
106 }
106 }
107 }
107 }
108 return IPython.DOMWidgetView.update.apply(this);
108 return ToggleButtonView.__super__.update.apply(this);
109 },
109 },
110
110
111 events: {"click button" : "handleClick"},
111 events: {"click" : "handleClick"},
112
112
113 // Handles and validates user input.
113 // Handles and validates user input.
114 handleClick: function(e) {
114 handleClick: function(e) {
115
115
116 // Calling model.set will trigger all of the other views of the
116 // Calling model.set will trigger all of the other views of the
117 // model to update.
117 // model to update.
118 this.model.set('value', ! $(e.target).hasClass('active'), {updated_view: this});
118 this.model.set('value', ! $(this.$el).hasClass('active'), {updated_view: this});
119 this.touch();
119 this.touch();
120 },
120 },
121 });
121 });
@@ -49,7 +49,7 b' define(["notebook/js/widgets/widget"], function(widget_manager){'
49 this.$el.removeAttr('disabled');
49 this.$el.removeAttr('disabled');
50 }
50 }
51
51
52 return IPython.DOMWidgetView.update.apply(this);
52 return ButtonView.__super__.update.apply(this);
53 },
53 },
54
54
55 events: {
55 events: {
@@ -64,7 +64,7 b' define(["notebook/js/widgets/widget"], function(widget_manager) {'
64 this.$el.empty();
64 this.$el.empty();
65 this.update_child_views(old_list, new_list);
65 this.update_child_views(old_list, new_list);
66 _.each(new_list, function(element, index, list) {
66 _.each(new_list, function(element, index, list) {
67 this.$el.append(this.child_views[element].$el);
67 this.$el.append(this.child_views[element.id].$el);
68 }, this)
68 }, this)
69 },
69 },
70
70
@@ -74,7 +74,7 b' define(["notebook/js/widgets/widget"], function(widget_manager) {'
74 // Called when the model is changed. The model may have been
74 // Called when the model is changed. The model may have been
75 // changed by another view or by a state update from the back-end.
75 // changed by another view or by a state update from the back-end.
76 set_flex_properties(this, this.$el);
76 set_flex_properties(this, this.$el);
77 return IPython.DOMWidgetView.update.apply(this);
77 return ContainerView.__super__.update.apply(this);
78 },
78 },
79 });
79 });
80
80
@@ -258,7 +258,7 b' define(["notebook/js/widgets/widget"], function(widget_manager) {'
258 this.show();
258 this.show();
259 }
259 }
260
260
261 return IPython.DOMWidgetView.update.apply(this);
261 return ModalView.__super__.update.apply(this);
262 },
262 },
263
263
264 _get_selector_element: function(selector) {
264 _get_selector_element: function(selector) {
@@ -277,7 +277,7 b' define(["notebook/js/widgets/widget"], function(widget_manager) {'
277 return this.$window.find(selector.substring(6));
277 return this.$window.find(selector.substring(6));
278 }
278 }
279 } else {
279 } else {
280 return IPython.DOMWidgetView._get_selector_element.apply(this, [selector]);
280 return ModalView.__super__._get_selector_element.apply(this, [selector]);
281 }
281 }
282 },
282 },
283
283
@@ -107,7 +107,7 b' define(["notebook/js/widgets/widget"], function(widget_manager){'
107 this.$label.show();
107 this.$label.show();
108 }
108 }
109 }
109 }
110 return IPython.DOMWidgetView.update.apply(this);
110 return FloatSliderView.__super__.update.apply(this);
111 },
111 },
112
112
113 // Handles: User input
113 // Handles: User input
@@ -168,7 +168,7 b' define(["notebook/js/widgets/widget"], function(widget_manager){'
168 this.$label.show();
168 this.$label.show();
169 }
169 }
170 }
170 }
171 return IPython.DOMWidgetView.update.apply(this);
171 return FloatTextView.__super__.update.apply(this);
172 },
172 },
173
173
174
174
@@ -260,7 +260,7 b' define(["notebook/js/widgets/widget"], function(widget_manager){'
260 this.$label.html(description);
260 this.$label.html(description);
261 this.$label.show();
261 this.$label.show();
262 }
262 }
263 return IPython.DOMWidgetView.update.apply(this);
263 return ProgressView.__super__.update.apply(this);
264 },
264 },
265
265
266 });
266 });
@@ -47,7 +47,7 b' define(["notebook/js/widgets/widget"], function(widget_manager){'
47 } else {
47 } else {
48 this.$el.removeAttr('height');
48 this.$el.removeAttr('height');
49 }
49 }
50 return IPython.DOMWidgetView.update.apply(this);
50 return ImageView.__super__.update.apply(this);
51 },
51 },
52
52
53 });
53 });
@@ -106,7 +106,7 b' define(["notebook/js/widgets/widget"], function(widget_manager){'
106 this.$label.show();
106 this.$label.show();
107 }
107 }
108 }
108 }
109 return IPython.DOMWidgetView.update.apply(this);
109 return IntSliderView.__super__.update.apply(this);
110 },
110 },
111
111
112 // Handles: User input
112 // Handles: User input
@@ -165,7 +165,7 b' define(["notebook/js/widgets/widget"], function(widget_manager){'
165 this.$label.show();
165 this.$label.show();
166 }
166 }
167 }
167 }
168 return IPython.DOMWidgetView.update.apply(this);
168 return IntTextView.__super__.update.apply(this);
169 },
169 },
170
170
171
171
@@ -101,7 +101,7 b' define(["notebook/js/widgets/widget"], function(widget_manager){'
101 this.$label.show();
101 this.$label.show();
102 }
102 }
103 }
103 }
104 return IPython.DOMWidgetView.update.apply(this);
104 return DropdownView.__super__.update.apply(this);
105 },
105 },
106
106
107 // Handle when a value is clicked.
107 // Handle when a value is clicked.
@@ -193,7 +193,7 b' define(["notebook/js/widgets/widget"], function(widget_manager){'
193 this.$label.show();
193 this.$label.show();
194 }
194 }
195 }
195 }
196 return IPython.DOMWidgetView.update.apply(this);
196 return RadioButtonsView.__super__.update.apply(this);
197 },
197 },
198
198
199 // Handle when a value is clicked.
199 // Handle when a value is clicked.
@@ -280,7 +280,7 b' define(["notebook/js/widgets/widget"], function(widget_manager){'
280 this.$label.show();
280 this.$label.show();
281 }
281 }
282 }
282 }
283 return IPython.DOMWidgetView.update.apply(this);
283 return ToggleButtonsView.__super__.update.apply(this);
284 },
284 },
285
285
286 // Handle when a value is clicked.
286 // Handle when a value is clicked.
@@ -364,7 +364,7 b' define(["notebook/js/widgets/widget"], function(widget_manager){'
364 this.$label.show();
364 this.$label.show();
365 }
365 }
366 }
366 }
367 return IPython.DOMWidgetView.update.apply(this);
367 return ListBoxView.__super__.update.apply(this);
368 },
368 },
369
369
370 // Handle when a value is clicked.
370 // Handle when a value is clicked.
@@ -77,7 +77,7 b' define(["notebook/js/widgets/widget"], function(widget_manager){'
77 }
77 }
78 }
78 }
79 }
79 }
80 return IPython.DOMWidgetView.update.apply(this);
80 return AccordionView.__super__.update.apply(this);
81 },
81 },
82
82
83 add_child_view: function(view) {
83 add_child_view: function(view) {
@@ -130,7 +130,7 b' define(["notebook/js/widgets/widget"], function(widget_manager){'
130
130
131 initialize: function() {
131 initialize: function() {
132 this.containers = [];
132 this.containers = [];
133 IPython.DOMWidgetView.initialize.apply(this, arguments);
133 TabView.__super__.initialize.apply(this, arguments);
134 },
134 },
135
135
136 render: function(){
136 render: function(){
@@ -181,7 +181,7 b' define(["notebook/js/widgets/widget"], function(widget_manager){'
181 this.select_page(selected_index);
181 this.select_page(selected_index);
182 }
182 }
183 }
183 }
184 return IPython.DOMWidgetView.update.apply(this);
184 return TabView.__super__.update.apply(this);
185 },
185 },
186
186
187 add_child_view: function(view) {
187 add_child_view: function(view) {
@@ -31,7 +31,7 b' define(["notebook/js/widgets/widget"], function(widget_manager){'
31 // Called when the model is changed. The model may have been
31 // Called when the model is changed. The model may have been
32 // changed by another view or by a state update from the back-end.
32 // changed by another view or by a state update from the back-end.
33 this.$el.html(this.model.get('value'));
33 this.$el.html(this.model.get('value'));
34 return IPython.DOMWidgetView.update.apply(this);
34 return HTMLView.__super__.update.apply(this);
35 },
35 },
36
36
37 });
37 });
@@ -54,7 +54,7 b' define(["notebook/js/widgets/widget"], function(widget_manager){'
54 this.$el.html(this.model.get('value'));
54 this.$el.html(this.model.get('value'));
55 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$el.get(0)]);
55 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$el.get(0)]);
56
56
57 return IPython.DOMWidgetView.update.apply(this);
57 return LatexView.__super__.update.apply(this);
58 },
58 },
59
59
60 });
60 });
@@ -114,7 +114,7 b' define(["notebook/js/widgets/widget"], function(widget_manager){'
114 this.$label.show();
114 this.$label.show();
115 }
115 }
116 }
116 }
117 return IPython.DOMWidgetView.update.apply(this);
117 return TextAreaView.__super__.update.apply(this);
118 },
118 },
119
119
120 events: {"keyup textarea": "handleChanging",
120 events: {"keyup textarea": "handleChanging",
@@ -173,7 +173,7 b' define(["notebook/js/widgets/widget"], function(widget_manager){'
173 this.$label.show();
173 this.$label.show();
174 }
174 }
175 }
175 }
176 return IPython.DOMWidgetView.update.apply(this);
176 return TextBoxView.__super__.update.apply(this);
177 },
177 },
178
178
179 events: {"keyup input": "handleChanging",
179 events: {"keyup input": "handleChanging",
@@ -27,7 +27,7 b' casper.notebook_test(function () {'
27
27
28 index = this.append_cell(
28 index = this.append_cell(
29 'names = [name for name in dir(widgets)' +
29 'names = [name for name in dir(widgets)' +
30 ' if name.endswith("Widget") and name!= "Widget"]\n' +
30 ' if name.endswith("Widget") and name!= "Widget" and name!= "DOMWidget"]\n' +
31 'for name in names:\n' +
31 'for name in names:\n' +
32 ' print(name)\n');
32 ' print(name)\n');
33 this.execute_cell_then(index, function(index){
33 this.execute_cell_then(index, function(index){
@@ -37,7 +37,7 b' casper.notebook_test(function () {'
37 // suffixed).
37 // suffixed).
38 var javascript_names = this.evaluate(function () {
38 var javascript_names = this.evaluate(function () {
39 names = [];
39 names = [];
40 for (var name in IPython.widget_manager.widget_model_types) {
40 for (var name in IPython.widget_manager._model_types) {
41 names.push(name.replace('Model',''));
41 names.push(name.replace('Model',''));
42 }
42 }
43 return names;
43 return names;
@@ -7,9 +7,15 b' casper.notebook_test(function () {'
7 this.execute_cell_then(index);
7 this.execute_cell_then(index);
8
8
9 var bool_index = this.append_cell(
9 var bool_index = this.append_cell(
10 'bool_widget = widgets.BoolWidget(description="Title", value=True)\n' +
10 'bool_widgets = [widgets.BoolWidget(description="Title", value=True) for i in range(2)]\n' +
11 'display(bool_widget)\n'+
11 'display(bool_widgets[0])\n' +
12 'display(bool_widget, view_name="ToggleButtonView")\n' +
12 'bool_widgets[1].view_name = "ToggleButtonView"\n' +
13 'display(bool_widgets[1])\n' +
14 'for widget in bool_widgets:\n' +
15 ' def handle_change(name,old,new):\n' +
16 ' for other_widget in bool_widgets:\n' +
17 ' other_widget.value = new\n' +
18 ' widget.on_trait_change(handle_change, "value")\n' +
13 'print("Success")');
19 'print("Success")');
14 this.execute_cell_then(bool_index, function(index){
20 this.execute_cell_then(bool_index, function(index){
15
21
@@ -37,21 +43,21 b' casper.notebook_test(function () {'
37 'Checkbox labeled correctly.');
43 'Checkbox labeled correctly.');
38
44
39 this.test.assert(this.cell_element_exists(index,
45 this.test.assert(this.cell_element_exists(index,
40 '.widget-area .widget-subarea div button'),
46 '.widget-area .widget-subarea button'),
41 'Toggle button exists.');
47 'Toggle button exists.');
42
48
43 this.test.assert(this.cell_element_function(index,
49 this.test.assert(this.cell_element_function(index,
44 '.widget-area .widget-subarea div button', 'html')=="Title",
50 '.widget-area .widget-subarea button', 'html')=="Title",
45 'Toggle button labeled correctly.');
51 'Toggle button labeled correctly.');
46
52
47 this.test.assert(this.cell_element_function(index,
53 this.test.assert(this.cell_element_function(index,
48 '.widget-area .widget-subarea div button', 'hasClass', ['active']),
54 '.widget-area .widget-subarea button', 'hasClass', ['active']),
49 'Toggle button is toggled.');
55 'Toggle button is toggled.');
50
56
51 });
57 });
52
58
53 index = this.append_cell(
59 index = this.append_cell(
54 'bool_widget.value = False\n' +
60 'bool_widgets[0].value = False\n' +
55 'print("Success")');
61 'print("Success")');
56 this.execute_cell_then(index, function(index){
62 this.execute_cell_then(index, function(index){
57
63
@@ -63,18 +69,18 b' casper.notebook_test(function () {'
63 'Checkbox is not checked. (1)');
69 'Checkbox is not checked. (1)');
64
70
65 this.test.assert(! this.cell_element_function(bool_index,
71 this.test.assert(! this.cell_element_function(bool_index,
66 '.widget-area .widget-subarea div button', 'hasClass', ['active']),
72 '.widget-area .widget-subarea button', 'hasClass', ['active']),
67 'Toggle button is not toggled. (1)');
73 'Toggle button is not toggled. (1)');
68
74
69 // Try toggling the bool by clicking on the toggle button.
75 // Try toggling the bool by clicking on the toggle button.
70 this.cell_element_function(bool_index, '.widget-area .widget-subarea div button', 'click');
76 this.cell_element_function(bool_index, '.widget-area .widget-subarea button', 'click');
71
77
72 this.test.assert(this.cell_element_function(bool_index,
78 this.test.assert(this.cell_element_function(bool_index,
73 '.widget-area .widget-subarea .widget-hbox-single input', 'prop', ['checked']),
79 '.widget-area .widget-subarea .widget-hbox-single input', 'prop', ['checked']),
74 'Checkbox is checked. (2)');
80 'Checkbox is checked. (2)');
75
81
76 this.test.assert(this.cell_element_function(bool_index,
82 this.test.assert(this.cell_element_function(bool_index,
77 '.widget-area .widget-subarea div button', 'hasClass', ['active']),
83 '.widget-area .widget-subarea button', 'hasClass', ['active']),
78 'Toggle button is toggled. (2)');
84 'Toggle button is toggled. (2)');
79
85
80 // Try toggling the bool by clicking on the checkbox.
86 // Try toggling the bool by clicking on the checkbox.
@@ -85,7 +91,7 b' casper.notebook_test(function () {'
85 'Checkbox is not checked. (3)');
91 'Checkbox is not checked. (3)');
86
92
87 this.test.assert(! this.cell_element_function(bool_index,
93 this.test.assert(! this.cell_element_function(bool_index,
88 '.widget-area .widget-subarea div button', 'hasClass', ['active']),
94 '.widget-area .widget-subarea button', 'hasClass', ['active']),
89 'Toggle button is not toggled. (3)');
95 'Toggle button is not toggled. (3)');
90
96
91 });
97 });
@@ -42,11 +42,16 b' casper.notebook_test(function () {'
42 }
42 }
43
43
44 selection_index = this.append_cell(
44 selection_index = this.append_cell(
45 'selection = widgets.SelectionWidget(values=["' + selection_values + '"[i] for i in range(4)])\n' +
45 'selection = [widgets.SelectionWidget(values=["' + selection_values + '"[i] for i in range(4)]) for j in range(4)]\n' +
46 'display(selection)\n' +
46 'selection[1].view_name="ToggleButtonsView"\n' +
47 'display(selection, view_name="ToggleButtonsView")\n' +
47 'selection[2].view_name="RadioButtonsView"\n' +
48 'display(selection, view_name="RadioButtonsView")\n' +
48 'selection[3].view_name="ListBoxView"\n' +
49 'display(selection, view_name="ListBoxView")\n' +
49 '[display(selection[i]) for i in range(4)]\n' +
50 'for widget in selection:\n' +
51 ' def handle_change(name,old,new):\n' +
52 ' for other_widget in selection:\n' +
53 ' other_widget.value = new\n' +
54 ' widget.on_trait_change(handle_change, "value")\n' +
50 'print("Success")\n');
55 'print("Success")\n');
51 this.execute_cell_then(selection_index, function(index){
56 this.execute_cell_then(selection_index, function(index){
52 this.test.assert(this.get_output_cell(index).text == 'Success\n',
57 this.test.assert(this.get_output_cell(index).text == 'Success\n',
@@ -73,7 +78,8 b' casper.notebook_test(function () {'
73 });
78 });
74
79
75 index = this.append_cell(
80 index = this.append_cell(
76 'selection.value = "a"\n' +
81 'for widget in selection:\n' +
82 ' widget.value = "a"\n' +
77 'print("Success")\n');
83 'print("Success")\n');
78 this.execute_cell_then(index, function(index){
84 this.execute_cell_then(index, function(index){
79 this.test.assert(this.get_output_cell(index).text == 'Success\n',
85 this.test.assert(this.get_output_cell(index).text == 'Success\n',
@@ -7,12 +7,15 b' casper.notebook_test(function () {'
7 this.execute_cell_then(index);
7 this.execute_cell_then(index);
8
8
9 var string_index = this.append_cell(
9 var string_index = this.append_cell(
10 'string_widget = widgets.StringWidget()\n' +
10 'string_widget = [widgets.StringWidget(), widgets.StringWidget(), widgets.StringWidget(), widgets.StringWidget()]\n' +
11 'display(string_widget)\n'+
11 'string_widget[0].value = "xyz"\n' +
12 'display(string_widget, view_name="TextAreaView")\n' +
12 'string_widget[1].view_name = "TextAreaView"\n' +
13 'display(string_widget, view_name="HTMLView")\n' +
13 'string_widget[1].value = "xyz"\n' +
14 'display(string_widget, view_name="LatexView")\n' +
14 'string_widget[2].view_name = "HTMLView"\n' +
15 'string_widget.value = "xyz"\n' +
15 'string_widget[2].value = "xyz"\n' +
16 'string_widget[3].view_name = "LatexView"\n' +
17 'string_widget[3].value = "$\\\\LaTeX{}$"\n' +
18 '[display(widget) for widget in string_widget]\n'+
16 'print("Success")');
19 'print("Success")');
17 this.execute_cell_then(string_index, function(index){
20 this.execute_cell_then(string_index, function(index){
18
21
@@ -39,29 +42,12 b' casper.notebook_test(function () {'
39 '.widget-area .widget-subarea .widget-hbox-single input[type=text]', 'val')=='xyz',
42 '.widget-area .widget-subarea .widget-hbox-single input[type=text]', 'val')=='xyz',
40 'Python set textbox value.');
43 'Python set textbox value.');
41
44
42 this.cell_element_function(index,
43 '.widget-area .widget-subarea .widget-hbox-single input[type=text]', 'val', [''])
44 this.sendKeys('.widget-area .widget-subarea .widget-hbox-single input[type=text]', 'abc');
45
46 this.test.assert(this.cell_element_function(index,
47 '.widget-area .widget-subarea .widget-hbox textarea', 'val')=='abc',
48 'Textarea updated to textbox contents.');
49
50 this.cell_element_function(index,
51 '.widget-area .widget-subarea .widget-hbox textarea', 'val', ['']);
52 this.sendKeys('.widget-area .widget-subarea .widget-hbox textarea', '$\\LaTeX{}$');
53
54 this.test.assert(this.cell_element_function(index,
55 '.widget-area .widget-subarea .widget-hbox-single input[type=text]', 'val')=='$\\LaTeX{}$',
56 'Textbox updated to textarea contents.');
57 });
45 });
58
46
59 this.wait(500); // Wait for change to execute in kernel
47 this.wait(500); // Wait for change to execute in kernel
60
48
61 index = this.append_cell('print(string_widget.value)');
49 index = this.append_cell('print(string_widget.value)');
62 this.execute_cell_then(index, function(index){
50 this.execute_cell_then(index, function(index){
63 this.test.assert(this.get_output_cell(index).text == '$\\LaTeX{}$\n',
64 'Python updated with correct string widget value.');
65
51
66 this.test.assert(this.cell_element_exists(string_index,
52 this.test.assert(this.cell_element_exists(string_index,
67 '.widget-area .widget-subarea div span.MathJax_Preview'),
53 '.widget-area .widget-subarea div span.MathJax_Preview'),
@@ -34,11 +34,13 b' from IPython.utils.py3compat import string_types'
34 @contextmanager
34 @contextmanager
35 def PropertyLock(instance, key, value):
35 def PropertyLock(instance, key, value):
36 instance._property_lock = (key, value)
36 instance._property_lock = (key, value)
37 yield
37 try:
38 del instance._property_lock
38 yield
39 finally:
40 del instance._property_lock
39
41
40 def should_send_property(instance, key, value):
42 def should_send_property(instance, key, value):
41 return not hasattr(instance, _property_lock) or \
43 return not hasattr(instance, '_property_lock') or \
42 key != instance._property_lock[0] or \
44 key != instance._property_lock[0] or \
43 value != instance._property_lock[1]
45 value != instance._property_lock[1]
44
46
@@ -47,8 +49,9 b' class Widget(LoggingConfigurable):'
47
49
48 # Shared declarations (Class level)
50 # Shared declarations (Class level)
49 widget_construction_callback = None
51 widget_construction_callback = None
52 widgets = []
50
53
51 keys = ['view_name']
54 keys = ['view_name'] # TODO: Sync = True
52
55
53 def on_widget_constructed(callback):
56 def on_widget_constructed(callback):
54 """Class method, registers a callback to be called when a widget is
57 """Class method, registers a callback to be called when a widget is
@@ -66,6 +69,7 b' class Widget(LoggingConfigurable):'
66 # Public declarations (Instance level)
69 # Public declarations (Instance level)
67 target_name = Unicode('widget', help="""Name of the backbone model
70 target_name = Unicode('widget', help="""Name of the backbone model
68 registered in the frontend to create and sync this widget with.""")
71 registered in the frontend to create and sync this widget with.""")
72 # model_name
69 view_name = Unicode(help="""Default view registered in the frontend
73 view_name = Unicode(help="""Default view registered in the frontend
70 to use to represent the widget.""")
74 to use to represent the widget.""")
71
75
@@ -75,23 +79,27 b' class Widget(LoggingConfigurable):'
75 def __init__(self, **kwargs):
79 def __init__(self, **kwargs):
76 """Public constructor
80 """Public constructor
77 """
81 """
82 self.closed = False
78 self._display_callbacks = []
83 self._display_callbacks = []
79 self._msg_callbacks = []
84 self._msg_callbacks = []
80 super(Widget, self).__init__(**kwargs)
85 super(Widget, self).__init__(**kwargs)
81
86
82 self.on_trait_change(self._handle_property_changed, self.keys)
87 self.on_trait_change(self._handle_property_changed, self.keys)
88 Widget.widgets.append(self)
83 Widget._call_widget_constructed(self)
89 Widget._call_widget_constructed(self)
84
90
85 def __del__(self):
91 def __del__(self):
86 """Object disposal"""
92 """Object disposal"""
87 self.close()
93 self.close()
88
94
89
90 def close(self):
95 def close(self):
91 """Close method. Closes the widget which closes the underlying comm.
96 """Close method. Closes the widget which closes the underlying comm.
92 When the comm is closed, all of the widget views are automatically
97 When the comm is closed, all of the widget views are automatically
93 removed from the frontend."""
98 removed from the frontend."""
94 self._close_communication()
99 if not self.closed:
100 self.closed = True
101 self._close_communication()
102 Widget.widgets.remove(self)
95
103
96 @property
104 @property
97 def comm(self):
105 def comm(self):
@@ -109,6 +117,8 b' class Widget(LoggingConfigurable):'
109 data = msg['content']['data']
117 data = msg['content']['data']
110 method = data['method']
118 method = data['method']
111
119
120 # TODO: Log unrecog.
121
112 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
122 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
113 if method == 'backbone' and 'sync_data' in data:
123 if method == 'backbone' and 'sync_data' in data:
114 sync_data = data['sync_data']
124 sync_data = data['sync_data']
@@ -124,7 +134,7 b' class Widget(LoggingConfigurable):'
124 """Called when a state is recieved from the frontend."""
134 """Called when a state is recieved from the frontend."""
125 for name in self.keys:
135 for name in self.keys:
126 if name in sync_data:
136 if name in sync_data:
127 value = sync_data[name]
137 value = self._unpack_widgets(sync_data[name])
128 with PropertyLock(self, name, value):
138 with PropertyLock(self, name, value):
129 setattr(self, name, value)
139 setattr(self, name, value)
130
140
@@ -204,22 +214,57 b' class Widget(LoggingConfigurable):'
204 else:
214 else:
205 keys = [key]
215 keys = [key]
206 for k in keys:
216 for k in keys:
207 value = getattr(self, k)
217 state[k] = self._pack_widgets(getattr(self, k))
208
209 # a more elegant solution to encoding Widgets would be
210 # to tap into the JSON encoder and teach it how to deal
211 # with Widget objects, or maybe just teach the JSON
212 # encoder to look for a _repr_json property before giving
213 # up encoding
214 if isinstance(value, Widget):
215 value = value.model_id
216 elif isinstance(value, list) and len(value)>0 and isinstance(value[0], Widget):
217 # assume all elements of the list are widgets
218 value = [i.model_id for i in value]
219 state[k] = value
220 return state
218 return state
221
219
222
220
221 def _pack_widgets(self, values):
222 """This function recursively converts all widget instances to model id
223 strings.
224
225 Children widgets will be stored and transmitted to the front-end by
226 their model ids."""
227 if isinstance(values, dict):
228 new_dict = {}
229 for key in values.keys():
230 new_dict[key] = self._pack_widgets(values[key])
231 return new_dict
232 elif isinstance(values, list):
233 new_list = []
234 for value in values:
235 new_list.append(self._pack_widgets(value))
236 return new_list
237 elif isinstance(values, Widget):
238 return values.model_id
239 else:
240 return values
241
242
243 def _unpack_widgets(self, values):
244 """This function recursively converts all model id strings to widget
245 instances.
246
247 Children widgets will be stored and transmitted to the front-end by
248 their model ids."""
249 if isinstance(values, dict):
250 new_dict = {}
251 for key in values.keys():
252 new_dict[key] = self._unpack_widgets(values[key])
253 return new_dict
254 elif isinstance(values, list):
255 new_list = []
256 for value in values:
257 new_list.append(self._unpack_widgets(value))
258 return new_list
259 elif isinstance(values, string_types):
260 for widget in Widget.widgets:
261 if widget.model_id == values:
262 return widget
263 return values
264 else:
265 return values
266
267
223 def send(self, content):
268 def send(self, content):
224 """Sends a custom msg to the widget model in the front-end.
269 """Sends a custom msg to the widget model in the front-end.
225
270
@@ -232,8 +277,9 b' class Widget(LoggingConfigurable):'
232 "custom_content": content})
277 "custom_content": content})
233
278
234
279
235 def on_msg(self, callback, remove=False):
280 def on_msg(self, callback, remove=False): # TODO: Use lambdas and inspect here
236 """Register a callback for when a custom msg is recieved from the front-end
281 """Register or unregister a callback for when a custom msg is recieved
282 from the front-end.
237
283
238 Parameters
284 Parameters
239 ----------
285 ----------
@@ -250,7 +296,8 b' class Widget(LoggingConfigurable):'
250
296
251
297
252 def on_displayed(self, callback, remove=False):
298 def on_displayed(self, callback, remove=False):
253 """Register a callback to be called when the widget has been displayed
299 """Register or unregister a callback to be called when the widget has
300 been displayed.
254
301
255 Parameters
302 Parameters
256 ----------
303 ----------
@@ -282,10 +329,9 b' class Widget(LoggingConfigurable):'
282 def _open_communication(self):
329 def _open_communication(self):
283 """Opens a communication with the front-end."""
330 """Opens a communication with the front-end."""
284 # Create a comm.
331 # Create a comm.
285 if self._comm is None:
332 self._comm = Comm(target_name=self.target_name)
286 self._comm = Comm(target_name=self.target_name)
333 self._comm.on_msg(self._handle_msg)
287 self._comm.on_msg(self._handle_msg)
334 self._comm.on_close(self._close_communication)
288 self._comm.on_close(self._close_communication)
289
335
290 # first update
336 # first update
291 self.send_state()
337 self.send_state()
@@ -295,7 +341,7 b' class Widget(LoggingConfigurable):'
295 """Closes a communication with the front-end."""
341 """Closes a communication with the front-end."""
296 if self._comm is not None:
342 if self._comm is not None:
297 try:
343 try:
298 self._comm.close()
344 self._comm.close() # TODO: Check
299 finally:
345 finally:
300 self._comm = None
346 self._comm = None
301
347
@@ -361,7 +407,7 b' class DOMWidget(Widget):'
361 if len(args) == 1:
407 if len(args) == 1:
362 if isinstance(args[0], dict):
408 if isinstance(args[0], dict):
363 for (key, value) in args[0].items():
409 for (key, value) in args[0].items():
364 if not (key in self._css[selector] and value in self._css[selector][key]):
410 if not (key in self._css[selector] and value == self._css[selector][key]):
365 self._css[selector][key] = value
411 self._css[selector][key] = value
366 self.send_state('_css')
412 self.send_state('_css')
367 else:
413 else:
@@ -379,7 +425,7 b' class DOMWidget(Widget):'
379 # Only update the property if it has changed.
425 # Only update the property if it has changed.
380 key = args[0]
426 key = args[0]
381 value = args[1]
427 value = args[1]
382 if not (key in self._css[selector] and value in self._css[selector][key]):
428 if not (key in self._css[selector] and value == self._css[selector][key]):
383 self._css[selector][key] = value
429 self._css[selector][key] = value
384 self.send_state('_css') # Send new state to client.
430 self.send_state('_css') # Send new state to client.
385 else:
431 else:
@@ -398,7 +444,7 b' class DOMWidget(Widget):'
398 be added to.
444 be added to.
399 """
445 """
400 class_list = class_names
446 class_list = class_names
401 if isinstance(list, class_list):
447 if isinstance(class_list, list):
402 class_list = ' '.join(class_list)
448 class_list = ' '.join(class_list)
403
449
404 self.send({"msg_type": "add_class",
450 self.send({"msg_type": "add_class",
@@ -418,7 +464,7 b' class DOMWidget(Widget):'
418 be removed from.
464 be removed from.
419 """
465 """
420 class_list = class_names
466 class_list = class_names
421 if isinstance(list, class_list):
467 if isinstance(class_list, list):
422 class_list = ' '.join(class_list)
468 class_list = ' '.join(class_list)
423
469
424 self.send({"msg_type": "remove_class",
470 self.send({"msg_type": "remove_class",
General Comments 0
You need to be logged in to leave comments. Login now