##// END OF EJS Templates
Got containers and mutlicontainers working! Yay
Jonathan Frederic -
Show More
@@ -1,379 +1,366 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 // Base Widget Model and View classes
9 // Base Widget Model and View classes
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/widgetmanager",
17 define(["notebook/js/widgetmanager",
18 "underscore",
18 "underscore",
19 "backbone"],
19 "backbone"],
20 function(widget_manager, underscore, backbone){
20 function(widget_manager, underscore, backbone){
21
21
22 //--------------------------------------------------------------------
22 //--------------------------------------------------------------------
23 // WidgetModel class
23 // WidgetModel class
24 //--------------------------------------------------------------------
24 //--------------------------------------------------------------------
25 var WidgetModel = Backbone.Model.extend({
25 var WidgetModel = Backbone.Model.extend({
26 constructor: function (widget_manager, model_id, comm) {
26 constructor: function (widget_manager, model_id, comm) {
27 // Construcctor
27 // Construcctor
28 //
28 //
29 // Creates a WidgetModel instance.
29 // Creates a WidgetModel instance.
30 //
30 //
31 // Parameters
31 // Parameters
32 // ----------
32 // ----------
33 // widget_manager : WidgetManager instance
33 // widget_manager : WidgetManager instance
34 // model_id : string
34 // model_id : string
35 // An ID unique to this model.
35 // An ID unique to this model.
36 // comm : Comm instance (optional)
36 // comm : Comm instance (optional)
37 this.widget_manager = widget_manager;
37 this.widget_manager = widget_manager;
38 this.pending_msgs = 0;
38 this.pending_msgs = 0;
39 this.msg_throttle = 2;
39 this.msg_throttle = 2;
40 this.msg_buffer = null;
40 this.msg_buffer = null;
41 this.key_value_lock = null;
41 this.key_value_lock = null;
42 this.id = model_id;
42 this.id = model_id;
43 this.views = [];
43 this.views = [];
44
44
45 if (comm !== undefined) {
45 if (comm !== undefined) {
46 // Remember comm associated with the model.
46 // Remember comm associated with the model.
47 this.comm = comm;
47 this.comm = comm;
48 comm.model = this;
48 comm.model = this;
49
49
50 // Hook comm messages up to model.
50 // Hook comm messages up to model.
51 comm.on_close($.proxy(this._handle_comm_closed, this));
51 comm.on_close($.proxy(this._handle_comm_closed, this));
52 comm.on_msg($.proxy(this._handle_comm_msg, this));
52 comm.on_msg($.proxy(this._handle_comm_msg, this));
53 }
53 }
54 return Backbone.Model.apply(this);
54 return Backbone.Model.apply(this);
55 },
55 },
56
56
57 send: function (content, callbacks) {
57 send: function (content, callbacks) {
58 if (this.comm !== undefined) {
58 if (this.comm !== undefined) {
59 var data = {method: 'custom', custom_content: content};
59 var data = {method: 'custom', custom_content: content};
60 this.comm.send(data, callbacks);
60 this.comm.send(data, callbacks);
61 }
61 }
62 },
62 },
63
63
64 // Handle when a widget is closed.
64 // Handle when a widget is closed.
65 _handle_comm_closed: function (msg) {
65 _handle_comm_closed: function (msg) {
66 this.trigger('comm:close');
66 this.trigger('comm:close');
67 delete this.comm.model; // Delete ref so GC will collect widget model.
67 delete this.comm.model; // Delete ref so GC will collect widget model.
68 delete this.comm;
68 delete this.comm;
69 delete this.model_id; // Delete id from model so widget manager cleans up.
69 delete this.model_id; // Delete id from model so widget manager cleans up.
70 // TODO: Handle deletion, like this.destroy(), and delete views, etc.
70 // TODO: Handle deletion, like this.destroy(), and delete views, etc.
71 },
71 },
72
72
73
73
74 // Handle incoming comm msg.
74 // Handle incoming comm msg.
75 _handle_comm_msg: function (msg) {
75 _handle_comm_msg: function (msg) {
76 var method = msg.content.data.method;
76 var method = msg.content.data.method;
77 switch (method) {
77 switch (method) {
78 case 'update':
78 case 'update':
79 this.apply_update(msg.content.data.state);
79 this.apply_update(msg.content.data.state);
80 break;
80 break;
81 case 'custom':
81 case 'custom':
82 this.trigger('msg:custom', msg.content.data.custom_content);
82 this.trigger('msg:custom', msg.content.data.custom_content);
83 break;
83 break;
84 case 'display':
84 case 'display':
85 this.widget_manager.display_view(msg.parent_header.msg_id, this);
85 this.widget_manager.display_view(msg.parent_header.msg_id, this);
86 break;
86 break;
87 }
87 }
88 },
88 },
89
89
90
90
91 // Handle when a widget is updated via the python side.
91 // Handle when a widget is updated via the python side.
92 apply_update: function (state) {
92 apply_update: function (state) {
93 for (var key in state) {
93 for (var key in state) {
94 if (state.hasOwnProperty(key)) {
94 if (state.hasOwnProperty(key)) {
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, this._unpack_models(value));
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 }
102 }
102 }
103 }
103 }
104 //TODO: are there callbacks that make sense in this case? If so, attach them here as an option
104 //TODO: are there callbacks that make sense in this case? If so, attach them here as an option
105 this.save();
105 this.save();
106 },
106 },
107
107
108
108
109 _handle_status: function (msg, callbacks) {
109 _handle_status: function (msg, callbacks) {
110 //execution_state : ('busy', 'idle', 'starting')
110 //execution_state : ('busy', 'idle', 'starting')
111 if (this.comm !== undefined) {
111 if (this.comm !== undefined) {
112 if (msg.content.execution_state ==='idle') {
112 if (msg.content.execution_state ==='idle') {
113 // Send buffer if this message caused another message to be
113 // Send buffer if this message caused another message to be
114 // throttled.
114 // throttled.
115 if (this.msg_buffer !== null &&
115 if (this.msg_buffer !== null &&
116 this.msg_throttle === this.pending_msgs) {
116 this.msg_throttle === this.pending_msgs) {
117 var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
117 var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
118 this.comm.send(data, callbacks);
118 this.comm.send(data, callbacks);
119 this.msg_buffer = null;
119 this.msg_buffer = null;
120 } else {
120 } else {
121 --this.pending_msgs;
121 --this.pending_msgs;
122 }
122 }
123 }
123 }
124 }
124 }
125 },
125 },
126
126
127
127
128 // Custom syncronization logic.
128 // Custom syncronization logic.
129 _handle_sync: function (method, options) {
129 _handle_sync: function (method, options) {
130 var model_json = this.toJSON();
130 var model_json = this.toJSON();
131 var attr;
131 var attr;
132
132
133 // Only send updated state if the state hasn't been changed
133 // Only send updated state if the state hasn't been changed
134 // during an update.
134 // during an update.
135 if (this.comm !== undefined) {
135 if (this.comm !== undefined) {
136 if (this.pending_msgs >= this.msg_throttle) {
136 if (this.pending_msgs >= this.msg_throttle) {
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 (this.msg_buffer === null) {
140 if (this.msg_buffer === null) {
141 this.msg_buffer = $.extend({}, model_json); // Copy
141 this.msg_buffer = $.extend({}, model_json); // Copy
142 }
142 }
143 for (attr in options.attrs) {
143 for (attr in options.attrs) {
144 var value = this._pack_models(options.attrs[attr]);
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]) {
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;
146 this.msg_buffer[attr] = value;
147 }
147 }
148 }
148 }
149
149
150 } else {
150 } else {
151 // We haven't exceeded the throttle, send the message like
151 // We haven't exceeded the throttle, send the message like
152 // normal. If this is a patch operation, just send the
152 // normal. If this is a patch operation, just send the
153 // changes.
153 // changes.
154 var send_json = model_json;
154 var send_json = model_json;
155 send_json = {};
155 send_json = {};
156 for (attr in options.attrs) {
156 for (attr in options.attrs) {
157 var value = this._pack_models(options.attrs[attr]);
157 var value = this._pack_models(options.attrs[attr]);
158 if (this.key_value_lock === null || attr !== this.key_value_lock[0] || value !== this.key_value_lock[1]) {
158 if (this.key_value_lock === null || attr !== this.key_value_lock[0] || value !== this.key_value_lock[1]) {
159 send_json[attr] = value;
159 send_json[attr] = value;
160 }
160 }
161 }
161 }
162
162
163 var is_empty = true;
163 var is_empty = true;
164 for (var prop in send_json) if (send_json.hasOwnProperty(prop)) is_empty = false;
164 for (var prop in send_json) if (send_json.hasOwnProperty(prop)) is_empty = false;
165 if (!is_empty) {
165 if (!is_empty) {
166 ++this.pending_msgs;
166 ++this.pending_msgs;
167 var data = {method: 'backbone', sync_data: send_json};
167 var data = {method: 'backbone', sync_data: send_json};
168 this.comm.send(data, options.callbacks);
168 this.comm.send(data, options.callbacks);
169 }
169 }
170 }
170 }
171 }
171 }
172
172
173 // Since the comm is a one-way communication, assume the message
173 // Since the comm is a one-way communication, assume the message
174 // arrived.
174 // arrived.
175 return model_json;
175 return model_json;
176 },
176 },
177
177
178 _pack_models: function(value) {
178 _pack_models: function(value) {
179 if (value instanceof Backbone.Model) {
179 if (value instanceof Backbone.Model) {
180 return value.id;
180 return value.id;
181 } else if (value instanceof Object) {
181 } else if (value instanceof Object) {
182 var packed = {};
182 var packed = {};
183 for (var key in value) {
183 for (var key in value) {
184 packed[key] = this._pack_models(value[key]);
184 packed[key] = this._pack_models(value[key]);
185 }
185 }
186 return packed;
186 return packed;
187 } else {
187 } else {
188 return value;
188 return value;
189 }
189 }
190 },
190 },
191
191
192 _unpack_models: function(value) {
192 _unpack_models: function(value) {
193 if (value instanceof Object) {
193 if (value instanceof Object) {
194 var unpacked = {};
194 var unpacked = {};
195 for (var key in value) {
195 for (var key in value) {
196 unpacked[key] = this._unpack_models(value[key]);
196 unpacked[key] = this._unpack_models(value[key]);
197 }
197 }
198 return unpacked;
198 return unpacked;
199 } else {
199 } else {
200 var model = this.widget_manager.get_model(value);
200 var model = this.widget_manager.get_model(value);
201 if (model !== null) {
201 if (model !== null) {
202 return model;
202 return model;
203 } else {
203 } else {
204 return value;
204 return value;
205 }
205 }
206 }
206 }
207 },
207 },
208
208
209 });
209 });
210 widget_manager.register_widget_model('WidgetModel', WidgetModel);
210 widget_manager.register_widget_model('WidgetModel', WidgetModel);
211
211
212
212
213 //--------------------------------------------------------------------
213 //--------------------------------------------------------------------
214 // WidgetView class
214 // WidgetView class
215 //--------------------------------------------------------------------
215 //--------------------------------------------------------------------
216 var WidgetView = Backbone.View.extend({
216 var WidgetView = Backbone.View.extend({
217 initialize: function(parameters) {
217 initialize: function(parameters) {
218 this.model.on('change',this.update,this);
218 this.model.on('change',this.update,this);
219 this.options = parameters.options;
219 this.options = parameters.options;
220 this.child_views = [];
220 this.child_views = [];
221 this.model.views.push(this);
221 this.model.views.push(this);
222 },
222 },
223
223
224 update: function(){
224 update: function(){
225 // update view to be consistent with this.model
225 // update view to be consistent with this.model
226 // triggered on model change
226 // triggered on model change
227 },
227 },
228
228
229 child_view: function(child_model, options) {
229 create_child_view: function(child_model, options) {
230 // create and return a child view, given a model and (optionally) a view name
230 // create and return a child view, given a model and (optionally) a view name
231 // if the view name is not given, it defaults to the model's default view attribute
231 // if the view name is not given, it defaults to the model's default view attribute
232 var child_view = this.model.widget_manager.create_view(child_model, options);
232 var child_view = this.model.widget_manager.create_view(child_model, options);
233 this.child_views[child_model.id] = child_view;
233 this.child_views[child_model.id] = child_view;
234 return child_view;
234 return child_view;
235 },
235 },
236
236
237 update_child_views: function(old_list, new_list) {
237 delete_child_view: function(child_model, options) {
238 // this function takes an old list and new list of models
238 var view = this.child_views[child_model.id];
239 // views in child_views that correspond to deleted ids are deleted
239 delete this.child_views[child_model.id];
240 // views corresponding to added ids are added child_views
240 view.remove();
241
242 // delete old views
243 _.each(_.difference(old_list, new_list), function(element, index, list) {
244 var view = this.child_views[element.id];
245 delete this.child_views[element.id];
246 view.remove();
247 }, this);
248
249 // add new views
250 _.each(_.difference(new_list, old_list), function(element, index, list) {
251 // this function adds the view to the child_views dictionary
252 this.child_view(element);
253 }, this);
254 },
241 },
255
242
256 do_diff: function(old_list, new_list, removed_callback, added_callback) {
243 do_diff: function(old_list, new_list, removed_callback, added_callback) {
257 // Difference a changed list and call remove and add callbacks for
244 // Difference a changed list and call remove and add callbacks for
258 // each removed and added item in the new list.
245 // each removed and added item in the new list.
259 //
246 //
260 // Parameters
247 // Parameters
261 // ----------
248 // ----------
262 // old_list : array
249 // old_list : array
263 // new_list : array
250 // new_list : array
264 // removed_callback : Callback(item)
251 // removed_callback : Callback(item)
265 // Callback that is called for each item removed.
252 // Callback that is called for each item removed.
266 // added_callback : Callback(item)
253 // added_callback : Callback(item)
267 // Callback that is called for each item added.
254 // Callback that is called for each item added.
268
255
269
256
270 // removed items
257 // removed items
271 _.each(_.difference(old_list, new_list), function(item, index, list) {
258 _.each(_.difference(old_list, new_list), function(item, index, list) {
272 removed_callback(item);
259 removed_callback(item);
273 }, this);
260 }, this);
274
261
275 // added items
262 // added items
276 _.each(_.difference(new_list, old_list), function(item, index, list) {
263 _.each(_.difference(new_list, old_list), function(item, index, list) {
277 added_callback(item);
264 added_callback(item);
278 }, this);
265 }, this);
279 },
266 },
280
267
281 callbacks: function(){
268 callbacks: function(){
282 return this.model.widget_manager.callbacks(this);
269 return this.model.widget_manager.callbacks(this);
283 },
270 },
284
271
285 render: function(){
272 render: function(){
286 // render the view. By default, this is only called the first time the view is created
273 // render the view. By default, this is only called the first time the view is created
287 },
274 },
288 send: function (content) {
275 send: function (content) {
289 this.model.send(content, this.callbacks());
276 this.model.send(content, this.callbacks());
290 },
277 },
291
278
292 touch: function () {
279 touch: function () {
293 this.model.save(this.model.changedAttributes(), {patch: true, callbacks: this.callbacks()});
280 this.model.save(this.model.changedAttributes(), {patch: true, callbacks: this.callbacks()});
294 },
281 },
295
282
296 });
283 });
297
284
298 var DOMWidgetView = WidgetView.extend({
285 var DOMWidgetView = WidgetView.extend({
299 initialize: function (options) {
286 initialize: function (options) {
300 // TODO: make changes more granular (e.g., trigger on visible:change)
287 // TODO: make changes more granular (e.g., trigger on visible:change)
301 this.model.on('change', this.update, this);
288 this.model.on('change', this.update, this);
302 this.model.on('msg:custom', this.on_msg, this);
289 this.model.on('msg:custom', this.on_msg, this);
303 DOMWidgetView.__super__.initialize.apply(this, arguments);
290 DOMWidgetView.__super__.initialize.apply(this, arguments);
304 },
291 },
305
292
306 on_msg: function(msg) {
293 on_msg: function(msg) {
307 switch(msg.msg_type) {
294 switch(msg.msg_type) {
308 case 'add_class':
295 case 'add_class':
309 this.add_class(msg.selector, msg.class_list);
296 this.add_class(msg.selector, msg.class_list);
310 break;
297 break;
311 case 'remove_class':
298 case 'remove_class':
312 this.remove_class(msg.selector, msg.class_list);
299 this.remove_class(msg.selector, msg.class_list);
313 break;
300 break;
314 }
301 }
315 },
302 },
316
303
317 add_class: function (selector, class_list) {
304 add_class: function (selector, class_list) {
318 this._get_selector_element(selector).addClass(class_list);
305 this._get_selector_element(selector).addClass(class_list);
319 },
306 },
320
307
321 remove_class: function (selector, class_list) {
308 remove_class: function (selector, class_list) {
322 this._get_selector_element(selector).removeClass(class_list);
309 this._get_selector_element(selector).removeClass(class_list);
323 },
310 },
324
311
325 update: function () {
312 update: function () {
326 // Update the contents of this view
313 // Update the contents of this view
327 //
314 //
328 // Called when the model is changed. The model may have been
315 // Called when the model is changed. The model may have been
329 // changed by another view or by a state update from the back-end.
316 // changed by another view or by a state update from the back-end.
330 // The very first update seems to happen before the element is
317 // The very first update seems to happen before the element is
331 // finished rendering so we use setTimeout to give the element time
318 // finished rendering so we use setTimeout to give the element time
332 // to render
319 // to render
333 var e = this.$el;
320 var e = this.$el;
334 var visible = this.model.get('visible');
321 var visible = this.model.get('visible');
335 setTimeout(function() {e.toggle(visible)},0);
322 setTimeout(function() {e.toggle(visible)},0);
336
323
337 var css = this.model.get('_css');
324 var css = this.model.get('_css');
338 if (css === undefined) {return;}
325 if (css === undefined) {return;}
339 for (var selector in css) {
326 for (var selector in css) {
340 if (css.hasOwnProperty(selector)) {
327 if (css.hasOwnProperty(selector)) {
341 // Apply the css traits to all elements that match the selector.
328 // Apply the css traits to all elements that match the selector.
342 var elements = this._get_selector_element(selector);
329 var elements = this._get_selector_element(selector);
343 if (elements.length > 0) {
330 if (elements.length > 0) {
344 var css_traits = css[selector];
331 var css_traits = css[selector];
345 for (var css_key in css_traits) {
332 for (var css_key in css_traits) {
346 if (css_traits.hasOwnProperty(css_key)) {
333 if (css_traits.hasOwnProperty(css_key)) {
347 elements.css(css_key, css_traits[css_key]);
334 elements.css(css_key, css_traits[css_key]);
348 }
335 }
349 }
336 }
350 }
337 }
351 }
338 }
352 }
339 }
353 },
340 },
354
341
355 _get_selector_element: function (selector) {
342 _get_selector_element: function (selector) {
356 // Get the elements via the css selector. If the selector is
343 // Get the elements via the css selector. If the selector is
357 // blank, apply the style to the $el_to_style element. If
344 // blank, apply the style to the $el_to_style element. If
358 // the $el_to_style element is not defined, use apply the
345 // the $el_to_style element is not defined, use apply the
359 // style to the view's element.
346 // style to the view's element.
360 var elements;
347 var elements;
361 if (selector === undefined || selector === null || selector === '') {
348 if (selector === undefined || selector === null || selector === '') {
362 if (this.$el_to_style === undefined) {
349 if (this.$el_to_style === undefined) {
363 elements = this.$el;
350 elements = this.$el;
364 } else {
351 } else {
365 elements = this.$el_to_style;
352 elements = this.$el_to_style;
366 }
353 }
367 } else {
354 } else {
368 elements = this.$el.find(selector);
355 elements = this.$el.find(selector);
369 }
356 }
370 return elements;
357 return elements;
371 },
358 },
372 });
359 });
373
360
374 IPython.WidgetModel = WidgetModel;
361 IPython.WidgetModel = WidgetModel;
375 IPython.WidgetView = WidgetView;
362 IPython.WidgetView = WidgetView;
376 IPython.DOMWidgetView = DOMWidgetView;
363 IPython.DOMWidgetView = DOMWidgetView;
377
364
378 return widget_manager;
365 return widget_manager;
379 });
366 });
@@ -1,252 +1,270 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 // ContainerWidget
9 // ContainerWidget
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/widgets/widget"], function(widget_manager) {
17 define(["notebook/js/widgets/widget"], function(widget_manager) {
18 var ContainerView = IPython.DOMWidgetView.extend({
18 var ContainerView = IPython.DOMWidgetView.extend({
19
19
20 render: function(){
20 render: function(){
21 this.$el
21 this.$el
22 .addClass('widget-container');
22 .addClass('widget-container');
23 this.children={};
23 this.children={};
24 this.update_children([], this.model.get('children'));
24 this.update_children([], this.model.get('children'));
25 this.model.on('change:children', function(model, value, options) {
25 this.model.on('change:children', function(model, value, options) {
26 this.update_children(model.previous('children'), value);
26 this.update_children(model.previous('children'), value);
27 }, this);
27 }, this);
28 this.update()
28 this.update()
29 },
29 },
30
30
31 update_children: function(old_list, new_list) {
31 update_children: function(old_list, new_list) {
32 this.$el.empty();
32 this.do_diff(old_list,
33 this.update_child_views(old_list, new_list);
33 new_list,
34 _.each(new_list, function(element, index, list) {
34 $.proxy(this.remove_child_model, this),
35 this.$el.append(this.child_views[element.id].$el);
35 $.proxy(this.add_child_model, this));
36 }, this)
36 },
37
38 remove_child_model: function(model) {
39 this.child_views[model.id].remove();
40 this.delete_child_view(model);
41 },
42
43 add_child_model: function(model) {
44 var view = this.create_child_view(model);
45 this.$el.append(view.$el);
37 },
46 },
38
47
39 update: function(){
48 update: function(){
40 // Update the contents of this view
49 // Update the contents of this view
41 //
50 //
42 // Called when the model is changed. The model may have been
51 // Called when the model is changed. The model may have been
43 // changed by another view or by a state update from the back-end.
52 // changed by another view or by a state update from the back-end.
44 return ContainerView.__super__.update.apply(this);
53 return ContainerView.__super__.update.apply(this);
45 },
54 },
46 });
55 });
47
56
48 widget_manager.register_widget_view('ContainerView', ContainerView);
57 widget_manager.register_widget_view('ContainerView', ContainerView);
49
58
50
59
51 var ModalView = IPython.DOMWidgetView.extend({
60 var ModalView = IPython.DOMWidgetView.extend({
52
61
53 render: function(){
62 render: function(){
54 var that = this;
63 var that = this;
55 this.children={};
64 this.children={};
56 this.update_children([], this.model.get('children'));
65 this.update_children([], this.model.get('children'));
57 this.model.on('change:children', function(model, value, options) {
66 this.model.on('change:children', function(model, value, options) {
58 this.update_children(model.previous('children'), value);
67 this.update_children(model.previous('children'), value);
59 }, this);
68 }, this);
60
69
61 this.$el
70 this.$el
62 .html('')
71 .html('')
63 .on("remove", function(){
72 .on("remove", function(){
64 that.$window.remove();
73 that.$window.remove();
65 });
74 });
66 this.$window = $('<div />')
75 this.$window = $('<div />')
67 .addClass('modal widget-modal')
76 .addClass('modal widget-modal')
68 .appendTo($('#notebook-container'))
77 .appendTo($('#notebook-container'))
69 .mousedown(function(){
78 .mousedown(function(){
70 that.bring_to_front();
79 that.bring_to_front();
71 });
80 });
72 this.$title_bar = $('<div />')
81 this.$title_bar = $('<div />')
73 .addClass('popover-title')
82 .addClass('popover-title')
74 .appendTo(this.$window)
83 .appendTo(this.$window)
75 .mousedown(function(){
84 .mousedown(function(){
76 that.bring_to_front();
85 that.bring_to_front();
77 });
86 });
78 this.$close = $('<button />')
87 this.$close = $('<button />')
79 .addClass('close icon-remove')
88 .addClass('close icon-remove')
80 .css('margin-left', '5px')
89 .css('margin-left', '5px')
81 .appendTo(this.$title_bar)
90 .appendTo(this.$title_bar)
82 .click(function(){
91 .click(function(){
83 that.hide();
92 that.hide();
84 event.stopPropagation();
93 event.stopPropagation();
85 });
94 });
86 this.$minimize = $('<button />')
95 this.$minimize = $('<button />')
87 .addClass('close icon-arrow-down')
96 .addClass('close icon-arrow-down')
88 .appendTo(this.$title_bar)
97 .appendTo(this.$title_bar)
89 .click(function(){
98 .click(function(){
90 that.popped_out = !that.popped_out;
99 that.popped_out = !that.popped_out;
91 if (!that.popped_out) {
100 if (!that.popped_out) {
92 that.$minimize
101 that.$minimize
93 .removeClass('icon-arrow-down')
102 .removeClass('icon-arrow-down')
94 .addClass('icon-arrow-up');
103 .addClass('icon-arrow-up');
95
104
96 that.$window
105 that.$window
97 .draggable('destroy')
106 .draggable('destroy')
98 .resizable('destroy')
107 .resizable('destroy')
99 .removeClass('widget-modal modal')
108 .removeClass('widget-modal modal')
100 .addClass('docked-widget-modal')
109 .addClass('docked-widget-modal')
101 .detach()
110 .detach()
102 .insertBefore(that.$show_button);
111 .insertBefore(that.$show_button);
103 that.$show_button.hide();
112 that.$show_button.hide();
104 that.$close.hide();
113 that.$close.hide();
105 } else {
114 } else {
106 that.$minimize
115 that.$minimize
107 .addClass('icon-arrow-down')
116 .addClass('icon-arrow-down')
108 .removeClass('icon-arrow-up');
117 .removeClass('icon-arrow-up');
109
118
110 that.$window
119 that.$window
111 .removeClass('docked-widget-modal')
120 .removeClass('docked-widget-modal')
112 .addClass('widget-modal modal')
121 .addClass('widget-modal modal')
113 .detach()
122 .detach()
114 .appendTo($('#notebook-container'))
123 .appendTo($('#notebook-container'))
115 .draggable({handle: '.popover-title', snap: '#notebook, .modal', snapMode: 'both'})
124 .draggable({handle: '.popover-title', snap: '#notebook, .modal', snapMode: 'both'})
116 .resizable()
125 .resizable()
117 .children('.ui-resizable-handle').show();
126 .children('.ui-resizable-handle').show();
118 that.show();
127 that.show();
119 that.$show_button.show();
128 that.$show_button.show();
120 that.$close.show();
129 that.$close.show();
121 }
130 }
122 event.stopPropagation();
131 event.stopPropagation();
123 });
132 });
124 this.$title = $('<div />')
133 this.$title = $('<div />')
125 .addClass('widget-modal-title')
134 .addClass('widget-modal-title')
126 .html('&nbsp;')
135 .html('&nbsp;')
127 .appendTo(this.$title_bar);
136 .appendTo(this.$title_bar);
128 this.$body = $('<div />')
137 this.$body = $('<div />')
129 .addClass('modal-body')
138 .addClass('modal-body')
130 .addClass('widget-modal-body')
139 .addClass('widget-modal-body')
131 .addClass('widget-container')
140 .addClass('widget-container')
132 .appendTo(this.$window);
141 .appendTo(this.$window);
133
142
134 this.$show_button = $('<button />')
143 this.$show_button = $('<button />')
135 .html('&nbsp;')
144 .html('&nbsp;')
136 .addClass('btn btn-info widget-modal-show')
145 .addClass('btn btn-info widget-modal-show')
137 .appendTo(this.$el)
146 .appendTo(this.$el)
138 .click(function(){
147 .click(function(){
139 that.show();
148 that.show();
140 });
149 });
141
150
142 this.$window.draggable({handle: '.popover-title', snap: '#notebook, .modal', snapMode: 'both'});
151 this.$window.draggable({handle: '.popover-title', snap: '#notebook, .modal', snapMode: 'both'});
143 this.$window.resizable();
152 this.$window.resizable();
144 this.$window.on('resize', function(){
153 this.$window.on('resize', function(){
145 that.$body.outerHeight(that.$window.innerHeight() - that.$title_bar.outerHeight());
154 that.$body.outerHeight(that.$window.innerHeight() - that.$title_bar.outerHeight());
146 });
155 });
147
156
148 this.$el_to_style = this.$body;
157 this.$el_to_style = this.$body;
149 this._shown_once = false;
158 this._shown_once = false;
150 this.popped_out = true;
159 this.popped_out = true;
151 },
160 },
152
161
153 hide: function() {
162 hide: function() {
154 this.$window.hide();
163 this.$window.hide();
155 this.$show_button.removeClass('btn-info');
164 this.$show_button.removeClass('btn-info');
156 },
165 },
157
166
158 show: function() {
167 show: function() {
159 this.$show_button.addClass('btn-info');
168 this.$show_button.addClass('btn-info');
160
169
161 this.$window.show();
170 this.$window.show();
162 if (this.popped_out) {
171 if (this.popped_out) {
163 this.$window.css("positon", "absolute");
172 this.$window.css("positon", "absolute");
164 this.$window.css("top", "0px");
173 this.$window.css("top", "0px");
165 this.$window.css("left", Math.max(0, (($('body').outerWidth() - this.$window.outerWidth()) / 2) +
174 this.$window.css("left", Math.max(0, (($('body').outerWidth() - this.$window.outerWidth()) / 2) +
166 $(window).scrollLeft()) + "px");
175 $(window).scrollLeft()) + "px");
167 this.bring_to_front();
176 this.bring_to_front();
168 }
177 }
169 },
178 },
170
179
171 bring_to_front: function() {
180 bring_to_front: function() {
172 var $widget_modals = $(".widget-modal");
181 var $widget_modals = $(".widget-modal");
173 var max_zindex = 0;
182 var max_zindex = 0;
174 $widget_modals.each(function (index, el){
183 $widget_modals.each(function (index, el){
175 max_zindex = Math.max(max_zindex, parseInt($(el).css('z-index')));
184 max_zindex = Math.max(max_zindex, parseInt($(el).css('z-index')));
176 });
185 });
177
186
178 // Start z-index of widget modals at 2000
187 // Start z-index of widget modals at 2000
179 max_zindex = Math.max(max_zindex, 2000);
188 max_zindex = Math.max(max_zindex, 2000);
180
189
181 $widget_modals.each(function (index, el){
190 $widget_modals.each(function (index, el){
182 $el = $(el);
191 $el = $(el);
183 if (max_zindex == parseInt($el.css('z-index'))) {
192 if (max_zindex == parseInt($el.css('z-index'))) {
184 $el.css('z-index', max_zindex - 1);
193 $el.css('z-index', max_zindex - 1);
185 }
194 }
186 });
195 });
187 this.$window.css('z-index', max_zindex);
196 this.$window.css('z-index', max_zindex);
188 },
197 },
189
198
190 update_children: function(old_list, new_list) {
199 update_children: function(old_list, new_list) {
191 this.$el.empty();
200 this.do_diff(old_list,
192 this.update_child_views(old_list, new_list);
201 new_list,
193 _.each(new_list, function(element, index, list) {
202 $.proxy(this.remove_child_model, this),
194 this.$body.append(this.child_views[element].$el);
203 $.proxy(this.add_child_model, this));
195 }, this)
204 },
205
206 remove_child_model: function(model) {
207 this.child_views[model.id].remove();
208 this.delete_child_view(model);
209 },
210
211 add_child_model: function(model) {
212 var view = this.create_child_view(model);
213 this.$body.append(view.$el);
196 },
214 },
197
215
198 update: function(){
216 update: function(){
199 // Update the contents of this view
217 // Update the contents of this view
200 //
218 //
201 // Called when the model is changed. The model may have been
219 // Called when the model is changed. The model may have been
202 // changed by another view or by a state update from the back-end.
220 // changed by another view or by a state update from the back-end.
203 var description = this.model.get('description');
221 var description = this.model.get('description');
204 description = description.replace(/ /g, '&nbsp;', 'm');
222 description = description.replace(/ /g, '&nbsp;', 'm');
205 description = description.replace(/\n/g, '<br>\n', 'm');
223 description = description.replace(/\n/g, '<br>\n', 'm');
206 if (description.length === 0) {
224 if (description.length === 0) {
207 this.$title.html('&nbsp;'); // Preserve title height
225 this.$title.html('&nbsp;'); // Preserve title height
208 } else {
226 } else {
209 this.$title.html(description);
227 this.$title.html(description);
210 }
228 }
211
229
212 var button_text = this.model.get('button_text');
230 var button_text = this.model.get('button_text');
213 button_text = button_text.replace(/ /g, '&nbsp;', 'm');
231 button_text = button_text.replace(/ /g, '&nbsp;', 'm');
214 button_text = button_text.replace(/\n/g, '<br>\n', 'm');
232 button_text = button_text.replace(/\n/g, '<br>\n', 'm');
215 if (button_text.length === 0) {
233 if (button_text.length === 0) {
216 this.$show_button.html('&nbsp;'); // Preserve button height
234 this.$show_button.html('&nbsp;'); // Preserve button height
217 } else {
235 } else {
218 this.$show_button.html(button_text);
236 this.$show_button.html(button_text);
219 }
237 }
220
238
221 if (!this._shown_once) {
239 if (!this._shown_once) {
222 this._shown_once = true;
240 this._shown_once = true;
223 this.show();
241 this.show();
224 }
242 }
225
243
226 return ModalView.__super__.update.apply(this);
244 return ModalView.__super__.update.apply(this);
227 },
245 },
228
246
229 _get_selector_element: function(selector) {
247 _get_selector_element: function(selector) {
230
248
231 // Since the modal actually isn't within the $el in the DOM, we need to extend
249 // Since the modal actually isn't within the $el in the DOM, we need to extend
232 // the selector logic to allow the user to set css on the modal if need be.
250 // the selector logic to allow the user to set css on the modal if need be.
233 // The convention used is:
251 // The convention used is:
234 // "modal" - select the modal div
252 // "modal" - select the modal div
235 // "modal [selector]" - select element(s) within the modal div.
253 // "modal [selector]" - select element(s) within the modal div.
236 // "[selector]" - select elements within $el
254 // "[selector]" - select elements within $el
237 // "" - select the $el_to_style
255 // "" - select the $el_to_style
238 if (selector.substring(0, 5) == 'modal') {
256 if (selector.substring(0, 5) == 'modal') {
239 if (selector == 'modal') {
257 if (selector == 'modal') {
240 return this.$window;
258 return this.$window;
241 } else {
259 } else {
242 return this.$window.find(selector.substring(6));
260 return this.$window.find(selector.substring(6));
243 }
261 }
244 } else {
262 } else {
245 return ModalView.__super__._get_selector_element.apply(this, [selector]);
263 return ModalView.__super__._get_selector_element.apply(this, [selector]);
246 }
264 }
247 },
265 },
248
266
249 });
267 });
250
268
251 widget_manager.register_widget_view('ModalView', ModalView);
269 widget_manager.register_widget_view('ModalView', ModalView);
252 });
270 });
@@ -1,222 +1,248 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 // SelectionContainerWidget
9 // SelectionContainerWidget
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/widgets/widget"], function(widget_manager){
17 define(["notebook/js/widgets/widget"], function(widget_manager){
18 var AccordionView = IPython.DOMWidgetView.extend({
18 var AccordionView = IPython.DOMWidgetView.extend({
19
19
20 render: function(){
20 render: function(){
21 var guid = 'accordion' + IPython.utils.uuid();
21 var guid = 'accordion' + IPython.utils.uuid();
22 this.$el
22 this.$el
23 .attr('id', guid)
23 .attr('id', guid)
24 .addClass('accordion');
24 .addClass('accordion');
25 this.containers = [];
25 this.containers = [];
26 this.model_containers = {};
26 this.update_children([], this.model.get('children'));
27 this.update_children([], this.model.get('children'));
27 this.model.on('change:children', function(model, value, options) {
28 this.model.on('change:children', function(model, value, options) {
28 this.update_children(model.previous('children'), value);
29 this.update_children(model.previous('children'), value);
29 }, this);
30 }, this);
30 },
31 },
31
32
32 update_children: function(old_list, new_list) {
33 _.each(this.containers, function(element, index, list) {
34 element.remove();
35 }, this);
36 this.containers = [];
37 this.update_child_views(old_list, new_list);
38 _.each(new_list, function(element, index, list) {
39 this.add_child_view(this.child_views[element]);
40 }, this)
41 },
42
43
44 update: function(options) {
33 update: function(options) {
45 // Update the contents of this view
34 // Update the contents of this view
46 //
35 //
47 // Called when the model is changed. The model may have been
36 // Called when the model is changed. The model may have been
48 // changed by another view or by a state update from the back-end.
37 // changed by another view or by a state update from the back-end.
49
38
50 if (options === undefined || options.updated_view != this) {
39 if (options === undefined || options.updated_view != this) {
51 // Set tab titles
40 // Set tab titles
52 var titles = this.model.get('_titles');
41 var titles = this.model.get('_titles');
53 for (var page_index in titles) {
42 for (var page_index in titles) {
54
43
55 var accordian = this.containers[page_index];
44 var accordian = this.containers[page_index];
56 if (accordian !== undefined) {
45 if (accordian !== undefined) {
57 accordian
46 accordian
58 .find('.accordion-heading')
47 .find('.accordion-heading')
59 .find('.accordion-toggle')
48 .find('.accordion-toggle')
60 .html(titles[page_index]);
49 .html(titles[page_index]);
61 }
50 }
62 }
51 }
63
52
64 // Set selected page
53 // Set selected page
65 var selected_index = this.model.get("selected_index");
54 var selected_index = this.model.get("selected_index");
66 if (0 <= selected_index && selected_index < this.containers.length) {
55 if (0 <= selected_index && selected_index < this.containers.length) {
67 for (var index in this.containers) {
56 for (var index in this.containers) {
68 if (index==selected_index) {
57 if (index==selected_index) {
69 this.containers[index].find('.accordion-body').collapse('show');
58 this.containers[index].find('.accordion-body').collapse('show');
70 } else {
59 } else {
71 this.containers[index].find('.accordion-body').collapse('hide');
60 this.containers[index].find('.accordion-body').collapse('hide');
72 }
61 }
73
62
74 }
63 }
75 }
64 }
76 }
65 }
77 return AccordionView.__super__.update.apply(this);
66 return AccordionView.__super__.update.apply(this);
78 },
67 },
68
69 update_children: function(old_list, new_list) {
70 this.do_diff(old_list,
71 new_list,
72 $.proxy(this.remove_child_model, this),
73 $.proxy(this.add_child_model, this));
74 },
79
75
80 add_child_view: function(view) {
76 remove_child_model: function(model) {
77 var accordion_group = this.model_containers[model.id];
78 this.containers.splice(accordion_group.container_index, 1);
79 delete this.model_containers[model.id];
80 accordion_group.remove();
81 this.delete_child_view(model);
82 },
81
83
84 add_child_model: function(model) {
85 var view = this.create_child_view(model);
82 var index = this.containers.length;
86 var index = this.containers.length;
83 var uuid = IPython.utils.uuid();
87 var uuid = IPython.utils.uuid();
84 var accordion_group = $('<div />')
88 var accordion_group = $('<div />')
85 .addClass('accordion-group')
89 .addClass('accordion-group')
86 .appendTo(this.$el);
90 .appendTo(this.$el);
87 var accordion_heading = $('<div />')
91 var accordion_heading = $('<div />')
88 .addClass('accordion-heading')
92 .addClass('accordion-heading')
89 .appendTo(accordion_group);
93 .appendTo(accordion_group);
90 var that = this;
94 var that = this;
91 var accordion_toggle = $('<a />')
95 var accordion_toggle = $('<a />')
92 .addClass('accordion-toggle')
96 .addClass('accordion-toggle')
93 .attr('data-toggle', 'collapse')
97 .attr('data-toggle', 'collapse')
94 .attr('data-parent', '#' + this.$el.attr('id'))
98 .attr('data-parent', '#' + this.$el.attr('id'))
95 .attr('href', '#' + uuid)
99 .attr('href', '#' + uuid)
96 .click(function(evt){
100 .click(function(evt){
97
101
98 // Calling model.set will trigger all of the other views of the
102 // Calling model.set will trigger all of the other views of the
99 // model to update.
103 // model to update.
100 that.model.set("selected_index", index, {updated_view: this});
104 that.model.set("selected_index", index, {updated_view: this});
101 that.touch();
105 that.touch();
102 })
106 })
103 .html('Page ' + index)
107 .html('Page ' + index)
104 .appendTo(accordion_heading);
108 .appendTo(accordion_heading);
105 var accordion_body = $('<div />', {id: uuid})
109 var accordion_body = $('<div />', {id: uuid})
106 .addClass('accordion-body collapse')
110 .addClass('accordion-body collapse')
107 .appendTo(accordion_group);
111 .appendTo(accordion_group);
108 var accordion_inner = $('<div />')
112 var accordion_inner = $('<div />')
109 .addClass('accordion-inner')
113 .addClass('accordion-inner')
110 .appendTo(accordion_body);
114 .appendTo(accordion_body);
111 this.containers.push(accordion_group);
115 var container_index = this.containers.push(accordion_group) - 1;
116 accordion_group.container_index = container_index;
117 this.model_containers[model.id] = accordion_group;
112 accordion_inner.append(view.$el);
118 accordion_inner.append(view.$el);
113
119
114 this.update();
120 this.update();
115
121
116 // Stupid workaround to close the bootstrap accordion tabs which
122 // Stupid workaround to close the bootstrap accordion tabs which
117 // open by default even though they don't have the `in` class
123 // open by default even though they don't have the `in` class
118 // attached to them. For some reason a delay is required.
124 // attached to them. For some reason a delay is required.
119 // TODO: Better fix.
125 // TODO: Better fix.
120 setTimeout(function(){ that.update(); }, 500);
126 setTimeout(function(){ that.update(); }, 500);
121 },
127 },
122 });
128 });
123
129
124 widget_manager.register_widget_view('AccordionView', AccordionView);
130 widget_manager.register_widget_view('AccordionView', AccordionView);
125
131
126 var TabView = IPython.DOMWidgetView.extend({
132 var TabView = IPython.DOMWidgetView.extend({
127
133
128 initialize: function() {
134 initialize: function() {
129 this.containers = [];
135 this.containers = [];
130 TabView.__super__.initialize.apply(this, arguments);
136 TabView.__super__.initialize.apply(this, arguments);
131 },
137 },
132
138
133 render: function(){
139 render: function(){
134 var uuid = 'tabs'+IPython.utils.uuid();
140 var uuid = 'tabs'+IPython.utils.uuid();
135 var that = this;
141 var that = this;
136 this.$tabs = $('<div />', {id: uuid})
142 this.$tabs = $('<div />', {id: uuid})
137 .addClass('nav')
143 .addClass('nav')
138 .addClass('nav-tabs')
144 .addClass('nav-tabs')
139 .appendTo(this.$el);
145 .appendTo(this.$el);
140 this.$tab_contents = $('<div />', {id: uuid + 'Content'})
146 this.$tab_contents = $('<div />', {id: uuid + 'Content'})
141 .addClass('tab-content')
147 .addClass('tab-content')
142 .appendTo(this.$el);
148 .appendTo(this.$el);
143 this.containers = [];
149 this.containers = [];
144 this.update_children([], this.model.get('children'));
150 this.update_children([], this.model.get('children'));
145 this.model.on('change:children', function(model, value, options) {
151 this.model.on('change:children', function(model, value, options) {
146 this.update_children(model.previous('children'), value);
152 this.update_children(model.previous('children'), value);
147 }, this);
153 }, this);
148 },
154 },
149
155
150 update_children: function(old_list, new_list) {
156 update_children: function(old_list, new_list) {
151 _.each(this.containers, function(element, index, list) {
157 _.each(this.containers, function(element, index, list) {
152 element.remove();
158 element.remove();
153 }, this);
159 }, this);
154 this.containers = [];
160 this.containers = [];
155 this.update_child_views(old_list, new_list);
161 this.update_child_views(old_list, new_list);
156 _.each(new_list, function(element, index, list) {
162 _.each(new_list, function(element, index, list) {
157 this.add_child_view(this.child_views[element]);
163 this.add_child_view(this.child_views[element]);
158 }, this)
164 }, this)
159 },
165 },
166
167 update_children: function(old_list, new_list) {
168 this.do_diff(old_list,
169 new_list,
170 $.proxy(this.remove_child_model, this),
171 $.proxy(this.add_child_model, this));
172 },
160
173
161 update: function(options) {
174 remove_child_model: function(model) {
162 // Update the contents of this view
175 var view = this.child_views[model.id];
163 //
176 this.containers.splice(view.parent_tab.tab_text_index, 1);
164 // Called when the model is changed. The model may have been
177 view.parent_tab.remove();
165 // changed by another view or by a state update from the back-end.
178 view.parent_container.remove();
166 if (options === undefined || options.updated_view != this) {
179 view.remove();
167 // Set tab titles
180 this.delete_child_view(model);
168 var titles = this.model.get('_titles');
169 for (var page_index in titles) {
170 var tab_text = this.containers[page_index];
171 if (tab_text !== undefined) {
172 tab_text.html(titles[page_index]);
173 }
174 }
175
176 var selected_index = this.model.get('selected_index');
177 if (0 <= selected_index && selected_index < this.containers.length) {
178 this.select_page(selected_index);
179 }
180 }
181 return TabView.__super__.update.apply(this);
182 },
181 },
183
182
184 add_child_view: function(view) {
183 add_child_model: function(model) {
184 var view = this.create_child_view(model);
185 var index = this.containers.length;
185 var index = this.containers.length;
186 var uuid = IPython.utils.uuid();
186 var uuid = IPython.utils.uuid();
187
187
188 var that = this;
188 var that = this;
189 var tab = $('<li />')
189 var tab = $('<li />')
190 .css('list-style-type', 'none')
190 .css('list-style-type', 'none')
191 .appendTo(this.$tabs);
191 .appendTo(this.$tabs);
192 view.parent_tab = tab;
193
192 var tab_text = $('<a />')
194 var tab_text = $('<a />')
193 .attr('href', '#' + uuid)
195 .attr('href', '#' + uuid)
194 .attr('data-toggle', 'tab')
196 .attr('data-toggle', 'tab')
195 .html('Page ' + index)
197 .html('Page ' + index)
196 .appendTo(tab)
198 .appendTo(tab)
197 .click(function (e) {
199 .click(function (e) {
198
200
199 // Calling model.set will trigger all of the other views of the
201 // Calling model.set will trigger all of the other views of the
200 // model to update.
202 // model to update.
201 that.model.set("selected_index", index, {updated_view: this});
203 that.model.set("selected_index", index, {updated_view: this});
202 that.touch();
204 that.touch();
203 that.select_page(index);
205 that.select_page(index);
204 });
206 });
205 this.containers.push(tab_text);
207 tab.tab_text_index = this.containers.push(tab_text) - 1;
206
208
207 var contents_div = $('<div />', {id: uuid})
209 var contents_div = $('<div />', {id: uuid})
208 .addClass('tab-pane')
210 .addClass('tab-pane')
209 .addClass('fade')
211 .addClass('fade')
210 .append(view.$el)
212 .append(view.$el)
211 .appendTo(this.$tab_contents);
213 .appendTo(this.$tab_contents);
214 view.parent_container = contents_div;
215 },
216
217 update: function(options) {
218 // Update the contents of this view
219 //
220 // Called when the model is changed. The model may have been
221 // changed by another view or by a state update from the back-end.
222 if (options === undefined || options.updated_view != this) {
223 // Set tab titles
224 var titles = this.model.get('_titles');
225 for (var page_index in titles) {
226 var tab_text = this.containers[page_index];
227 if (tab_text !== undefined) {
228 tab_text.html(titles[page_index]);
229 }
230 }
231
232 var selected_index = this.model.get('selected_index');
233 if (0 <= selected_index && selected_index < this.containers.length) {
234 this.select_page(selected_index);
235 }
236 }
237 return TabView.__super__.update.apply(this);
212 },
238 },
213
239
214 select_page: function(index) {
240 select_page: function(index) {
215 this.$tabs.find('li')
241 this.$tabs.find('li')
216 .removeClass('active');
242 .removeClass('active');
217 this.containers[index].tab('show');
243 this.containers[index].tab('show');
218 },
244 },
219 });
245 });
220
246
221 widget_manager.register_widget_view('TabView', TabView);
247 widget_manager.register_widget_view('TabView', TabView);
222 });
248 });
@@ -1,60 +1,60 b''
1 """SelectionContainerWidget class.
1 """SelectionContainerWidget class.
2
2
3 Represents a multipage container that can be used to group other widgets into
3 Represents a multipage container that can be used to group other widgets into
4 pages.
4 pages.
5 """
5 """
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (c) 2013, the IPython Development Team.
7 # Copyright (c) 2013, the IPython Development Team.
8 #
8 #
9 # Distributed under the terms of the Modified BSD License.
9 # Distributed under the terms of the Modified BSD License.
10 #
10 #
11 # The full license is in the file COPYING.txt, distributed with this software.
11 # The full license is in the file COPYING.txt, distributed with this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 from .widget import DOMWidget
17 from .widget import DOMWidget
18 from IPython.utils.traitlets import Unicode, Dict, Int, List, Instance
18 from IPython.utils.traitlets import Unicode, Dict, Int, List, Instance
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Classes
21 # Classes
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 class AccordionWidget(DOMWidget):
23 class AccordionWidget(DOMWidget):
24 view_name = Unicode('AccordionView', sync=True)
24 view_name = Unicode('AccordionView', sync=True)
25
25
26 # Keys
26 # Keys
27 _titles = Dict(help="Titles of the pages", sync=True)
27 _titles = Dict(help="Titles of the pages", sync=True)
28 selected_index = Int(0, sync=True)
28 selected_index = Int(0, sync=True)
29
29
30 children = List(Instance(DOMWidget))
30 children = List(Instance(DOMWidget), sync=True)
31
31
32 # Public methods
32 # Public methods
33 def set_title(self, index, title):
33 def set_title(self, index, title):
34 """Sets the title of a container page
34 """Sets the title of a container page
35
35
36 Parameters
36 Parameters
37 ----------
37 ----------
38 index : int
38 index : int
39 Index of the container page
39 Index of the container page
40 title : unicode
40 title : unicode
41 New title"""
41 New title"""
42 self._titles[index] = title
42 self._titles[index] = title
43 self.send_state('_titles')
43 self.send_state('_titles')
44
44
45
45
46 def get_title(self, index):
46 def get_title(self, index):
47 """Gets the title of a container pages
47 """Gets the title of a container pages
48
48
49 Parameters
49 Parameters
50 ----------
50 ----------
51 index : int
51 index : int
52 Index of the container page"""
52 Index of the container page"""
53 if index in self._titles:
53 if index in self._titles:
54 return self._titles[index]
54 return self._titles[index]
55 else:
55 else:
56 return None
56 return None
57
57
58
58
59 class TabWidget(AccordionWidget):
59 class TabWidget(AccordionWidget):
60 view_name = Unicode('TabView', sync=True)
60 view_name = Unicode('TabView', sync=True)
General Comments 0
You need to be logged in to leave comments. Login now