Show More
@@ -1,140 +1,296 b'' | |||||
1 | var xor = function (a, b) {return !a ^ !b;}; |
|
1 | var xor = function (a, b) {return !a ^ !b;}; | |
2 | var isArray = function (a) { |
|
2 | var isArray = function (a) { | |
3 | try { |
|
3 | try { | |
4 | return Object.toString.call(a) === "[object Array]" || Object.toString.call(a) === "[object RuntimeArray]"; |
|
4 | return Object.toString.call(a) === "[object Array]" || Object.toString.call(a) === "[object RuntimeArray]"; | |
5 | } catch (e) { |
|
5 | } catch (e) { | |
6 | return Array.isArray(a); |
|
6 | return Array.isArray(a); | |
7 | } |
|
7 | } | |
8 | }; |
|
8 | }; | |
9 | var recursive_compare = function(a, b) { |
|
9 | var recursive_compare = function(a, b) { | |
10 | // Recursively compare two objects. |
|
10 | // Recursively compare two objects. | |
11 | var same = true; |
|
11 | var same = true; | |
12 | same = same && !xor(a instanceof Object || typeof a == 'object', b instanceof Object || typeof b == 'object'); |
|
12 | same = same && !xor(a instanceof Object || typeof a == 'object', b instanceof Object || typeof b == 'object'); | |
13 | same = same && !xor(isArray(a), isArray(b)); |
|
13 | same = same && !xor(isArray(a), isArray(b)); | |
14 |
|
14 | |||
15 | if (same) { |
|
15 | if (same) { | |
16 | if (a instanceof Object) { |
|
16 | if (a instanceof Object) { | |
17 | var key; |
|
17 | var key; | |
18 | for (key in a) { |
|
18 | for (key in a) { | |
19 | if (a.hasOwnProperty(key) && !recursive_compare(a[key], b[key])) { |
|
19 | if (a.hasOwnProperty(key) && !recursive_compare(a[key], b[key])) { | |
20 | same = false; |
|
20 | same = false; | |
21 | break; |
|
21 | break; | |
22 | } |
|
22 | } | |
23 | } |
|
23 | } | |
24 | for (key in b) { |
|
24 | for (key in b) { | |
25 | if (b.hasOwnProperty(key) && !recursive_compare(a[key], b[key])) { |
|
25 | if (b.hasOwnProperty(key) && !recursive_compare(a[key], b[key])) { | |
26 | same = false; |
|
26 | same = false; | |
27 | break; |
|
27 | break; | |
28 | } |
|
28 | } | |
29 | } |
|
29 | } | |
30 | } else { |
|
30 | } else { | |
31 | return a === b; |
|
31 | return a === b; | |
32 | } |
|
32 | } | |
33 | } |
|
33 | } | |
34 |
|
34 | |||
35 | return same; |
|
35 | return same; | |
36 | }; |
|
36 | }; | |
37 |
|
37 | |||
38 | // Test the widget framework. |
|
38 | // Test the widget framework. | |
39 | casper.notebook_test(function () { |
|
39 | casper.notebook_test(function () { | |
40 | var index; |
|
40 | var index; | |
41 |
|
41 | |||
42 | index = this.append_cell( |
|
42 | index = this.append_cell( | |
43 |
'from IPython.html import widgets |
|
43 | ['from IPython.html import widgets', | |
44 |
'from IPython.display import display, clear_output |
|
44 | 'from IPython.display import display, clear_output', | |
45 | 'print("Success")'); |
|
45 | 'print("Success")'].join('\n')); | |
46 | this.execute_cell_then(index); |
|
46 | this.execute_cell_then(index); | |
47 |
|
47 | |||
48 | this.then(function () { |
|
48 | this.then(function () { | |
49 | // Test multi-set, single touch code. First create a custom widget. |
|
49 | // Test multi-set, single touch code. First create a custom widget. | |
50 | this.thenEvaluate(function() { |
|
50 | this.thenEvaluate(function() { | |
51 | var MultiSetView = IPython.DOMWidgetView.extend({ |
|
51 | var MultiSetView = IPython.DOMWidgetView.extend({ | |
52 | render: function(){ |
|
52 | render: function(){ | |
53 | this.model.set('a', 1); |
|
53 | this.model.set('a', 1); | |
54 | this.model.set('b', 2); |
|
54 | this.model.set('b', 2); | |
55 | this.model.set('c', 3); |
|
55 | this.model.set('c', 3); | |
56 | this.touch(); |
|
56 | this.touch(); | |
57 | }, |
|
57 | }, | |
58 | }); |
|
58 | }); | |
59 | IPython.WidgetManager.register_widget_view('MultiSetView', MultiSetView); |
|
59 | IPython.WidgetManager.register_widget_view('MultiSetView', MultiSetView); | |
60 | }, {}); |
|
60 | }, {}); | |
61 | }); |
|
61 | }); | |
62 |
|
62 | |||
63 | // Try creating the multiset widget, verify that sets the values correctly. |
|
63 | // Try creating the multiset widget, verify that sets the values correctly. | |
64 | var multiset = {}; |
|
64 | var multiset = {}; | |
65 | multiset.index = this.append_cell( |
|
65 | multiset.index = this.append_cell([ | |
66 |
'from IPython.utils.traitlets import Unicode, CInt |
|
66 | 'from IPython.utils.traitlets import Unicode, CInt', | |
67 |
'class MultiSetWidget(widgets.Widget): |
|
67 | 'class MultiSetWidget(widgets.Widget):', | |
68 |
' _view_name = Unicode("MultiSetView", sync=True) |
|
68 | ' _view_name = Unicode("MultiSetView", sync=True)', | |
69 |
' a = CInt(0, sync=True) |
|
69 | ' a = CInt(0, sync=True)', | |
70 |
' b = CInt(0, sync=True) |
|
70 | ' b = CInt(0, sync=True)', | |
71 |
' c = CInt(0, sync=True) |
|
71 | ' c = CInt(0, sync=True)', | |
72 |
' d = CInt(-1, sync=True) |
|
72 | ' d = CInt(-1, sync=True)', // See if it sends a full state. | |
73 |
' def set_state(self, sync_data): |
|
73 | ' def set_state(self, sync_data):', | |
74 |
' widgets.Widget.set_state(self, sync_data) |
|
74 | ' widgets.Widget.set_state(self, sync_data)'+ | |
75 |
' self.d = len(sync_data) |
|
75 | ' self.d = len(sync_data)', | |
76 |
'multiset = MultiSetWidget() |
|
76 | 'multiset = MultiSetWidget()', | |
77 |
'display(multiset) |
|
77 | 'display(multiset)', | |
78 | 'print(multiset.model_id)'); |
|
78 | 'print(multiset.model_id)'].join('\n')); | |
79 | this.execute_cell_then(multiset.index, function(index) { |
|
79 | this.execute_cell_then(multiset.index, function(index) { | |
80 | multiset.model_id = this.get_output_cell(index).text.trim(); |
|
80 | multiset.model_id = this.get_output_cell(index).text.trim(); | |
81 | }); |
|
81 | }); | |
82 |
|
82 | |||
83 | this.wait_for_widget(multiset); |
|
83 | this.wait_for_widget(multiset); | |
84 |
|
84 | |||
85 | index = this.append_cell( |
|
85 | index = this.append_cell( | |
86 | 'print("%d%d%d" % (multiset.a, multiset.b, multiset.c))'); |
|
86 | 'print("%d%d%d" % (multiset.a, multiset.b, multiset.c))'); | |
87 | this.execute_cell_then(index, function(index) { |
|
87 | this.execute_cell_then(index, function(index) { | |
88 | this.test.assertEquals(this.get_output_cell(index).text.trim(), '123', |
|
88 | this.test.assertEquals(this.get_output_cell(index).text.trim(), '123', | |
89 | 'Multiple model.set calls and one view.touch update state in back-end.'); |
|
89 | 'Multiple model.set calls and one view.touch update state in back-end.'); | |
90 | }); |
|
90 | }); | |
91 |
|
91 | |||
92 | index = this.append_cell( |
|
92 | index = this.append_cell( | |
93 | 'print("%d" % (multiset.d))'); |
|
93 | 'print("%d" % (multiset.d))'); | |
94 | this.execute_cell_then(index, function(index) { |
|
94 | this.execute_cell_then(index, function(index) { | |
95 | this.test.assertEquals(this.get_output_cell(index).text.trim(), '3', |
|
95 | this.test.assertEquals(this.get_output_cell(index).text.trim(), '3', | |
96 | 'Multiple model.set calls sent a partial state.'); |
|
96 | 'Multiple model.set calls sent a partial state.'); | |
97 | }); |
|
97 | }); | |
98 |
|
98 | |||
99 | var textbox = {}; |
|
99 | var textbox = {}; | |
100 | throttle_index = this.append_cell( |
|
100 | throttle_index = this.append_cell([ | |
101 |
'import time |
|
101 | 'import time', | |
102 |
'textbox = widgets.Text() |
|
102 | 'textbox = widgets.Text()', | |
103 |
'display(textbox) |
|
103 | 'display(textbox)', | |
104 |
'textbox._dom_classes = ["my-throttle-textbox"] |
|
104 | 'textbox._dom_classes = ["my-throttle-textbox"]', | |
105 |
'def handle_change(name, old, new): |
|
105 | 'def handle_change(name, old, new):', | |
106 |
' display(len(new)) |
|
106 | ' display(len(new))', | |
107 |
' time.sleep(0.5) |
|
107 | ' time.sleep(0.5)', | |
108 |
'textbox.on_trait_change(handle_change, "value") |
|
108 | 'textbox.on_trait_change(handle_change, "value")', | |
109 | 'print(textbox.model_id)'); |
|
109 | 'print(textbox.model_id)'].join('\n')); | |
110 | this.execute_cell_then(throttle_index, function(index){ |
|
110 | this.execute_cell_then(throttle_index, function(index){ | |
111 | textbox.model_id = this.get_output_cell(index).text.trim(); |
|
111 | textbox.model_id = this.get_output_cell(index).text.trim(); | |
112 |
|
112 | |||
113 | this.test.assert(this.cell_element_exists(index, |
|
113 | this.test.assert(this.cell_element_exists(index, | |
114 | '.widget-area .widget-subarea'), |
|
114 | '.widget-area .widget-subarea'), | |
115 | 'Widget subarea exists.'); |
|
115 | 'Widget subarea exists.'); | |
116 |
|
116 | |||
117 | this.test.assert(this.cell_element_exists(index, |
|
117 | this.test.assert(this.cell_element_exists(index, | |
118 | '.my-throttle-textbox'), 'Textbox exists.'); |
|
118 | '.my-throttle-textbox'), 'Textbox exists.'); | |
119 |
|
119 | |||
120 | // Send 20 characters |
|
120 | // Send 20 characters | |
121 | this.sendKeys('.my-throttle-textbox input', '12345678901234567890'); |
|
121 | this.sendKeys('.my-throttle-textbox input', '12345678901234567890'); | |
122 | }); |
|
122 | }); | |
123 |
|
123 | |||
124 | this.wait_for_widget(textbox); |
|
124 | this.wait_for_widget(textbox); | |
125 |
|
125 | |||
126 | this.then(function () { |
|
126 | this.then(function () { | |
127 | var outputs = this.evaluate(function(i) { |
|
127 | var outputs = this.evaluate(function(i) { | |
128 | return IPython.notebook.get_cell(i).output_area.outputs; |
|
128 | return IPython.notebook.get_cell(i).output_area.outputs; | |
129 | }, {i : throttle_index}); |
|
129 | }, {i : throttle_index}); | |
130 |
|
130 | |||
131 | // Only 4 outputs should have printed, but because of timing, sometimes |
|
131 | // Only 4 outputs should have printed, but because of timing, sometimes | |
132 | // 5 outputs will print. All we need to do is verify num outputs <= 5 |
|
132 | // 5 outputs will print. All we need to do is verify num outputs <= 5 | |
133 | // because that is much less than 20. |
|
133 | // because that is much less than 20. | |
134 | this.test.assert(outputs.length <= 5, 'Messages throttled.'); |
|
134 | this.test.assert(outputs.length <= 5, 'Messages throttled.'); | |
135 |
|
135 | |||
136 | // We also need to verify that the last state sent was correct. |
|
136 | // We also need to verify that the last state sent was correct. | |
137 | var last_state = outputs[outputs.length-1].data['text/plain']; |
|
137 | var last_state = outputs[outputs.length-1].data['text/plain']; | |
138 | this.test.assertEquals(last_state, "20", "Last state sent when throttling."); |
|
138 | this.test.assertEquals(last_state, "20", "Last state sent when throttling."); | |
139 | }); |
|
139 | }); | |
|
140 | ||||
|
141 | ||||
|
142 | /* New Test | |||
|
143 | ||||
|
144 | ||||
|
145 | %%javascript | |||
|
146 | define('TestWidget', ['widgets/js/widget', 'base/js/utils'], function(widget, utils) { | |||
|
147 | var TestWidget = widget.DOMWidgetView.extend({ | |||
|
148 | render: function () { | |||
|
149 | this.listenTo(this.model, 'msg:custom', this.handle_msg); | |||
|
150 | window.w = this; | |||
|
151 | console.log('data:', this.model.get('data')); | |||
|
152 | }, | |||
|
153 | handle_msg: function(content, buffers) { | |||
|
154 | this.msg = [content, buffers]; | |||
|
155 | } | |||
|
156 | }); | |||
|
157 | ||||
|
158 | var floatArray = { | |||
|
159 | deserialize: function (value, model) { | |||
|
160 | // DataView -> float64 typed array | |||
|
161 | return new Float64Array(value.buffer); | |||
|
162 | }, | |||
|
163 | // serialization automatically handled by message buffers | |||
|
164 | }; | |||
|
165 | ||||
|
166 | ||||
|
167 | var floatList = { | |||
|
168 | deserialize: function (value, model) { | |||
|
169 | // list of floats -> list of strings | |||
|
170 | return value.map(function(x) {return x.toString()}); | |||
|
171 | }, | |||
|
172 | serialize: function(value, model) { | |||
|
173 | // list of strings -> list of floats | |||
|
174 | return value.map(function(x) {return parseFloat(x);}) | |||
|
175 | } | |||
|
176 | }; | |||
|
177 | return {TestWidget: TestWidget, floatArray: floatArray, floatList: floatList}; | |||
|
178 | }); | |||
|
179 | ||||
|
180 | ||||
|
181 | -------------- | |||
|
182 | ||||
|
183 | ||||
|
184 | from IPython.html import widgets | |||
|
185 | from IPython.utils.traitlets import Unicode, Instance, List | |||
|
186 | from IPython.display import display | |||
|
187 | from array import array | |||
|
188 | def _array_to_memoryview(x): | |||
|
189 | if x is None: return None, {} | |||
|
190 | try: | |||
|
191 | y = memoryview(x) | |||
|
192 | except TypeError: | |||
|
193 | # in python 2, arrays don't support the new buffer protocol | |||
|
194 | y = memoryview(buffer(x)) | |||
|
195 | return y, {'serialization': ('floatArray', 'TestWidget')} | |||
|
196 | ||||
|
197 | def _memoryview_to_array(x): | |||
|
198 | return array('d', x.tobytes()) | |||
|
199 | ||||
|
200 | arrays_binary = { | |||
|
201 | 'from_json': _memoryview_to_array, | |||
|
202 | 'to_json': _array_to_memoryview | |||
|
203 | } | |||
|
204 | ||||
|
205 | def _array_to_list(x): | |||
|
206 | if x is None: return None, {} | |||
|
207 | return list(x), {'serialization': ('floatList', 'TestWidget')} | |||
|
208 | ||||
|
209 | def _list_to_array(x): | |||
|
210 | return array('d',x) | |||
|
211 | ||||
|
212 | arrays_list = { | |||
|
213 | 'from_json': _list_to_array, | |||
|
214 | 'to_json': _array_to_list | |||
|
215 | } | |||
|
216 | ||||
|
217 | ||||
|
218 | class TestWidget(widgets.DOMWidget): | |||
|
219 | _view_module = Unicode('TestWidget', sync=True) | |||
|
220 | _view_name = Unicode('TestWidget', sync=True) | |||
|
221 | array_binary = Instance(array, sync=True, **arrays_binary) | |||
|
222 | array_list = Instance(array, sync=True, **arrays_list) | |||
|
223 | def __init__(self, **kwargs): | |||
|
224 | super(widgets.DOMWidget, self).__init__(**kwargs) | |||
|
225 | self.on_msg(self._msg) | |||
|
226 | def _msg(self, _, content, buffers): | |||
|
227 | self.msg = [content, buffers] | |||
|
228 | ||||
|
229 | ||||
|
230 | ---------------- | |||
|
231 | ||||
|
232 | x=TestWidget() | |||
|
233 | display(x) | |||
|
234 | x.array_binary=array('d', [1.5,2.5,5]) | |||
|
235 | print x.model_id | |||
|
236 | ||||
|
237 | ----------------- | |||
|
238 | ||||
|
239 | %%javascript | |||
|
240 | console.log(w.model.get('array_binary')) | |||
|
241 | var z = w.model.get('array_binary') | |||
|
242 | z[0]*=3 | |||
|
243 | z[1]*=3 | |||
|
244 | z[2]*=3 | |||
|
245 | // we set to null so that we recognize the change | |||
|
246 | // when we set data back to z | |||
|
247 | w.model.set('array_binary', null) | |||
|
248 | w.model.set('array_binary', z) | |||
|
249 | console.log(w.model.get('array_binary')) | |||
|
250 | w.touch() | |||
|
251 | ||||
|
252 | ---------------- | |||
|
253 | x.array_binary.tolist() == [4.5, 7.5, 15.0] | |||
|
254 | ---------------- | |||
|
255 | ||||
|
256 | x.array_list = array('d', [1.5, 2.0, 3.1]) | |||
|
257 | ---------------- | |||
|
258 | ||||
|
259 | %%javascript | |||
|
260 | console.log(w.model.get('array_list')) | |||
|
261 | var z = w.model.get('array_list') | |||
|
262 | z[0]+="1234" | |||
|
263 | z[1]+="5678" | |||
|
264 | // we set to null so that we recognize the change | |||
|
265 | // when we set data back to z | |||
|
266 | w.model.set('array_list', null) | |||
|
267 | w.model.set('array_list', z) | |||
|
268 | w.touch() | |||
|
269 | ||||
|
270 | ----------------- | |||
|
271 | ||||
|
272 | x.array_list.tolist() == [1.51234, 25678.0, 3.1] | |||
|
273 | ||||
|
274 | ------------------- | |||
|
275 | x.send('some content', [memoryview(b'binarycontent'), memoryview('morecontent')]) | |||
|
276 | ||||
|
277 | ------------------- | |||
|
278 | ||||
|
279 | %%javascript | |||
|
280 | console.log(w.msg[0] === 'some content') | |||
|
281 | var d=new TextDecoder('utf-8') | |||
|
282 | console.log(d.decode(w.msg[1][0])==='binarycontent') | |||
|
283 | console.log(d.decode(w.msg[1][1])==='morecontent') | |||
|
284 | w.send('content back', [new Uint8Array([1,2,3,4]), new Float64Array([2.1828, 3.14159])]) | |||
|
285 | ||||
|
286 | -------------------- | |||
|
287 | ||||
|
288 | print x.msg[0] == 'content back' | |||
|
289 | print x.msg[1][0].tolist() == [1,2,3,4] | |||
|
290 | print array('d', x.msg[1][1].tobytes()).tolist() == [2.1828, 3.14159] | |||
|
291 | ||||
|
292 | ||||
|
293 | */ | |||
|
294 | ||||
|
295 | ||||
140 | }); |
|
296 | }); |
General Comments 0
You need to be logged in to leave comments.
Login now