##// END OF EJS Templates
Changed parent/child api widgets
Jonathan Frederic -
Show More
@@ -1,351 +1,351 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 // WidgetModel, WidgetView, and WidgetManager
9 // WidgetModel, WidgetView, and WidgetManager
10 //============================================================================
10 //============================================================================
11 /**
11 /**
12 * Base Widget classes
12 * Base Widget classes
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule widget
15 * @submodule widget
16 */
16 */
17
17
18 "use strict";
18 "use strict";
19
19
20 // Use require.js 'define' method so that require.js is intelligent enough to
20 // Use require.js 'define' method so that require.js is intelligent enough to
21 // syncronously load everything within this file when it is being 'required'
21 // syncronously load everything within this file when it is being 'required'
22 // elsewhere.
22 // elsewhere.
23 define(["../../components/underscore/underscore-min.js",
23 define(["../../components/underscore/underscore-min.js",
24 "../../components/backbone/backbone-min.js",
24 "../../components/backbone/backbone-min.js",
25 ], function(){
25 ], function(){
26
26
27 // Only run once on a notebook.
27 // Only run once on a notebook.
28 if (IPython.notebook.widget_manager == undefined) {
28 if (IPython.notebook.widget_manager == undefined) {
29
29
30 //--------------------------------------------------------------------
30 //--------------------------------------------------------------------
31 // WidgetModel class
31 // WidgetModel class
32 //--------------------------------------------------------------------
32 //--------------------------------------------------------------------
33 var WidgetModel = Backbone.Model.extend({
33 var WidgetModel = Backbone.Model.extend({
34 constructor: function(comm_manager, comm, widget_view_types) {
34 constructor: function(comm_manager, comm, widget_view_types) {
35 this.comm_manager = comm_manager;
35 this.comm_manager = comm_manager;
36 this.widget_view_types = widget_view_types;
36 this.widget_view_types = widget_view_types;
37 this.pending_msgs = 0;
37 this.pending_msgs = 0;
38 this.msg_throttle = 3;
38 this.msg_throttle = 3;
39 this.msg_buffer = {};
39 this.msg_buffer = {};
40 this.views = {};
40 this.views = {};
41
41
42 // Remember comm associated with the model.
42 // Remember comm associated with the model.
43 this.comm = comm;
43 this.comm = comm;
44 comm.model = this;
44 comm.model = this;
45
45
46 // Hook comm messages up to model.
46 // Hook comm messages up to model.
47 comm.on_close($.proxy(this.handle_comm_closed, this));
47 comm.on_close($.proxy(this.handle_comm_closed, this));
48 comm.on_msg($.proxy(this.handle_comm_msg, this));
48 comm.on_msg($.proxy(this.handle_comm_msg, this));
49
49
50 return Backbone.Model.apply(this);
50 return Backbone.Model.apply(this);
51 },
51 },
52
52
53
53
54 update_other_views: function(caller) {
54 update_other_views: function(caller) {
55 this.last_modified_view = caller;
55 this.last_modified_view = caller;
56 this.save(this.changedAttributes(), {patch: true});
56 this.save(this.changedAttributes(), {patch: true});
57
57
58 for (var cell_index in this.views) {
58 for (var cell_index in this.views) {
59 var view = this.views[cell_index];
59 var view = this.views[cell_index];
60 if (view !== caller) {
60 if (view !== caller) {
61 view.update();
61 view.update();
62 }
62 }
63 }
63 }
64 },
64 },
65
65
66
66
67 handle_status: function (output_area, msg) {
67 handle_status: function (output_area, msg) {
68 //execution_state : ('busy', 'idle', 'starting')
68 //execution_state : ('busy', 'idle', 'starting')
69 if (msg.content.execution_state=='idle') {
69 if (msg.content.execution_state=='idle') {
70
70
71 // Send buffer if this message caused another message to be
71 // Send buffer if this message caused another message to be
72 // throttled.
72 // throttled.
73 if (this.msg_throttle == this.pending_msgs &&
73 if (this.msg_throttle == this.pending_msgs &&
74 this.msg_buffer.length > 0) {
74 this.msg_buffer.length > 0) {
75
75
76 var output_area = this._get_msg_output_area(msg);
76 var output_area = this._get_msg_output_area(msg);
77 var callbacks = this._make_callbacks(output_area);
77 var callbacks = this._make_callbacks(output_area);
78 var data = {sync_method: 'patch', sync_data: this.msg_buffer};
78 var data = {sync_method: 'patch', sync_data: this.msg_buffer};
79 comm.send(data, callbacks);
79 comm.send(data, callbacks);
80 this.msg_buffer = {};
80 this.msg_buffer = {};
81 } else {
81 } else {
82
82
83 // Only decrease the pending message count if the buffer
83 // Only decrease the pending message count if the buffer
84 // doesn't get flushed (sent).
84 // doesn't get flushed (sent).
85 --this.pending_msgs;
85 --this.pending_msgs;
86 }
86 }
87 }
87 }
88 },
88 },
89
89
90
90
91 // Custom syncronization logic.
91 // Custom syncronization logic.
92 handle_sync: function (method, options) {
92 handle_sync: function (method, options) {
93 var model_json = this.toJSON();
93 var model_json = this.toJSON();
94
94
95 // Only send updated state if the state hasn't been changed
95 // Only send updated state if the state hasn't been changed
96 // during an update.
96 // during an update.
97 if (!this.updating) {
97 if (!this.updating) {
98 if (this.pending_msgs >= this.msg_throttle) {
98 if (this.pending_msgs >= this.msg_throttle) {
99 // The throttle has been exceeded, buffer the current msg so
99 // The throttle has been exceeded, buffer the current msg so
100 // it can be sent once the kernel has finished processing
100 // it can be sent once the kernel has finished processing
101 // some of the existing messages.
101 // some of the existing messages.
102 if (method=='patch') {
102 if (method=='patch') {
103 for (var attr in options.attrs) {
103 for (var attr in options.attrs) {
104 this.msg_buffer[attr] = options.attrs[attr];
104 this.msg_buffer[attr] = options.attrs[attr];
105 }
105 }
106 } else {
106 } else {
107 this.msg_buffer = $.extend({}, model_json); // Copy
107 this.msg_buffer = $.extend({}, model_json); // Copy
108 }
108 }
109
109
110 } else {
110 } else {
111 // We haven't exceeded the throttle, send the message like
111 // We haven't exceeded the throttle, send the message like
112 // normal. If this is a patch operation, just send the
112 // normal. If this is a patch operation, just send the
113 // changes.
113 // changes.
114 var send_json = model_json;
114 var send_json = model_json;
115 if (method=='patch') {
115 if (method=='patch') {
116 send_json = {};
116 send_json = {};
117 for (var attr in options.attrs) {
117 for (var attr in options.attrs) {
118 send_json[attr] = options.attrs[attr];
118 send_json[attr] = options.attrs[attr];
119 }
119 }
120 }
120 }
121
121
122 var data = {sync_method: method, sync_data: send_json};
122 var data = {sync_method: method, sync_data: send_json};
123 var output_area = this._get_view_output_area(this.last_modified_view);
123 var output_area = this._get_view_output_area(this.last_modified_view);
124 var callbacks = this._make_callbacks(output_area);
124 var callbacks = this._make_callbacks(output_area);
125 this.comm.send(data, callbacks);
125 this.comm.send(data, callbacks);
126 this.pending_msgs++;
126 this.pending_msgs++;
127 }
127 }
128 }
128 }
129
129
130 // Since the comm is a one-way communication, assume the message
130 // Since the comm is a one-way communication, assume the message
131 // arrived.
131 // arrived.
132 return model_json;
132 return model_json;
133 },
133 },
134
134
135
135
136 // Handle incomming comm msg.
136 // Handle incomming comm msg.
137 handle_comm_msg: function (msg) {
137 handle_comm_msg: function (msg) {
138 var method = msg.content.data.method;
138 var method = msg.content.data.method;
139 switch (method){
139 switch (method){
140 case 'display':
140 case 'display':
141
141
142 ////////////////////////// TODO: Get cell index via currently executing cell.
142 ////////////////////////// TODO: Get cell index via currently executing cell.
143 var cell_index = IPython.notebook.get_selected_index()-1;
143 var cell_index = IPython.notebook.get_selected_index()-1;
144
144
145 this.display_view(msg.content.data.view_name,
145 this.display_view(msg.content.data.view_name,
146 msg.content.data.parent,
146 msg.content.data.parent,
147 cell_index);
147 cell_index);
148 break;
148 break;
149 case 'update':
149 case 'update':
150 this.handle_update(msg.content.data.state);
150 this.handle_update(msg.content.data.state);
151 break;
151 break;
152 }
152 }
153 },
153 },
154
154
155
155
156 // Handle when a widget is updated via the python side.
156 // Handle when a widget is updated via the python side.
157 handle_update: function (state) {
157 handle_update: function (state) {
158 this.updating = true;
158 this.updating = true;
159 try {
159 try {
160 for (var key in state) {
160 for (var key in state) {
161 if (state.hasOwnProperty(key)) {
161 if (state.hasOwnProperty(key)) {
162 if (key == "_css"){
162 if (key == "_css"){
163 this.css = state[key];
163 this.css = state[key];
164 } else {
164 } else {
165 this.set(key, state[key]);
165 this.set(key, state[key]);
166 }
166 }
167 }
167 }
168 }
168 }
169 this.id = this.comm.comm_id;
169 this.id = this.comm.comm_id;
170 this.save();
170 this.save();
171 } finally {
171 } finally {
172 this.updating = false;
172 this.updating = false;
173 }
173 }
174 },
174 },
175
175
176
176
177 // Handle when a widget is closed.
177 // Handle when a widget is closed.
178 handle_comm_closed: function (msg) {
178 handle_comm_closed: function (msg) {
179 for (var cell_index in this.views) {
179 for (var cell_index in this.views) {
180 var view = this.views[cell_index];
180 var view = this.views[cell_index];
181 view.remove();
181 view.remove();
182 }
182 }
183 },
183 },
184
184
185
185
186 // Create view that represents the model.
186 // Create view that represents the model.
187 display_view: function (view_name, parent_comm_id, cell_index) {
187 display_view: function (view_name, parent_comm_id, cell_index) {
188 var view = new this.widget_view_types[view_name]({model: this});
188 var view = new this.widget_view_types[view_name]({model: this});
189 view.render();
189 view.render();
190 this.views[cell_index] = view;
190 this.views[cell_index] = view;
191 view.cell_index = cell_index;
191 view.cell_index = cell_index;
192
192
193 // Handle when the view element is remove from the page.
193 // Handle when the view element is remove from the page.
194 var that = this;
194 var that = this;
195 view.$el.on("remove", function(){
195 view.$el.on("remove", function(){
196 var index = that.views.indexOf(view);
196 var index = that.views.indexOf(view);
197 if (index > -1) {
197 if (index > -1) {
198 that.views.splice(index, 1);
198 that.views.splice(index, 1);
199 }
199 }
200 view.remove(); // Clean-up view
200 view.remove(); // Clean-up view
201
201
202 // Close the comm if there are no views left.
202 // Close the comm if there are no views left.
203 if (that.views.length()==0) {
203 if (that.views.length()==0) {
204 that.comm.close();
204 that.comm.close();
205 }
205 }
206 });
206 });
207
207
208 var displayed = false;
208 var displayed = false;
209 if (parent_comm_id != undefined) {
209 if (parent_comm_id != undefined) {
210 var parent_comm = this.comm_manager.comms[parent_comm_id];
210 var parent_comm = this.comm_manager.comms[parent_comm_id];
211 var parent_model = parent_comm.model;
211 var parent_model = parent_comm.model;
212 var parent_view = parent_model.views[cell_index];
212 var parent_view = parent_model.views[cell_index];
213 if (parent_view.display_child != undefined) {
213 if (parent_view.display_child != undefined) {
214 parent_view.display_child(view.$el);
214 parent_view.display_child(view);
215 displayed = true;
215 displayed = true;
216 }
216 }
217 }
217 }
218
218
219 if (!displayed) {
219 if (!displayed) {
220 // No parent view is defined or exists. Add the view's
220 // No parent view is defined or exists. Add the view's
221 // element to cell's widget div.
221 // element to cell's widget div.
222 var cell = IPython.notebook.get_cell(cell_index);
222 var cell = IPython.notebook.get_cell(cell_index);
223 cell.element.find('.widget_area').find('.widget_subarea')
223 cell.element.find('.widget_area').find('.widget_subarea')
224 .append(view.$el)
224 .append(view.$el)
225 .parent().show(); // Show the widget_area (parent of widget_subarea)
225 .parent().show(); // Show the widget_area (parent of widget_subarea)
226
226
227 }
227 }
228
228
229 // Update the view based on the model contents.
229 // Update the view based on the model contents.
230 view.update();
230 view.update();
231 },
231 },
232
232
233
233
234 // Build a callback dict.
234 // Build a callback dict.
235 _make_callbacks: function (output_area) {
235 _make_callbacks: function (output_area) {
236 var callbacks = {};
236 var callbacks = {};
237 if (output_area != null) {
237 if (output_area != null) {
238 var that = this;
238 var that = this;
239 callbacks = {
239 callbacks = {
240 iopub : {
240 iopub : {
241 output : $.proxy(output_area.handle_output, output_area),
241 output : $.proxy(output_area.handle_output, output_area),
242 clear_output : $.proxy(output_area.handle_clear_output, output_area),
242 clear_output : $.proxy(output_area.handle_clear_output, output_area),
243 status : function(msg){
243 status : function(msg){
244 that.handle_status(output_area, msg);
244 that.handle_status(output_area, msg);
245 },
245 },
246 },
246 },
247 };
247 };
248 }
248 }
249 return callbacks;
249 return callbacks;
250 },
250 },
251
251
252
252
253 // Get the cell output area corresponding to the view.
253 // Get the cell output area corresponding to the view.
254 _get_view_output_area: function (view) {
254 _get_view_output_area: function (view) {
255 return this._get_cell_output_area(view.cell_index);
255 return this._get_cell_output_area(view.cell_index);
256 },
256 },
257
257
258
258
259 // Get the cell output area corresponding to the cell id.
259 // Get the cell output area corresponding to the cell id.
260 _get_cell_output_area: function (cell_id) {
260 _get_cell_output_area: function (cell_id) {
261 var cell = IPython.notebook.get_cell(cell_id)
261 var cell = IPython.notebook.get_cell(cell_id)
262 return cell.output_area;
262 return cell.output_area;
263 },
263 },
264 });
264 });
265
265
266
266
267 //--------------------------------------------------------------------
267 //--------------------------------------------------------------------
268 // WidgetView class
268 // WidgetView class
269 //--------------------------------------------------------------------
269 //--------------------------------------------------------------------
270 var WidgetView = Backbone.View.extend({
270 var WidgetView = Backbone.View.extend({
271
271
272 initialize: function() {
272 initialize: function() {
273 this.model.on('change',this.update,this);
273 this.model.on('change',this.update,this);
274 },
274 },
275
275
276 update: function() {
276 update: function() {
277 if (this.model.css != undefined) {
277 if (this.model.css != undefined) {
278 for (var selector in this.model.css) {
278 for (var selector in this.model.css) {
279 if (this.model.css.hasOwnProperty(selector)) {
279 if (this.model.css.hasOwnProperty(selector)) {
280
280
281 // Get the elements via the css selector. If the selector is
281 // Get the elements via the css selector. If the selector is
282 // blank, assume the current element is the target.
282 // blank, assume the current element is the target.
283 var elements = this.$el.find(selector);
283 var elements = this.$el.find(selector);
284 if (selector=='') {
284 if (selector=='') {
285 elements = this.$el;
285 elements = this.$el;
286 }
286 }
287
287
288 // Apply the css traits to all elements that match the selector.
288 // Apply the css traits to all elements that match the selector.
289 if (elements.length>0){
289 if (elements.length>0){
290 var css_traits = this.model.css[selector];
290 var css_traits = this.model.css[selector];
291 for (var css_key in css_traits) {
291 for (var css_key in css_traits) {
292 if (css_traits.hasOwnProperty(css_key)) {
292 if (css_traits.hasOwnProperty(css_key)) {
293 elements.css(css_key, css_traits[css_key]);
293 elements.css(css_key, css_traits[css_key]);
294 }
294 }
295 }
295 }
296 }
296 }
297 }
297 }
298 }
298 }
299 }
299 }
300 },
300 },
301 });
301 });
302
302
303
303
304 //--------------------------------------------------------------------
304 //--------------------------------------------------------------------
305 // WidgetManager class
305 // WidgetManager class
306 //--------------------------------------------------------------------
306 //--------------------------------------------------------------------
307 var WidgetManager = function(comm_manager){
307 var WidgetManager = function(comm_manager){
308 this.comm_manager = comm_manager;
308 this.comm_manager = comm_manager;
309 this.widget_model_types = {};
309 this.widget_model_types = {};
310 this.widget_view_types = {};
310 this.widget_view_types = {};
311
311
312 var that = this;
312 var that = this;
313 Backbone.sync = function(method, model, options, error) {
313 Backbone.sync = function(method, model, options, error) {
314 var result = model.handle_sync(method, options);
314 var result = model.handle_sync(method, options);
315 if (options.success) {
315 if (options.success) {
316 options.success(result);
316 options.success(result);
317 }
317 }
318 };
318 };
319 }
319 }
320
320
321
321
322 WidgetManager.prototype.register_widget_model = function (widget_model_name, widget_model_type) {
322 WidgetManager.prototype.register_widget_model = function (widget_model_name, widget_model_type) {
323 // Register the widget with the comm manager. Make sure to pass this object's context
323 // Register the widget with the comm manager. Make sure to pass this object's context
324 // in so `this` works in the call back.
324 // in so `this` works in the call back.
325 this.comm_manager.register_target(widget_model_name, $.proxy(this.handle_com_open, this));
325 this.comm_manager.register_target(widget_model_name, $.proxy(this.handle_com_open, this));
326 this.widget_model_types[widget_model_name] = widget_model_type;
326 this.widget_model_types[widget_model_name] = widget_model_type;
327 }
327 }
328
328
329
329
330 WidgetManager.prototype.register_widget_view = function (widget_view_name, widget_view_type) {
330 WidgetManager.prototype.register_widget_view = function (widget_view_name, widget_view_type) {
331 this.widget_view_types[widget_view_name] = widget_view_type;
331 this.widget_view_types[widget_view_name] = widget_view_type;
332 }
332 }
333
333
334
334
335 WidgetManager.prototype.handle_com_open = function (comm, msg) {
335 WidgetManager.prototype.handle_com_open = function (comm, msg) {
336 var widget_type_name = msg.content.target_name;
336 var widget_type_name = msg.content.target_name;
337 var widget_model = new this.widget_model_types[widget_type_name](this.comm_manager, comm, this.widget_view_types);
337 var widget_model = new this.widget_model_types[widget_type_name](this.comm_manager, comm, this.widget_view_types);
338 }
338 }
339
339
340
340
341 //--------------------------------------------------------------------
341 //--------------------------------------------------------------------
342 // Init code
342 // Init code
343 //--------------------------------------------------------------------
343 //--------------------------------------------------------------------
344 IPython.WidgetManager = WidgetManager;
344 IPython.WidgetManager = WidgetManager;
345 IPython.WidgetModel = WidgetModel;
345 IPython.WidgetModel = WidgetModel;
346 IPython.WidgetView = WidgetView;
346 IPython.WidgetView = WidgetView;
347
347
348 IPython.notebook.widget_manager = new WidgetManager(IPython.notebook.kernel.comm_manager);
348 IPython.notebook.widget_manager = new WidgetManager(IPython.notebook.kernel.comm_manager);
349
349
350 };
350 };
351 });
351 });
@@ -1,34 +1,34 b''
1 require(["../static/notebook/js/widget"], function(){
1 require(["../static/notebook/js/widget"], function(){
2 var ContainerModel = IPython.WidgetModel.extend({});
2 var ContainerModel = IPython.WidgetModel.extend({});
3 IPython.notebook.widget_manager.register_widget_model('ContainerWidgetModel', ContainerModel);
3 IPython.notebook.widget_manager.register_widget_model('ContainerWidgetModel', ContainerModel);
4
4
5 var ContainerView = IPython.WidgetView.extend({
5 var ContainerView = IPython.WidgetView.extend({
6
6
7 render : function(){
7 render : function(){
8 this.$el = $('<div />')
8 this.$el = $('<div />')
9 .addClass('widget_container');
9 .addClass('widget_container');
10 },
10 },
11
11
12 update : function(){
12 update : function(){
13
13
14 // Apply flexible box model properties by adding and removing
14 // Apply flexible box model properties by adding and removing
15 // corrosponding CSS classes.
15 // corrosponding CSS classes.
16 // Defined in IPython/html/static/base/less/flexbox.less
16 // Defined in IPython/html/static/base/less/flexbox.less
17 var flex_properties = ['vbox', 'hbox', 'center', 'end', 'center'];
17 var flex_properties = ['vbox', 'hbox', 'center', 'end', 'center'];
18 for (var index in flex_properties) {
18 for (var index in flex_properties) {
19 if (this.model.get('_' + flex_properties[index])) {
19 if (this.model.get('_' + flex_properties[index])) {
20 this.$el.addClass(flex_properties[index]);
20 this.$el.addClass(flex_properties[index]);
21 } else {
21 } else {
22 this.$el.removeClass(flex_properties[index]);
22 this.$el.removeClass(flex_properties[index]);
23 }
23 }
24 }
24 }
25 return IPython.WidgetView.prototype.update.call(this);
25 return IPython.WidgetView.prototype.update.call(this);
26 },
26 },
27
27
28 display_child : function($element) {
28 display_child : function(view) {
29 this.$el.append($element);
29 this.$el.append(view.$el);
30 },
30 },
31 });
31 });
32
32
33 IPython.notebook.widget_manager.register_widget_view('ContainerView', ContainerView);
33 IPython.notebook.widget_manager.register_widget_view('ContainerView', ContainerView);
34 }); No newline at end of file
34 });
General Comments 0
You need to be logged in to leave comments. Login now