##// END OF EJS Templates
Move the display Promise into a lower level method,...
Jonathan Frederic -
Show More
@@ -1,242 +1,249
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 "jquery",
7 "jquery",
8 "base/js/utils",
8 "base/js/utils",
9 "base/js/namespace",
9 "base/js/namespace",
10 'rsvp',
10 'rsvp',
11 ], function (_, Backbone, $, utils, IPython, rsvp) {
11 ], function (_, Backbone, $, utils, IPython, rsvp) {
12 "use strict";
12 "use strict";
13 //--------------------------------------------------------------------
13 //--------------------------------------------------------------------
14 // WidgetManager class
14 // WidgetManager class
15 //--------------------------------------------------------------------
15 //--------------------------------------------------------------------
16 var WidgetManager = function (comm_manager, notebook) {
16 var WidgetManager = function (comm_manager, notebook) {
17 // Public constructor
17 // Public constructor
18 WidgetManager._managers.push(this);
18 WidgetManager._managers.push(this);
19
19
20 // Attach a comm manager to the
20 // Attach a comm manager to the
21 this.keyboard_manager = notebook.keyboard_manager;
21 this.keyboard_manager = notebook.keyboard_manager;
22 this.notebook = notebook;
22 this.notebook = notebook;
23 this.comm_manager = comm_manager;
23 this.comm_manager = comm_manager;
24 this._models = {}; /* Dictionary of model ids and model instances */
24 this._models = {}; /* Dictionary of model ids and model instances */
25
25
26 // Register with the comm manager.
26 // Register with the comm manager.
27 this.comm_manager.register_target('ipython.widget', $.proxy(this._handle_comm_open, this));
27 this.comm_manager.register_target('ipython.widget', $.proxy(this._handle_comm_open, this));
28 };
28 };
29
29
30 //--------------------------------------------------------------------
30 //--------------------------------------------------------------------
31 // Class level
31 // Class level
32 //--------------------------------------------------------------------
32 //--------------------------------------------------------------------
33 WidgetManager._model_types = {}; /* Dictionary of model type names (target_name) and model types. */
33 WidgetManager._model_types = {}; /* Dictionary of model type names (target_name) and model types. */
34 WidgetManager._view_types = {}; /* Dictionary of view names and view types. */
34 WidgetManager._view_types = {}; /* Dictionary of view names and view types. */
35 WidgetManager._managers = []; /* List of widget managers */
35 WidgetManager._managers = []; /* List of widget managers */
36
36
37 WidgetManager.register_widget_model = function (model_name, model_type) {
37 WidgetManager.register_widget_model = function (model_name, model_type) {
38 // Registers a widget model by name.
38 // Registers a widget model by name.
39 WidgetManager._model_types[model_name] = model_type;
39 WidgetManager._model_types[model_name] = model_type;
40 };
40 };
41
41
42 WidgetManager.register_widget_view = function (view_name, view_type) {
42 WidgetManager.register_widget_view = function (view_name, view_type) {
43 // Registers a widget view by name.
43 // Registers a widget view by name.
44 WidgetManager._view_types[view_name] = view_type;
44 WidgetManager._view_types[view_name] = view_type;
45 };
45 };
46
46
47 //--------------------------------------------------------------------
47 //--------------------------------------------------------------------
48 // Instance level
48 // Instance level
49 //--------------------------------------------------------------------
49 //--------------------------------------------------------------------
50 WidgetManager.prototype.display_view = function(msg, model) {
50 WidgetManager.prototype.display_view = function(msg, model) {
51 // Displays a view for a particular model.
51 // Displays a view for a particular model.
52 var that = this;
52 var that = this;
53 return new rsvp.Promise(function(resolve, reject) {
53 return new rsvp.Promise(function(resolve, reject) {
54 var cell = that.get_msg_cell(msg.parent_header.msg_id);
54 var cell = that.get_msg_cell(msg.parent_header.msg_id);
55 if (cell === null) {
55 if (cell === null) {
56 reject(new Error("Could not determine where the display" +
56 reject(new Error("Could not determine where the display" +
57 " message was from. Widget will not be displayed"));
57 " message was from. Widget will not be displayed"));
58 } else {
58 } else {
59 var dummy = null;
59 var dummy = null;
60 if (cell.widget_subarea) {
60 if (cell.widget_subarea) {
61 dummy = $('<div />');
61 dummy = $('<div />');
62 cell.widget_subarea.append(dummy);
62 cell.widget_subarea.append(dummy);
63 }
63 }
64
64
65 that.create_view(model, {cell: cell}).then(function(view) {
65 that.create_view(model, {cell: cell}).then(function(view) {
66 that._handle_display_view(view);
66 that._handle_display_view(view);
67 if (dummy) {
67 if (dummy) {
68 dummy.replaceWith(view.$el);
68 dummy.replaceWith(view.$el);
69 }
69 }
70 view.trigger('displayed');
70 view.trigger('displayed');
71 resolve(view);
71 resolve(view);
72 }, function(error) {
72 }, function(error) {
73 reject(new utils.WrappedError('Could not display view', error));
73 reject(new utils.WrappedError('Could not display view', error));
74 });
74 });
75 }
75 }
76 });
76 });
77 };
77 };
78
78
79 WidgetManager.prototype._handle_display_view = function (view) {
79 WidgetManager.prototype._handle_display_view = function (view) {
80 // Have the IPython keyboard manager disable its event
80 // Have the IPython keyboard manager disable its event
81 // handling so the widget can capture keyboard input.
81 // handling so the widget can capture keyboard input.
82 // Note, this is only done on the outer most widgets.
82 // Note, this is only done on the outer most widgets.
83 if (this.keyboard_manager) {
83 if (this.keyboard_manager) {
84 this.keyboard_manager.register_events(view.$el);
84 this.keyboard_manager.register_events(view.$el);
85
85
86 if (view.additional_elements) {
86 if (view.additional_elements) {
87 for (var i = 0; i < view.additional_elements.length; i++) {
87 for (var i = 0; i < view.additional_elements.length; i++) {
88 this.keyboard_manager.register_events(view.additional_elements[i]);
88 this.keyboard_manager.register_events(view.additional_elements[i]);
89 }
89 }
90 }
90 }
91 }
91 }
92 };
92 };
93
93
94 WidgetManager.prototype.create_view = function(model, options) {
94 WidgetManager.prototype.create_view = function(model, options) {
95 // Creates a promise for a view of a given model
95 // Creates a promise for a view of a given model
96 return utils.load_class(model.get('_view_name'), model.get('_view_module'),
96
97 // Make sure the view creation is not out of order with
98 // any state updates.
99 model.state_change = model.state_change.then(function() {
100 console.log('create_view ' + model.id);
101 return utils.load_class(model.get('_view_name'), model.get('_view_module'),
97 WidgetManager._view_types).then(function(ViewType) {
102 WidgetManager._view_types).then(function(ViewType) {
98
103
99 // If a view is passed into the method, use that view's cell as
104 // If a view is passed into the method, use that view's cell as
100 // the cell for the view that is created.
105 // the cell for the view that is created.
101 options = options || {};
106 options = options || {};
102 if (options.parent !== undefined) {
107 if (options.parent !== undefined) {
103 options.cell = options.parent.options.cell;
108 options.cell = options.parent.options.cell;
104 }
109 }
105 // Create and render the view...
110 // Create and render the view...
106 var parameters = {model: model, options: options};
111 var parameters = {model: model, options: options};
107 var view = new ViewType(parameters);
112 var view = new ViewType(parameters);
108 view.listenTo(model, 'destroy', view.remove);
113 view.listenTo(model, 'destroy', view.remove);
109 view.render();
114 view.render();
110 return view;
115 return view;
111 }, utils.reject("Couldn't create a view for model id '" + String(model.id) + "'"));
116 }, utils.reject("Couldn't create a view for model id '" + String(model.id) + "'"));
117 });
118 return model.state_change;
112 };
119 };
113
120
114 WidgetManager.prototype.get_msg_cell = function (msg_id) {
121 WidgetManager.prototype.get_msg_cell = function (msg_id) {
115 var cell = null;
122 var cell = null;
116 // First, check to see if the msg was triggered by cell execution.
123 // First, check to see if the msg was triggered by cell execution.
117 if (this.notebook) {
124 if (this.notebook) {
118 cell = this.notebook.get_msg_cell(msg_id);
125 cell = this.notebook.get_msg_cell(msg_id);
119 }
126 }
120 if (cell !== null) {
127 if (cell !== null) {
121 return cell;
128 return cell;
122 }
129 }
123 // Second, check to see if a get_cell callback was defined
130 // Second, check to see if a get_cell callback was defined
124 // for the message. get_cell callbacks are registered for
131 // for the message. get_cell callbacks are registered for
125 // widget messages, so this block is actually checking to see if the
132 // widget messages, so this block is actually checking to see if the
126 // message was triggered by a widget.
133 // message was triggered by a widget.
127 var kernel = this.comm_manager.kernel;
134 var kernel = this.comm_manager.kernel;
128 if (kernel) {
135 if (kernel) {
129 var callbacks = kernel.get_callbacks_for_msg(msg_id);
136 var callbacks = kernel.get_callbacks_for_msg(msg_id);
130 if (callbacks && callbacks.iopub &&
137 if (callbacks && callbacks.iopub &&
131 callbacks.iopub.get_cell !== undefined) {
138 callbacks.iopub.get_cell !== undefined) {
132 return callbacks.iopub.get_cell();
139 return callbacks.iopub.get_cell();
133 }
140 }
134 }
141 }
135
142
136 // Not triggered by a cell or widget (no get_cell callback
143 // Not triggered by a cell or widget (no get_cell callback
137 // exists).
144 // exists).
138 return null;
145 return null;
139 };
146 };
140
147
141 WidgetManager.prototype.callbacks = function (view) {
148 WidgetManager.prototype.callbacks = function (view) {
142 // callback handlers specific a view
149 // callback handlers specific a view
143 var callbacks = {};
150 var callbacks = {};
144 if (view && view.options.cell) {
151 if (view && view.options.cell) {
145
152
146 // Try to get output handlers
153 // Try to get output handlers
147 var cell = view.options.cell;
154 var cell = view.options.cell;
148 var handle_output = null;
155 var handle_output = null;
149 var handle_clear_output = null;
156 var handle_clear_output = null;
150 if (cell.output_area) {
157 if (cell.output_area) {
151 handle_output = $.proxy(cell.output_area.handle_output, cell.output_area);
158 handle_output = $.proxy(cell.output_area.handle_output, cell.output_area);
152 handle_clear_output = $.proxy(cell.output_area.handle_clear_output, cell.output_area);
159 handle_clear_output = $.proxy(cell.output_area.handle_clear_output, cell.output_area);
153 }
160 }
154
161
155 // Create callback dictionary using what is known
162 // Create callback dictionary using what is known
156 var that = this;
163 var that = this;
157 callbacks = {
164 callbacks = {
158 iopub : {
165 iopub : {
159 output : handle_output,
166 output : handle_output,
160 clear_output : handle_clear_output,
167 clear_output : handle_clear_output,
161
168
162 // Special function only registered by widget messages.
169 // Special function only registered by widget messages.
163 // Allows us to get the cell for a message so we know
170 // Allows us to get the cell for a message so we know
164 // where to add widgets if the code requires it.
171 // where to add widgets if the code requires it.
165 get_cell : function () {
172 get_cell : function () {
166 return cell;
173 return cell;
167 },
174 },
168 },
175 },
169 };
176 };
170 }
177 }
171 return callbacks;
178 return callbacks;
172 };
179 };
173
180
174 WidgetManager.prototype.get_model = function (model_id) {
181 WidgetManager.prototype.get_model = function (model_id) {
175 return this._models[model_id];
182 return this._models[model_id];
176 };
183 };
177
184
178 WidgetManager.prototype._handle_comm_open = function (comm, msg) {
185 WidgetManager.prototype._handle_comm_open = function (comm, msg) {
179 // Handle when a comm is opened.
186 // Handle when a comm is opened.
180 this.create_model({
187 this.create_model({
181 model_name: msg.content.data.model_name,
188 model_name: msg.content.data.model_name,
182 model_module: msg.content.data.model_module,
189 model_module: msg.content.data.model_module,
183 comm: comm}).catch($.proxy(console.error, console));
190 comm: comm}).catch($.proxy(console.error, console));
184 };
191 };
185
192
186 WidgetManager.prototype.create_model = function (options) {
193 WidgetManager.prototype.create_model = function (options) {
187 // Create and return a promise for a new widget model
194 // Create and return a promise for a new widget model
188 //
195 //
189 // Minimally, one must provide the model_name and widget_class
196 // Minimally, one must provide the model_name and widget_class
190 // parameters to create a model from Javascript.
197 // parameters to create a model from Javascript.
191 //
198 //
192 // Example
199 // Example
193 // --------
200 // --------
194 // JS:
201 // JS:
195 // IPython.notebook.kernel.widget_manager.create_model({
202 // IPython.notebook.kernel.widget_manager.create_model({
196 // model_name: 'WidgetModel',
203 // model_name: 'WidgetModel',
197 // widget_class: 'IPython.html.widgets.widget_int.IntSlider'})
204 // widget_class: 'IPython.html.widgets.widget_int.IntSlider'})
198 // .then(function(model) { console.log('Create success!', model); },
205 // .then(function(model) { console.log('Create success!', model); },
199 // $.proxy(console.error, console));
206 // $.proxy(console.error, console));
200 //
207 //
201 // Parameters
208 // Parameters
202 // ----------
209 // ----------
203 // options: dictionary
210 // options: dictionary
204 // Dictionary of options with the following contents:
211 // Dictionary of options with the following contents:
205 // model_name: string
212 // model_name: string
206 // Target name of the widget model to create.
213 // Target name of the widget model to create.
207 // model_module: (optional) string
214 // model_module: (optional) string
208 // Module name of the widget model to create.
215 // Module name of the widget model to create.
209 // widget_class: (optional) string
216 // widget_class: (optional) string
210 // Target name of the widget in the back-end.
217 // Target name of the widget in the back-end.
211 // comm: (optional) Comm
218 // comm: (optional) Comm
212
219
213 // Create a comm if it wasn't provided.
220 // Create a comm if it wasn't provided.
214 var comm = options.comm;
221 var comm = options.comm;
215 if (!comm) {
222 if (!comm) {
216 comm = this.comm_manager.new_comm('ipython.widget', {'widget_class': options.widget_class});
223 comm = this.comm_manager.new_comm('ipython.widget', {'widget_class': options.widget_class});
217 }
224 }
218
225
219 var that = this;
226 var that = this;
220 var model_id = comm.comm_id;
227 var model_id = comm.comm_id;
221 var model_promise = utils.load_class(options.model_name, options.model_module, WidgetManager._model_types)
228 var model_promise = utils.load_class(options.model_name, options.model_module, WidgetManager._model_types)
222 .then(function(ModelType) {
229 .then(function(ModelType) {
223 var widget_model = new ModelType(that, model_id, comm);
230 var widget_model = new ModelType(that, model_id, comm);
224 widget_model.once('comm:close', function () {
231 widget_model.once('comm:close', function () {
225 delete that._models[model_id];
232 delete that._models[model_id];
226 });
233 });
227 return widget_model;
234 return widget_model;
228
235
229 }, function(error) {
236 }, function(error) {
230 delete that._models[model_id];
237 delete that._models[model_id];
231 var wrapped_error = new utils.WrappedError("Couldn't create model", error);
238 var wrapped_error = new utils.WrappedError("Couldn't create model", error);
232 return rsvp.Promise.reject(wrapped_error);
239 return rsvp.Promise.reject(wrapped_error);
233 });
240 });
234 this._models[model_id] = model_promise;
241 this._models[model_id] = model_promise;
235 return model_promise;
242 return model_promise;
236 };
243 };
237
244
238 // Backwards compatibility.
245 // Backwards compatibility.
239 IPython.WidgetManager = WidgetManager;
246 IPython.WidgetManager = WidgetManager;
240
247
241 return {'WidgetManager': WidgetManager};
248 return {'WidgetManager': WidgetManager};
242 });
249 });
@@ -1,605 +1,606
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 "jquery",
7 "jquery",
8 "base/js/utils",
8 "base/js/utils",
9 "base/js/namespace",
9 "base/js/namespace",
10 "rsvp",
10 "rsvp",
11 ], function(widgetmanager, _, Backbone, $, utils, IPython, rsvp){
11 ], function(widgetmanager, _, Backbone, $, utils, IPython, rsvp){
12
12
13 var WidgetModel = Backbone.Model.extend({
13 var WidgetModel = Backbone.Model.extend({
14 constructor: function (widget_manager, model_id, comm) {
14 constructor: function (widget_manager, model_id, comm) {
15 // Constructor
15 // Constructor
16 //
16 //
17 // Creates a WidgetModel instance.
17 // Creates a WidgetModel instance.
18 //
18 //
19 // Parameters
19 // Parameters
20 // ----------
20 // ----------
21 // widget_manager : WidgetManager instance
21 // widget_manager : WidgetManager instance
22 // model_id : string
22 // model_id : string
23 // An ID unique to this model.
23 // An ID unique to this model.
24 // comm : Comm instance (optional)
24 // comm : Comm instance (optional)
25 this.widget_manager = widget_manager;
25 this.widget_manager = widget_manager;
26 this.state_change = rsvp.Promise.resolve();
26 this.state_change = rsvp.Promise.resolve();
27 this._buffered_state_diff = {};
27 this._buffered_state_diff = {};
28 this.pending_msgs = 0;
28 this.pending_msgs = 0;
29 this.msg_buffer = null;
29 this.msg_buffer = null;
30 this.state_lock = null;
30 this.state_lock = null;
31 this.id = model_id;
31 this.id = model_id;
32 this.views = {};
32 this.views = {};
33
33
34 if (comm !== undefined) {
34 if (comm !== undefined) {
35 // Remember comm associated with the model.
35 // Remember comm associated with the model.
36 this.comm = comm;
36 this.comm = comm;
37 comm.model = this;
37 comm.model = this;
38
38
39 // Hook comm messages up to model.
39 // Hook comm messages up to model.
40 comm.on_close($.proxy(this._handle_comm_closed, this));
40 comm.on_close($.proxy(this._handle_comm_closed, this));
41 comm.on_msg($.proxy(this._handle_comm_msg, this));
41 comm.on_msg($.proxy(this._handle_comm_msg, this));
42 }
42 }
43 return Backbone.Model.apply(this);
43 return Backbone.Model.apply(this);
44 },
44 },
45
45
46 send: function (content, callbacks) {
46 send: function (content, callbacks) {
47 // Send a custom msg over the comm.
47 // Send a custom msg over the comm.
48 if (this.comm !== undefined) {
48 if (this.comm !== undefined) {
49 var data = {method: 'custom', content: content};
49 var data = {method: 'custom', content: content};
50 this.comm.send(data, callbacks);
50 this.comm.send(data, callbacks);
51 this.pending_msgs++;
51 this.pending_msgs++;
52 }
52 }
53 },
53 },
54
54
55 _handle_comm_closed: function (msg) {
55 _handle_comm_closed: function (msg) {
56 // Handle when a widget is closed.
56 // Handle when a widget is closed.
57 this.trigger('comm:close');
57 this.trigger('comm:close');
58 this.stopListening();
58 this.stopListening();
59 this.trigger('destroy', this);
59 this.trigger('destroy', this);
60 delete this.comm.model; // Delete ref so GC will collect widget model.
60 delete this.comm.model; // Delete ref so GC will collect widget model.
61 delete this.comm;
61 delete this.comm;
62 delete this.model_id; // Delete id from model so widget manager cleans up.
62 delete this.model_id; // Delete id from model so widget manager cleans up.
63 for (var id in this.views) {
63 for (var id in this.views) {
64 if (this.views.hasOwnProperty(id)) {
64 if (this.views.hasOwnProperty(id)) {
65 this.views[id].remove();
65 this.views[id].remove();
66 }
66 }
67 }
67 }
68 },
68 },
69
69
70 _handle_comm_msg: function (msg) {
70 _handle_comm_msg: function (msg) {
71 // Handle incoming comm msg.
71 // Handle incoming comm msg.
72 var method = msg.content.data.method;
72 var method = msg.content.data.method;
73 var that = this;
73 var that = this;
74 switch (method) {
74 switch (method) {
75 case 'update':
75 case 'update':
76 this.state_change = this.state_change.then(function() {
76 this.state_change = this.state_change.then(function() {
77 return that.set_state(msg.content.data.state);
77 return that.set_state(msg.content.data.state);
78 }).catch(utils.reject("Couldn't process update msg for model id '" + String(that.id) + "'", true));
78 }).catch(utils.reject("Couldn't process update msg for model id '" + String(that.id) + "'", true));
79 break;
79 break;
80 case 'custom':
80 case 'custom':
81 this.trigger('msg:custom', msg.content.data.content);
81 this.trigger('msg:custom', msg.content.data.content);
82 break;
82 break;
83 case 'display':
83 case 'display':
84 this.state_change = this.state_change.then(function () {
84 that.widget_manager.display_view(msg, that)
85 return that.widget_manager.display_view(msg, that);
85 .catch(utils.reject("Couldn't process display msg for model id '" + String(that.id) + "'", true));
86 }).catch(utils.reject("Couldn't process display msg for model id '" + String(that.id) + "'", true));
87 break;
86 break;
88 }
87 }
89 },
88 },
90
89
91 set_state: function (state) {
90 set_state: function (state) {
92 var that = this;
91 var that = this;
93 // Handle when a widget is updated via the python side.
92 // Handle when a widget is updated via the python side.
94 return this._unpack_models(state).then(function(state) {
93 return this._unpack_models(state).then(function(state) {
95 that.state_lock = state;
94 that.state_lock = state;
96 try {
95 try {
96 console.log('set_state ' + that.id);
97 console.log(state);
97 WidgetModel.__super__.set.call(that, state);
98 WidgetModel.__super__.set.call(that, state);
98 } finally {
99 } finally {
99 that.state_lock = null;
100 that.state_lock = null;
100 }
101 }
101 }, utils.reject("Couldn't set model state", true));
102 }, utils.reject("Couldn't set model state", true));
102 },
103 },
103
104
104 _handle_status: function (msg, callbacks) {
105 _handle_status: function (msg, callbacks) {
105 // Handle status msgs.
106 // Handle status msgs.
106
107
107 // execution_state : ('busy', 'idle', 'starting')
108 // execution_state : ('busy', 'idle', 'starting')
108 if (this.comm !== undefined) {
109 if (this.comm !== undefined) {
109 if (msg.content.execution_state ==='idle') {
110 if (msg.content.execution_state ==='idle') {
110 // Send buffer if this message caused another message to be
111 // Send buffer if this message caused another message to be
111 // throttled.
112 // throttled.
112 if (this.msg_buffer !== null &&
113 if (this.msg_buffer !== null &&
113 (this.get('msg_throttle') || 3) === this.pending_msgs) {
114 (this.get('msg_throttle') || 3) === this.pending_msgs) {
114 var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
115 var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
115 this.comm.send(data, callbacks);
116 this.comm.send(data, callbacks);
116 this.msg_buffer = null;
117 this.msg_buffer = null;
117 } else {
118 } else {
118 --this.pending_msgs;
119 --this.pending_msgs;
119 }
120 }
120 }
121 }
121 }
122 }
122 },
123 },
123
124
124 callbacks: function(view) {
125 callbacks: function(view) {
125 // Create msg callbacks for a comm msg.
126 // Create msg callbacks for a comm msg.
126 var callbacks = this.widget_manager.callbacks(view);
127 var callbacks = this.widget_manager.callbacks(view);
127
128
128 if (callbacks.iopub === undefined) {
129 if (callbacks.iopub === undefined) {
129 callbacks.iopub = {};
130 callbacks.iopub = {};
130 }
131 }
131
132
132 var that = this;
133 var that = this;
133 callbacks.iopub.status = function (msg) {
134 callbacks.iopub.status = function (msg) {
134 that._handle_status(msg, callbacks);
135 that._handle_status(msg, callbacks);
135 };
136 };
136 return callbacks;
137 return callbacks;
137 },
138 },
138
139
139 set: function(key, val, options) {
140 set: function(key, val, options) {
140 // Set a value.
141 // Set a value.
141 var return_value = WidgetModel.__super__.set.apply(this, arguments);
142 var return_value = WidgetModel.__super__.set.apply(this, arguments);
142
143
143 // Backbone only remembers the diff of the most recent set()
144 // Backbone only remembers the diff of the most recent set()
144 // operation. Calling set multiple times in a row results in a
145 // operation. Calling set multiple times in a row results in a
145 // loss of diff information. Here we keep our own running diff.
146 // loss of diff information. Here we keep our own running diff.
146 this._buffered_state_diff = $.extend(this._buffered_state_diff, this.changedAttributes() || {});
147 this._buffered_state_diff = $.extend(this._buffered_state_diff, this.changedAttributes() || {});
147 return return_value;
148 return return_value;
148 },
149 },
149
150
150 sync: function (method, model, options) {
151 sync: function (method, model, options) {
151 // Handle sync to the back-end. Called when a model.save() is called.
152 // Handle sync to the back-end. Called when a model.save() is called.
152
153
153 // Make sure a comm exists.
154 // Make sure a comm exists.
154 var error = options.error || function() {
155 var error = options.error || function() {
155 console.error('Backbone sync error:', arguments);
156 console.error('Backbone sync error:', arguments);
156 };
157 };
157 if (this.comm === undefined) {
158 if (this.comm === undefined) {
158 error();
159 error();
159 return false;
160 return false;
160 }
161 }
161
162
162 // Delete any key value pairs that the back-end already knows about.
163 // Delete any key value pairs that the back-end already knows about.
163 var attrs = (method === 'patch') ? options.attrs : model.toJSON(options);
164 var attrs = (method === 'patch') ? options.attrs : model.toJSON(options);
164 if (this.state_lock !== null) {
165 if (this.state_lock !== null) {
165 var keys = Object.keys(this.state_lock);
166 var keys = Object.keys(this.state_lock);
166 for (var i=0; i<keys.length; i++) {
167 for (var i=0; i<keys.length; i++) {
167 var key = keys[i];
168 var key = keys[i];
168 if (attrs[key] === this.state_lock[key]) {
169 if (attrs[key] === this.state_lock[key]) {
169 delete attrs[key];
170 delete attrs[key];
170 }
171 }
171 }
172 }
172 }
173 }
173
174
174 // Only sync if there are attributes to send to the back-end.
175 // Only sync if there are attributes to send to the back-end.
175 attrs = this._pack_models(attrs);
176 attrs = this._pack_models(attrs);
176 if (_.size(attrs) > 0) {
177 if (_.size(attrs) > 0) {
177
178
178 // If this message was sent via backbone itself, it will not
179 // If this message was sent via backbone itself, it will not
179 // have any callbacks. It's important that we create callbacks
180 // have any callbacks. It's important that we create callbacks
180 // so we can listen for status messages, etc...
181 // so we can listen for status messages, etc...
181 var callbacks = options.callbacks || this.callbacks();
182 var callbacks = options.callbacks || this.callbacks();
182
183
183 // Check throttle.
184 // Check throttle.
184 if (this.pending_msgs >= (this.get('msg_throttle') || 3)) {
185 if (this.pending_msgs >= (this.get('msg_throttle') || 3)) {
185 // The throttle has been exceeded, buffer the current msg so
186 // The throttle has been exceeded, buffer the current msg so
186 // it can be sent once the kernel has finished processing
187 // it can be sent once the kernel has finished processing
187 // some of the existing messages.
188 // some of the existing messages.
188
189
189 // Combine updates if it is a 'patch' sync, otherwise replace updates
190 // Combine updates if it is a 'patch' sync, otherwise replace updates
190 switch (method) {
191 switch (method) {
191 case 'patch':
192 case 'patch':
192 this.msg_buffer = $.extend(this.msg_buffer || {}, attrs);
193 this.msg_buffer = $.extend(this.msg_buffer || {}, attrs);
193 break;
194 break;
194 case 'update':
195 case 'update':
195 case 'create':
196 case 'create':
196 this.msg_buffer = attrs;
197 this.msg_buffer = attrs;
197 break;
198 break;
198 default:
199 default:
199 error();
200 error();
200 return false;
201 return false;
201 }
202 }
202 this.msg_buffer_callbacks = callbacks;
203 this.msg_buffer_callbacks = callbacks;
203
204
204 } else {
205 } else {
205 // We haven't exceeded the throttle, send the message like
206 // We haven't exceeded the throttle, send the message like
206 // normal.
207 // normal.
207 var data = {method: 'backbone', sync_data: attrs};
208 var data = {method: 'backbone', sync_data: attrs};
208 this.comm.send(data, callbacks);
209 this.comm.send(data, callbacks);
209 this.pending_msgs++;
210 this.pending_msgs++;
210 }
211 }
211 }
212 }
212 // Since the comm is a one-way communication, assume the message
213 // Since the comm is a one-way communication, assume the message
213 // arrived. Don't call success since we don't have a model back from the server
214 // arrived. Don't call success since we don't have a model back from the server
214 // this means we miss out on the 'sync' event.
215 // this means we miss out on the 'sync' event.
215 this._buffered_state_diff = {};
216 this._buffered_state_diff = {};
216 },
217 },
217
218
218 save_changes: function(callbacks) {
219 save_changes: function(callbacks) {
219 // Push this model's state to the back-end
220 // Push this model's state to the back-end
220 //
221 //
221 // This invokes a Backbone.Sync.
222 // This invokes a Backbone.Sync.
222 this.save(this._buffered_state_diff, {patch: true, callbacks: callbacks});
223 this.save(this._buffered_state_diff, {patch: true, callbacks: callbacks});
223 },
224 },
224
225
225 _pack_models: function(value) {
226 _pack_models: function(value) {
226 // Replace models with model ids recursively.
227 // Replace models with model ids recursively.
227 var that = this;
228 var that = this;
228 var packed;
229 var packed;
229 if (value instanceof Backbone.Model) {
230 if (value instanceof Backbone.Model) {
230 return "IPY_MODEL_" + value.id;
231 return "IPY_MODEL_" + value.id;
231
232
232 } else if ($.isArray(value)) {
233 } else if ($.isArray(value)) {
233 packed = [];
234 packed = [];
234 _.each(value, function(sub_value, key) {
235 _.each(value, function(sub_value, key) {
235 packed.push(that._pack_models(sub_value));
236 packed.push(that._pack_models(sub_value));
236 });
237 });
237 return packed;
238 return packed;
238 } else if (value instanceof Date || value instanceof String) {
239 } else if (value instanceof Date || value instanceof String) {
239 return value;
240 return value;
240 } else if (value instanceof Object) {
241 } else if (value instanceof Object) {
241 packed = {};
242 packed = {};
242 _.each(value, function(sub_value, key) {
243 _.each(value, function(sub_value, key) {
243 packed[key] = that._pack_models(sub_value);
244 packed[key] = that._pack_models(sub_value);
244 });
245 });
245 return packed;
246 return packed;
246
247
247 } else {
248 } else {
248 return value;
249 return value;
249 }
250 }
250 },
251 },
251
252
252 _unpack_models: function(value) {
253 _unpack_models: function(value) {
253 // Replace model ids with models recursively.
254 // Replace model ids with models recursively.
254 var that = this;
255 var that = this;
255 var unpacked;
256 var unpacked;
256 if ($.isArray(value)) {
257 if ($.isArray(value)) {
257 unpacked = [];
258 unpacked = [];
258 _.each(value, function(sub_value, key) {
259 _.each(value, function(sub_value, key) {
259 unpacked.push(that._unpack_models(sub_value));
260 unpacked.push(that._unpack_models(sub_value));
260 });
261 });
261 return rsvp.Promise.all(unpacked);
262 return rsvp.Promise.all(unpacked);
262 } else if (value instanceof Object) {
263 } else if (value instanceof Object) {
263 unpacked = {};
264 unpacked = {};
264 _.each(value, function(sub_value, key) {
265 _.each(value, function(sub_value, key) {
265 unpacked[key] = that._unpack_models(sub_value);
266 unpacked[key] = that._unpack_models(sub_value);
266 });
267 });
267 return utils.resolve_dict(unpacked);
268 return utils.resolve_dict(unpacked);
268 } else if (typeof value === 'string' && value.slice(0,10) === "IPY_MODEL_") {
269 } else if (typeof value === 'string' && value.slice(0,10) === "IPY_MODEL_") {
269 // get_model returns a promise already
270 // get_model returns a promise already
270 return this.widget_manager.get_model(value.slice(10, value.length));
271 return this.widget_manager.get_model(value.slice(10, value.length));
271 } else {
272 } else {
272 return rsvp.Promise.resolve(value);
273 return rsvp.Promise.resolve(value);
273 }
274 }
274 },
275 },
275
276
276 on_some_change: function(keys, callback, context) {
277 on_some_change: function(keys, callback, context) {
277 // on_some_change(["key1", "key2"], foo, context) differs from
278 // on_some_change(["key1", "key2"], foo, context) differs from
278 // on("change:key1 change:key2", foo, context).
279 // on("change:key1 change:key2", foo, context).
279 // If the widget attributes key1 and key2 are both modified,
280 // If the widget attributes key1 and key2 are both modified,
280 // the second form will result in foo being called twice
281 // the second form will result in foo being called twice
281 // while the first will call foo only once.
282 // while the first will call foo only once.
282 this.on('change', function() {
283 this.on('change', function() {
283 if (keys.some(this.hasChanged, this)) {
284 if (keys.some(this.hasChanged, this)) {
284 callback.apply(context);
285 callback.apply(context);
285 }
286 }
286 }, this);
287 }, this);
287
288
288 },
289 },
289 });
290 });
290 widgetmanager.WidgetManager.register_widget_model('WidgetModel', WidgetModel);
291 widgetmanager.WidgetManager.register_widget_model('WidgetModel', WidgetModel);
291
292
292
293
293 var WidgetView = Backbone.View.extend({
294 var WidgetView = Backbone.View.extend({
294 initialize: function(parameters) {
295 initialize: function(parameters) {
295 // Public constructor.
296 // Public constructor.
296 this.model.on('change',this.update,this);
297 this.model.on('change',this.update,this);
297 this.options = parameters.options;
298 this.options = parameters.options;
298 this.child_model_views = {};
299 this.child_model_views = {};
299 this.child_views = {};
300 this.child_views = {};
300 this.id = this.id || utils.uuid();
301 this.id = this.id || utils.uuid();
301 this.model.views[this.id] = this;
302 this.model.views[this.id] = this;
302 this.on('displayed', function() {
303 this.on('displayed', function() {
303 this.is_displayed = true;
304 this.is_displayed = true;
304 }, this);
305 }, this);
305 },
306 },
306
307
307 update: function(){
308 update: function(){
308 // Triggered on model change.
309 // Triggered on model change.
309 //
310 //
310 // Update view to be consistent with this.model
311 // Update view to be consistent with this.model
311 },
312 },
312
313
313 create_child_view: function(child_model, options) {
314 create_child_view: function(child_model, options) {
314 // Create and promise that resolves to a child view of a given model
315 // Create and promise that resolves to a child view of a given model
315 var that = this;
316 var that = this;
316 options = $.extend({ parent: this }, options || {});
317 options = $.extend({ parent: this }, options || {});
317 return this.model.widget_manager.create_view(child_model, options).then(function(child_view) {
318 return this.model.widget_manager.create_view(child_model, options).then(function(child_view) {
318 // Associate the view id with the model id.
319 // Associate the view id with the model id.
319 if (that.child_model_views[child_model.id] === undefined) {
320 if (that.child_model_views[child_model.id] === undefined) {
320 that.child_model_views[child_model.id] = [];
321 that.child_model_views[child_model.id] = [];
321 }
322 }
322 that.child_model_views[child_model.id].push(child_view.id);
323 that.child_model_views[child_model.id].push(child_view.id);
323 // Remember the view by id.
324 // Remember the view by id.
324 that.child_views[child_view.id] = child_view;
325 that.child_views[child_view.id] = child_view;
325 return child_view;
326 return child_view;
326 }, utils.reject("Couldn't create child view"));
327 }, utils.reject("Couldn't create child view"));
327 },
328 },
328
329
329 pop_child_view: function(child_model) {
330 pop_child_view: function(child_model) {
330 // Delete a child view that was previously created using create_child_view.
331 // Delete a child view that was previously created using create_child_view.
331 var view_ids = this.child_model_views[child_model.id];
332 var view_ids = this.child_model_views[child_model.id];
332 if (view_ids !== undefined) {
333 if (view_ids !== undefined) {
333
334
334 // Only delete the first view in the list.
335 // Only delete the first view in the list.
335 var view_id = view_ids[0];
336 var view_id = view_ids[0];
336 var view = this.child_views[view_id];
337 var view = this.child_views[view_id];
337 delete this.child_views[view_id];
338 delete this.child_views[view_id];
338 view_ids.splice(0,1);
339 view_ids.splice(0,1);
339 delete child_model.views[view_id];
340 delete child_model.views[view_id];
340
341
341 // Remove the view list specific to this model if it is empty.
342 // Remove the view list specific to this model if it is empty.
342 if (view_ids.length === 0) {
343 if (view_ids.length === 0) {
343 delete this.child_model_views[child_model.id];
344 delete this.child_model_views[child_model.id];
344 }
345 }
345 return view;
346 return view;
346 }
347 }
347 return null;
348 return null;
348 },
349 },
349
350
350 do_diff: function(old_list, new_list, removed_callback, added_callback) {
351 do_diff: function(old_list, new_list, removed_callback, added_callback) {
351 // Difference a changed list and call remove and add callbacks for
352 // Difference a changed list and call remove and add callbacks for
352 // each removed and added item in the new list.
353 // each removed and added item in the new list.
353 //
354 //
354 // Parameters
355 // Parameters
355 // ----------
356 // ----------
356 // old_list : array
357 // old_list : array
357 // new_list : array
358 // new_list : array
358 // removed_callback : Callback(item)
359 // removed_callback : Callback(item)
359 // Callback that is called for each item removed.
360 // Callback that is called for each item removed.
360 // added_callback : Callback(item)
361 // added_callback : Callback(item)
361 // Callback that is called for each item added.
362 // Callback that is called for each item added.
362
363
363 // Walk the lists until an unequal entry is found.
364 // Walk the lists until an unequal entry is found.
364 var i;
365 var i;
365 for (i = 0; i < new_list.length; i++) {
366 for (i = 0; i < new_list.length; i++) {
366 if (i >= old_list.length || new_list[i] !== old_list[i]) {
367 if (i >= old_list.length || new_list[i] !== old_list[i]) {
367 break;
368 break;
368 }
369 }
369 }
370 }
370
371
371 // Remove the non-matching items from the old list.
372 // Remove the non-matching items from the old list.
372 for (var j = i; j < old_list.length; j++) {
373 for (var j = i; j < old_list.length; j++) {
373 removed_callback(old_list[j]);
374 removed_callback(old_list[j]);
374 }
375 }
375
376
376 // Add the rest of the new list items.
377 // Add the rest of the new list items.
377 for (; i < new_list.length; i++) {
378 for (; i < new_list.length; i++) {
378 added_callback(new_list[i]);
379 added_callback(new_list[i]);
379 }
380 }
380 },
381 },
381
382
382 callbacks: function(){
383 callbacks: function(){
383 // Create msg callbacks for a comm msg.
384 // Create msg callbacks for a comm msg.
384 return this.model.callbacks(this);
385 return this.model.callbacks(this);
385 },
386 },
386
387
387 render: function(){
388 render: function(){
388 // Render the view.
389 // Render the view.
389 //
390 //
390 // By default, this is only called the first time the view is created
391 // By default, this is only called the first time the view is created
391 },
392 },
392
393
393 show: function(){
394 show: function(){
394 // Show the widget-area
395 // Show the widget-area
395 if (this.options && this.options.cell &&
396 if (this.options && this.options.cell &&
396 this.options.cell.widget_area !== undefined) {
397 this.options.cell.widget_area !== undefined) {
397 this.options.cell.widget_area.show();
398 this.options.cell.widget_area.show();
398 }
399 }
399 },
400 },
400
401
401 send: function (content) {
402 send: function (content) {
402 // Send a custom msg associated with this view.
403 // Send a custom msg associated with this view.
403 this.model.send(content, this.callbacks());
404 this.model.send(content, this.callbacks());
404 },
405 },
405
406
406 touch: function () {
407 touch: function () {
407 this.model.save_changes(this.callbacks());
408 this.model.save_changes(this.callbacks());
408 },
409 },
409
410
410 after_displayed: function (callback, context) {
411 after_displayed: function (callback, context) {
411 // Calls the callback right away is the view is already displayed
412 // Calls the callback right away is the view is already displayed
412 // otherwise, register the callback to the 'displayed' event.
413 // otherwise, register the callback to the 'displayed' event.
413 if (this.is_displayed) {
414 if (this.is_displayed) {
414 callback.apply(context);
415 callback.apply(context);
415 } else {
416 } else {
416 this.on('displayed', callback, context);
417 this.on('displayed', callback, context);
417 }
418 }
418 },
419 },
419 });
420 });
420
421
421
422
422 var DOMWidgetView = WidgetView.extend({
423 var DOMWidgetView = WidgetView.extend({
423 initialize: function (parameters) {
424 initialize: function (parameters) {
424 // Public constructor
425 // Public constructor
425 DOMWidgetView.__super__.initialize.apply(this, [parameters]);
426 DOMWidgetView.__super__.initialize.apply(this, [parameters]);
426 this.on('displayed', this.show, this);
427 this.on('displayed', this.show, this);
427 this.model.on('change:visible', this.update_visible, this);
428 this.model.on('change:visible', this.update_visible, this);
428 this.model.on('change:_css', this.update_css, this);
429 this.model.on('change:_css', this.update_css, this);
429
430
430 this.model.on('change:_dom_classes', function(model, new_classes) {
431 this.model.on('change:_dom_classes', function(model, new_classes) {
431 var old_classes = model.previous('_dom_classes');
432 var old_classes = model.previous('_dom_classes');
432 this.update_classes(old_classes, new_classes);
433 this.update_classes(old_classes, new_classes);
433 }, this);
434 }, this);
434
435
435 this.model.on('change:color', function (model, value) {
436 this.model.on('change:color', function (model, value) {
436 this.update_attr('color', value); }, this);
437 this.update_attr('color', value); }, this);
437
438
438 this.model.on('change:background_color', function (model, value) {
439 this.model.on('change:background_color', function (model, value) {
439 this.update_attr('background', value); }, this);
440 this.update_attr('background', value); }, this);
440
441
441 this.model.on('change:width', function (model, value) {
442 this.model.on('change:width', function (model, value) {
442 this.update_attr('width', value); }, this);
443 this.update_attr('width', value); }, this);
443
444
444 this.model.on('change:height', function (model, value) {
445 this.model.on('change:height', function (model, value) {
445 this.update_attr('height', value); }, this);
446 this.update_attr('height', value); }, this);
446
447
447 this.model.on('change:border_color', function (model, value) {
448 this.model.on('change:border_color', function (model, value) {
448 this.update_attr('border-color', value); }, this);
449 this.update_attr('border-color', value); }, this);
449
450
450 this.model.on('change:border_width', function (model, value) {
451 this.model.on('change:border_width', function (model, value) {
451 this.update_attr('border-width', value); }, this);
452 this.update_attr('border-width', value); }, this);
452
453
453 this.model.on('change:border_style', function (model, value) {
454 this.model.on('change:border_style', function (model, value) {
454 this.update_attr('border-style', value); }, this);
455 this.update_attr('border-style', value); }, this);
455
456
456 this.model.on('change:font_style', function (model, value) {
457 this.model.on('change:font_style', function (model, value) {
457 this.update_attr('font-style', value); }, this);
458 this.update_attr('font-style', value); }, this);
458
459
459 this.model.on('change:font_weight', function (model, value) {
460 this.model.on('change:font_weight', function (model, value) {
460 this.update_attr('font-weight', value); }, this);
461 this.update_attr('font-weight', value); }, this);
461
462
462 this.model.on('change:font_size', function (model, value) {
463 this.model.on('change:font_size', function (model, value) {
463 this.update_attr('font-size', this._default_px(value)); }, this);
464 this.update_attr('font-size', this._default_px(value)); }, this);
464
465
465 this.model.on('change:font_family', function (model, value) {
466 this.model.on('change:font_family', function (model, value) {
466 this.update_attr('font-family', value); }, this);
467 this.update_attr('font-family', value); }, this);
467
468
468 this.model.on('change:padding', function (model, value) {
469 this.model.on('change:padding', function (model, value) {
469 this.update_attr('padding', value); }, this);
470 this.update_attr('padding', value); }, this);
470
471
471 this.model.on('change:margin', function (model, value) {
472 this.model.on('change:margin', function (model, value) {
472 this.update_attr('margin', this._default_px(value)); }, this);
473 this.update_attr('margin', this._default_px(value)); }, this);
473
474
474 this.model.on('change:border_radius', function (model, value) {
475 this.model.on('change:border_radius', function (model, value) {
475 this.update_attr('border-radius', this._default_px(value)); }, this);
476 this.update_attr('border-radius', this._default_px(value)); }, this);
476
477
477 this.after_displayed(function() {
478 this.after_displayed(function() {
478 this.update_visible(this.model, this.model.get("visible"));
479 this.update_visible(this.model, this.model.get("visible"));
479 this.update_classes([], this.model.get('_dom_classes'));
480 this.update_classes([], this.model.get('_dom_classes'));
480
481
481 this.update_attr('color', this.model.get('color'));
482 this.update_attr('color', this.model.get('color'));
482 this.update_attr('background', this.model.get('background_color'));
483 this.update_attr('background', this.model.get('background_color'));
483 this.update_attr('width', this.model.get('width'));
484 this.update_attr('width', this.model.get('width'));
484 this.update_attr('height', this.model.get('height'));
485 this.update_attr('height', this.model.get('height'));
485 this.update_attr('border-color', this.model.get('border_color'));
486 this.update_attr('border-color', this.model.get('border_color'));
486 this.update_attr('border-width', this.model.get('border_width'));
487 this.update_attr('border-width', this.model.get('border_width'));
487 this.update_attr('border-style', this.model.get('border_style'));
488 this.update_attr('border-style', this.model.get('border_style'));
488 this.update_attr('font-style', this.model.get('font_style'));
489 this.update_attr('font-style', this.model.get('font_style'));
489 this.update_attr('font-weight', this.model.get('font_weight'));
490 this.update_attr('font-weight', this.model.get('font_weight'));
490 this.update_attr('font-size', this.model.get('font_size'));
491 this.update_attr('font-size', this.model.get('font_size'));
491 this.update_attr('font-family', this.model.get('font_family'));
492 this.update_attr('font-family', this.model.get('font_family'));
492 this.update_attr('padding', this.model.get('padding'));
493 this.update_attr('padding', this.model.get('padding'));
493 this.update_attr('margin', this.model.get('margin'));
494 this.update_attr('margin', this.model.get('margin'));
494 this.update_attr('border-radius', this.model.get('border_radius'));
495 this.update_attr('border-radius', this.model.get('border_radius'));
495
496
496 this.update_css(this.model, this.model.get("_css"));
497 this.update_css(this.model, this.model.get("_css"));
497 }, this);
498 }, this);
498 },
499 },
499
500
500 _default_px: function(value) {
501 _default_px: function(value) {
501 // Makes browser interpret a numerical string as a pixel value.
502 // Makes browser interpret a numerical string as a pixel value.
502 if (/^\d+\.?(\d+)?$/.test(value.trim())) {
503 if (/^\d+\.?(\d+)?$/.test(value.trim())) {
503 return value.trim() + 'px';
504 return value.trim() + 'px';
504 }
505 }
505 return value;
506 return value;
506 },
507 },
507
508
508 update_attr: function(name, value) {
509 update_attr: function(name, value) {
509 // Set a css attr of the widget view.
510 // Set a css attr of the widget view.
510 this.$el.css(name, value);
511 this.$el.css(name, value);
511 },
512 },
512
513
513 update_visible: function(model, value) {
514 update_visible: function(model, value) {
514 // Update visibility
515 // Update visibility
515 this.$el.toggle(value);
516 this.$el.toggle(value);
516 },
517 },
517
518
518 update_css: function (model, css) {
519 update_css: function (model, css) {
519 // Update the css styling of this view.
520 // Update the css styling of this view.
520 var e = this.$el;
521 var e = this.$el;
521 if (css === undefined) {return;}
522 if (css === undefined) {return;}
522 for (var i = 0; i < css.length; i++) {
523 for (var i = 0; i < css.length; i++) {
523 // Apply the css traits to all elements that match the selector.
524 // Apply the css traits to all elements that match the selector.
524 var selector = css[i][0];
525 var selector = css[i][0];
525 var elements = this._get_selector_element(selector);
526 var elements = this._get_selector_element(selector);
526 if (elements.length > 0) {
527 if (elements.length > 0) {
527 var trait_key = css[i][1];
528 var trait_key = css[i][1];
528 var trait_value = css[i][2];
529 var trait_value = css[i][2];
529 elements.css(trait_key ,trait_value);
530 elements.css(trait_key ,trait_value);
530 }
531 }
531 }
532 }
532 },
533 },
533
534
534 update_classes: function (old_classes, new_classes, $el) {
535 update_classes: function (old_classes, new_classes, $el) {
535 // Update the DOM classes applied to an element, default to this.$el.
536 // Update the DOM classes applied to an element, default to this.$el.
536 if ($el===undefined) {
537 if ($el===undefined) {
537 $el = this.$el;
538 $el = this.$el;
538 }
539 }
539 this.do_diff(old_classes, new_classes, function(removed) {
540 this.do_diff(old_classes, new_classes, function(removed) {
540 $el.removeClass(removed);
541 $el.removeClass(removed);
541 }, function(added) {
542 }, function(added) {
542 $el.addClass(added);
543 $el.addClass(added);
543 });
544 });
544 },
545 },
545
546
546 update_mapped_classes: function(class_map, trait_name, previous_trait_value, $el) {
547 update_mapped_classes: function(class_map, trait_name, previous_trait_value, $el) {
547 // Update the DOM classes applied to the widget based on a single
548 // Update the DOM classes applied to the widget based on a single
548 // trait's value.
549 // trait's value.
549 //
550 //
550 // Given a trait value classes map, this function automatically
551 // Given a trait value classes map, this function automatically
551 // handles applying the appropriate classes to the widget element
552 // handles applying the appropriate classes to the widget element
552 // and removing classes that are no longer valid.
553 // and removing classes that are no longer valid.
553 //
554 //
554 // Parameters
555 // Parameters
555 // ----------
556 // ----------
556 // class_map: dictionary
557 // class_map: dictionary
557 // Dictionary of trait values to class lists.
558 // Dictionary of trait values to class lists.
558 // Example:
559 // Example:
559 // {
560 // {
560 // success: ['alert', 'alert-success'],
561 // success: ['alert', 'alert-success'],
561 // info: ['alert', 'alert-info'],
562 // info: ['alert', 'alert-info'],
562 // warning: ['alert', 'alert-warning'],
563 // warning: ['alert', 'alert-warning'],
563 // danger: ['alert', 'alert-danger']
564 // danger: ['alert', 'alert-danger']
564 // };
565 // };
565 // trait_name: string
566 // trait_name: string
566 // Name of the trait to check the value of.
567 // Name of the trait to check the value of.
567 // previous_trait_value: optional string, default ''
568 // previous_trait_value: optional string, default ''
568 // Last trait value
569 // Last trait value
569 // $el: optional jQuery element handle, defaults to this.$el
570 // $el: optional jQuery element handle, defaults to this.$el
570 // Element that the classes are applied to.
571 // Element that the classes are applied to.
571 var key = previous_trait_value;
572 var key = previous_trait_value;
572 if (key === undefined) {
573 if (key === undefined) {
573 key = this.model.previous(trait_name);
574 key = this.model.previous(trait_name);
574 }
575 }
575 var old_classes = class_map[key] ? class_map[key] : [];
576 var old_classes = class_map[key] ? class_map[key] : [];
576 key = this.model.get(trait_name);
577 key = this.model.get(trait_name);
577 var new_classes = class_map[key] ? class_map[key] : [];
578 var new_classes = class_map[key] ? class_map[key] : [];
578
579
579 this.update_classes(old_classes, new_classes, $el || this.$el);
580 this.update_classes(old_classes, new_classes, $el || this.$el);
580 },
581 },
581
582
582 _get_selector_element: function (selector) {
583 _get_selector_element: function (selector) {
583 // Get the elements via the css selector.
584 // Get the elements via the css selector.
584 var elements;
585 var elements;
585 if (!selector) {
586 if (!selector) {
586 elements = this.$el;
587 elements = this.$el;
587 } else {
588 } else {
588 elements = this.$el.find(selector).addBack(selector);
589 elements = this.$el.find(selector).addBack(selector);
589 }
590 }
590 return elements;
591 return elements;
591 },
592 },
592 });
593 });
593
594
594
595
595 var widget = {
596 var widget = {
596 'WidgetModel': WidgetModel,
597 'WidgetModel': WidgetModel,
597 'WidgetView': WidgetView,
598 'WidgetView': WidgetView,
598 'DOMWidgetView': DOMWidgetView,
599 'DOMWidgetView': DOMWidgetView,
599 };
600 };
600
601
601 // For backwards compatability.
602 // For backwards compatability.
602 $.extend(IPython, widget);
603 $.extend(IPython, widget);
603
604
604 return widget;
605 return widget;
605 });
606 });
General Comments 0
You need to be logged in to leave comments. Login now