##// END OF EJS Templates
Removed for () loops where necessary. Replaced with _.each
Jonathan Frederic -
Show More
@@ -1,189 +1,185
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 (function () {
18 (function () {
19 "use strict";
19 "use strict";
20
20
21 // Use require.js 'define' method so that require.js is intelligent enough to
21 // Use require.js 'define' method so that require.js is intelligent enough to
22 // syncronously load everything within this file when it is being 'required'
22 // syncronously load everything within this file when it is being 'required'
23 // elsewhere.
23 // elsewhere.
24 define(["underscore",
24 define(["underscore",
25 "backbone",
25 "backbone",
26 ], function (Underscore, Backbone) {
26 ], function (Underscore, Backbone) {
27
27
28 //--------------------------------------------------------------------
28 //--------------------------------------------------------------------
29 // WidgetManager class
29 // WidgetManager class
30 //--------------------------------------------------------------------
30 //--------------------------------------------------------------------
31 var WidgetManager = function (comm_manager) {
31 var WidgetManager = function (comm_manager) {
32 // Public constructor
32 // Public constructor
33 WidgetManager._managers.push(this);
33 WidgetManager._managers.push(this);
34
34
35 // Attach a comm manager to the
35 // Attach a comm manager to the
36 this.comm_manager = comm_manager;
36 this.comm_manager = comm_manager;
37 this._models = {}; /* Dictionary of model ids and model instances */
37
38
38 // Register already-registered widget model types with the comm manager.
39 // Register already-registered widget model types with the comm manager.
39 for (var name in WidgetManager._model_types) {
40 _.each(WidgetManager._model_types, function(value, key) {
40 if (WidgetManager._model_types.hasOwnProperty(name)) {
41 this.comm_manager.register_target(value, $.proxy(this._handle_comm_open, this));
41 this.comm_manager.register_target(name, $.proxy(this._handle_comm_open, this));
42 });
42
43 }
44 }
45 };
43 };
46
44
47 //--------------------------------------------------------------------
45 //--------------------------------------------------------------------
48 // Class level
46 // Class level
49 //--------------------------------------------------------------------
47 //--------------------------------------------------------------------
50 WidgetManager._model_types = {}; /* Dictionary of model type names (target_name) and model types. */
48 WidgetManager._model_types = {}; /* Dictionary of model type names (target_name) and model types. */
51 WidgetManager._view_types = {}; /* Dictionary of view names and view types. */
49 WidgetManager._view_types = {}; /* Dictionary of view names and view types. */
52 WidgetManager._models = {}; /* Dictionary of model ids and model instances */
53 WidgetManager._managers = []; /* List of widget managers */
50 WidgetManager._managers = []; /* List of widget managers */
54
51
55 WidgetManager.register_widget_model = function (model_name, model_type) {
52 WidgetManager.register_widget_model = function (model_name, model_type) {
56 // Registers a widget model by name.
53 // Registers a widget model by name.
57 WidgetManager._model_types[model_name] = model_type;
54 WidgetManager._model_types[model_name] = model_type;
58
55
59 // Register the widget with the comm manager. Make sure to pass this object's context
56 // Register the widget with the comm manager. Make sure to pass this object's context
60 // in so `this` works in the call back.
57 // in so `this` works in the call back.
61 for (var i = 0; i < WidgetManager._managers.length; i++) {
58 _.each(WidgetManager._managers, function(instance, i) {
62 var instance = WidgetManager._managers[i];
63 if (instance.comm_manager !== null) {
59 if (instance.comm_manager !== null) {
64 instance.comm_manager.register_target(model_name, $.proxy(instance._handle_comm_open, instance));
60 instance.comm_manager.register_target(model_name, $.proxy(instance._handle_comm_open, instance));
65 }
61 }
66 }
62 });
67 };
63 };
68
64
69 WidgetManager.register_widget_view = function (view_name, view_type) {
65 WidgetManager.register_widget_view = function (view_name, view_type) {
70 // Registers a widget view by name.
66 // Registers a widget view by name.
71 WidgetManager._view_types[view_name] = view_type;
67 WidgetManager._view_types[view_name] = view_type;
72 };
68 };
73
69
74 //--------------------------------------------------------------------
70 //--------------------------------------------------------------------
75 // Instance level
71 // Instance level
76 //--------------------------------------------------------------------
72 //--------------------------------------------------------------------
77 WidgetManager.prototype.display_view = function(msg, model) {
73 WidgetManager.prototype.display_view = function(msg, model) {
78 var cell = this.get_msg_cell(msg.parent_header.msg_id);
74 var cell = this.get_msg_cell(msg.parent_header.msg_id);
79 if (cell === null) {
75 if (cell === null) {
80 console.log("Could not determine where the display" +
76 console.log("Could not determine where the display" +
81 " message was from. Widget will not be displayed");
77 " message was from. Widget will not be displayed");
82 } else {
78 } else {
83 var view = this.create_view(model, {cell: cell});
79 var view = this.create_view(model, {cell: cell});
84 if (view === undefined) {
80 if (view === undefined) {
85 console.error("View creation failed", model);
81 console.error("View creation failed", model);
86 }
82 }
87 if (cell.widget_subarea !== undefined
83 if (cell.widget_subarea !== undefined
88 && cell.widget_subarea !== null) {
84 && cell.widget_subarea !== null) {
89
85
90 cell.widget_area.show();
86 cell.widget_area.show();
91 cell.widget_subarea.append(view.$el);
87 cell.widget_subarea.append(view.$el);
92 }
88 }
93 }
89 }
94 },
90 },
95
91
96 WidgetManager.prototype.create_view = function(model, options) {
92 WidgetManager.prototype.create_view = function(model, options) {
97 var view_name = model.get('view_name');
93 var view_name = model.get('view_name');
98 var ViewType = WidgetManager._view_types[view_name];
94 var ViewType = WidgetManager._view_types[view_name];
99 if (ViewType !== undefined && ViewType !== null) {
95 if (ViewType !== undefined && ViewType !== null) {
100 var parameters = {model: model, options: options};
96 var parameters = {model: model, options: options};
101 var view = new ViewType(parameters);
97 var view = new ViewType(parameters);
102 view.render();
98 view.render();
103 IPython.keyboard_manager.register_events(view.$el);
99 IPython.keyboard_manager.register_events(view.$el);
104 model.views.push(view);
100 model.views.push(view);
105 model.on('destroy', view.remove, view);
101 model.on('destroy', view.remove, view);
106 return view;
102 return view;
107 }
103 }
108 },
104 },
109
105
110 WidgetManager.prototype.get_msg_cell = function (msg_id) {
106 WidgetManager.prototype.get_msg_cell = function (msg_id) {
111 var cell = null;
107 var cell = null;
112 // First, check to see if the msg was triggered by cell execution.
108 // First, check to see if the msg was triggered by cell execution.
113 if (IPython.notebook !== undefined && IPython.notebook !== null) {
109 if (IPython.notebook !== undefined && IPython.notebook !== null) {
114 cell = IPython.notebook.get_msg_cell(msg_id);
110 cell = IPython.notebook.get_msg_cell(msg_id);
115 }
111 }
116 if (cell !== null) {
112 if (cell !== null) {
117 return cell
113 return cell
118 }
114 }
119 // Second, check to see if a get_cell callback was defined
115 // Second, check to see if a get_cell callback was defined
120 // for the message. get_cell callbacks are registered for
116 // for the message. get_cell callbacks are registered for
121 // widget messages, so this block is actually checking to see if the
117 // widget messages, so this block is actually checking to see if the
122 // message was triggered by a widget.
118 // message was triggered by a widget.
123 var kernel = this.comm_manager.kernel;
119 var kernel = this.comm_manager.kernel;
124 if (kernel !== undefined && kernel !== null) {
120 if (kernel !== undefined && kernel !== null) {
125 var callbacks = kernel.get_callbacks_for_msg(msg_id);
121 var callbacks = kernel.get_callbacks_for_msg(msg_id);
126 if (callbacks !== undefined &&
122 if (callbacks !== undefined &&
127 callbacks.iopub !== undefined &&
123 callbacks.iopub !== undefined &&
128 callbacks.iopub.get_cell !== undefined) {
124 callbacks.iopub.get_cell !== undefined) {
129
125
130 return callbacks.iopub.get_cell();
126 return callbacks.iopub.get_cell();
131 }
127 }
132 }
128 }
133
129
134 // Not triggered by a cell or widget (no get_cell callback
130 // Not triggered by a cell or widget (no get_cell callback
135 // exists).
131 // exists).
136 return null;
132 return null;
137 };
133 };
138
134
139 WidgetManager.prototype.callbacks = function (view) {
135 WidgetManager.prototype.callbacks = function (view) {
140 // callback handlers specific a view
136 // callback handlers specific a view
141 var callbacks = {};
137 var callbacks = {};
142 var cell = view.options.cell;
138 var cell = view.options.cell;
143 if (cell !== null) {
139 if (cell !== null) {
144 // Try to get output handlers
140 // Try to get output handlers
145 var handle_output = null;
141 var handle_output = null;
146 var handle_clear_output = null;
142 var handle_clear_output = null;
147 if (cell.output_area !== undefined && cell.output_area !== null) {
143 if (cell.output_area !== undefined && cell.output_area !== null) {
148 handle_output = $.proxy(cell.output_area.handle_output, cell.output_area);
144 handle_output = $.proxy(cell.output_area.handle_output, cell.output_area);
149 handle_clear_output = $.proxy(cell.output_area.handle_clear_output, cell.output_area);
145 handle_clear_output = $.proxy(cell.output_area.handle_clear_output, cell.output_area);
150 }
146 }
151
147
152 // Create callback dict using what is known
148 // Create callback dict using what is known
153 var that = this;
149 var that = this;
154 callbacks = {
150 callbacks = {
155 iopub : {
151 iopub : {
156 output : handle_output,
152 output : handle_output,
157 clear_output : handle_clear_output,
153 clear_output : handle_clear_output,
158
154
159 // Special function only registered by widget messages.
155 // Special function only registered by widget messages.
160 // Allows us to get the cell for a message so we know
156 // Allows us to get the cell for a message so we know
161 // where to add widgets if the code requires it.
157 // where to add widgets if the code requires it.
162 get_cell : function () {
158 get_cell : function () {
163 return cell;
159 return cell;
164 },
160 },
165 },
161 },
166 };
162 };
167 }
163 }
168 return callbacks;
164 return callbacks;
169 };
165 };
170
166
171 WidgetManager.prototype.get_model = function (model_id) {
167 WidgetManager.prototype.get_model = function (model_id) {
172 var model = WidgetManager._models[model_id];
168 var model = this._models[model_id];
173 if (model !== undefined && model.id == model_id) {
169 if (model !== undefined && model.id == model_id) {
174 return model;
170 return model;
175 }
171 }
176 return null;
172 return null;
177 };
173 };
178
174
179 WidgetManager.prototype._handle_comm_open = function (comm, msg) {
175 WidgetManager.prototype._handle_comm_open = function (comm, msg) {
180 var model_id = comm.comm_id;
176 var model_id = comm.comm_id;
181 var widget_type_name = msg.content.target_name;
177 var widget_type_name = msg.content.target_name;
182 var widget_model = new WidgetManager._model_types[widget_type_name](this, model_id, comm);
178 var widget_model = new WidgetManager._model_types[widget_type_name](this, model_id, comm);
183 WidgetManager._models[model_id] = widget_model;
179 this._models[model_id] = widget_model;
184 };
180 };
185
181
186 IPython.WidgetManager = WidgetManager;
182 IPython.WidgetManager = WidgetManager;
187 return IPython.WidgetManager;
183 return IPython.WidgetManager;
188 });
184 });
189 }());
185 }());
@@ -1,414 +1,407
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(WidgetManager, Underscore, Backbone){
20 function(WidgetManager, Underscore, Backbone){
21
21
22 var WidgetModel = Backbone.Model.extend({
22 var WidgetModel = Backbone.Model.extend({
23 constructor: function (widget_manager, model_id, comm) {
23 constructor: function (widget_manager, model_id, comm) {
24 // Construcctor
24 // Construcctor
25 //
25 //
26 // Creates a WidgetModel instance.
26 // Creates a WidgetModel instance.
27 //
27 //
28 // Parameters
28 // Parameters
29 // ----------
29 // ----------
30 // widget_manager : WidgetManager instance
30 // widget_manager : WidgetManager instance
31 // model_id : string
31 // model_id : string
32 // An ID unique to this model.
32 // An ID unique to this model.
33 // comm : Comm instance (optional)
33 // comm : Comm instance (optional)
34 this.widget_manager = widget_manager;
34 this.widget_manager = widget_manager;
35 this.pending_msgs = 0;
35 this.pending_msgs = 0;
36 this.msg_throttle = 2;
36 this.msg_throttle = 2;
37 this.msg_buffer = null;
37 this.msg_buffer = null;
38 this.key_value_lock = null;
38 this.key_value_lock = null;
39 this.id = model_id;
39 this.id = model_id;
40 this.views = [];
40 this.views = [];
41
41
42 if (comm !== undefined) {
42 if (comm !== undefined) {
43 // Remember comm associated with the model.
43 // Remember comm associated with the model.
44 this.comm = comm;
44 this.comm = comm;
45 comm.model = this;
45 comm.model = this;
46
46
47 // Hook comm messages up to model.
47 // Hook comm messages up to model.
48 comm.on_close($.proxy(this._handle_comm_closed, this));
48 comm.on_close($.proxy(this._handle_comm_closed, this));
49 comm.on_msg($.proxy(this._handle_comm_msg, this));
49 comm.on_msg($.proxy(this._handle_comm_msg, this));
50 }
50 }
51 return Backbone.Model.apply(this);
51 return Backbone.Model.apply(this);
52 },
52 },
53
53
54 send: function (content, callbacks) {
54 send: function (content, callbacks) {
55 // Send a custom msg over the comm.
55 // Send a custom msg over the comm.
56 if (this.comm !== undefined) {
56 if (this.comm !== undefined) {
57 var data = {method: 'custom', content: content};
57 var data = {method: 'custom', content: content};
58 this.comm.send(data, callbacks);
58 this.comm.send(data, callbacks);
59 }
59 }
60 },
60 },
61
61
62 _handle_comm_closed: function (msg) {
62 _handle_comm_closed: function (msg) {
63 // Handle when a widget is closed.
63 // Handle when a widget is closed.
64 this.trigger('comm:close');
64 this.trigger('comm:close');
65 delete this.comm.model; // Delete ref so GC will collect widget model.
65 delete this.comm.model; // Delete ref so GC will collect widget model.
66 delete this.comm;
66 delete this.comm;
67 delete this.model_id; // Delete id from model so widget manager cleans up.
67 delete this.model_id; // Delete id from model so widget manager cleans up.
68 // TODO: Handle deletion, like this.destroy(), and delete views, etc.
68 // TODO: Handle deletion, like this.destroy(), and delete views, etc.
69 },
69 },
70
70
71 _handle_comm_msg: function (msg) {
71 _handle_comm_msg: function (msg) {
72 // Handle incoming comm msg.
72 // Handle incoming comm msg.
73 var method = msg.content.data.method;
73 var method = msg.content.data.method;
74 switch (method) {
74 switch (method) {
75 case 'update':
75 case 'update':
76 this.apply_update(msg.content.data.state);
76 this.apply_update(msg.content.data.state);
77 break;
77 break;
78 case 'custom':
78 case 'custom':
79 this.trigger('msg:custom', msg.content.data.content);
79 this.trigger('msg:custom', msg.content.data.content);
80 break;
80 break;
81 case 'display':
81 case 'display':
82 this.widget_manager.display_view(msg, this);
82 this.widget_manager.display_view(msg, this);
83 break;
83 break;
84 }
84 }
85 },
85 },
86
86
87 apply_update: function (state) {
87 apply_update: function (state) {
88 // Handle when a widget is updated via the python side.
88 // Handle when a widget is updated via the python side.
89 for (var key in state) {
89 _.each(state, function(value, key) {
90 if (state.hasOwnProperty(key)) {
90 this.key_value_lock = [key, value];
91 var value = state[key];
91 try {
92 this.key_value_lock = [key, value];
92 this.set(key, this._unpack_models(value));
93 try {
93 } finally {
94 this.set(key, this._unpack_models(value));
94 this.key_value_lock = null;
95 } finally {
96 this.key_value_lock = null;
97 }
98 }
95 }
99 }
96 });
100 },
97 },
101
98
102 _handle_status: function (msg, callbacks) {
99 _handle_status: function (msg, callbacks) {
103 // Handle status msgs.
100 // Handle status msgs.
104
101
105 // execution_state : ('busy', 'idle', 'starting')
102 // execution_state : ('busy', 'idle', 'starting')
106 if (this.comm !== undefined) {
103 if (this.comm !== undefined) {
107 if (msg.content.execution_state ==='idle') {
104 if (msg.content.execution_state ==='idle') {
108 // Send buffer if this message caused another message to be
105 // Send buffer if this message caused another message to be
109 // throttled.
106 // throttled.
110 if (this.msg_buffer !== null &&
107 if (this.msg_buffer !== null &&
111 this.msg_throttle === this.pending_msgs) {
108 this.msg_throttle === this.pending_msgs) {
112 var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
109 var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
113 this.comm.send(data, callbacks);
110 this.comm.send(data, callbacks);
114 this.msg_buffer = null;
111 this.msg_buffer = null;
115 } else {
112 } else {
116 --this.pending_msgs;
113 --this.pending_msgs;
117 }
114 }
118 }
115 }
119 }
116 }
120 },
117 },
121
118
122 callbacks: function(view) {
119 callbacks: function(view) {
123 // Create msg callbacks for a comm msg.
120 // Create msg callbacks for a comm msg.
124 var callbacks = this.widget_manager.callbacks(view);
121 var callbacks = this.widget_manager.callbacks(view);
125
122
126 if (callbacks.iopub === undefined) {
123 if (callbacks.iopub === undefined) {
127 callbacks.iopub = {};
124 callbacks.iopub = {};
128 }
125 }
129
126
130 var that = this;
127 var that = this;
131 callbacks.iopub.status = function (msg) {
128 callbacks.iopub.status = function (msg) {
132 that._handle_status(msg, callbacks);
129 that._handle_status(msg, callbacks);
133 }
130 }
134 return callbacks;
131 return callbacks;
135 },
132 },
136
133
137 sync: function (method, model, options) {
134 sync: function (method, model, options) {
138
135
139 // Make sure a comm exists.
136 // Make sure a comm exists.
140 var error = options.error || function() {
137 var error = options.error || function() {
141 console.error('Backbone sync error:', arguments);
138 console.error('Backbone sync error:', arguments);
142 }
139 }
143 if (this.comm === undefined) {
140 if (this.comm === undefined) {
144 error();
141 error();
145 return false;
142 return false;
146 }
143 }
147
144
148 // Delete any key value pairs that the back-end already knows about.
145 // Delete any key value pairs that the back-end already knows about.
149 var attrs = (method === 'patch') ? options.attrs : model.toJSON(options);
146 var attrs = (method === 'patch') ? options.attrs : model.toJSON(options);
150 if (this.key_value_lock !== null) {
147 if (this.key_value_lock !== null) {
151 var key = this.key_value_lock[0];
148 var key = this.key_value_lock[0];
152 var value = this.key_value_lock[1];
149 var value = this.key_value_lock[1];
153 if (attrs[key] === value) {
150 if (attrs[key] === value) {
154 delete attrs[key];
151 delete attrs[key];
155 }
152 }
156 }
153 }
157
154
158 // Only sync if there are attributes to send to the back-end.
155 // Only sync if there are attributes to send to the back-end.
159 if (attr.length > 0) {
156 if (attr.length > 0) {
160 var callbacks = options.callbacks || {};
157 var callbacks = options.callbacks || {};
161 if (this.pending_msgs >= this.msg_throttle) {
158 if (this.pending_msgs >= this.msg_throttle) {
162 // The throttle has been exceeded, buffer the current msg so
159 // The throttle has been exceeded, buffer the current msg so
163 // it can be sent once the kernel has finished processing
160 // it can be sent once the kernel has finished processing
164 // some of the existing messages.
161 // some of the existing messages.
165
162
166 // Combine updates if it is a 'patch' sync, otherwise replace updates
163 // Combine updates if it is a 'patch' sync, otherwise replace updates
167 switch (method) {
164 switch (method) {
168 case 'patch':
165 case 'patch':
169 this.msg_buffer = $.extend(this.msg_buffer || {}, attrs);
166 this.msg_buffer = $.extend(this.msg_buffer || {}, attrs);
170 break;
167 break;
171 case 'update':
168 case 'update':
172 case 'create':
169 case 'create':
173 this.msg_buffer = attrs;
170 this.msg_buffer = attrs;
174 break;
171 break;
175 default:
172 default:
176 error();
173 error();
177 return false;
174 return false;
178 }
175 }
179 this.msg_buffer_callbacks = callbacks;
176 this.msg_buffer_callbacks = callbacks;
180
177
181 } else {
178 } else {
182 // We haven't exceeded the throttle, send the message like
179 // We haven't exceeded the throttle, send the message like
183 // normal. If this is a patch operation, just send the
180 // normal. If this is a patch operation, just send the
184 // changes.
181 // changes.
185 var data = {method: 'backbone', sync_data: attrs};
182 var data = {method: 'backbone', sync_data: attrs};
186 this.comm.send(data, callbacks);
183 this.comm.send(data, callbacks);
187 this.pending_msgs++;
184 this.pending_msgs++;
188 }
185 }
189 }
186 }
190 // Since the comm is a one-way communication, assume the message
187 // Since the comm is a one-way communication, assume the message
191 // arrived. Don't call success since we don't have a model back from the server
188 // arrived. Don't call success since we don't have a model back from the server
192 // this means we miss out on the 'sync' event.
189 // this means we miss out on the 'sync' event.
193 },
190 },
194
191
195 save_changes: function(callbacks) {
192 save_changes: function(callbacks) {
196 // Push this model's state to the back-end
193 // Push this model's state to the back-end
197 //
194 //
198 // This invokes a Backbone.Sync.
195 // This invokes a Backbone.Sync.
199 this.save(this.changedAttributes(), {patch: true, callbacks: callbacks});
196 this.save(this.changedAttributes(), {patch: true, callbacks: callbacks});
200 },
197 },
201
198
202 _pack_models: function(value) {
199 _pack_models: function(value) {
203 // Replace models with model ids recursively.
200 // Replace models with model ids recursively.
204 if (value instanceof Backbone.Model) {
201 if (value instanceof Backbone.Model) {
205 return value.id;
202 return value.id;
206 } else if (value instanceof Object) {
203 } else if (value instanceof Object) {
207 var packed = {};
204 var packed = {};
208 for (var key in value) {
205 _.each(value, function(sub_value, key) {
209 packed[key] = this._pack_models(value[key]);
206 packed[key] = this._pack_models(sub_value);
210 }
207 });
211 return packed;
208 return packed;
212 } else {
209 } else {
213 return value;
210 return value;
214 }
211 }
215 },
212 },
216
213
217 _unpack_models: function(value) {
214 _unpack_models: function(value) {
218 // Replace model ids with models recursively.
215 // Replace model ids with models recursively.
219 if (value instanceof Object) {
216 if (value instanceof Object) {
220 var unpacked = {};
217 var unpacked = {};
221 for (var key in value) {
218 _.each(value, function(sub_value, key) {
222 unpacked[key] = this._unpack_models(value[key]);
219 unpacked[key] = this._unpack_models(sub_value);
223 }
220 });
224 return unpacked;
221 return unpacked;
225 } else {
222 } else {
226 var model = this.widget_manager.get_model(value);
223 var model = this.widget_manager.get_model(value);
227 if (model !== null) {
224 if (model !== null) {
228 return model;
225 return model;
229 } else {
226 } else {
230 return value;
227 return value;
231 }
228 }
232 }
229 }
233 },
230 },
234
231
235 });
232 });
236 WidgetManager.register_widget_model('WidgetModel', WidgetModel);
233 WidgetManager.register_widget_model('WidgetModel', WidgetModel);
237
234
238
235
239 var WidgetView = Backbone.View.extend({
236 var WidgetView = Backbone.View.extend({
240 initialize: function(parameters) {
237 initialize: function(parameters) {
241 // Public constructor.
238 // Public constructor.
242 this.model.on('change',this.update,this);
239 this.model.on('change',this.update,this);
243 this.options = parameters.options;
240 this.options = parameters.options;
244 this.child_views = [];
241 this.child_views = [];
245 this.model.views.push(this);
242 this.model.views.push(this);
246 },
243 },
247
244
248 update: function(){
245 update: function(){
249 // Triggered on model change.
246 // Triggered on model change.
250 //
247 //
251 // Update view to be consistent with this.model
248 // Update view to be consistent with this.model
252 },
249 },
253
250
254 create_child_view: function(child_model, options) {
251 create_child_view: function(child_model, options) {
255 // Create and return a child view.
252 // Create and return a child view.
256 //
253 //
257 // -given a model and (optionally) a view name if the view name is
254 // -given a model and (optionally) a view name if the view name is
258 // not given, it defaults to the model's default view attribute.
255 // not given, it defaults to the model's default view attribute.
259
256
260 // TODO: this is hacky, and makes the view depend on this cell attribute and widget manager behavior
257 // TODO: this is hacky, and makes the view depend on this cell attribute and widget manager behavior
261 // it would be great to have the widget manager add the cell metadata
258 // it would be great to have the widget manager add the cell metadata
262 // to the subview without having to add it here.
259 // to the subview without having to add it here.
263 options = options || {};
260 options = options || {};
264 options.cell = this.options.cell;
261 options.cell = this.options.cell;
265 var child_view = this.model.widget_manager.create_view(child_model, options);
262 var child_view = this.model.widget_manager.create_view(child_model, options);
266 this.child_views[child_model.id] = child_view;
263 this.child_views[child_model.id] = child_view;
267 return child_view;
264 return child_view;
268 },
265 },
269
266
270 delete_child_view: function(child_model, options) {
267 delete_child_view: function(child_model, options) {
271 // Delete a child view that was previously created using create_child_view.
268 // Delete a child view that was previously created using create_child_view.
272 var view = this.child_views[child_model.id];
269 var view = this.child_views[child_model.id];
273 delete this.child_views[child_model.id];
270 delete this.child_views[child_model.id];
274 view.remove();
271 view.remove();
275 },
272 },
276
273
277 do_diff: function(old_list, new_list, removed_callback, added_callback) {
274 do_diff: function(old_list, new_list, removed_callback, added_callback) {
278 // Difference a changed list and call remove and add callbacks for
275 // Difference a changed list and call remove and add callbacks for
279 // each removed and added item in the new list.
276 // each removed and added item in the new list.
280 //
277 //
281 // Parameters
278 // Parameters
282 // ----------
279 // ----------
283 // old_list : array
280 // old_list : array
284 // new_list : array
281 // new_list : array
285 // removed_callback : Callback(item)
282 // removed_callback : Callback(item)
286 // Callback that is called for each item removed.
283 // Callback that is called for each item removed.
287 // added_callback : Callback(item)
284 // added_callback : Callback(item)
288 // Callback that is called for each item added.
285 // Callback that is called for each item added.
289
286
290
287
291 // removed items
288 // removed items
292 _.each(_.difference(old_list, new_list), function(item, index, list) {
289 _.each(_.difference(old_list, new_list), function(item, index, list) {
293 removed_callback(item);
290 removed_callback(item);
294 }, this);
291 }, this);
295
292
296 // added items
293 // added items
297 _.each(_.difference(new_list, old_list), function(item, index, list) {
294 _.each(_.difference(new_list, old_list), function(item, index, list) {
298 added_callback(item);
295 added_callback(item);
299 }, this);
296 }, this);
300 },
297 },
301
298
302 callbacks: function(){
299 callbacks: function(){
303 // Create msg callbacks for a comm msg.
300 // Create msg callbacks for a comm msg.
304 return this.model.callbacks(this);
301 return this.model.callbacks(this);
305 },
302 },
306
303
307 render: function(){
304 render: function(){
308 // Render the view.
305 // Render the view.
309 //
306 //
310 // By default, this is only called the first time the view is created
307 // By default, this is only called the first time the view is created
311 },
308 },
312
309
313 send: function (content) {
310 send: function (content) {
314 // Send a custom msg associated with this view.
311 // Send a custom msg associated with this view.
315 this.model.send(content, this.callbacks());
312 this.model.send(content, this.callbacks());
316 },
313 },
317
314
318 touch: function () {
315 touch: function () {
319 this.model.save_changes(this.callbacks());
316 this.model.save_changes(this.callbacks());
320 },
317 },
321
318
322 });
319 });
323
320
324
321
325 var DOMWidgetView = WidgetView.extend({
322 var DOMWidgetView = WidgetView.extend({
326 initialize: function (options) {
323 initialize: function (options) {
327 // Public constructor
324 // Public constructor
328
325
329 // In the future we may want to make changes more granular
326 // In the future we may want to make changes more granular
330 // (e.g., trigger on visible:change).
327 // (e.g., trigger on visible:change).
331 this.model.on('change', this.update, this);
328 this.model.on('change', this.update, this);
332 this.model.on('msg:custom', this.on_msg, this);
329 this.model.on('msg:custom', this.on_msg, this);
333 DOMWidgetView.__super__.initialize.apply(this, arguments);
330 DOMWidgetView.__super__.initialize.apply(this, arguments);
334 },
331 },
335
332
336 on_msg: function(msg) {
333 on_msg: function(msg) {
337 // Handle DOM specific msgs.
334 // Handle DOM specific msgs.
338 switch(msg.msg_type) {
335 switch(msg.msg_type) {
339 case 'add_class':
336 case 'add_class':
340 this.add_class(msg.selector, msg.class_list);
337 this.add_class(msg.selector, msg.class_list);
341 break;
338 break;
342 case 'remove_class':
339 case 'remove_class':
343 this.remove_class(msg.selector, msg.class_list);
340 this.remove_class(msg.selector, msg.class_list);
344 break;
341 break;
345 }
342 }
346 },
343 },
347
344
348 add_class: function (selector, class_list) {
345 add_class: function (selector, class_list) {
349 // Add a DOM class to an element.
346 // Add a DOM class to an element.
350 this._get_selector_element(selector).addClass(class_list);
347 this._get_selector_element(selector).addClass(class_list);
351 },
348 },
352
349
353 remove_class: function (selector, class_list) {
350 remove_class: function (selector, class_list) {
354 // Remove a DOM class from an element.
351 // Remove a DOM class from an element.
355 this._get_selector_element(selector).removeClass(class_list);
352 this._get_selector_element(selector).removeClass(class_list);
356 },
353 },
357
354
358 update: function () {
355 update: function () {
359 // Update the contents of this view
356 // Update the contents of this view
360 //
357 //
361 // Called when the model is changed. The model may have been
358 // Called when the model is changed. The model may have been
362 // changed by another view or by a state update from the back-end.
359 // changed by another view or by a state update from the back-end.
363 // The very first update seems to happen before the element is
360 // The very first update seems to happen before the element is
364 // finished rendering so we use setTimeout to give the element time
361 // finished rendering so we use setTimeout to give the element time
365 // to render
362 // to render
366 var e = this.$el;
363 var e = this.$el;
367 var visible = this.model.get('visible');
364 var visible = this.model.get('visible');
368 setTimeout(function() {e.toggle(visible)},0);
365 setTimeout(function() {e.toggle(visible)},0);
369
366
370 var css = this.model.get('_css');
367 var css = this.model.get('_css');
371 if (css === undefined) {return;}
368 if (css === undefined) {return;}
372 for (var selector in css) {
369 _.each(css, function(css_traits, selector){
373 if (css.hasOwnProperty(selector)) {
370 // Apply the css traits to all elements that match the selector.
374 // Apply the css traits to all elements that match the selector.
371 var elements = this._get_selector_element(selector);
375 var elements = this._get_selector_element(selector);
372 if (elements.length > 0) {
376 if (elements.length > 0) {
373 _.each(css_traits, function(css_value, css_key){
377 var css_traits = css[selector];
374 elements.css(css_key, css_value);
378 for (var css_key in css_traits) {
375 });
379 if (css_traits.hasOwnProperty(css_key)) {
380 elements.css(css_key, css_traits[css_key]);
381 }
382 }
383 }
384 }
376 }
385 }
377 });
378
386 },
379 },
387
380
388 _get_selector_element: function (selector) {
381 _get_selector_element: function (selector) {
389 // Get the elements via the css selector.
382 // Get the elements via the css selector.
390
383
391 // If the selector is blank, apply the style to the $el_to_style
384 // If the selector is blank, apply the style to the $el_to_style
392 // element. If the $el_to_style element is not defined, use apply
385 // element. If the $el_to_style element is not defined, use apply
393 // the style to the view's element.
386 // the style to the view's element.
394 var elements;
387 var elements;
395 if (selector === undefined || selector === null || selector === '') {
388 if (selector === undefined || selector === null || selector === '') {
396 if (this.$el_to_style === undefined) {
389 if (this.$el_to_style === undefined) {
397 elements = this.$el;
390 elements = this.$el;
398 } else {
391 } else {
399 elements = this.$el_to_style;
392 elements = this.$el_to_style;
400 }
393 }
401 } else {
394 } else {
402 elements = this.$el.find(selector);
395 elements = this.$el.find(selector);
403 }
396 }
404 return elements;
397 return elements;
405 },
398 },
406 });
399 });
407
400
408 IPython.WidgetModel = WidgetModel;
401 IPython.WidgetModel = WidgetModel;
409 IPython.WidgetView = WidgetView;
402 IPython.WidgetView = WidgetView;
410 IPython.DOMWidgetView = DOMWidgetView;
403 IPython.DOMWidgetView = DOMWidgetView;
411
404
412 // Pass through WidgetManager namespace.
405 // Pass through WidgetManager namespace.
413 return WidgetManager;
406 return WidgetManager;
414 });
407 });
@@ -1,267 +1,267
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 // FloatRangeWidget
9 // FloatRangeWidget
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(WidgetManager){
17 define(["notebook/js/widgets/widget"], function(WidgetManager){
18
18
19 var FloatSliderView = IPython.DOMWidgetView.extend({
19 var FloatSliderView = IPython.DOMWidgetView.extend({
20 render : function(){
20 render : function(){
21 // Called when view is rendered.
21 // Called when view is rendered.
22 this.$el
22 this.$el
23 .addClass('widget-hbox-single');
23 .addClass('widget-hbox-single');
24 this.$label = $('<div />')
24 this.$label = $('<div />')
25 .appendTo(this.$el)
25 .appendTo(this.$el)
26 .addClass('widget-hlabel')
26 .addClass('widget-hlabel')
27 .hide();
27 .hide();
28 this.$slider = $('<div />')
28 this.$slider = $('<div />')
29 .slider({})
29 .slider({})
30 .addClass('slider');
30 .addClass('slider');
31
31
32 // Put the slider in a container
32 // Put the slider in a container
33 this.$slider_container = $('<div />')
33 this.$slider_container = $('<div />')
34 .addClass('widget-hslider')
34 .addClass('widget-hslider')
35 .append(this.$slider);
35 .append(this.$slider);
36 this.$el_to_style = this.$slider_container; // Set default element to style
36 this.$el_to_style = this.$slider_container; // Set default element to style
37 this.$el.append(this.$slider_container);
37 this.$el.append(this.$slider_container);
38
38
39 // Set defaults.
39 // Set defaults.
40 this.update();
40 this.update();
41 },
41 },
42
42
43 update : function(options){
43 update : function(options){
44 // Update the contents of this view
44 // Update the contents of this view
45 //
45 //
46 // Called when the model is changed. The model may have been
46 // Called when the model is changed. The model may have been
47 // changed by another view or by a state update from the back-end.
47 // changed by another view or by a state update from the back-end.
48
48
49 if (options === undefined || options.updated_view != this) {
49 if (options === undefined || options.updated_view != this) {
50 // JQuery slider option keys. These keys happen to have a
50 // JQuery slider option keys. These keys happen to have a
51 // one-to-one mapping with the corrosponding keys of the model.
51 // one-to-one mapping with the corrosponding keys of the model.
52 var jquery_slider_keys = ['step', 'max', 'min', 'disabled'];
52 var jquery_slider_keys = ['step', 'max', 'min', 'disabled'];
53 for (var index in jquery_slider_keys) {
53 _.each(jquery_slider_keys, function(key, i) {
54 var key = jquery_slider_keys[index];
54 var model_value = this.model.get(key);
55 if (this.model.get(key) !== undefined) {
55 if (model_value !== undefined) {
56 this.$slider.slider("option", key, this.model.get(key));
56 this.$slider.slider("option", key, model_value);
57 }
57 }
58 }
58 });
59
59
60 // WORKAROUND FOR JQUERY SLIDER BUG.
60 // WORKAROUND FOR JQUERY SLIDER BUG.
61 // The horizontal position of the slider handle
61 // The horizontal position of the slider handle
62 // depends on the value of the slider at the time
62 // depends on the value of the slider at the time
63 // of orientation change. Before applying the new
63 // of orientation change. Before applying the new
64 // workaround, we set the value to the minimum to
64 // workaround, we set the value to the minimum to
65 // make sure that the horizontal placement of the
65 // make sure that the horizontal placement of the
66 // handle in the vertical slider is always
66 // handle in the vertical slider is always
67 // consistent.
67 // consistent.
68 var orientation = this.model.get('orientation');
68 var orientation = this.model.get('orientation');
69 var value = this.model.get('min');
69 var value = this.model.get('min');
70 this.$slider.slider('option', 'value', value);
70 this.$slider.slider('option', 'value', value);
71 this.$slider.slider('option', 'orientation', orientation);
71 this.$slider.slider('option', 'orientation', orientation);
72 value = this.model.get('value');
72 value = this.model.get('value');
73 this.$slider.slider('option', 'value', value);
73 this.$slider.slider('option', 'value', value);
74
74
75 // Use the right CSS classes for vertical & horizontal sliders
75 // Use the right CSS classes for vertical & horizontal sliders
76 if (orientation=='vertical') {
76 if (orientation=='vertical') {
77 this.$slider_container
77 this.$slider_container
78 .removeClass('widget-hslider')
78 .removeClass('widget-hslider')
79 .addClass('widget-vslider');
79 .addClass('widget-vslider');
80 this.$el
80 this.$el
81 .removeClass('widget-hbox-single')
81 .removeClass('widget-hbox-single')
82 .addClass('widget-vbox-single');
82 .addClass('widget-vbox-single');
83 this.$label
83 this.$label
84 .removeClass('widget-hlabel')
84 .removeClass('widget-hlabel')
85 .addClass('widget-vlabel');
85 .addClass('widget-vlabel');
86
86
87 } else {
87 } else {
88 this.$slider_container
88 this.$slider_container
89 .removeClass('widget-vslider')
89 .removeClass('widget-vslider')
90 .addClass('widget-hslider');
90 .addClass('widget-hslider');
91 this.$el
91 this.$el
92 .removeClass('widget-vbox-single')
92 .removeClass('widget-vbox-single')
93 .addClass('widget-hbox-single');
93 .addClass('widget-hbox-single');
94 this.$label
94 this.$label
95 .removeClass('widget-vlabel')
95 .removeClass('widget-vlabel')
96 .addClass('widget-hlabel');
96 .addClass('widget-hlabel');
97 }
97 }
98
98
99 var description = this.model.get('description');
99 var description = this.model.get('description');
100 if (description.length === 0) {
100 if (description.length === 0) {
101 this.$label.hide();
101 this.$label.hide();
102 } else {
102 } else {
103 this.$label.text(description);
103 this.$label.text(description);
104 this.$label.show();
104 this.$label.show();
105 }
105 }
106 }
106 }
107 return FloatSliderView.__super__.update.apply(this);
107 return FloatSliderView.__super__.update.apply(this);
108 },
108 },
109
109
110 events: {
110 events: {
111 // Dictionary of events and their handlers.
111 // Dictionary of events and their handlers.
112 "slide" : "handleSliderChange"
112 "slide" : "handleSliderChange"
113 },
113 },
114
114
115 handleSliderChange: function(e, ui) {
115 handleSliderChange: function(e, ui) {
116 // Handle when the slider value is changed.
116 // Handle when the slider value is changed.
117
117
118 // Calling model.set will trigger all of the other views of the
118 // Calling model.set will trigger all of the other views of the
119 // model to update.
119 // model to update.
120 this.model.set('value', ui.value, {updated_view: this});
120 this.model.set('value', ui.value, {updated_view: this});
121 this.touch();
121 this.touch();
122 },
122 },
123 });
123 });
124 WidgetManager.register_widget_view('FloatSliderView', FloatSliderView);
124 WidgetManager.register_widget_view('FloatSliderView', FloatSliderView);
125
125
126
126
127 var FloatTextView = IPython.DOMWidgetView.extend({
127 var FloatTextView = IPython.DOMWidgetView.extend({
128 render : function(){
128 render : function(){
129 // Called when view is rendered.
129 // Called when view is rendered.
130 this.$el
130 this.$el
131 .addClass('widget-hbox-single');
131 .addClass('widget-hbox-single');
132 this.$label = $('<div />')
132 this.$label = $('<div />')
133 .appendTo(this.$el)
133 .appendTo(this.$el)
134 .addClass('widget-hlabel')
134 .addClass('widget-hlabel')
135 .hide();
135 .hide();
136 this.$textbox = $('<input type="text" />')
136 this.$textbox = $('<input type="text" />')
137 .addClass('input')
137 .addClass('input')
138 .addClass('widget-numeric-text')
138 .addClass('widget-numeric-text')
139 .appendTo(this.$el);
139 .appendTo(this.$el);
140 this.$el_to_style = this.$textbox; // Set default element to style
140 this.$el_to_style = this.$textbox; // Set default element to style
141 this.update(); // Set defaults.
141 this.update(); // Set defaults.
142 },
142 },
143
143
144 update : function(options){
144 update : function(options){
145 // Update the contents of this view
145 // Update the contents of this view
146 //
146 //
147 // Called when the model is changed. The model may have been
147 // Called when the model is changed. The model may have been
148 // changed by another view or by a state update from the back-end.
148 // changed by another view or by a state update from the back-end.
149 if (options === undefined || options.updated_view != this) {
149 if (options === undefined || options.updated_view != this) {
150 var value = this.model.get('value');
150 var value = this.model.get('value');
151 if (parseFloat(this.$textbox.val()) != value) {
151 if (parseFloat(this.$textbox.val()) != value) {
152 this.$textbox.val(value);
152 this.$textbox.val(value);
153 }
153 }
154
154
155 if (this.model.get('disabled')) {
155 if (this.model.get('disabled')) {
156 this.$textbox.attr('disabled','disabled');
156 this.$textbox.attr('disabled','disabled');
157 } else {
157 } else {
158 this.$textbox.removeAttr('disabled');
158 this.$textbox.removeAttr('disabled');
159 }
159 }
160
160
161 var description = this.model.get('description');
161 var description = this.model.get('description');
162 if (description.length === 0) {
162 if (description.length === 0) {
163 this.$label.hide();
163 this.$label.hide();
164 } else {
164 } else {
165 this.$label.text(description);
165 this.$label.text(description);
166 this.$label.show();
166 this.$label.show();
167 }
167 }
168 }
168 }
169 return FloatTextView.__super__.update.apply(this);
169 return FloatTextView.__super__.update.apply(this);
170 },
170 },
171
171
172 events: {
172 events: {
173 // Dictionary of events and their handlers.
173 // Dictionary of events and their handlers.
174
174
175 "keyup input" : "handleChanging",
175 "keyup input" : "handleChanging",
176 "paste input" : "handleChanging",
176 "paste input" : "handleChanging",
177 "cut input" : "handleChanging",
177 "cut input" : "handleChanging",
178
178
179 // Fires only when control is validated or looses focus.
179 // Fires only when control is validated or looses focus.
180 "change input" : "handleChanged"
180 "change input" : "handleChanged"
181 },
181 },
182
182
183 handleChanging: function(e) {
183 handleChanging: function(e) {
184 // Handles and validates user input.
184 // Handles and validates user input.
185
185
186 // Try to parse value as a float.
186 // Try to parse value as a float.
187 var numericalValue = 0.0;
187 var numericalValue = 0.0;
188 if (e.target.value !== '') {
188 if (e.target.value !== '') {
189 numericalValue = parseFloat(e.target.value);
189 numericalValue = parseFloat(e.target.value);
190 }
190 }
191
191
192 // If parse failed, reset value to value stored in model.
192 // If parse failed, reset value to value stored in model.
193 if (isNaN(numericalValue)) {
193 if (isNaN(numericalValue)) {
194 e.target.value = this.model.get('value');
194 e.target.value = this.model.get('value');
195 } else if (!isNaN(numericalValue)) {
195 } else if (!isNaN(numericalValue)) {
196 if (this.model.get('max') !== undefined) {
196 if (this.model.get('max') !== undefined) {
197 numericalValue = Math.min(this.model.get('max'), numericalValue);
197 numericalValue = Math.min(this.model.get('max'), numericalValue);
198 }
198 }
199 if (this.model.get('min') !== undefined) {
199 if (this.model.get('min') !== undefined) {
200 numericalValue = Math.max(this.model.get('min'), numericalValue);
200 numericalValue = Math.max(this.model.get('min'), numericalValue);
201 }
201 }
202
202
203 // Apply the value if it has changed.
203 // Apply the value if it has changed.
204 if (numericalValue != this.model.get('value')) {
204 if (numericalValue != this.model.get('value')) {
205
205
206 // Calling model.set will trigger all of the other views of the
206 // Calling model.set will trigger all of the other views of the
207 // model to update.
207 // model to update.
208 this.model.set('value', numericalValue, {updated_view: this});
208 this.model.set('value', numericalValue, {updated_view: this});
209 this.touch();
209 this.touch();
210 }
210 }
211 }
211 }
212 },
212 },
213
213
214 handleChanged: function(e) {
214 handleChanged: function(e) {
215 // Applies validated input.
215 // Applies validated input.
216 if (this.model.get('value') != e.target.value) {
216 if (this.model.get('value') != e.target.value) {
217 e.target.value = this.model.get('value');
217 e.target.value = this.model.get('value');
218 }
218 }
219 }
219 }
220 });
220 });
221 WidgetManager.register_widget_view('FloatTextView', FloatTextView);
221 WidgetManager.register_widget_view('FloatTextView', FloatTextView);
222
222
223
223
224 var ProgressView = IPython.DOMWidgetView.extend({
224 var ProgressView = IPython.DOMWidgetView.extend({
225 render : function(){
225 render : function(){
226 // Called when view is rendered.
226 // Called when view is rendered.
227 this.$el
227 this.$el
228 .addClass('widget-hbox-single');
228 .addClass('widget-hbox-single');
229 this.$label = $('<div />')
229 this.$label = $('<div />')
230 .appendTo(this.$el)
230 .appendTo(this.$el)
231 .addClass('widget-hlabel')
231 .addClass('widget-hlabel')
232 .hide();
232 .hide();
233 this.$progress = $('<div />')
233 this.$progress = $('<div />')
234 .addClass('progress')
234 .addClass('progress')
235 .addClass('widget-progress')
235 .addClass('widget-progress')
236 .appendTo(this.$el);
236 .appendTo(this.$el);
237 this.$el_to_style = this.$progress; // Set default element to style
237 this.$el_to_style = this.$progress; // Set default element to style
238 this.$bar = $('<div />')
238 this.$bar = $('<div />')
239 .addClass('bar')
239 .addClass('bar')
240 .css('width', '50%')
240 .css('width', '50%')
241 .appendTo(this.$progress);
241 .appendTo(this.$progress);
242 this.update(); // Set defaults.
242 this.update(); // Set defaults.
243 },
243 },
244
244
245 update : function(){
245 update : function(){
246 // Update the contents of this view
246 // Update the contents of this view
247 //
247 //
248 // Called when the model is changed. The model may have been
248 // Called when the model is changed. The model may have been
249 // changed by another view or by a state update from the back-end.
249 // changed by another view or by a state update from the back-end.
250 var value = this.model.get('value');
250 var value = this.model.get('value');
251 var max = this.model.get('max');
251 var max = this.model.get('max');
252 var min = this.model.get('min');
252 var min = this.model.get('min');
253 var percent = 100.0 * (value - min) / (max - min);
253 var percent = 100.0 * (value - min) / (max - min);
254 this.$bar.css('width', percent + '%');
254 this.$bar.css('width', percent + '%');
255
255
256 var description = this.model.get('description');
256 var description = this.model.get('description');
257 if (description.length === 0) {
257 if (description.length === 0) {
258 this.$label.hide();
258 this.$label.hide();
259 } else {
259 } else {
260 this.$label.text(description);
260 this.$label.text(description);
261 this.$label.show();
261 this.$label.show();
262 }
262 }
263 return ProgressView.__super__.update.apply(this);
263 return ProgressView.__super__.update.apply(this);
264 },
264 },
265 });
265 });
266 WidgetManager.register_widget_view('ProgressView', ProgressView);
266 WidgetManager.register_widget_view('ProgressView', ProgressView);
267 });
267 });
@@ -1,220 +1,220
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 // IntRangeWidget
9 // IntRangeWidget
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(WidgetManager){
17 define(["notebook/js/widgets/widget"], function(WidgetManager){
18
18
19 var IntSliderView = IPython.DOMWidgetView.extend({
19 var IntSliderView = IPython.DOMWidgetView.extend({
20 render : function(){
20 render : function(){
21 // Called when view is rendered.
21 // Called when view is rendered.
22 this.$el
22 this.$el
23 .addClass('widget-hbox-single');
23 .addClass('widget-hbox-single');
24 this.$label = $('<div />')
24 this.$label = $('<div />')
25 .appendTo(this.$el)
25 .appendTo(this.$el)
26 .addClass('widget-hlabel')
26 .addClass('widget-hlabel')
27 .hide();
27 .hide();
28 this.$slider = $('<div />')
28 this.$slider = $('<div />')
29 .slider({})
29 .slider({})
30 .addClass('slider');
30 .addClass('slider');
31
31
32 // Put the slider in a container
32 // Put the slider in a container
33 this.$slider_container = $('<div />')
33 this.$slider_container = $('<div />')
34 .addClass('widget-hslider')
34 .addClass('widget-hslider')
35 .append(this.$slider);
35 .append(this.$slider);
36 this.$el_to_style = this.$slider_container; // Set default element to style
36 this.$el_to_style = this.$slider_container; // Set default element to style
37 this.$el.append(this.$slider_container);
37 this.$el.append(this.$slider_container);
38
38
39 // Set defaults.
39 // Set defaults.
40 this.update();
40 this.update();
41 },
41 },
42
42
43 update : function(options){
43 update : function(options){
44 // Update the contents of this view
44 // Update the contents of this view
45 //
45 //
46 // Called when the model is changed. The model may have been
46 // Called when the model is changed. The model may have been
47 // changed by another view or by a state update from the back-end.
47 // changed by another view or by a state update from the back-end.
48 if (options === undefined || options.updated_view != this) {
48 if (options === undefined || options.updated_view != this) {
49 // JQuery slider option keys. These keys happen to have a
49 // JQuery slider option keys. These keys happen to have a
50 // one-to-one mapping with the corrosponding keys of the model.
50 // one-to-one mapping with the corrosponding keys of the model.
51 var jquery_slider_keys = ['step', 'max', 'min', 'disabled'];
51 var jquery_slider_keys = ['step', 'max', 'min', 'disabled'];
52 for (var index in jquery_slider_keys) {
52 _.each(jquery_slider_keys, function(key, i) {
53 var key = jquery_slider_keys[index];
53 var model_value = this.model.get(key);
54 if (this.model.get(key) !== undefined) {
54 if (model_value !== undefined) {
55 this.$slider.slider("option", key, this.model.get(key));
55 this.$slider.slider("option", key, model_value);
56 }
56 }
57 }
57 });
58
58
59 // WORKAROUND FOR JQUERY SLIDER BUG.
59 // WORKAROUND FOR JQUERY SLIDER BUG.
60 // The horizontal position of the slider handle
60 // The horizontal position of the slider handle
61 // depends on the value of the slider at the time
61 // depends on the value of the slider at the time
62 // of orientation change. Before applying the new
62 // of orientation change. Before applying the new
63 // workaround, we set the value to the minimum to
63 // workaround, we set the value to the minimum to
64 // make sure that the horizontal placement of the
64 // make sure that the horizontal placement of the
65 // handle in the vertical slider is always
65 // handle in the vertical slider is always
66 // consistent.
66 // consistent.
67 var orientation = this.model.get('orientation');
67 var orientation = this.model.get('orientation');
68 var value = this.model.get('min');
68 var value = this.model.get('min');
69 this.$slider.slider('option', 'value', value);
69 this.$slider.slider('option', 'value', value);
70 this.$slider.slider('option', 'orientation', orientation);
70 this.$slider.slider('option', 'orientation', orientation);
71 value = this.model.get('value');
71 value = this.model.get('value');
72 this.$slider.slider('option', 'value', value);
72 this.$slider.slider('option', 'value', value);
73
73
74 // Use the right CSS classes for vertical & horizontal sliders
74 // Use the right CSS classes for vertical & horizontal sliders
75 if (orientation=='vertical') {
75 if (orientation=='vertical') {
76 this.$slider_container
76 this.$slider_container
77 .removeClass('widget-hslider')
77 .removeClass('widget-hslider')
78 .addClass('widget-vslider');
78 .addClass('widget-vslider');
79 this.$el
79 this.$el
80 .removeClass('widget-hbox-single')
80 .removeClass('widget-hbox-single')
81 .addClass('widget-vbox-single');
81 .addClass('widget-vbox-single');
82 this.$label
82 this.$label
83 .removeClass('widget-hlabel')
83 .removeClass('widget-hlabel')
84 .addClass('widget-vlabel');
84 .addClass('widget-vlabel');
85
85
86 } else {
86 } else {
87 this.$slider_container
87 this.$slider_container
88 .removeClass('widget-vslider')
88 .removeClass('widget-vslider')
89 .addClass('widget-hslider');
89 .addClass('widget-hslider');
90 this.$el
90 this.$el
91 .removeClass('widget-vbox-single')
91 .removeClass('widget-vbox-single')
92 .addClass('widget-hbox-single');
92 .addClass('widget-hbox-single');
93 this.$label
93 this.$label
94 .removeClass('widget-vlabel')
94 .removeClass('widget-vlabel')
95 .addClass('widget-hlabel');
95 .addClass('widget-hlabel');
96 }
96 }
97
97
98 var description = this.model.get('description');
98 var description = this.model.get('description');
99 if (description.length === 0) {
99 if (description.length === 0) {
100 this.$label.hide();
100 this.$label.hide();
101 } else {
101 } else {
102 this.$label.text(description);
102 this.$label.text(description);
103 this.$label.show();
103 this.$label.show();
104 }
104 }
105 }
105 }
106 return IntSliderView.__super__.update.apply(this);
106 return IntSliderView.__super__.update.apply(this);
107 },
107 },
108
108
109 events: {
109 events: {
110 // Dictionary of events and their handlers.
110 // Dictionary of events and their handlers.
111 "slide" : "handleSliderChange"
111 "slide" : "handleSliderChange"
112 },
112 },
113
113
114 handleSliderChange: function(e, ui) {
114 handleSliderChange: function(e, ui) {
115 // Called when the slider value is changed.
115 // Called when the slider value is changed.
116
116
117 // Calling model.set will trigger all of the other views of the
117 // Calling model.set will trigger all of the other views of the
118 // model to update.
118 // model to update.
119 this.model.set('value', ~~ui.value, {updated_view: this}); // Double bit-wise not to truncate decimel
119 this.model.set('value', ~~ui.value, {updated_view: this}); // Double bit-wise not to truncate decimel
120 this.touch();
120 this.touch();
121 },
121 },
122 });
122 });
123 WidgetManager.register_widget_view('IntSliderView', IntSliderView);
123 WidgetManager.register_widget_view('IntSliderView', IntSliderView);
124
124
125
125
126 var IntTextView = IPython.DOMWidgetView.extend({
126 var IntTextView = IPython.DOMWidgetView.extend({
127 render : function(){
127 render : function(){
128 // Called when view is rendered.
128 // Called when view is rendered.
129 this.$el
129 this.$el
130 .addClass('widget-hbox-single');
130 .addClass('widget-hbox-single');
131 this.$label = $('<div />')
131 this.$label = $('<div />')
132 .appendTo(this.$el)
132 .appendTo(this.$el)
133 .addClass('widget-hlabel')
133 .addClass('widget-hlabel')
134 .hide();
134 .hide();
135 this.$textbox = $('<input type="text" />')
135 this.$textbox = $('<input type="text" />')
136 .addClass('input')
136 .addClass('input')
137 .addClass('widget-numeric-text')
137 .addClass('widget-numeric-text')
138 .appendTo(this.$el);
138 .appendTo(this.$el);
139 this.$el_to_style = this.$textbox; // Set default element to style
139 this.$el_to_style = this.$textbox; // Set default element to style
140 this.update(); // Set defaults.
140 this.update(); // Set defaults.
141 },
141 },
142
142
143 update : function(options){
143 update : function(options){
144 // Update the contents of this view
144 // Update the contents of this view
145 //
145 //
146 // Called when the model is changed. The model may have been
146 // Called when the model is changed. The model may have been
147 // changed by another view or by a state update from the back-end.
147 // changed by another view or by a state update from the back-end.
148 if (options === undefined || options.updated_view != this) {
148 if (options === undefined || options.updated_view != this) {
149 var value = this.model.get('value');
149 var value = this.model.get('value');
150 if (parseInt(this.$textbox.val()) != value) {
150 if (parseInt(this.$textbox.val()) != value) {
151 this.$textbox.val(value);
151 this.$textbox.val(value);
152 }
152 }
153
153
154 if (this.model.get('disabled')) {
154 if (this.model.get('disabled')) {
155 this.$textbox.attr('disabled','disabled');
155 this.$textbox.attr('disabled','disabled');
156 } else {
156 } else {
157 this.$textbox.removeAttr('disabled');
157 this.$textbox.removeAttr('disabled');
158 }
158 }
159
159
160 var description = this.model.get('description');
160 var description = this.model.get('description');
161 if (description.length === 0) {
161 if (description.length === 0) {
162 this.$label.hide();
162 this.$label.hide();
163 } else {
163 } else {
164 this.$label.text(description);
164 this.$label.text(description);
165 this.$label.show();
165 this.$label.show();
166 }
166 }
167 }
167 }
168 return IntTextView.__super__.update.apply(this);
168 return IntTextView.__super__.update.apply(this);
169 },
169 },
170
170
171 events: {
171 events: {
172 // Dictionary of events and their handlers.
172 // Dictionary of events and their handlers.
173 "keyup input" : "handleChanging",
173 "keyup input" : "handleChanging",
174 "paste input" : "handleChanging",
174 "paste input" : "handleChanging",
175 "cut input" : "handleChanging",
175 "cut input" : "handleChanging",
176
176
177 // Fires only when control is validated or looses focus.
177 // Fires only when control is validated or looses focus.
178 "change input" : "handleChanged"
178 "change input" : "handleChanged"
179 },
179 },
180
180
181 handleChanging: function(e) {
181 handleChanging: function(e) {
182 // Handles and validates user input.
182 // Handles and validates user input.
183
183
184 // Try to parse value as a float.
184 // Try to parse value as a float.
185 var numericalValue = 0;
185 var numericalValue = 0;
186 if (e.target.value !== '') {
186 if (e.target.value !== '') {
187 numericalValue = parseInt(e.target.value);
187 numericalValue = parseInt(e.target.value);
188 }
188 }
189
189
190 // If parse failed, reset value to value stored in model.
190 // If parse failed, reset value to value stored in model.
191 if (isNaN(numericalValue)) {
191 if (isNaN(numericalValue)) {
192 e.target.value = this.model.get('value');
192 e.target.value = this.model.get('value');
193 } else if (!isNaN(numericalValue)) {
193 } else if (!isNaN(numericalValue)) {
194 if (this.model.get('max') !== undefined) {
194 if (this.model.get('max') !== undefined) {
195 numericalValue = Math.min(this.model.get('max'), numericalValue);
195 numericalValue = Math.min(this.model.get('max'), numericalValue);
196 }
196 }
197 if (this.model.get('min') !== undefined) {
197 if (this.model.get('min') !== undefined) {
198 numericalValue = Math.max(this.model.get('min'), numericalValue);
198 numericalValue = Math.max(this.model.get('min'), numericalValue);
199 }
199 }
200
200
201 // Apply the value if it has changed.
201 // Apply the value if it has changed.
202 if (numericalValue != this.model.get('value')) {
202 if (numericalValue != this.model.get('value')) {
203
203
204 // Calling model.set will trigger all of the other views of the
204 // Calling model.set will trigger all of the other views of the
205 // model to update.
205 // model to update.
206 this.model.set('value', numericalValue, {updated_view: this});
206 this.model.set('value', numericalValue, {updated_view: this});
207 this.touch();
207 this.touch();
208 }
208 }
209 }
209 }
210 },
210 },
211
211
212 handleChanged: function(e) {
212 handleChanged: function(e) {
213 // Applies validated input.
213 // Applies validated input.
214 if (this.model.get('value') != e.target.value) {
214 if (this.model.get('value') != e.target.value) {
215 e.target.value = this.model.get('value');
215 e.target.value = this.model.get('value');
216 }
216 }
217 }
217 }
218 });
218 });
219 WidgetManager.register_widget_view('IntTextView', IntTextView);
219 WidgetManager.register_widget_view('IntTextView', IntTextView);
220 });
220 });
@@ -1,372 +1,372
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 // SelectionWidget
9 // SelectionWidget
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(WidgetManager){
17 define(["notebook/js/widgets/widget"], function(WidgetManager){
18
18
19 var DropdownView = IPython.DOMWidgetView.extend({
19 var DropdownView = IPython.DOMWidgetView.extend({
20 render : function(){
20 render : function(){
21 // Called when view is rendered.
21 // Called when view is rendered.
22 this.$el
22 this.$el
23 .addClass('widget-hbox-single');
23 .addClass('widget-hbox-single');
24 this.$label = $('<div />')
24 this.$label = $('<div />')
25 .appendTo(this.$el)
25 .appendTo(this.$el)
26 .addClass('widget-hlabel')
26 .addClass('widget-hlabel')
27 .hide();
27 .hide();
28 this.$buttongroup = $('<div />')
28 this.$buttongroup = $('<div />')
29 .addClass('widget_item')
29 .addClass('widget_item')
30 .addClass('btn-group')
30 .addClass('btn-group')
31 .appendTo(this.$el);
31 .appendTo(this.$el);
32 this.$el_to_style = this.$buttongroup; // Set default element to style
32 this.$el_to_style = this.$buttongroup; // Set default element to style
33 this.$droplabel = $('<button />')
33 this.$droplabel = $('<button />')
34 .addClass('btn')
34 .addClass('btn')
35 .addClass('widget-combo-btn')
35 .addClass('widget-combo-btn')
36 .text(' ')
36 .text(' ')
37 .appendTo(this.$buttongroup);
37 .appendTo(this.$buttongroup);
38 this.$dropbutton = $('<button />')
38 this.$dropbutton = $('<button />')
39 .addClass('btn')
39 .addClass('btn')
40 .addClass('dropdown-toggle')
40 .addClass('dropdown-toggle')
41 .addClass('widget-combo-carrot-btn')
41 .addClass('widget-combo-carrot-btn')
42 .attr('data-toggle', 'dropdown')
42 .attr('data-toggle', 'dropdown')
43 .append($('<span />').addClass("caret"))
43 .append($('<span />').addClass("caret"))
44 .appendTo(this.$buttongroup);
44 .appendTo(this.$buttongroup);
45 this.$droplist = $('<ul />')
45 this.$droplist = $('<ul />')
46 .addClass('dropdown-menu')
46 .addClass('dropdown-menu')
47 .appendTo(this.$buttongroup);
47 .appendTo(this.$buttongroup);
48
48
49 // Set defaults.
49 // Set defaults.
50 this.update();
50 this.update();
51 },
51 },
52
52
53 update : function(options){
53 update : function(options){
54 // Update the contents of this view
54 // Update the contents of this view
55 //
55 //
56 // Called when the model is changed. The model may have been
56 // Called when the model is changed. The model may have been
57 // changed by another view or by a state update from the back-end.
57 // changed by another view or by a state update from the back-end.
58
58
59 if (options === undefined || options.updated_view != this) {
59 if (options === undefined || options.updated_view != this) {
60 var selected_item_text = this.model.get('value');
60 var selected_item_text = this.model.get('value');
61 if (selected_item_text.length === 0) {
61 if (selected_item_text.length === 0) {
62 this.$droplabel.text(' ');
62 this.$droplabel.text(' ');
63 } else {
63 } else {
64 this.$droplabel.text(selected_item_text);
64 this.$droplabel.text(selected_item_text);
65 }
65 }
66
66
67 var items = this.model.get('values');
67 var items = this.model.get('values');
68 var $replace_droplist = $('<ul />')
68 var $replace_droplist = $('<ul />')
69 .addClass('dropdown-menu');
69 .addClass('dropdown-menu');
70 for (var index in items) {
70 _.each(items, function(item, i) {
71 var that = this;
72 var item_button = $('<a href="#"/>')
71 var item_button = $('<a href="#"/>')
73 .text(items[index])
72 .text(item)
74 .on('click', $.proxy(this.handle_click, this));
73 .on('click', $.proxy(this.handle_click, this));
75 $replace_droplist.append($('<li />').append(item_button));
74 $replace_droplist.append($('<li />').append(item_button));
76 }
75 });
76
77 this.$droplist.replaceWith($replace_droplist);
77 this.$droplist.replaceWith($replace_droplist);
78 this.$droplist.remove();
78 this.$droplist.remove();
79 this.$droplist = $replace_droplist;
79 this.$droplist = $replace_droplist;
80
80
81 if (this.model.get('disabled')) {
81 if (this.model.get('disabled')) {
82 this.$buttongroup.attr('disabled','disabled');
82 this.$buttongroup.attr('disabled','disabled');
83 this.$droplabel.attr('disabled','disabled');
83 this.$droplabel.attr('disabled','disabled');
84 this.$dropbutton.attr('disabled','disabled');
84 this.$dropbutton.attr('disabled','disabled');
85 this.$droplist.attr('disabled','disabled');
85 this.$droplist.attr('disabled','disabled');
86 } else {
86 } else {
87 this.$buttongroup.removeAttr('disabled');
87 this.$buttongroup.removeAttr('disabled');
88 this.$droplabel.removeAttr('disabled');
88 this.$droplabel.removeAttr('disabled');
89 this.$dropbutton.removeAttr('disabled');
89 this.$dropbutton.removeAttr('disabled');
90 this.$droplist.removeAttr('disabled');
90 this.$droplist.removeAttr('disabled');
91 }
91 }
92
92
93 var description = this.model.get('description');
93 var description = this.model.get('description');
94 if (description.length === 0) {
94 if (description.length === 0) {
95 this.$label.hide();
95 this.$label.hide();
96 } else {
96 } else {
97 this.$label.text(description);
97 this.$label.text(description);
98 this.$label.show();
98 this.$label.show();
99 }
99 }
100 }
100 }
101 return DropdownView.__super__.update.apply(this);
101 return DropdownView.__super__.update.apply(this);
102 },
102 },
103
103
104 handle_click: function (e) {
104 handle_click: function (e) {
105 // Handle when a value is clicked.
105 // Handle when a value is clicked.
106
106
107 // Calling model.set will trigger all of the other views of the
107 // Calling model.set will trigger all of the other views of the
108 // model to update.
108 // model to update.
109 this.model.set('value', $(e.target).text(), {updated_view: this});
109 this.model.set('value', $(e.target).text(), {updated_view: this});
110 this.touch();
110 this.touch();
111 },
111 },
112
112
113 });
113 });
114 WidgetManager.register_widget_view('DropdownView', DropdownView);
114 WidgetManager.register_widget_view('DropdownView', DropdownView);
115
115
116
116
117 var RadioButtonsView = IPython.DOMWidgetView.extend({
117 var RadioButtonsView = IPython.DOMWidgetView.extend({
118 render : function(){
118 render : function(){
119 // Called when view is rendered.
119 // Called when view is rendered.
120 this.$el
120 this.$el
121 .addClass('widget-hbox');
121 .addClass('widget-hbox');
122 this.$label = $('<div />')
122 this.$label = $('<div />')
123 .appendTo(this.$el)
123 .appendTo(this.$el)
124 .addClass('widget-hlabel')
124 .addClass('widget-hlabel')
125 .hide();
125 .hide();
126 this.$container = $('<div />')
126 this.$container = $('<div />')
127 .appendTo(this.$el)
127 .appendTo(this.$el)
128 .addClass('widget-container')
128 .addClass('widget-container')
129 .addClass('vbox');
129 .addClass('vbox');
130 this.$el_to_style = this.$container; // Set default element to style
130 this.$el_to_style = this.$container; // Set default element to style
131 this.update();
131 this.update();
132 },
132 },
133
133
134 update : function(options){
134 update : function(options){
135 // Update the contents of this view
135 // Update the contents of this view
136 //
136 //
137 // Called when the model is changed. The model may have been
137 // Called when the model is changed. The model may have been
138 // changed by another view or by a state update from the back-end.
138 // changed by another view or by a state update from the back-end.
139 if (options === undefined || options.updated_view != this) {
139 if (options === undefined || options.updated_view != this) {
140 // Add missing items to the DOM.
140 // Add missing items to the DOM.
141 var items = this.model.get('values');
141 var items = this.model.get('values');
142 var disabled = this.model.get('disabled');
142 var disabled = this.model.get('disabled');
143 for (var index in items) {
143 _.each(items, function(item, index) {
144 var item_query = ' :input[value="' + items[index] + '"]';
144 var item_query = ' :input[value="' + item + '"]';
145 if (this.$el.find(item_query).length === 0) {
145 if (this.$el.find(item_query).length === 0) {
146 var $label = $('<label />')
146 var $label = $('<label />')
147 .addClass('radio')
147 .addClass('radio')
148 .text(items[index])
148 .text(item)
149 .appendTo(this.$container);
149 .appendTo(this.$container);
150
150
151 $('<input />')
151 $('<input />')
152 .attr('type', 'radio')
152 .attr('type', 'radio')
153 .addClass(this.model)
153 .addClass(this.model)
154 .val(items[index])
154 .val(item)
155 .prependTo($label)
155 .prependTo($label)
156 .on('click', $.proxy(this.handle_click, this));
156 .on('click', $.proxy(this.handle_click, this));
157 }
157 }
158
158
159 var $item_element = this.$container.find(item_query);
159 var $item_element = this.$container.find(item_query);
160 if (this.model.get('value') == items[index]) {
160 if (this.model.get('value') == item) {
161 $item_element.prop('checked', true);
161 $item_element.prop('checked', true);
162 } else {
162 } else {
163 $item_element.prop('checked', false);
163 $item_element.prop('checked', false);
164 }
164 }
165 $item_element.prop('disabled', disabled);
165 $item_element.prop('disabled', disabled);
166 }
166 });
167
167
168 // Remove items that no longer exist.
168 // Remove items that no longer exist.
169 this.$container.find('input').each(function(i, obj) {
169 this.$container.find('input').each(function(i, obj) {
170 var value = $(obj).val();
170 var value = $(obj).val();
171 var found = false;
171 var found = false;
172 for (var index in items) {
172 _.each(items, function(item, index) {
173 if (items[index] == value) {
173 if (item == value) {
174 found = true;
174 found = true;
175 break;
175 break;
176 }
176 }
177 }
177 });
178
178
179 if (!found) {
179 if (!found) {
180 $(obj).parent().remove();
180 $(obj).parent().remove();
181 }
181 }
182 });
182 });
183
183
184 var description = this.model.get('description');
184 var description = this.model.get('description');
185 if (description.length === 0) {
185 if (description.length === 0) {
186 this.$label.hide();
186 this.$label.hide();
187 } else {
187 } else {
188 this.$label.text(description);
188 this.$label.text(description);
189 this.$label.show();
189 this.$label.show();
190 }
190 }
191 }
191 }
192 return RadioButtonsView.__super__.update.apply(this);
192 return RadioButtonsView.__super__.update.apply(this);
193 },
193 },
194
194
195 handle_click: function (e) {
195 handle_click: function (e) {
196 // Handle when a value is clicked.
196 // Handle when a value is clicked.
197
197
198 // Calling model.set will trigger all of the other views of the
198 // Calling model.set will trigger all of the other views of the
199 // model to update.
199 // model to update.
200 this.model.set('value', $(e.target).val(), {updated_view: this});
200 this.model.set('value', $(e.target).val(), {updated_view: this});
201 this.touch();
201 this.touch();
202 },
202 },
203 });
203 });
204 WidgetManager.register_widget_view('RadioButtonsView', RadioButtonsView);
204 WidgetManager.register_widget_view('RadioButtonsView', RadioButtonsView);
205
205
206
206
207 var ToggleButtonsView = IPython.DOMWidgetView.extend({
207 var ToggleButtonsView = IPython.DOMWidgetView.extend({
208 render : function(){
208 render : function(){
209 // Called when view is rendered.
209 // Called when view is rendered.
210 this.$el
210 this.$el
211 .addClass('widget-hbox-single');
211 .addClass('widget-hbox-single');
212 this.$label = $('<div />')
212 this.$label = $('<div />')
213 .appendTo(this.$el)
213 .appendTo(this.$el)
214 .addClass('widget-hlabel')
214 .addClass('widget-hlabel')
215 .hide();
215 .hide();
216 this.$buttongroup = $('<div />')
216 this.$buttongroup = $('<div />')
217 .addClass('btn-group')
217 .addClass('btn-group')
218 .attr('data-toggle', 'buttons-radio')
218 .attr('data-toggle', 'buttons-radio')
219 .appendTo(this.$el);
219 .appendTo(this.$el);
220 this.$el_to_style = this.$buttongroup; // Set default element to style
220 this.$el_to_style = this.$buttongroup; // Set default element to style
221 this.update();
221 this.update();
222 },
222 },
223
223
224 update : function(options){
224 update : function(options){
225 // Update the contents of this view
225 // Update the contents of this view
226 //
226 //
227 // Called when the model is changed. The model may have been
227 // Called when the model is changed. The model may have been
228 // changed by another view or by a state update from the back-end.
228 // changed by another view or by a state update from the back-end.
229 if (options === undefined || options.updated_view != this) {
229 if (options === undefined || options.updated_view != this) {
230 // Add missing items to the DOM.
230 // Add missing items to the DOM.
231 var items = this.model.get('values');
231 var items = this.model.get('values');
232 var disabled = this.model.get('disabled');
232 var disabled = this.model.get('disabled');
233 for (var index in items) {
233 _.each(items, function(item, index) {
234 var item_query = ' :contains("' + items[index] + '")';
234 var item_query = ' :contains("' + item + '")';
235 if (this.$buttongroup.find(item_query).length === 0) {
235 if (this.$buttongroup.find(item_query).length === 0) {
236 $('<button />')
236 $('<button />')
237 .attr('type', 'button')
237 .attr('type', 'button')
238 .addClass('btn')
238 .addClass('btn')
239 .text(items[index])
239 .text(item)
240 .appendTo(this.$buttongroup)
240 .appendTo(this.$buttongroup)
241 .on('click', $.proxy(this.handle_click, this));
241 .on('click', $.proxy(this.handle_click, this));
242 }
242 }
243
243
244 var $item_element = this.$buttongroup.find(item_query);
244 var $item_element = this.$buttongroup.find(item_query);
245 if (this.model.get('value') == items[index]) {
245 if (this.model.get('value') == item) {
246 $item_element.addClass('active');
246 $item_element.addClass('active');
247 } else {
247 } else {
248 $item_element.removeClass('active');
248 $item_element.removeClass('active');
249 }
249 }
250 $item_element.prop('disabled', disabled);
250 $item_element.prop('disabled', disabled);
251 }
251 });
252
252
253 // Remove items that no longer exist.
253 // Remove items that no longer exist.
254 this.$buttongroup.find('button').each(function(i, obj) {
254 this.$buttongroup.find('button').each(function(i, obj) {
255 var value = $(obj).text();
255 var value = $(obj).text();
256 var found = false;
256 var found = false;
257 for (var index in items) {
257 _.each(items, function(item, index) {
258 if (items[index] == value) {
258 if (item == value) {
259 found = true;
259 found = true;
260 break;
260 break;
261 }
261 }
262 }
262 });
263
263
264 if (!found) {
264 if (!found) {
265 $(obj).remove();
265 $(obj).remove();
266 }
266 }
267 });
267 });
268
268
269 var description = this.model.get('description');
269 var description = this.model.get('description');
270 if (description.length === 0) {
270 if (description.length === 0) {
271 this.$label.hide();
271 this.$label.hide();
272 } else {
272 } else {
273 this.$label.text(description);
273 this.$label.text(description);
274 this.$label.show();
274 this.$label.show();
275 }
275 }
276 }
276 }
277 return ToggleButtonsView.__super__.update.apply(this);
277 return ToggleButtonsView.__super__.update.apply(this);
278 },
278 },
279
279
280 handle_click: function (e) {
280 handle_click: function (e) {
281 // Handle when a value is clicked.
281 // Handle when a value is clicked.
282
282
283 // Calling model.set will trigger all of the other views of the
283 // Calling model.set will trigger all of the other views of the
284 // model to update.
284 // model to update.
285 this.model.set('value', $(e.target).text(), {updated_view: this});
285 this.model.set('value', $(e.target).text(), {updated_view: this});
286 this.touch();
286 this.touch();
287 },
287 },
288 });
288 });
289 WidgetManager.register_widget_view('ToggleButtonsView', ToggleButtonsView);
289 WidgetManager.register_widget_view('ToggleButtonsView', ToggleButtonsView);
290
290
291
291
292 var ListBoxView = IPython.DOMWidgetView.extend({
292 var ListBoxView = IPython.DOMWidgetView.extend({
293 render : function(){
293 render : function(){
294 // Called when view is rendered.
294 // Called when view is rendered.
295 this.$el
295 this.$el
296 .addClass('widget-hbox');
296 .addClass('widget-hbox');
297 this.$label = $('<div />')
297 this.$label = $('<div />')
298 .appendTo(this.$el)
298 .appendTo(this.$el)
299 .addClass('widget-hlabel')
299 .addClass('widget-hlabel')
300 .hide();
300 .hide();
301 this.$listbox = $('<select />')
301 this.$listbox = $('<select />')
302 .addClass('widget-listbox')
302 .addClass('widget-listbox')
303 .attr('size', 6)
303 .attr('size', 6)
304 .appendTo(this.$el);
304 .appendTo(this.$el);
305 this.$el_to_style = this.$listbox; // Set default element to style
305 this.$el_to_style = this.$listbox; // Set default element to style
306 this.update();
306 this.update();
307 },
307 },
308
308
309 update : function(options){
309 update : function(options){
310 // Update the contents of this view
310 // Update the contents of this view
311 //
311 //
312 // Called when the model is changed. The model may have been
312 // Called when the model is changed. The model may have been
313 // changed by another view or by a state update from the back-end.
313 // changed by another view or by a state update from the back-end.
314 if (options === undefined || options.updated_view != this) {
314 if (options === undefined || options.updated_view != this) {
315 // Add missing items to the DOM.
315 // Add missing items to the DOM.
316 var items = this.model.get('values');
316 var items = this.model.get('values');
317 for (var index in items) {
317 _.each(items, function(item, index) {
318 var item_query = ' :contains("' + items[index] + '")';
318 var item_query = ' :contains("' + item + '")';
319 if (this.$listbox.find(item_query).length === 0) {
319 if (this.$listbox.find(item_query).length === 0) {
320 $('<option />')
320 $('<option />')
321 .text(items[index])
321 .text(item)
322 .attr('value', items[index])
322 .attr('value', item)
323 .appendTo(this.$listbox)
323 .appendTo(this.$listbox)
324 .on('click', $.proxy(this.handle_click, this));
324 .on('click', $.proxy(this.handle_click, this));
325 }
325 }
326 }
326 });
327
327
328 // Select the correct element
328 // Select the correct element
329 this.$listbox.val(this.model.get('value'));
329 this.$listbox.val(this.model.get('value'));
330
330
331 // Disable listbox if needed
331 // Disable listbox if needed
332 var disabled = this.model.get('disabled');
332 var disabled = this.model.get('disabled');
333 this.$listbox.prop('disabled', disabled);
333 this.$listbox.prop('disabled', disabled);
334
334
335 // Remove items that no longer exist.
335 // Remove items that no longer exist.
336 this.$listbox.find('option').each(function(i, obj) {
336 this.$listbox.find('option').each(function(i, obj) {
337 var value = $(obj).text();
337 var value = $(obj).text();
338 var found = false;
338 var found = false;
339 for (var index in items) {
339 _.each(items, function(item, index) {
340 if (items[index] == value) {
340 if (item == value) {
341 found = true;
341 found = true;
342 break;
342 break;
343 }
343 }
344 }
344 });
345
345
346 if (!found) {
346 if (!found) {
347 $(obj).remove();
347 $(obj).remove();
348 }
348 }
349 });
349 });
350
350
351 var description = this.model.get('description');
351 var description = this.model.get('description');
352 if (description.length === 0) {
352 if (description.length === 0) {
353 this.$label.hide();
353 this.$label.hide();
354 } else {
354 } else {
355 this.$label.text(description);
355 this.$label.text(description);
356 this.$label.show();
356 this.$label.show();
357 }
357 }
358 }
358 }
359 return ListBoxView.__super__.update.apply(this);
359 return ListBoxView.__super__.update.apply(this);
360 },
360 },
361
361
362 handle_click: function (e) {
362 handle_click: function (e) {
363 // Handle when a value is clicked.
363 // Handle when a value is clicked.
364
364
365 // Calling model.set will trigger all of the other views of the
365 // Calling model.set will trigger all of the other views of the
366 // model to update.
366 // model to update.
367 this.model.set('value', $(e.target).text(), {updated_view: this});
367 this.model.set('value', $(e.target).text(), {updated_view: this});
368 this.touch();
368 this.touch();
369 },
369 },
370 });
370 });
371 WidgetManager.register_widget_view('ListBoxView', ListBoxView);
371 WidgetManager.register_widget_view('ListBoxView', ListBoxView);
372 });
372 });
@@ -1,244 +1,242
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(WidgetManager){
17 define(["notebook/js/widgets/widget"], function(WidgetManager){
18
18
19 var AccordionView = IPython.DOMWidgetView.extend({
19 var AccordionView = IPython.DOMWidgetView.extend({
20 render: function(){
20 render: function(){
21 // Called when view is rendered.
21 // Called when view is rendered.
22 var guid = 'accordion' + IPython.utils.uuid();
22 var guid = 'accordion' + IPython.utils.uuid();
23 this.$el
23 this.$el
24 .attr('id', guid)
24 .attr('id', guid)
25 .addClass('accordion');
25 .addClass('accordion');
26 this.containers = [];
26 this.containers = [];
27 this.model_containers = {};
27 this.model_containers = {};
28 this.update_children([], this.model.get('_children'));
28 this.update_children([], this.model.get('_children'));
29 this.model.on('change:_children', function(model, value, options) {
29 this.model.on('change:_children', function(model, value, options) {
30 this.update_children(model.previous('_children'), value);
30 this.update_children(model.previous('_children'), value);
31 }, this);
31 }, this);
32 },
32 },
33
33
34 update: function(options) {
34 update: function(options) {
35 // Update the contents of this view
35 // Update the contents of this view
36 //
36 //
37 // Called when the model is changed. The model may have been
37 // Called when the model is changed. The model may have been
38 // changed by another view or by a state update from the back-end.
38 // changed by another view or by a state update from the back-end.
39 if (options === undefined || options.updated_view != this) {
39 if (options === undefined || options.updated_view != this) {
40 // Set tab titles
40 // Set tab titles
41 var titles = this.model.get('_titles');
41 var titles = this.model.get('_titles');
42 for (var page_index in titles) {
42 _.each(titles, function(title, page_index) {
43
44 var accordian = this.containers[page_index];
43 var accordian = this.containers[page_index];
45 if (accordian !== undefined) {
44 if (accordian !== undefined) {
46 accordian
45 accordian
47 .find('.accordion-heading')
46 .find('.accordion-heading')
48 .find('.accordion-toggle')
47 .find('.accordion-toggle')
49 .text(titles[page_index]);
48 .text(title);
50 }
49 }
51 }
50 });
52
51
53 // Set selected page
52 // Set selected page
54 var selected_index = this.model.get("selected_index");
53 var selected_index = this.model.get("selected_index");
55 if (0 <= selected_index && selected_index < this.containers.length) {
54 if (0 <= selected_index && selected_index < this.containers.length) {
56 for (var index in this.containers) {
55 _.each(this.containers, function(container, index) {
57 if (index==selected_index) {
56 if (index==selected_index) {
58 this.containers[index].find('.accordion-body').collapse('show');
57 container.find('.accordion-body').collapse('show');
59 } else {
58 } else {
60 this.containers[index].find('.accordion-body').collapse('hide');
59 container.find('.accordion-body').collapse('hide');
61 }
60 }
62
61 });
63 }
64 }
62 }
65 }
63 }
66 return AccordionView.__super__.update.apply(this);
64 return AccordionView.__super__.update.apply(this);
67 },
65 },
68
66
69 update_children: function(old_list, new_list) {
67 update_children: function(old_list, new_list) {
70 // Called when the children list is modified.
68 // Called when the children list is modified.
71 this.do_diff(old_list,
69 this.do_diff(old_list,
72 new_list,
70 new_list,
73 $.proxy(this.remove_child_model, this),
71 $.proxy(this.remove_child_model, this),
74 $.proxy(this.add_child_model, this));
72 $.proxy(this.add_child_model, this));
75 },
73 },
76
74
77 remove_child_model: function(model) {
75 remove_child_model: function(model) {
78 // Called when a child is removed from children list.
76 // Called when a child is removed from children list.
79 var accordion_group = this.model_containers[model.id];
77 var accordion_group = this.model_containers[model.id];
80 this.containers.splice(accordion_group.container_index, 1);
78 this.containers.splice(accordion_group.container_index, 1);
81 delete this.model_containers[model.id];
79 delete this.model_containers[model.id];
82 accordion_group.remove();
80 accordion_group.remove();
83 this.delete_child_view(model);
81 this.delete_child_view(model);
84 },
82 },
85
83
86 add_child_model: function(model) {
84 add_child_model: function(model) {
87 // Called when a child is added to children list.
85 // Called when a child is added to children list.
88 var view = this.create_child_view(model);
86 var view = this.create_child_view(model);
89 var index = this.containers.length;
87 var index = this.containers.length;
90 var uuid = IPython.utils.uuid();
88 var uuid = IPython.utils.uuid();
91 var accordion_group = $('<div />')
89 var accordion_group = $('<div />')
92 .addClass('accordion-group')
90 .addClass('accordion-group')
93 .appendTo(this.$el);
91 .appendTo(this.$el);
94 var accordion_heading = $('<div />')
92 var accordion_heading = $('<div />')
95 .addClass('accordion-heading')
93 .addClass('accordion-heading')
96 .appendTo(accordion_group);
94 .appendTo(accordion_group);
97 var that = this;
95 var that = this;
98 var accordion_toggle = $('<a />')
96 var accordion_toggle = $('<a />')
99 .addClass('accordion-toggle')
97 .addClass('accordion-toggle')
100 .attr('data-toggle', 'collapse')
98 .attr('data-toggle', 'collapse')
101 .attr('data-parent', '#' + this.$el.attr('id'))
99 .attr('data-parent', '#' + this.$el.attr('id'))
102 .attr('href', '#' + uuid)
100 .attr('href', '#' + uuid)
103 .click(function(evt){
101 .click(function(evt){
104
102
105 // Calling model.set will trigger all of the other views of the
103 // Calling model.set will trigger all of the other views of the
106 // model to update.
104 // model to update.
107 that.model.set("selected_index", index, {updated_view: this});
105 that.model.set("selected_index", index, {updated_view: this});
108 that.touch();
106 that.touch();
109 })
107 })
110 .text('Page ' + index)
108 .text('Page ' + index)
111 .appendTo(accordion_heading);
109 .appendTo(accordion_heading);
112 var accordion_body = $('<div />', {id: uuid})
110 var accordion_body = $('<div />', {id: uuid})
113 .addClass('accordion-body collapse')
111 .addClass('accordion-body collapse')
114 .appendTo(accordion_group);
112 .appendTo(accordion_group);
115 var accordion_inner = $('<div />')
113 var accordion_inner = $('<div />')
116 .addClass('accordion-inner')
114 .addClass('accordion-inner')
117 .appendTo(accordion_body);
115 .appendTo(accordion_body);
118 var container_index = this.containers.push(accordion_group) - 1;
116 var container_index = this.containers.push(accordion_group) - 1;
119 accordion_group.container_index = container_index;
117 accordion_group.container_index = container_index;
120 this.model_containers[model.id] = accordion_group;
118 this.model_containers[model.id] = accordion_group;
121 accordion_inner.append(view.$el);
119 accordion_inner.append(view.$el);
122
120
123 this.update();
121 this.update();
124
122
125 // Stupid workaround to close the bootstrap accordion tabs which
123 // Stupid workaround to close the bootstrap accordion tabs which
126 // open by default even though they don't have the `in` class
124 // open by default even though they don't have the `in` class
127 // attached to them. For some reason a delay is required.
125 // attached to them. For some reason a delay is required.
128 // TODO: Better fix.
126 // TODO: Better fix.
129 setTimeout(function(){ that.update(); }, 500);
127 setTimeout(function(){ that.update(); }, 500);
130 },
128 },
131 });
129 });
132 WidgetManager.register_widget_view('AccordionView', AccordionView);
130 WidgetManager.register_widget_view('AccordionView', AccordionView);
133
131
134
132
135 var TabView = IPython.DOMWidgetView.extend({
133 var TabView = IPython.DOMWidgetView.extend({
136 initialize: function() {
134 initialize: function() {
137 // Public constructor.
135 // Public constructor.
138 this.containers = [];
136 this.containers = [];
139 TabView.__super__.initialize.apply(this, arguments);
137 TabView.__super__.initialize.apply(this, arguments);
140 },
138 },
141
139
142 render: function(){
140 render: function(){
143 // Called when view is rendered.
141 // Called when view is rendered.
144 var uuid = 'tabs'+IPython.utils.uuid();
142 var uuid = 'tabs'+IPython.utils.uuid();
145 var that = this;
143 var that = this;
146 this.$tabs = $('<div />', {id: uuid})
144 this.$tabs = $('<div />', {id: uuid})
147 .addClass('nav')
145 .addClass('nav')
148 .addClass('nav-tabs')
146 .addClass('nav-tabs')
149 .appendTo(this.$el);
147 .appendTo(this.$el);
150 this.$tab_contents = $('<div />', {id: uuid + 'Content'})
148 this.$tab_contents = $('<div />', {id: uuid + 'Content'})
151 .addClass('tab-content')
149 .addClass('tab-content')
152 .appendTo(this.$el);
150 .appendTo(this.$el);
153 this.containers = [];
151 this.containers = [];
154 this.update_children([], this.model.get('_children'));
152 this.update_children([], this.model.get('_children'));
155 this.model.on('change:_children', function(model, value, options) {
153 this.model.on('change:_children', function(model, value, options) {
156 this.update_children(model.previous('_children'), value);
154 this.update_children(model.previous('_children'), value);
157 }, this);
155 }, this);
158 },
156 },
159
157
160 update_children: function(old_list, new_list) {
158 update_children: function(old_list, new_list) {
161 // Called when the children list is modified.
159 // Called when the children list is modified.
162 this.do_diff(old_list,
160 this.do_diff(old_list,
163 new_list,
161 new_list,
164 $.proxy(this.remove_child_model, this),
162 $.proxy(this.remove_child_model, this),
165 $.proxy(this.add_child_model, this));
163 $.proxy(this.add_child_model, this));
166 },
164 },
167
165
168 remove_child_model: function(model) {
166 remove_child_model: function(model) {
169 // Called when a child is removed from children list.
167 // Called when a child is removed from children list.
170 var view = this.child_views[model.id];
168 var view = this.child_views[model.id];
171 this.containers.splice(view.parent_tab.tab_text_index, 1);
169 this.containers.splice(view.parent_tab.tab_text_index, 1);
172 view.parent_tab.remove();
170 view.parent_tab.remove();
173 view.parent_container.remove();
171 view.parent_container.remove();
174 view.remove();
172 view.remove();
175 this.delete_child_view(model);
173 this.delete_child_view(model);
176 },
174 },
177
175
178 add_child_model: function(model) {
176 add_child_model: function(model) {
179 // Called when a child is added to children list.
177 // Called when a child is added to children list.
180 var view = this.create_child_view(model);
178 var view = this.create_child_view(model);
181 var index = this.containers.length;
179 var index = this.containers.length;
182 var uuid = IPython.utils.uuid();
180 var uuid = IPython.utils.uuid();
183
181
184 var that = this;
182 var that = this;
185 var tab = $('<li />')
183 var tab = $('<li />')
186 .css('list-style-type', 'none')
184 .css('list-style-type', 'none')
187 .appendTo(this.$tabs);
185 .appendTo(this.$tabs);
188 view.parent_tab = tab;
186 view.parent_tab = tab;
189
187
190 var tab_text = $('<a />')
188 var tab_text = $('<a />')
191 .attr('href', '#' + uuid)
189 .attr('href', '#' + uuid)
192 .attr('data-toggle', 'tab')
190 .attr('data-toggle', 'tab')
193 .text('Page ' + index)
191 .text('Page ' + index)
194 .appendTo(tab)
192 .appendTo(tab)
195 .click(function (e) {
193 .click(function (e) {
196
194
197 // Calling model.set will trigger all of the other views of the
195 // Calling model.set will trigger all of the other views of the
198 // model to update.
196 // model to update.
199 that.model.set("selected_index", index, {updated_view: this});
197 that.model.set("selected_index", index, {updated_view: this});
200 that.touch();
198 that.touch();
201 that.select_page(index);
199 that.select_page(index);
202 });
200 });
203 tab.tab_text_index = this.containers.push(tab_text) - 1;
201 tab.tab_text_index = this.containers.push(tab_text) - 1;
204
202
205 var contents_div = $('<div />', {id: uuid})
203 var contents_div = $('<div />', {id: uuid})
206 .addClass('tab-pane')
204 .addClass('tab-pane')
207 .addClass('fade')
205 .addClass('fade')
208 .append(view.$el)
206 .append(view.$el)
209 .appendTo(this.$tab_contents);
207 .appendTo(this.$tab_contents);
210 view.parent_container = contents_div;
208 view.parent_container = contents_div;
211 },
209 },
212
210
213 update: function(options) {
211 update: function(options) {
214 // Update the contents of this view
212 // Update the contents of this view
215 //
213 //
216 // Called when the model is changed. The model may have been
214 // Called when the model is changed. The model may have been
217 // changed by another view or by a state update from the back-end.
215 // changed by another view or by a state update from the back-end.
218 if (options === undefined || options.updated_view != this) {
216 if (options === undefined || options.updated_view != this) {
219 // Set tab titles
217 // Set tab titles
220 var titles = this.model.get('_titles');
218 var titles = this.model.get('_titles');
221 for (var page_index in titles) {
219 _.each(titles, function(title, page_index) {
222 var tab_text = this.containers[page_index];
220 var tab_text = this.containers[page_index];
223 if (tab_text !== undefined) {
221 if (tab_text !== undefined) {
224 tab_text.text(titles[page_index]);
222 tab_text.text(title);
225 }
223 }
226 }
224 });
227
225
228 var selected_index = this.model.get('selected_index');
226 var selected_index = this.model.get('selected_index');
229 if (0 <= selected_index && selected_index < this.containers.length) {
227 if (0 <= selected_index && selected_index < this.containers.length) {
230 this.select_page(selected_index);
228 this.select_page(selected_index);
231 }
229 }
232 }
230 }
233 return TabView.__super__.update.apply(this);
231 return TabView.__super__.update.apply(this);
234 },
232 },
235
233
236 select_page: function(index) {
234 select_page: function(index) {
237 // Select a page.
235 // Select a page.
238 this.$tabs.find('li')
236 this.$tabs.find('li')
239 .removeClass('active');
237 .removeClass('active');
240 this.containers[index].tab('show');
238 this.containers[index].tab('show');
241 },
239 },
242 });
240 });
243 WidgetManager.register_widget_view('TabView', TabView);
241 WidgetManager.register_widget_view('TabView', TabView);
244 });
242 });
General Comments 0
You need to be logged in to leave comments. Login now