##// END OF EJS Templates
Done with major changes,...
Jonathan Frederic -
Show More
@@ -1,196 +1,197
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 "underscore",
5 "underscore",
6 "backbone",
6 "backbone",
7 ], function (_, Backbone) {
7 ], function (_, Backbone) {
8
8
9 //--------------------------------------------------------------------
9 //--------------------------------------------------------------------
10 // WidgetManager class
10 // WidgetManager class
11 //--------------------------------------------------------------------
11 //--------------------------------------------------------------------
12 var WidgetManager = function (comm_manager) {
12 var WidgetManager = function (comm_manager, keyboard_manager, notebook) {
13 // Public constructor
13 // Public constructor
14 WidgetManager._managers.push(this);
14 WidgetManager._managers.push(this);
15
15
16 // Attach a comm manager to the
16 // Attach a comm manager to the
17 this.keyboard_manager = keyboard_manager;
18 this.notebook = notebook;
17 this.comm_manager = comm_manager;
19 this.comm_manager = comm_manager;
18 this._models = {}; /* Dictionary of model ids and model instances */
20 this._models = {}; /* Dictionary of model ids and model instances */
19
21
20 // Register already-registered widget model types with the comm manager.
22 // Register already-registered widget model types with the comm manager.
21 var that = this;
23 var that = this;
22 _.each(WidgetManager._model_types, function(model_type, model_name) {
24 _.each(WidgetManager._model_types, function(model_type, model_name) {
23 that.comm_manager.register_target(model_name, $.proxy(that._handle_comm_open, that));
25 that.comm_manager.register_target(model_name, $.proxy(that._handle_comm_open, that));
24 });
26 });
25 };
27 };
26
28
27 //--------------------------------------------------------------------
29 //--------------------------------------------------------------------
28 // Class level
30 // Class level
29 //--------------------------------------------------------------------
31 //--------------------------------------------------------------------
30 WidgetManager._model_types = {}; /* Dictionary of model type names (target_name) and model types. */
32 WidgetManager._model_types = {}; /* Dictionary of model type names (target_name) and model types. */
31 WidgetManager._view_types = {}; /* Dictionary of view names and view types. */
33 WidgetManager._view_types = {}; /* Dictionary of view names and view types. */
32 WidgetManager._managers = []; /* List of widget managers */
34 WidgetManager._managers = []; /* List of widget managers */
33
35
34 WidgetManager.register_widget_model = function (model_name, model_type) {
36 WidgetManager.register_widget_model = function (model_name, model_type) {
35 // Registers a widget model by name.
37 // Registers a widget model by name.
36 WidgetManager._model_types[model_name] = model_type;
38 WidgetManager._model_types[model_name] = model_type;
37
39
38 // Register the widget with the comm manager. Make sure to pass this object's context
40 // Register the widget with the comm manager. Make sure to pass this object's context
39 // in so `this` works in the call back.
41 // in so `this` works in the call back.
40 _.each(WidgetManager._managers, function(instance, i) {
42 _.each(WidgetManager._managers, function(instance, i) {
41 if (instance.comm_manager !== null) {
43 if (instance.comm_manager !== null) {
42 instance.comm_manager.register_target(model_name, $.proxy(instance._handle_comm_open, instance));
44 instance.comm_manager.register_target(model_name, $.proxy(instance._handle_comm_open, instance));
43 }
45 }
44 });
46 });
45 };
47 };
46
48
47 WidgetManager.register_widget_view = function (view_name, view_type) {
49 WidgetManager.register_widget_view = function (view_name, view_type) {
48 // Registers a widget view by name.
50 // Registers a widget view by name.
49 WidgetManager._view_types[view_name] = view_type;
51 WidgetManager._view_types[view_name] = view_type;
50 };
52 };
51
53
52 //--------------------------------------------------------------------
54 //--------------------------------------------------------------------
53 // Instance level
55 // Instance level
54 //--------------------------------------------------------------------
56 //--------------------------------------------------------------------
55 WidgetManager.prototype.display_view = function(msg, model) {
57 WidgetManager.prototype.display_view = function(msg, model) {
56 // Displays a view for a particular model.
58 // Displays a view for a particular model.
57 var cell = this.get_msg_cell(msg.parent_header.msg_id);
59 var cell = this.get_msg_cell(msg.parent_header.msg_id);
58 if (cell === null) {
60 if (cell === null) {
59 console.log("Could not determine where the display" +
61 console.log("Could not determine where the display" +
60 " message was from. Widget will not be displayed");
62 " message was from. Widget will not be displayed");
61 } else {
63 } else {
62 var view = this.create_view(model, {cell: cell});
64 var view = this.create_view(model, {cell: cell});
63 if (view === null) {
65 if (view === null) {
64 console.error("View creation failed", model);
66 console.error("View creation failed", model);
65 }
67 }
66 if (cell.widget_subarea) {
68 if (cell.widget_subarea) {
67 cell.widget_area.show();
69 cell.widget_area.show();
68 this._handle_display_view(view);
70 this._handle_display_view(view);
69 cell.widget_subarea.append(view.$el);
71 cell.widget_subarea.append(view.$el);
70 view.trigger('displayed');
72 view.trigger('displayed');
71 }
73 }
72 }
74 }
73 };
75 };
74
76
75 WidgetManager.prototype._handle_display_view = function (view) {
77 WidgetManager.prototype._handle_display_view = function (view) {
76 // Have the IPython keyboard manager disable its event
78 // Have the IPython keyboard manager disable its event
77 // handling so the widget can capture keyboard input.
79 // handling so the widget can capture keyboard input.
78 // Note, this is only done on the outer most widgets.
80 // Note, this is only done on the outer most widgets.
79 IPython.keyboard_manager.register_events(view.$el);
81 if (this.keyboard_manager) {
82 this.keyboard_manager.register_events(view.$el);
80
83
81 if (view.additional_elements) {
84 if (view.additional_elements) {
82 for (var i = 0; i < view.additional_elements.length; i++) {
85 for (var i = 0; i < view.additional_elements.length; i++) {
83 IPython.keyboard_manager.register_events(view.additional_elements[i]);
86 this.keyboard_manager.register_events(view.additional_elements[i]);
87 }
84 }
88 }
85 }
89 }
86 };
90 };
87
91
88 WidgetManager.prototype.create_view = function(model, options, view) {
92 WidgetManager.prototype.create_view = function(model, options, view) {
89 // Creates a view for a particular model.
93 // Creates a view for a particular model.
90 var view_name = model.get('_view_name');
94 var view_name = model.get('_view_name');
91 var ViewType = WidgetManager._view_types[view_name];
95 var ViewType = WidgetManager._view_types[view_name];
92 if (ViewType) {
96 if (ViewType) {
93
97
94 // If a view is passed into the method, use that view's cell as
98 // If a view is passed into the method, use that view's cell as
95 // the cell for the view that is created.
99 // the cell for the view that is created.
96 options = options || {};
100 options = options || {};
97 if (view !== undefined) {
101 if (view !== undefined) {
98 options.cell = view.options.cell;
102 options.cell = view.options.cell;
99 }
103 }
100
104
101 // Create and render the view...
105 // Create and render the view...
102 var parameters = {model: model, options: options};
106 var parameters = {model: model, options: options};
103 view = new ViewType(parameters);
107 view = new ViewType(parameters);
104 view.render();
108 view.render();
105 model.on('destroy', view.remove, view);
109 model.on('destroy', view.remove, view);
106 return view;
110 return view;
107 }
111 }
108 return null;
112 return null;
109 };
113 };
110
114
111 WidgetManager.prototype.get_msg_cell = function (msg_id) {
115 WidgetManager.prototype.get_msg_cell = function (msg_id) {
112 var cell = null;
116 var cell = null;
113 // First, check to see if the msg was triggered by cell execution.
117 // First, check to see if the msg was triggered by cell execution.
114 if (IPython.notebook) {
118 if (this.notebook) {
115 cell = IPython.notebook.get_msg_cell(msg_id);
119 cell = this.notebook.get_msg_cell(msg_id);
116 }
120 }
117 if (cell !== null) {
121 if (cell !== null) {
118 return cell;
122 return cell;
119 }
123 }
120 // Second, check to see if a get_cell callback was defined
124 // Second, check to see if a get_cell callback was defined
121 // for the message. get_cell callbacks are registered for
125 // for the message. get_cell callbacks are registered for
122 // widget messages, so this block is actually checking to see if the
126 // widget messages, so this block is actually checking to see if the
123 // message was triggered by a widget.
127 // message was triggered by a widget.
124 var kernel = this.comm_manager.kernel;
128 var kernel = this.comm_manager.kernel;
125 if (kernel) {
129 if (kernel) {
126 var callbacks = kernel.get_callbacks_for_msg(msg_id);
130 var callbacks = kernel.get_callbacks_for_msg(msg_id);
127 if (callbacks && callbacks.iopub &&
131 if (callbacks && callbacks.iopub &&
128 callbacks.iopub.get_cell !== undefined) {
132 callbacks.iopub.get_cell !== undefined) {
129 return callbacks.iopub.get_cell();
133 return callbacks.iopub.get_cell();
130 }
134 }
131 }
135 }
132
136
133 // Not triggered by a cell or widget (no get_cell callback
137 // Not triggered by a cell or widget (no get_cell callback
134 // exists).
138 // exists).
135 return null;
139 return null;
136 };
140 };
137
141
138 WidgetManager.prototype.callbacks = function (view) {
142 WidgetManager.prototype.callbacks = function (view) {
139 // callback handlers specific a view
143 // callback handlers specific a view
140 var callbacks = {};
144 var callbacks = {};
141 if (view && view.options.cell) {
145 if (view && view.options.cell) {
142
146
143 // Try to get output handlers
147 // Try to get output handlers
144 var cell = view.options.cell;
148 var cell = view.options.cell;
145 var handle_output = null;
149 var handle_output = null;
146 var handle_clear_output = null;
150 var handle_clear_output = null;
147 if (cell.output_area) {
151 if (cell.output_area) {
148 handle_output = $.proxy(cell.output_area.handle_output, cell.output_area);
152 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);
153 handle_clear_output = $.proxy(cell.output_area.handle_clear_output, cell.output_area);
150 }
154 }
151
155
152 // Create callback dict using what is known
156 // Create callback dict using what is known
153 var that = this;
157 var that = this;
154 callbacks = {
158 callbacks = {
155 iopub : {
159 iopub : {
156 output : handle_output,
160 output : handle_output,
157 clear_output : handle_clear_output,
161 clear_output : handle_clear_output,
158
162
159 // Special function only registered by widget messages.
163 // Special function only registered by widget messages.
160 // Allows us to get the cell for a message so we know
164 // Allows us to get the cell for a message so we know
161 // where to add widgets if the code requires it.
165 // where to add widgets if the code requires it.
162 get_cell : function () {
166 get_cell : function () {
163 return cell;
167 return cell;
164 },
168 },
165 },
169 },
166 };
170 };
167 }
171 }
168 return callbacks;
172 return callbacks;
169 };
173 };
170
174
171 WidgetManager.prototype.get_model = function (model_id) {
175 WidgetManager.prototype.get_model = function (model_id) {
172 // Look-up a model instance by its id.
176 // Look-up a model instance by its id.
173 var model = this._models[model_id];
177 var model = this._models[model_id];
174 if (model !== undefined && model.id == model_id) {
178 if (model !== undefined && model.id == model_id) {
175 return model;
179 return model;
176 }
180 }
177 return null;
181 return null;
178 };
182 };
179
183
180 WidgetManager.prototype._handle_comm_open = function (comm, msg) {
184 WidgetManager.prototype._handle_comm_open = function (comm, msg) {
181 // Handle when a comm is opened.
185 // Handle when a comm is opened.
182 var that = this;
186 var that = this;
183 var model_id = comm.comm_id;
187 var model_id = comm.comm_id;
184 var widget_type_name = msg.content.target_name;
188 var widget_type_name = msg.content.target_name;
185 var widget_model = new WidgetManager._model_types[widget_type_name](this, model_id, comm);
189 var widget_model = new WidgetManager._model_types[widget_type_name](this, model_id, comm);
186 widget_model.on('comm:close', function () {
190 widget_model.on('comm:close', function () {
187 delete that._models[model_id];
191 delete that._models[model_id];
188 });
192 });
189 this._models[model_id] = widget_model;
193 this._models[model_id] = widget_model;
190 };
194 };
191
195
192 // For backwards compatability.
193 IPython.WidgetManager = WidgetManager;
194
195 return WidgetManager;
196 return WidgetManager;
196 });
197 });
@@ -1,466 +1,466
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define(["widgets/js/manager",
4 define(["widgets/js/manager",
5 "underscore",
5 "underscore",
6 "backbone"],
6 "backbone"],
7 function(WidgetManager, _, Backbone){
7 function(WidgetManager, _, Backbone){
8
8
9 var WidgetModel = Backbone.Model.extend({
9 var WidgetModel = Backbone.Model.extend({
10 constructor: function (widget_manager, model_id, comm) {
10 constructor: function (widget_manager, model_id, comm) {
11 // Constructor
11 // Constructor
12 //
12 //
13 // Creates a WidgetModel instance.
13 // Creates a WidgetModel instance.
14 //
14 //
15 // Parameters
15 // Parameters
16 // ----------
16 // ----------
17 // widget_manager : WidgetManager instance
17 // widget_manager : WidgetManager instance
18 // model_id : string
18 // model_id : string
19 // An ID unique to this model.
19 // An ID unique to this model.
20 // comm : Comm instance (optional)
20 // comm : Comm instance (optional)
21 this.widget_manager = widget_manager;
21 this.widget_manager = widget_manager;
22 this._buffered_state_diff = {};
22 this._buffered_state_diff = {};
23 this.pending_msgs = 0;
23 this.pending_msgs = 0;
24 this.msg_buffer = null;
24 this.msg_buffer = null;
25 this.key_value_lock = null;
25 this.key_value_lock = null;
26 this.id = model_id;
26 this.id = model_id;
27 this.views = [];
27 this.views = [];
28
28
29 if (comm !== undefined) {
29 if (comm !== undefined) {
30 // Remember comm associated with the model.
30 // Remember comm associated with the model.
31 this.comm = comm;
31 this.comm = comm;
32 comm.model = this;
32 comm.model = this;
33
33
34 // Hook comm messages up to model.
34 // Hook comm messages up to model.
35 comm.on_close($.proxy(this._handle_comm_closed, this));
35 comm.on_close($.proxy(this._handle_comm_closed, this));
36 comm.on_msg($.proxy(this._handle_comm_msg, this));
36 comm.on_msg($.proxy(this._handle_comm_msg, this));
37 }
37 }
38 return Backbone.Model.apply(this);
38 return Backbone.Model.apply(this);
39 },
39 },
40
40
41 send: function (content, callbacks) {
41 send: function (content, callbacks) {
42 // Send a custom msg over the comm.
42 // Send a custom msg over the comm.
43 if (this.comm !== undefined) {
43 if (this.comm !== undefined) {
44 var data = {method: 'custom', content: content};
44 var data = {method: 'custom', content: content};
45 this.comm.send(data, callbacks);
45 this.comm.send(data, callbacks);
46 this.pending_msgs++;
46 this.pending_msgs++;
47 }
47 }
48 },
48 },
49
49
50 _handle_comm_closed: function (msg) {
50 _handle_comm_closed: function (msg) {
51 // Handle when a widget is closed.
51 // Handle when a widget is closed.
52 this.trigger('comm:close');
52 this.trigger('comm:close');
53 delete this.comm.model; // Delete ref so GC will collect widget model.
53 delete this.comm.model; // Delete ref so GC will collect widget model.
54 delete this.comm;
54 delete this.comm;
55 delete this.model_id; // Delete id from model so widget manager cleans up.
55 delete this.model_id; // Delete id from model so widget manager cleans up.
56 _.each(this.views, function(view, i) {
56 _.each(this.views, function(view, i) {
57 view.remove();
57 view.remove();
58 });
58 });
59 },
59 },
60
60
61 _handle_comm_msg: function (msg) {
61 _handle_comm_msg: function (msg) {
62 // Handle incoming comm msg.
62 // Handle incoming comm msg.
63 var method = msg.content.data.method;
63 var method = msg.content.data.method;
64 switch (method) {
64 switch (method) {
65 case 'update':
65 case 'update':
66 this.apply_update(msg.content.data.state);
66 this.apply_update(msg.content.data.state);
67 break;
67 break;
68 case 'custom':
68 case 'custom':
69 this.trigger('msg:custom', msg.content.data.content);
69 this.trigger('msg:custom', msg.content.data.content);
70 break;
70 break;
71 case 'display':
71 case 'display':
72 this.widget_manager.display_view(msg, this);
72 this.widget_manager.display_view(msg, this);
73 break;
73 break;
74 }
74 }
75 },
75 },
76
76
77 apply_update: function (state) {
77 apply_update: function (state) {
78 // Handle when a widget is updated via the python side.
78 // Handle when a widget is updated via the python side.
79 var that = this;
79 var that = this;
80 _.each(state, function(value, key) {
80 _.each(state, function(value, key) {
81 that.key_value_lock = [key, value];
81 that.key_value_lock = [key, value];
82 try {
82 try {
83 WidgetModel.__super__.set.apply(that, [key, that._unpack_models(value)]);
83 WidgetModel.__super__.set.apply(that, [key, that._unpack_models(value)]);
84 } finally {
84 } finally {
85 that.key_value_lock = null;
85 that.key_value_lock = null;
86 }
86 }
87 });
87 });
88 },
88 },
89
89
90 _handle_status: function (msg, callbacks) {
90 _handle_status: function (msg, callbacks) {
91 // Handle status msgs.
91 // Handle status msgs.
92
92
93 // execution_state : ('busy', 'idle', 'starting')
93 // execution_state : ('busy', 'idle', 'starting')
94 if (this.comm !== undefined) {
94 if (this.comm !== undefined) {
95 if (msg.content.execution_state ==='idle') {
95 if (msg.content.execution_state ==='idle') {
96 // Send buffer if this message caused another message to be
96 // Send buffer if this message caused another message to be
97 // throttled.
97 // throttled.
98 if (this.msg_buffer !== null &&
98 if (this.msg_buffer !== null &&
99 (this.get('msg_throttle') || 3) === this.pending_msgs) {
99 (this.get('msg_throttle') || 3) === this.pending_msgs) {
100 var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
100 var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
101 this.comm.send(data, callbacks);
101 this.comm.send(data, callbacks);
102 this.msg_buffer = null;
102 this.msg_buffer = null;
103 } else {
103 } else {
104 --this.pending_msgs;
104 --this.pending_msgs;
105 }
105 }
106 }
106 }
107 }
107 }
108 },
108 },
109
109
110 callbacks: function(view) {
110 callbacks: function(view) {
111 // Create msg callbacks for a comm msg.
111 // Create msg callbacks for a comm msg.
112 var callbacks = this.widget_manager.callbacks(view);
112 var callbacks = this.widget_manager.callbacks(view);
113
113
114 if (callbacks.iopub === undefined) {
114 if (callbacks.iopub === undefined) {
115 callbacks.iopub = {};
115 callbacks.iopub = {};
116 }
116 }
117
117
118 var that = this;
118 var that = this;
119 callbacks.iopub.status = function (msg) {
119 callbacks.iopub.status = function (msg) {
120 that._handle_status(msg, callbacks);
120 that._handle_status(msg, callbacks);
121 };
121 };
122 return callbacks;
122 return callbacks;
123 },
123 },
124
124
125 set: function(key, val, options) {
125 set: function(key, val, options) {
126 // Set a value.
126 // Set a value.
127 var return_value = WidgetModel.__super__.set.apply(this, arguments);
127 var return_value = WidgetModel.__super__.set.apply(this, arguments);
128
128
129 // Backbone only remembers the diff of the most recent set()
129 // Backbone only remembers the diff of the most recent set()
130 // operation. Calling set multiple times in a row results in a
130 // operation. Calling set multiple times in a row results in a
131 // loss of diff information. Here we keep our own running diff.
131 // loss of diff information. Here we keep our own running diff.
132 this._buffered_state_diff = $.extend(this._buffered_state_diff, this.changedAttributes() || {});
132 this._buffered_state_diff = $.extend(this._buffered_state_diff, this.changedAttributes() || {});
133 return return_value;
133 return return_value;
134 },
134 },
135
135
136 sync: function (method, model, options) {
136 sync: function (method, model, options) {
137 // Handle sync to the back-end. Called when a model.save() is called.
137 // Handle sync to the back-end. Called when a model.save() is called.
138
138
139 // Make sure a comm exists.
139 // Make sure a comm exists.
140 var error = options.error || function() {
140 var error = options.error || function() {
141 console.error('Backbone sync error:', arguments);
141 console.error('Backbone sync error:', arguments);
142 };
142 };
143 if (this.comm === undefined) {
143 if (this.comm === undefined) {
144 error();
144 error();
145 return false;
145 return false;
146 }
146 }
147
147
148 // Delete any key value pairs that the back-end already knows about.
148 // Delete any key value pairs that the back-end already knows about.
149 var attrs = (method === 'patch') ? options.attrs : model.toJSON(options);
149 var attrs = (method === 'patch') ? options.attrs : model.toJSON(options);
150 if (this.key_value_lock !== null) {
150 if (this.key_value_lock !== null) {
151 var key = this.key_value_lock[0];
151 var key = this.key_value_lock[0];
152 var value = this.key_value_lock[1];
152 var value = this.key_value_lock[1];
153 if (attrs[key] === value) {
153 if (attrs[key] === value) {
154 delete attrs[key];
154 delete attrs[key];
155 }
155 }
156 }
156 }
157
157
158 // Only sync if there are attributes to send to the back-end.
158 // Only sync if there are attributes to send to the back-end.
159 attrs = this._pack_models(attrs);
159 attrs = this._pack_models(attrs);
160 if (_.size(attrs) > 0) {
160 if (_.size(attrs) > 0) {
161
161
162 // If this message was sent via backbone itself, it will not
162 // If this message was sent via backbone itself, it will not
163 // have any callbacks. It's important that we create callbacks
163 // have any callbacks. It's important that we create callbacks
164 // so we can listen for status messages, etc...
164 // so we can listen for status messages, etc...
165 var callbacks = options.callbacks || this.callbacks();
165 var callbacks = options.callbacks || this.callbacks();
166
166
167 // Check throttle.
167 // Check throttle.
168 if (this.pending_msgs >= (this.get('msg_throttle') || 3)) {
168 if (this.pending_msgs >= (this.get('msg_throttle') || 3)) {
169 // The throttle has been exceeded, buffer the current msg so
169 // The throttle has been exceeded, buffer the current msg so
170 // it can be sent once the kernel has finished processing
170 // it can be sent once the kernel has finished processing
171 // some of the existing messages.
171 // some of the existing messages.
172
172
173 // Combine updates if it is a 'patch' sync, otherwise replace updates
173 // Combine updates if it is a 'patch' sync, otherwise replace updates
174 switch (method) {
174 switch (method) {
175 case 'patch':
175 case 'patch':
176 this.msg_buffer = $.extend(this.msg_buffer || {}, attrs);
176 this.msg_buffer = $.extend(this.msg_buffer || {}, attrs);
177 break;
177 break;
178 case 'update':
178 case 'update':
179 case 'create':
179 case 'create':
180 this.msg_buffer = attrs;
180 this.msg_buffer = attrs;
181 break;
181 break;
182 default:
182 default:
183 error();
183 error();
184 return false;
184 return false;
185 }
185 }
186 this.msg_buffer_callbacks = callbacks;
186 this.msg_buffer_callbacks = callbacks;
187
187
188 } else {
188 } else {
189 // We haven't exceeded the throttle, send the message like
189 // We haven't exceeded the throttle, send the message like
190 // normal.
190 // normal.
191 var data = {method: 'backbone', sync_data: attrs};
191 var data = {method: 'backbone', sync_data: attrs};
192 this.comm.send(data, callbacks);
192 this.comm.send(data, callbacks);
193 this.pending_msgs++;
193 this.pending_msgs++;
194 }
194 }
195 }
195 }
196 // Since the comm is a one-way communication, assume the message
196 // Since the comm is a one-way communication, assume the message
197 // arrived. Don't call success since we don't have a model back from the server
197 // arrived. Don't call success since we don't have a model back from the server
198 // this means we miss out on the 'sync' event.
198 // this means we miss out on the 'sync' event.
199 this._buffered_state_diff = {};
199 this._buffered_state_diff = {};
200 },
200 },
201
201
202 save_changes: function(callbacks) {
202 save_changes: function(callbacks) {
203 // Push this model's state to the back-end
203 // Push this model's state to the back-end
204 //
204 //
205 // This invokes a Backbone.Sync.
205 // This invokes a Backbone.Sync.
206 this.save(this._buffered_state_diff, {patch: true, callbacks: callbacks});
206 this.save(this._buffered_state_diff, {patch: true, callbacks: callbacks});
207 },
207 },
208
208
209 _pack_models: function(value) {
209 _pack_models: function(value) {
210 // Replace models with model ids recursively.
210 // Replace models with model ids recursively.
211 var that = this;
212 var packed;
211 if (value instanceof Backbone.Model) {
213 if (value instanceof Backbone.Model) {
212 return value.id;
214 return value.id;
213
215
214 } else if ($.isArray(value)) {
216 } else if ($.isArray(value)) {
215 var packed = [];
217 packed = [];
216 var that = this;
217 _.each(value, function(sub_value, key) {
218 _.each(value, function(sub_value, key) {
218 packed.push(that._pack_models(sub_value));
219 packed.push(that._pack_models(sub_value));
219 });
220 });
220 return packed;
221 return packed;
221
222
222 } else if (value instanceof Object) {
223 } else if (value instanceof Object) {
223 var packed = {};
224 packed = {};
224 var that = this;
225 _.each(value, function(sub_value, key) {
225 _.each(value, function(sub_value, key) {
226 packed[key] = that._pack_models(sub_value);
226 packed[key] = that._pack_models(sub_value);
227 });
227 });
228 return packed;
228 return packed;
229
229
230 } else {
230 } else {
231 return value;
231 return value;
232 }
232 }
233 },
233 },
234
234
235 _unpack_models: function(value) {
235 _unpack_models: function(value) {
236 // Replace model ids with models recursively.
236 // Replace model ids with models recursively.
237 var that = this;
237 var that = this;
238 var unpacked;
238 var unpacked;
239 if ($.isArray(value)) {
239 if ($.isArray(value)) {
240 unpacked = [];
240 unpacked = [];
241 _.each(value, function(sub_value, key) {
241 _.each(value, function(sub_value, key) {
242 unpacked.push(that._unpack_models(sub_value));
242 unpacked.push(that._unpack_models(sub_value));
243 });
243 });
244 return unpacked;
244 return unpacked;
245
245
246 } else if (value instanceof Object) {
246 } else if (value instanceof Object) {
247 unpacked = {};
247 unpacked = {};
248 _.each(value, function(sub_value, key) {
248 _.each(value, function(sub_value, key) {
249 unpacked[key] = that._unpack_models(sub_value);
249 unpacked[key] = that._unpack_models(sub_value);
250 });
250 });
251 return unpacked;
251 return unpacked;
252
252
253 } else {
253 } else {
254 var model = this.widget_manager.get_model(value);
254 var model = this.widget_manager.get_model(value);
255 if (model) {
255 if (model) {
256 return model;
256 return model;
257 } else {
257 } else {
258 return value;
258 return value;
259 }
259 }
260 }
260 }
261 },
261 },
262
262
263 });
263 });
264 WidgetManager.register_widget_model('WidgetModel', WidgetModel);
264 WidgetManager.register_widget_model('WidgetModel', WidgetModel);
265
265
266
266
267 var WidgetView = Backbone.View.extend({
267 var WidgetView = Backbone.View.extend({
268 initialize: function(parameters) {
268 initialize: function(parameters) {
269 // Public constructor.
269 // Public constructor.
270 this.model.on('change',this.update,this);
270 this.model.on('change',this.update,this);
271 this.options = parameters.options;
271 this.options = parameters.options;
272 this.child_model_views = {};
272 this.child_model_views = {};
273 this.child_views = {};
273 this.child_views = {};
274 this.model.views.push(this);
274 this.model.views.push(this);
275 this.id = this.id || IPython.utils.uuid();
275 this.id = this.id || IPython.utils.uuid();
276 },
276 },
277
277
278 update: function(){
278 update: function(){
279 // Triggered on model change.
279 // Triggered on model change.
280 //
280 //
281 // Update view to be consistent with this.model
281 // Update view to be consistent with this.model
282 },
282 },
283
283
284 create_child_view: function(child_model, options) {
284 create_child_view: function(child_model, options) {
285 // Create and return a child view.
285 // Create and return a child view.
286 //
286 //
287 // -given a model and (optionally) a view name if the view name is
287 // -given a model and (optionally) a view name if the view name is
288 // not given, it defaults to the model's default view attribute.
288 // not given, it defaults to the model's default view attribute.
289
289
290 // TODO: this is hacky, and makes the view depend on this cell attribute and widget manager behavior
290 // TODO: this is hacky, and makes the view depend on this cell attribute and widget manager behavior
291 // it would be great to have the widget manager add the cell metadata
291 // it would be great to have the widget manager add the cell metadata
292 // to the subview without having to add it here.
292 // to the subview without having to add it here.
293 options = $.extend({ parent: this }, options || {});
293 options = $.extend({ parent: this }, options || {});
294 var child_view = this.model.widget_manager.create_view(child_model, options, this);
294 var child_view = this.model.widget_manager.create_view(child_model, options, this);
295
295
296 // Associate the view id with the model id.
296 // Associate the view id with the model id.
297 if (this.child_model_views[child_model.id] === undefined) {
297 if (this.child_model_views[child_model.id] === undefined) {
298 this.child_model_views[child_model.id] = [];
298 this.child_model_views[child_model.id] = [];
299 }
299 }
300 this.child_model_views[child_model.id].push(child_view.id);
300 this.child_model_views[child_model.id].push(child_view.id);
301
301
302 // Remember the view by id.
302 // Remember the view by id.
303 this.child_views[child_view.id] = child_view;
303 this.child_views[child_view.id] = child_view;
304 return child_view;
304 return child_view;
305 },
305 },
306
306
307 pop_child_view: function(child_model) {
307 pop_child_view: function(child_model) {
308 // Delete a child view that was previously created using create_child_view.
308 // Delete a child view that was previously created using create_child_view.
309 var view_ids = this.child_model_views[child_model.id];
309 var view_ids = this.child_model_views[child_model.id];
310 if (view_ids !== undefined) {
310 if (view_ids !== undefined) {
311
311
312 // Only delete the first view in the list.
312 // Only delete the first view in the list.
313 var view_id = view_ids[0];
313 var view_id = view_ids[0];
314 var view = this.child_views[view_id];
314 var view = this.child_views[view_id];
315 delete this.child_views[view_id];
315 delete this.child_views[view_id];
316 view_ids.splice(0,1);
316 view_ids.splice(0,1);
317 child_model.views.pop(view);
317 child_model.views.pop(view);
318
318
319 // Remove the view list specific to this model if it is empty.
319 // Remove the view list specific to this model if it is empty.
320 if (view_ids.length === 0) {
320 if (view_ids.length === 0) {
321 delete this.child_model_views[child_model.id];
321 delete this.child_model_views[child_model.id];
322 }
322 }
323 return view;
323 return view;
324 }
324 }
325 return null;
325 return null;
326 },
326 },
327
327
328 do_diff: function(old_list, new_list, removed_callback, added_callback) {
328 do_diff: function(old_list, new_list, removed_callback, added_callback) {
329 // Difference a changed list and call remove and add callbacks for
329 // Difference a changed list and call remove and add callbacks for
330 // each removed and added item in the new list.
330 // each removed and added item in the new list.
331 //
331 //
332 // Parameters
332 // Parameters
333 // ----------
333 // ----------
334 // old_list : array
334 // old_list : array
335 // new_list : array
335 // new_list : array
336 // removed_callback : Callback(item)
336 // removed_callback : Callback(item)
337 // Callback that is called for each item removed.
337 // Callback that is called for each item removed.
338 // added_callback : Callback(item)
338 // added_callback : Callback(item)
339 // Callback that is called for each item added.
339 // Callback that is called for each item added.
340
340
341 // Walk the lists until an unequal entry is found.
341 // Walk the lists until an unequal entry is found.
342 var i;
342 var i;
343 for (i = 0; i < new_list.length; i++) {
343 for (i = 0; i < new_list.length; i++) {
344 if (i < old_list.length || new_list[i] !== old_list[i]) {
344 if (i < old_list.length || new_list[i] !== old_list[i]) {
345 break;
345 break;
346 }
346 }
347 }
347 }
348
348
349 // Remove the non-matching items from the old list.
349 // Remove the non-matching items from the old list.
350 for (var j = i; j < old_list.length; j++) {
350 for (var j = i; j < old_list.length; j++) {
351 removed_callback(old_list[j]);
351 removed_callback(old_list[j]);
352 }
352 }
353
353
354 // Add the rest of the new list items.
354 // Add the rest of the new list items.
355 for (i; i < new_list.length; i++) {
355 for (i; i < new_list.length; i++) {
356 added_callback(new_list[i]);
356 added_callback(new_list[i]);
357 }
357 }
358 },
358 },
359
359
360 callbacks: function(){
360 callbacks: function(){
361 // Create msg callbacks for a comm msg.
361 // Create msg callbacks for a comm msg.
362 return this.model.callbacks(this);
362 return this.model.callbacks(this);
363 },
363 },
364
364
365 render: function(){
365 render: function(){
366 // Render the view.
366 // Render the view.
367 //
367 //
368 // By default, this is only called the first time the view is created
368 // By default, this is only called the first time the view is created
369 },
369 },
370
370
371 send: function (content) {
371 send: function (content) {
372 // Send a custom msg associated with this view.
372 // Send a custom msg associated with this view.
373 this.model.send(content, this.callbacks());
373 this.model.send(content, this.callbacks());
374 },
374 },
375
375
376 touch: function () {
376 touch: function () {
377 this.model.save_changes(this.callbacks());
377 this.model.save_changes(this.callbacks());
378 },
378 },
379 });
379 });
380
380
381
381
382 var DOMWidgetView = WidgetView.extend({
382 var DOMWidgetView = WidgetView.extend({
383 initialize: function (options) {
383 initialize: function (options) {
384 // Public constructor
384 // Public constructor
385
385
386 // In the future we may want to make changes more granular
386 // In the future we may want to make changes more granular
387 // (e.g., trigger on visible:change).
387 // (e.g., trigger on visible:change).
388 this.model.on('change', this.update, this);
388 this.model.on('change', this.update, this);
389 this.model.on('msg:custom', this.on_msg, this);
389 this.model.on('msg:custom', this.on_msg, this);
390 DOMWidgetView.__super__.initialize.apply(this, arguments);
390 DOMWidgetView.__super__.initialize.apply(this, arguments);
391 },
391 },
392
392
393 on_msg: function(msg) {
393 on_msg: function(msg) {
394 // Handle DOM specific msgs.
394 // Handle DOM specific msgs.
395 switch(msg.msg_type) {
395 switch(msg.msg_type) {
396 case 'add_class':
396 case 'add_class':
397 this.add_class(msg.selector, msg.class_list);
397 this.add_class(msg.selector, msg.class_list);
398 break;
398 break;
399 case 'remove_class':
399 case 'remove_class':
400 this.remove_class(msg.selector, msg.class_list);
400 this.remove_class(msg.selector, msg.class_list);
401 break;
401 break;
402 }
402 }
403 },
403 },
404
404
405 add_class: function (selector, class_list) {
405 add_class: function (selector, class_list) {
406 // Add a DOM class to an element.
406 // Add a DOM class to an element.
407 this._get_selector_element(selector).addClass(class_list);
407 this._get_selector_element(selector).addClass(class_list);
408 },
408 },
409
409
410 remove_class: function (selector, class_list) {
410 remove_class: function (selector, class_list) {
411 // Remove a DOM class from an element.
411 // Remove a DOM class from an element.
412 this._get_selector_element(selector).removeClass(class_list);
412 this._get_selector_element(selector).removeClass(class_list);
413 },
413 },
414
414
415 update: function () {
415 update: function () {
416 // Update the contents of this view
416 // Update the contents of this view
417 //
417 //
418 // Called when the model is changed. The model may have been
418 // Called when the model is changed. The model may have been
419 // changed by another view or by a state update from the back-end.
419 // changed by another view or by a state update from the back-end.
420 // The very first update seems to happen before the element is
420 // The very first update seems to happen before the element is
421 // finished rendering so we use setTimeout to give the element time
421 // finished rendering so we use setTimeout to give the element time
422 // to render
422 // to render
423 var e = this.$el;
423 var e = this.$el;
424 var visible = this.model.get('visible');
424 var visible = this.model.get('visible');
425 setTimeout(function() {e.toggle(visible);},0);
425 setTimeout(function() {e.toggle(visible);},0);
426
426
427 var css = this.model.get('_css');
427 var css = this.model.get('_css');
428 if (css === undefined) {return;}
428 if (css === undefined) {return;}
429 for (var i = 0; i < css.length; i++) {
429 for (var i = 0; i < css.length; i++) {
430 // Apply the css traits to all elements that match the selector.
430 // Apply the css traits to all elements that match the selector.
431 var selector = css[i][0];
431 var selector = css[i][0];
432 var elements = this._get_selector_element(selector);
432 var elements = this._get_selector_element(selector);
433 if (elements.length > 0) {
433 if (elements.length > 0) {
434 var trait_key = css[i][1];
434 var trait_key = css[i][1];
435 var trait_value = css[i][2];
435 var trait_value = css[i][2];
436 elements.css(trait_key ,trait_value);
436 elements.css(trait_key ,trait_value);
437 }
437 }
438 }
438 }
439 },
439 },
440
440
441 _get_selector_element: function (selector) {
441 _get_selector_element: function (selector) {
442 // Get the elements via the css selector.
442 // Get the elements via the css selector.
443
443
444 // If the selector is blank, apply the style to the $el_to_style
444 // If the selector is blank, apply the style to the $el_to_style
445 // element. If the $el_to_style element is not defined, use apply
445 // element. If the $el_to_style element is not defined, use apply
446 // the style to the view's element.
446 // the style to the view's element.
447 var elements;
447 var elements;
448 if (!selector) {
448 if (!selector) {
449 if (this.$el_to_style === undefined) {
449 if (this.$el_to_style === undefined) {
450 elements = this.$el;
450 elements = this.$el;
451 } else {
451 } else {
452 elements = this.$el_to_style;
452 elements = this.$el_to_style;
453 }
453 }
454 } else {
454 } else {
455 elements = this.$el.find(selector);
455 elements = this.$el.find(selector);
456 }
456 }
457 return elements;
457 return elements;
458 },
458 },
459 });
459 });
460
460
461 return {
461 return {
462 'WidgetModel': WidgetModel,
462 'WidgetModel': WidgetModel,
463 'WidgetView': WidgetView,
463 'WidgetView': WidgetView,
464 'DOMWidgetView': DOMWidgetView,
464 'DOMWidgetView': DOMWidgetView,
465 };
465 };
466 });
466 });
@@ -1,377 +1,378
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 "widgets/js/widget",
5 "widgets/js/widget",
6 ], function(widget){
6 "base/js/utils",
7 ], function(widget, utils){
7
8
8 var DropdownView = widget.DOMWidgetView.extend({
9 var DropdownView = widget.DOMWidgetView.extend({
9 render : function(){
10 render : function(){
10 // Called when view is rendered.
11 // Called when view is rendered.
11 this.$el
12 this.$el
12 .addClass('widget-hbox-single');
13 .addClass('widget-hbox-single');
13 this.$label = $('<div />')
14 this.$label = $('<div />')
14 .appendTo(this.$el)
15 .appendTo(this.$el)
15 .addClass('widget-hlabel')
16 .addClass('widget-hlabel')
16 .hide();
17 .hide();
17 this.$buttongroup = $('<div />')
18 this.$buttongroup = $('<div />')
18 .addClass('widget_item')
19 .addClass('widget_item')
19 .addClass('btn-group')
20 .addClass('btn-group')
20 .appendTo(this.$el);
21 .appendTo(this.$el);
21 this.$el_to_style = this.$buttongroup; // Set default element to style
22 this.$el_to_style = this.$buttongroup; // Set default element to style
22 this.$droplabel = $('<button />')
23 this.$droplabel = $('<button />')
23 .addClass('btn btn-default')
24 .addClass('btn btn-default')
24 .addClass('widget-combo-btn')
25 .addClass('widget-combo-btn')
25 .html("&nbsp;")
26 .html("&nbsp;")
26 .appendTo(this.$buttongroup);
27 .appendTo(this.$buttongroup);
27 this.$dropbutton = $('<button />')
28 this.$dropbutton = $('<button />')
28 .addClass('btn btn-default')
29 .addClass('btn btn-default')
29 .addClass('dropdown-toggle')
30 .addClass('dropdown-toggle')
30 .addClass('widget-combo-carrot-btn')
31 .addClass('widget-combo-carrot-btn')
31 .attr('data-toggle', 'dropdown')
32 .attr('data-toggle', 'dropdown')
32 .append($('<span />').addClass("caret"))
33 .append($('<span />').addClass("caret"))
33 .appendTo(this.$buttongroup);
34 .appendTo(this.$buttongroup);
34 this.$droplist = $('<ul />')
35 this.$droplist = $('<ul />')
35 .addClass('dropdown-menu')
36 .addClass('dropdown-menu')
36 .appendTo(this.$buttongroup);
37 .appendTo(this.$buttongroup);
37
38
38 // Set defaults.
39 // Set defaults.
39 this.update();
40 this.update();
40 },
41 },
41
42
42 update : function(options){
43 update : function(options){
43 // Update the contents of this view
44 // Update the contents of this view
44 //
45 //
45 // Called when the model is changed. The model may have been
46 // Called when the model is changed. The model may have been
46 // 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.
47
48
48 if (options === undefined || options.updated_view != this) {
49 if (options === undefined || options.updated_view != this) {
49 var selected_item_text = this.model.get('value_name');
50 var selected_item_text = this.model.get('value_name');
50 if (selected_item_text.trim().length === 0) {
51 if (selected_item_text.trim().length === 0) {
51 this.$droplabel.html("&nbsp;");
52 this.$droplabel.html("&nbsp;");
52 } else {
53 } else {
53 this.$droplabel.text(selected_item_text);
54 this.$droplabel.text(selected_item_text);
54 }
55 }
55
56
56 var items = this.model.get('value_names');
57 var items = this.model.get('value_names');
57 var $replace_droplist = $('<ul />')
58 var $replace_droplist = $('<ul />')
58 .addClass('dropdown-menu');
59 .addClass('dropdown-menu');
59 var that = this;
60 var that = this;
60 _.each(items, function(item, i) {
61 _.each(items, function(item, i) {
61 var item_button = $('<a href="#"/>')
62 var item_button = $('<a href="#"/>')
62 .text(item)
63 .text(item)
63 .on('click', $.proxy(that.handle_click, that));
64 .on('click', $.proxy(that.handle_click, that));
64 $replace_droplist.append($('<li />').append(item_button));
65 $replace_droplist.append($('<li />').append(item_button));
65 });
66 });
66
67
67 this.$droplist.replaceWith($replace_droplist);
68 this.$droplist.replaceWith($replace_droplist);
68 this.$droplist.remove();
69 this.$droplist.remove();
69 this.$droplist = $replace_droplist;
70 this.$droplist = $replace_droplist;
70
71
71 if (this.model.get('disabled')) {
72 if (this.model.get('disabled')) {
72 this.$buttongroup.attr('disabled','disabled');
73 this.$buttongroup.attr('disabled','disabled');
73 this.$droplabel.attr('disabled','disabled');
74 this.$droplabel.attr('disabled','disabled');
74 this.$dropbutton.attr('disabled','disabled');
75 this.$dropbutton.attr('disabled','disabled');
75 this.$droplist.attr('disabled','disabled');
76 this.$droplist.attr('disabled','disabled');
76 } else {
77 } else {
77 this.$buttongroup.removeAttr('disabled');
78 this.$buttongroup.removeAttr('disabled');
78 this.$droplabel.removeAttr('disabled');
79 this.$droplabel.removeAttr('disabled');
79 this.$dropbutton.removeAttr('disabled');
80 this.$dropbutton.removeAttr('disabled');
80 this.$droplist.removeAttr('disabled');
81 this.$droplist.removeAttr('disabled');
81 }
82 }
82
83
83 var description = this.model.get('description');
84 var description = this.model.get('description');
84 if (description.length === 0) {
85 if (description.length === 0) {
85 this.$label.hide();
86 this.$label.hide();
86 } else {
87 } else {
87 this.$label.text(description);
88 this.$label.text(description);
88 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
89 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
89 this.$label.show();
90 this.$label.show();
90 }
91 }
91 }
92 }
92 return DropdownView.__super__.update.apply(this);
93 return DropdownView.__super__.update.apply(this);
93 },
94 },
94
95
95 handle_click: function (e) {
96 handle_click: function (e) {
96 // Handle when a value is clicked.
97 // Handle when a value is clicked.
97
98
98 // Calling model.set will trigger all of the other views of the
99 // Calling model.set will trigger all of the other views of the
99 // model to update.
100 // model to update.
100 this.model.set('value_name', $(e.target).text(), {updated_view: this});
101 this.model.set('value_name', $(e.target).text(), {updated_view: this});
101 this.touch();
102 this.touch();
102 },
103 },
103
104
104 });
105 });
105
106
106
107
107 var RadioButtonsView = widget.DOMWidgetView.extend({
108 var RadioButtonsView = widget.DOMWidgetView.extend({
108 render : function(){
109 render : function(){
109 // Called when view is rendered.
110 // Called when view is rendered.
110 this.$el
111 this.$el
111 .addClass('widget-hbox');
112 .addClass('widget-hbox');
112 this.$label = $('<div />')
113 this.$label = $('<div />')
113 .appendTo(this.$el)
114 .appendTo(this.$el)
114 .addClass('widget-hlabel')
115 .addClass('widget-hlabel')
115 .hide();
116 .hide();
116 this.$container = $('<div />')
117 this.$container = $('<div />')
117 .appendTo(this.$el)
118 .appendTo(this.$el)
118 .addClass('widget-radio-box');
119 .addClass('widget-radio-box');
119 this.$el_to_style = this.$container; // Set default element to style
120 this.$el_to_style = this.$container; // Set default element to style
120 this.update();
121 this.update();
121 },
122 },
122
123
123 update : function(options){
124 update : function(options){
124 // Update the contents of this view
125 // Update the contents of this view
125 //
126 //
126 // Called when the model is changed. The model may have been
127 // Called when the model is changed. The model may have been
127 // changed by another view or by a state update from the back-end.
128 // changed by another view or by a state update from the back-end.
128 if (options === undefined || options.updated_view != this) {
129 if (options === undefined || options.updated_view != this) {
129 // Add missing items to the DOM.
130 // Add missing items to the DOM.
130 var items = this.model.get('value_names');
131 var items = this.model.get('value_names');
131 var disabled = this.model.get('disabled');
132 var disabled = this.model.get('disabled');
132 var that = this;
133 var that = this;
133 _.each(items, function(item, index) {
134 _.each(items, function(item, index) {
134 var item_query = ' :input[value="' + item + '"]';
135 var item_query = ' :input[value="' + item + '"]';
135 if (that.$el.find(item_query).length === 0) {
136 if (that.$el.find(item_query).length === 0) {
136 var $label = $('<label />')
137 var $label = $('<label />')
137 .addClass('radio')
138 .addClass('radio')
138 .text(item)
139 .text(item)
139 .appendTo(that.$container);
140 .appendTo(that.$container);
140
141
141 $('<input />')
142 $('<input />')
142 .attr('type', 'radio')
143 .attr('type', 'radio')
143 .addClass(that.model)
144 .addClass(that.model)
144 .val(item)
145 .val(item)
145 .prependTo($label)
146 .prependTo($label)
146 .on('click', $.proxy(that.handle_click, that));
147 .on('click', $.proxy(that.handle_click, that));
147 }
148 }
148
149
149 var $item_element = that.$container.find(item_query);
150 var $item_element = that.$container.find(item_query);
150 if (that.model.get('value_name') == item) {
151 if (that.model.get('value_name') == item) {
151 $item_element.prop('checked', true);
152 $item_element.prop('checked', true);
152 } else {
153 } else {
153 $item_element.prop('checked', false);
154 $item_element.prop('checked', false);
154 }
155 }
155 $item_element.prop('disabled', disabled);
156 $item_element.prop('disabled', disabled);
156 });
157 });
157
158
158 // Remove items that no longer exist.
159 // Remove items that no longer exist.
159 this.$container.find('input').each(function(i, obj) {
160 this.$container.find('input').each(function(i, obj) {
160 var value = $(obj).val();
161 var value = $(obj).val();
161 var found = false;
162 var found = false;
162 _.each(items, function(item, index) {
163 _.each(items, function(item, index) {
163 if (item == value) {
164 if (item == value) {
164 found = true;
165 found = true;
165 return false;
166 return false;
166 }
167 }
167 });
168 });
168
169
169 if (!found) {
170 if (!found) {
170 $(obj).parent().remove();
171 $(obj).parent().remove();
171 }
172 }
172 });
173 });
173
174
174 var description = this.model.get('description');
175 var description = this.model.get('description');
175 if (description.length === 0) {
176 if (description.length === 0) {
176 this.$label.hide();
177 this.$label.hide();
177 } else {
178 } else {
178 this.$label.text(description);
179 this.$label.text(description);
179 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
180 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
180 this.$label.show();
181 this.$label.show();
181 }
182 }
182 }
183 }
183 return RadioButtonsView.__super__.update.apply(this);
184 return RadioButtonsView.__super__.update.apply(this);
184 },
185 },
185
186
186 handle_click: function (e) {
187 handle_click: function (e) {
187 // Handle when a value is clicked.
188 // Handle when a value is clicked.
188
189
189 // Calling model.set will trigger all of the other views of the
190 // Calling model.set will trigger all of the other views of the
190 // model to update.
191 // model to update.
191 this.model.set('value_name', $(e.target).val(), {updated_view: this});
192 this.model.set('value_name', $(e.target).val(), {updated_view: this});
192 this.touch();
193 this.touch();
193 },
194 },
194 });
195 });
195
196
196
197
197 var ToggleButtonsView = widget.DOMWidgetView.extend({
198 var ToggleButtonsView = widget.DOMWidgetView.extend({
198 render : function(){
199 render : function(){
199 // Called when view is rendered.
200 // Called when view is rendered.
200 this.$el
201 this.$el
201 .addClass('widget-hbox-single');
202 .addClass('widget-hbox-single');
202 this.$label = $('<div />')
203 this.$label = $('<div />')
203 .appendTo(this.$el)
204 .appendTo(this.$el)
204 .addClass('widget-hlabel')
205 .addClass('widget-hlabel')
205 .hide();
206 .hide();
206 this.$buttongroup = $('<div />')
207 this.$buttongroup = $('<div />')
207 .addClass('btn-group')
208 .addClass('btn-group')
208 .attr('data-toggle', 'buttons-radio')
209 .attr('data-toggle', 'buttons-radio')
209 .appendTo(this.$el);
210 .appendTo(this.$el);
210 this.$el_to_style = this.$buttongroup; // Set default element to style
211 this.$el_to_style = this.$buttongroup; // Set default element to style
211 this.update();
212 this.update();
212 },
213 },
213
214
214 update : function(options){
215 update : function(options){
215 // Update the contents of this view
216 // Update the contents of this view
216 //
217 //
217 // Called when the model is changed. The model may have been
218 // Called when the model is changed. The model may have been
218 // changed by another view or by a state update from the back-end.
219 // changed by another view or by a state update from the back-end.
219 if (options === undefined || options.updated_view != this) {
220 if (options === undefined || options.updated_view != this) {
220 // Add missing items to the DOM.
221 // Add missing items to the DOM.
221 var items = this.model.get('value_names');
222 var items = this.model.get('value_names');
222 var disabled = this.model.get('disabled');
223 var disabled = this.model.get('disabled');
223 var that = this;
224 var that = this;
224 var item_html;
225 var item_html;
225 _.each(items, function(item, index) {
226 _.each(items, function(item, index) {
226 if (item.trim().length == 0) {
227 if (item.trim().length == 0) {
227 item_html = "&nbsp;";
228 item_html = "&nbsp;";
228 } else {
229 } else {
229 item_html = IPython.utils.escape_html(item);
230 item_html = utils.escape_html(item);
230 }
231 }
231 var item_query = '[data-value="' + item + '"]';
232 var item_query = '[data-value="' + item + '"]';
232 var $item_element = that.$buttongroup.find(item_query);
233 var $item_element = that.$buttongroup.find(item_query);
233 if (!$item_element.length) {
234 if (!$item_element.length) {
234 $item_element = $('<button/>')
235 $item_element = $('<button/>')
235 .attr('type', 'button')
236 .attr('type', 'button')
236 .addClass('btn btn-default')
237 .addClass('btn btn-default')
237 .html(item_html)
238 .html(item_html)
238 .appendTo(that.$buttongroup)
239 .appendTo(that.$buttongroup)
239 .attr('data-value', item)
240 .attr('data-value', item)
240 .on('click', $.proxy(that.handle_click, that));
241 .on('click', $.proxy(that.handle_click, that));
241 }
242 }
242 if (that.model.get('value_name') == item) {
243 if (that.model.get('value_name') == item) {
243 $item_element.addClass('active');
244 $item_element.addClass('active');
244 } else {
245 } else {
245 $item_element.removeClass('active');
246 $item_element.removeClass('active');
246 }
247 }
247 $item_element.prop('disabled', disabled);
248 $item_element.prop('disabled', disabled);
248 });
249 });
249
250
250 // Remove items that no longer exist.
251 // Remove items that no longer exist.
251 this.$buttongroup.find('button').each(function(i, obj) {
252 this.$buttongroup.find('button').each(function(i, obj) {
252 var value = $(obj).data('value');
253 var value = $(obj).data('value');
253 var found = false;
254 var found = false;
254 _.each(items, function(item, index) {
255 _.each(items, function(item, index) {
255 if (item == value) {
256 if (item == value) {
256 found = true;
257 found = true;
257 return false;
258 return false;
258 }
259 }
259 });
260 });
260
261
261 if (!found) {
262 if (!found) {
262 $(obj).remove();
263 $(obj).remove();
263 }
264 }
264 });
265 });
265
266
266 var description = this.model.get('description');
267 var description = this.model.get('description');
267 if (description.length === 0) {
268 if (description.length === 0) {
268 this.$label.hide();
269 this.$label.hide();
269 } else {
270 } else {
270 this.$label.text(description);
271 this.$label.text(description);
271 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
272 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
272 this.$label.show();
273 this.$label.show();
273 }
274 }
274 }
275 }
275 return ToggleButtonsView.__super__.update.apply(this);
276 return ToggleButtonsView.__super__.update.apply(this);
276 },
277 },
277
278
278 handle_click: function (e) {
279 handle_click: function (e) {
279 // Handle when a value is clicked.
280 // Handle when a value is clicked.
280
281
281 // Calling model.set will trigger all of the other views of the
282 // Calling model.set will trigger all of the other views of the
282 // model to update.
283 // model to update.
283 this.model.set('value_name', $(e.target).data('value'), {updated_view: this});
284 this.model.set('value_name', $(e.target).data('value'), {updated_view: this});
284 this.touch();
285 this.touch();
285 },
286 },
286 });
287 });
287
288
288
289
289 var SelectView = widget.DOMWidgetView.extend({
290 var SelectView = widget.DOMWidgetView.extend({
290 render : function(){
291 render : function(){
291 // Called when view is rendered.
292 // Called when view is rendered.
292 this.$el
293 this.$el
293 .addClass('widget-hbox');
294 .addClass('widget-hbox');
294 this.$label = $('<div />')
295 this.$label = $('<div />')
295 .appendTo(this.$el)
296 .appendTo(this.$el)
296 .addClass('widget-hlabel')
297 .addClass('widget-hlabel')
297 .hide();
298 .hide();
298 this.$listbox = $('<select />')
299 this.$listbox = $('<select />')
299 .addClass('widget-listbox form-control')
300 .addClass('widget-listbox form-control')
300 .attr('size', 6)
301 .attr('size', 6)
301 .appendTo(this.$el);
302 .appendTo(this.$el);
302 this.$el_to_style = this.$listbox; // Set default element to style
303 this.$el_to_style = this.$listbox; // Set default element to style
303 this.update();
304 this.update();
304 },
305 },
305
306
306 update : function(options){
307 update : function(options){
307 // Update the contents of this view
308 // Update the contents of this view
308 //
309 //
309 // Called when the model is changed. The model may have been
310 // Called when the model is changed. The model may have been
310 // changed by another view or by a state update from the back-end.
311 // changed by another view or by a state update from the back-end.
311 if (options === undefined || options.updated_view != this) {
312 if (options === undefined || options.updated_view != this) {
312 // Add missing items to the DOM.
313 // Add missing items to the DOM.
313 var items = this.model.get('value_names');
314 var items = this.model.get('value_names');
314 var that = this;
315 var that = this;
315 _.each(items, function(item, index) {
316 _.each(items, function(item, index) {
316 var item_query = ' :contains("' + item + '")';
317 var item_query = ' :contains("' + item + '")';
317 if (that.$listbox.find(item_query).length === 0) {
318 if (that.$listbox.find(item_query).length === 0) {
318 $('<option />')
319 $('<option />')
319 .text(item)
320 .text(item)
320 .attr('value_name', item)
321 .attr('value_name', item)
321 .appendTo(that.$listbox)
322 .appendTo(that.$listbox)
322 .on('click', $.proxy(that.handle_click, that));
323 .on('click', $.proxy(that.handle_click, that));
323 }
324 }
324 });
325 });
325
326
326 // Select the correct element
327 // Select the correct element
327 this.$listbox.val(this.model.get('value_name'));
328 this.$listbox.val(this.model.get('value_name'));
328
329
329 // Disable listbox if needed
330 // Disable listbox if needed
330 var disabled = this.model.get('disabled');
331 var disabled = this.model.get('disabled');
331 this.$listbox.prop('disabled', disabled);
332 this.$listbox.prop('disabled', disabled);
332
333
333 // Remove items that no longer exist.
334 // Remove items that no longer exist.
334 this.$listbox.find('option').each(function(i, obj) {
335 this.$listbox.find('option').each(function(i, obj) {
335 var value = $(obj).text();
336 var value = $(obj).text();
336 var found = false;
337 var found = false;
337 _.each(items, function(item, index) {
338 _.each(items, function(item, index) {
338 if (item == value) {
339 if (item == value) {
339 found = true;
340 found = true;
340 return false;
341 return false;
341 }
342 }
342 });
343 });
343
344
344 if (!found) {
345 if (!found) {
345 $(obj).remove();
346 $(obj).remove();
346 }
347 }
347 });
348 });
348
349
349 var description = this.model.get('description');
350 var description = this.model.get('description');
350 if (description.length === 0) {
351 if (description.length === 0) {
351 this.$label.hide();
352 this.$label.hide();
352 } else {
353 } else {
353 this.$label.text(description);
354 this.$label.text(description);
354 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
355 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
355 this.$label.show();
356 this.$label.show();
356 }
357 }
357 }
358 }
358 return SelectView.__super__.update.apply(this);
359 return SelectView.__super__.update.apply(this);
359 },
360 },
360
361
361 handle_click: function (e) {
362 handle_click: function (e) {
362 // Handle when a value is clicked.
363 // Handle when a value is clicked.
363
364
364 // 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
365 // model to update.
366 // model to update.
366 this.model.set('value_name', $(e.target).text(), {updated_view: this});
367 this.model.set('value_name', $(e.target).text(), {updated_view: this});
367 this.touch();
368 this.touch();
368 },
369 },
369 });
370 });
370
371
371 return {
372 return {
372 'DropdownView': DropdownView,
373 'DropdownView': DropdownView,
373 'RadioButtonsView': RadioButtonsView,
374 'RadioButtonsView': RadioButtonsView,
374 'ToggleButtonsView': ToggleButtonsView,
375 'ToggleButtonsView': ToggleButtonsView,
375 'SelectView': SelectView,
376 'SelectView': SelectView,
376 };
377 };
377 });
378 });
@@ -1,264 +1,265
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 "widgets/js/widget",
5 "widgets/js/widget",
6 ], function(widget){
6 "base/js/utils",
7 ], function(widget, utils){
7
8
8 var AccordionView = widget.DOMWidgetView.extend({
9 var AccordionView = widget.DOMWidgetView.extend({
9 render: function(){
10 render: function(){
10 // Called when view is rendered.
11 // Called when view is rendered.
11 var guid = 'panel-group' + IPython.utils.uuid();
12 var guid = 'panel-group' + utils.uuid();
12 this.$el
13 this.$el
13 .attr('id', guid)
14 .attr('id', guid)
14 .addClass('panel-group');
15 .addClass('panel-group');
15 this.containers = [];
16 this.containers = [];
16 this.model_containers = {};
17 this.model_containers = {};
17 this.update_children([], this.model.get('children'));
18 this.update_children([], this.model.get('children'));
18 this.model.on('change:children', function(model, value, options) {
19 this.model.on('change:children', function(model, value, options) {
19 this.update_children(model.previous('children'), value);
20 this.update_children(model.previous('children'), value);
20 }, this);
21 }, this);
21 this.model.on('change:selected_index', function(model, value, options) {
22 this.model.on('change:selected_index', function(model, value, options) {
22 this.update_selected_index(model.previous('selected_index'), value, options);
23 this.update_selected_index(model.previous('selected_index'), value, options);
23 }, this);
24 }, this);
24 this.model.on('change:_titles', function(model, value, options) {
25 this.model.on('change:_titles', function(model, value, options) {
25 this.update_titles(value);
26 this.update_titles(value);
26 }, this);
27 }, this);
27 var that = this;
28 var that = this;
28 this.on('displayed', function() {
29 this.on('displayed', function() {
29 this.update_titles();
30 this.update_titles();
30 // Trigger model displayed events for any models that are child to
31 // Trigger model displayed events for any models that are child to
31 // this model when this model is displayed.
32 // this model when this model is displayed.
32 that.is_displayed = true;
33 that.is_displayed = true;
33 for (var property in that.child_views) {
34 for (var property in that.child_views) {
34 if (that.child_views.hasOwnProperty(property)) {
35 if (that.child_views.hasOwnProperty(property)) {
35 that.child_views[property].trigger('displayed');
36 that.child_views[property].trigger('displayed');
36 }
37 }
37 }
38 }
38 }, this);
39 }, this);
39 },
40 },
40
41
41 update_titles: function(titles) {
42 update_titles: function(titles) {
42 // Set tab titles
43 // Set tab titles
43 if (!titles) {
44 if (!titles) {
44 titles = this.model.get('_titles');
45 titles = this.model.get('_titles');
45 }
46 }
46
47
47 var that = this;
48 var that = this;
48 _.each(titles, function(title, page_index) {
49 _.each(titles, function(title, page_index) {
49 var accordian = that.containers[page_index];
50 var accordian = that.containers[page_index];
50 if (accordian !== undefined) {
51 if (accordian !== undefined) {
51 accordian
52 accordian
52 .find('.panel-heading')
53 .find('.panel-heading')
53 .find('.accordion-toggle')
54 .find('.accordion-toggle')
54 .text(title);
55 .text(title);
55 }
56 }
56 });
57 });
57 },
58 },
58
59
59 update_selected_index: function(old_index, new_index, options) {
60 update_selected_index: function(old_index, new_index, options) {
60 // Only update the selection if the selection wasn't triggered
61 // Only update the selection if the selection wasn't triggered
61 // by the front-end. It must be triggered by the back-end.
62 // by the front-end. It must be triggered by the back-end.
62 if (options === undefined || options.updated_view != this) {
63 if (options === undefined || options.updated_view != this) {
63 this.containers[old_index].find('.panel-collapse').collapse('hide');
64 this.containers[old_index].find('.panel-collapse').collapse('hide');
64 if (0 <= new_index && new_index < this.containers.length) {
65 if (0 <= new_index && new_index < this.containers.length) {
65 this.containers[new_index].find('.panel-collapse').collapse('show');
66 this.containers[new_index].find('.panel-collapse').collapse('show');
66 }
67 }
67 }
68 }
68 },
69 },
69
70
70 update_children: function(old_list, new_list) {
71 update_children: function(old_list, new_list) {
71 // Called when the children list is modified.
72 // Called when the children list is modified.
72 this.do_diff(old_list,
73 this.do_diff(old_list,
73 new_list,
74 new_list,
74 $.proxy(this.remove_child_model, this),
75 $.proxy(this.remove_child_model, this),
75 $.proxy(this.add_child_model, this));
76 $.proxy(this.add_child_model, this));
76 },
77 },
77
78
78 remove_child_model: function(model) {
79 remove_child_model: function(model) {
79 // Called when a child is removed from children list.
80 // Called when a child is removed from children list.
80 var accordion_group = this.model_containers[model.id];
81 var accordion_group = this.model_containers[model.id];
81 this.containers.splice(accordion_group.container_index, 1);
82 this.containers.splice(accordion_group.container_index, 1);
82 delete this.model_containers[model.id];
83 delete this.model_containers[model.id];
83 accordion_group.remove();
84 accordion_group.remove();
84 this.pop_child_view(model);
85 this.pop_child_view(model);
85 },
86 },
86
87
87 add_child_model: function(model) {
88 add_child_model: function(model) {
88 // Called when a child is added to children list.
89 // Called when a child is added to children list.
89 var view = this.create_child_view(model);
90 var view = this.create_child_view(model);
90 var index = this.containers.length;
91 var index = this.containers.length;
91 var uuid = IPython.utils.uuid();
92 var uuid = utils.uuid();
92 var accordion_group = $('<div />')
93 var accordion_group = $('<div />')
93 .addClass('panel panel-default')
94 .addClass('panel panel-default')
94 .appendTo(this.$el);
95 .appendTo(this.$el);
95 var accordion_heading = $('<div />')
96 var accordion_heading = $('<div />')
96 .addClass('panel-heading')
97 .addClass('panel-heading')
97 .appendTo(accordion_group);
98 .appendTo(accordion_group);
98 var that = this;
99 var that = this;
99 var accordion_toggle = $('<a />')
100 var accordion_toggle = $('<a />')
100 .addClass('accordion-toggle')
101 .addClass('accordion-toggle')
101 .attr('data-toggle', 'collapse')
102 .attr('data-toggle', 'collapse')
102 .attr('data-parent', '#' + this.$el.attr('id'))
103 .attr('data-parent', '#' + this.$el.attr('id'))
103 .attr('href', '#' + uuid)
104 .attr('href', '#' + uuid)
104 .click(function(evt){
105 .click(function(evt){
105
106
106 // 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
107 // model to update.
108 // model to update.
108 that.model.set("selected_index", index, {updated_view: that});
109 that.model.set("selected_index", index, {updated_view: that});
109 that.touch();
110 that.touch();
110 })
111 })
111 .text('Page ' + index)
112 .text('Page ' + index)
112 .appendTo(accordion_heading);
113 .appendTo(accordion_heading);
113 var accordion_body = $('<div />', {id: uuid})
114 var accordion_body = $('<div />', {id: uuid})
114 .addClass('panel-collapse collapse')
115 .addClass('panel-collapse collapse')
115 .appendTo(accordion_group);
116 .appendTo(accordion_group);
116 var accordion_inner = $('<div />')
117 var accordion_inner = $('<div />')
117 .addClass('panel-body')
118 .addClass('panel-body')
118 .appendTo(accordion_body);
119 .appendTo(accordion_body);
119 var container_index = this.containers.push(accordion_group) - 1;
120 var container_index = this.containers.push(accordion_group) - 1;
120 accordion_group.container_index = container_index;
121 accordion_group.container_index = container_index;
121 this.model_containers[model.id] = accordion_group;
122 this.model_containers[model.id] = accordion_group;
122 accordion_inner.append(view.$el);
123 accordion_inner.append(view.$el);
123
124
124 this.update();
125 this.update();
125 this.update_titles();
126 this.update_titles();
126
127
127 // Trigger the displayed event if this model is displayed.
128 // Trigger the displayed event if this model is displayed.
128 if (this.is_displayed) {
129 if (this.is_displayed) {
129 view.trigger('displayed');
130 view.trigger('displayed');
130 }
131 }
131 },
132 },
132 });
133 });
133
134
134
135
135 var TabView = widget.DOMWidgetView.extend({
136 var TabView = widget.DOMWidgetView.extend({
136 initialize: function() {
137 initialize: function() {
137 // Public constructor.
138 // Public constructor.
138 this.containers = [];
139 this.containers = [];
139 TabView.__super__.initialize.apply(this, arguments);
140 TabView.__super__.initialize.apply(this, arguments);
140 },
141 },
141
142
142 render: function(){
143 render: function(){
143 // Called when view is rendered.
144 // Called when view is rendered.
144 var uuid = 'tabs'+IPython.utils.uuid();
145 var uuid = 'tabs'+utils.uuid();
145 var that = this;
146 var that = this;
146 this.$tabs = $('<div />', {id: uuid})
147 this.$tabs = $('<div />', {id: uuid})
147 .addClass('nav')
148 .addClass('nav')
148 .addClass('nav-tabs')
149 .addClass('nav-tabs')
149 .appendTo(this.$el);
150 .appendTo(this.$el);
150 this.$tab_contents = $('<div />', {id: uuid + 'Content'})
151 this.$tab_contents = $('<div />', {id: uuid + 'Content'})
151 .addClass('tab-content')
152 .addClass('tab-content')
152 .appendTo(this.$el);
153 .appendTo(this.$el);
153 this.containers = [];
154 this.containers = [];
154 this.update_children([], this.model.get('children'));
155 this.update_children([], this.model.get('children'));
155 this.model.on('change:children', function(model, value, options) {
156 this.model.on('change:children', function(model, value, options) {
156 this.update_children(model.previous('children'), value);
157 this.update_children(model.previous('children'), value);
157 }, this);
158 }, this);
158
159
159 // Trigger model displayed events for any models that are child to
160 // Trigger model displayed events for any models that are child to
160 // this model when this model is displayed.
161 // this model when this model is displayed.
161 this.on('displayed', function(){
162 this.on('displayed', function(){
162 that.is_displayed = true;
163 that.is_displayed = true;
163 for (var property in that.child_views) {
164 for (var property in that.child_views) {
164 if (that.child_views.hasOwnProperty(property)) {
165 if (that.child_views.hasOwnProperty(property)) {
165 that.child_views[property].trigger('displayed');
166 that.child_views[property].trigger('displayed');
166 }
167 }
167 }
168 }
168 });
169 });
169 },
170 },
170
171
171 update_children: function(old_list, new_list) {
172 update_children: function(old_list, new_list) {
172 // Called when the children list is modified.
173 // Called when the children list is modified.
173 this.do_diff(old_list,
174 this.do_diff(old_list,
174 new_list,
175 new_list,
175 $.proxy(this.remove_child_model, this),
176 $.proxy(this.remove_child_model, this),
176 $.proxy(this.add_child_model, this));
177 $.proxy(this.add_child_model, this));
177 },
178 },
178
179
179 remove_child_model: function(model) {
180 remove_child_model: function(model) {
180 // Called when a child is removed from children list.
181 // Called when a child is removed from children list.
181 var view = this.pop_child_view(model);
182 var view = this.pop_child_view(model);
182 this.containers.splice(view.parent_tab.tab_text_index, 1);
183 this.containers.splice(view.parent_tab.tab_text_index, 1);
183 view.parent_tab.remove();
184 view.parent_tab.remove();
184 view.parent_container.remove();
185 view.parent_container.remove();
185 view.remove();
186 view.remove();
186 },
187 },
187
188
188 add_child_model: function(model) {
189 add_child_model: function(model) {
189 // Called when a child is added to children list.
190 // Called when a child is added to children list.
190 var view = this.create_child_view(model);
191 var view = this.create_child_view(model);
191 var index = this.containers.length;
192 var index = this.containers.length;
192 var uuid = IPython.utils.uuid();
193 var uuid = utils.uuid();
193
194
194 var that = this;
195 var that = this;
195 var tab = $('<li />')
196 var tab = $('<li />')
196 .css('list-style-type', 'none')
197 .css('list-style-type', 'none')
197 .appendTo(this.$tabs);
198 .appendTo(this.$tabs);
198 view.parent_tab = tab;
199 view.parent_tab = tab;
199
200
200 var tab_text = $('<a />')
201 var tab_text = $('<a />')
201 .attr('href', '#' + uuid)
202 .attr('href', '#' + uuid)
202 .attr('data-toggle', 'tab')
203 .attr('data-toggle', 'tab')
203 .text('Page ' + index)
204 .text('Page ' + index)
204 .appendTo(tab)
205 .appendTo(tab)
205 .click(function (e) {
206 .click(function (e) {
206
207
207 // Calling model.set will trigger all of the other views of the
208 // Calling model.set will trigger all of the other views of the
208 // model to update.
209 // model to update.
209 that.model.set("selected_index", index, {updated_view: this});
210 that.model.set("selected_index", index, {updated_view: this});
210 that.touch();
211 that.touch();
211 that.select_page(index);
212 that.select_page(index);
212 });
213 });
213 tab.tab_text_index = this.containers.push(tab_text) - 1;
214 tab.tab_text_index = this.containers.push(tab_text) - 1;
214
215
215 var contents_div = $('<div />', {id: uuid})
216 var contents_div = $('<div />', {id: uuid})
216 .addClass('tab-pane')
217 .addClass('tab-pane')
217 .addClass('fade')
218 .addClass('fade')
218 .append(view.$el)
219 .append(view.$el)
219 .appendTo(this.$tab_contents);
220 .appendTo(this.$tab_contents);
220 view.parent_container = contents_div;
221 view.parent_container = contents_div;
221
222
222 // Trigger the displayed event if this model is displayed.
223 // Trigger the displayed event if this model is displayed.
223 if (this.is_displayed) {
224 if (this.is_displayed) {
224 view.trigger('displayed');
225 view.trigger('displayed');
225 }
226 }
226 },
227 },
227
228
228 update: function(options) {
229 update: function(options) {
229 // Update the contents of this view
230 // Update the contents of this view
230 //
231 //
231 // Called when the model is changed. The model may have been
232 // Called when the model is changed. The model may have been
232 // changed by another view or by a state update from the back-end.
233 // changed by another view or by a state update from the back-end.
233 if (options === undefined || options.updated_view != this) {
234 if (options === undefined || options.updated_view != this) {
234 // Set tab titles
235 // Set tab titles
235 var titles = this.model.get('_titles');
236 var titles = this.model.get('_titles');
236 var that = this;
237 var that = this;
237 _.each(titles, function(title, page_index) {
238 _.each(titles, function(title, page_index) {
238 var tab_text = that.containers[page_index];
239 var tab_text = that.containers[page_index];
239 if (tab_text !== undefined) {
240 if (tab_text !== undefined) {
240 tab_text.text(title);
241 tab_text.text(title);
241 }
242 }
242 });
243 });
243
244
244 var selected_index = this.model.get('selected_index');
245 var selected_index = this.model.get('selected_index');
245 if (0 <= selected_index && selected_index < this.containers.length) {
246 if (0 <= selected_index && selected_index < this.containers.length) {
246 this.select_page(selected_index);
247 this.select_page(selected_index);
247 }
248 }
248 }
249 }
249 return TabView.__super__.update.apply(this);
250 return TabView.__super__.update.apply(this);
250 },
251 },
251
252
252 select_page: function(index) {
253 select_page: function(index) {
253 // Select a page.
254 // Select a page.
254 this.$tabs.find('li')
255 this.$tabs.find('li')
255 .removeClass('active');
256 .removeClass('active');
256 this.containers[index].tab('show');
257 this.containers[index].tab('show');
257 },
258 },
258 });
259 });
259
260
260 return {
261 return {
261 'AccordionView': AccordionView,
262 'AccordionView': AccordionView,
262 'TabView': TabView,
263 'TabView': TabView,
263 };
264 };
264 });
265 });
General Comments 0
You need to be logged in to leave comments. Login now