##// END OF EJS Templates
Remove view.show
Jonathan Frederic -
Show More
@@ -1,757 +1,746 b''
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 ], function(widgetmanager, _, Backbone, $, utils, IPython){
10 ], function(widgetmanager, _, Backbone, $, utils, IPython){
11
11
12 var WidgetModel = Backbone.Model.extend({
12 var WidgetModel = Backbone.Model.extend({
13 constructor: function (widget_manager, model_id, comm) {
13 constructor: function (widget_manager, model_id, comm) {
14 /**
14 /**
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 */
25 */
26 this.widget_manager = widget_manager;
26 this.widget_manager = widget_manager;
27 this.state_change = Promise.resolve();
27 this.state_change = Promise.resolve();
28 this._buffered_state_diff = {};
28 this._buffered_state_diff = {};
29 this.pending_msgs = 0;
29 this.pending_msgs = 0;
30 this.msg_buffer = null;
30 this.msg_buffer = null;
31 this.state_lock = null;
31 this.state_lock = null;
32 this.id = model_id;
32 this.id = model_id;
33 this.views = {};
33 this.views = {};
34 this._resolve_received_state = {};
34 this._resolve_received_state = {};
35
35
36 if (comm !== undefined) {
36 if (comm !== undefined) {
37 // Remember comm associated with the model.
37 // Remember comm associated with the model.
38 this.comm = comm;
38 this.comm = comm;
39 comm.model = this;
39 comm.model = this;
40
40
41 // Hook comm messages up to model.
41 // Hook comm messages up to model.
42 comm.on_close($.proxy(this._handle_comm_closed, this));
42 comm.on_close($.proxy(this._handle_comm_closed, this));
43 comm.on_msg($.proxy(this._handle_comm_msg, this));
43 comm.on_msg($.proxy(this._handle_comm_msg, this));
44
44
45 // Assume the comm is alive.
45 // Assume the comm is alive.
46 this.set_comm_live(true);
46 this.set_comm_live(true);
47 } else {
47 } else {
48 this.set_comm_live(false);
48 this.set_comm_live(false);
49 }
49 }
50 return Backbone.Model.apply(this);
50 return Backbone.Model.apply(this);
51 },
51 },
52
52
53 send: function (content, callbacks) {
53 send: function (content, callbacks) {
54 /**
54 /**
55 * Send a custom msg over the comm.
55 * Send a custom msg over the comm.
56 */
56 */
57 if (this.comm !== undefined) {
57 if (this.comm !== undefined) {
58 var data = {method: 'custom', content: content};
58 var data = {method: 'custom', content: content};
59 this.comm.send(data, callbacks);
59 this.comm.send(data, callbacks);
60 this.pending_msgs++;
60 this.pending_msgs++;
61 }
61 }
62 },
62 },
63
63
64 request_state: function(callbacks) {
64 request_state: function(callbacks) {
65 /**
65 /**
66 * Request a state push from the back-end.
66 * Request a state push from the back-end.
67 */
67 */
68 if (!this.comm) {
68 if (!this.comm) {
69 console.error("Could not request_state because comm doesn't exist!");
69 console.error("Could not request_state because comm doesn't exist!");
70 return;
70 return;
71 }
71 }
72
72
73 var msg_id = this.comm.send({method: 'request_state'}, callbacks || this.widget_manager.callbacks());
73 var msg_id = this.comm.send({method: 'request_state'}, callbacks || this.widget_manager.callbacks());
74
74
75 // Promise that is resolved when a state is received
75 // Promise that is resolved when a state is received
76 // from the back-end.
76 // from the back-end.
77 var that = this;
77 var that = this;
78 var received_state = new Promise(function(resolve) {
78 var received_state = new Promise(function(resolve) {
79 that._resolve_received_state[msg_id] = resolve;
79 that._resolve_received_state[msg_id] = resolve;
80 });
80 });
81 return received_state;
81 return received_state;
82 },
82 },
83
83
84 set_comm_live: function(live) {
84 set_comm_live: function(live) {
85 /**
85 /**
86 * Change the comm_live state of the model.
86 * Change the comm_live state of the model.
87 */
87 */
88 if (this.comm_live === undefined || this.comm_live != live) {
88 if (this.comm_live === undefined || this.comm_live != live) {
89 this.comm_live = live;
89 this.comm_live = live;
90 this.trigger(live ? 'comm:live' : 'comm:dead', {model: this});
90 this.trigger(live ? 'comm:live' : 'comm:dead', {model: this});
91 }
91 }
92 },
92 },
93
93
94 close: function(comm_closed) {
94 close: function(comm_closed) {
95 /**
95 /**
96 * Close model
96 * Close model
97 */
97 */
98 if (this.comm && !comm_closed) {
98 if (this.comm && !comm_closed) {
99 this.comm.close();
99 this.comm.close();
100 }
100 }
101 this.stopListening();
101 this.stopListening();
102 this.trigger('destroy', this);
102 this.trigger('destroy', this);
103 delete this.comm.model; // Delete ref so GC will collect widget model.
103 delete this.comm.model; // Delete ref so GC will collect widget model.
104 delete this.comm;
104 delete this.comm;
105 delete this.model_id; // Delete id from model so widget manager cleans up.
105 delete this.model_id; // Delete id from model so widget manager cleans up.
106 _.each(this.views, function(v, id, views) {
106 _.each(this.views, function(v, id, views) {
107 v.then(function(view) {
107 v.then(function(view) {
108 view.remove();
108 view.remove();
109 delete views[id];
109 delete views[id];
110 });
110 });
111 });
111 });
112 },
112 },
113
113
114 _handle_comm_closed: function (msg) {
114 _handle_comm_closed: function (msg) {
115 /**
115 /**
116 * Handle when a widget is closed.
116 * Handle when a widget is closed.
117 */
117 */
118 this.trigger('comm:close');
118 this.trigger('comm:close');
119 this.close(true);
119 this.close(true);
120 },
120 },
121
121
122 _handle_comm_msg: function (msg) {
122 _handle_comm_msg: function (msg) {
123 /**
123 /**
124 * Handle incoming comm msg.
124 * Handle incoming comm msg.
125 */
125 */
126 var method = msg.content.data.method;
126 var method = msg.content.data.method;
127 var that = this;
127 var that = this;
128 switch (method) {
128 switch (method) {
129 case 'update':
129 case 'update':
130 this.state_change = this.state_change
130 this.state_change = this.state_change
131 .then(function() {
131 .then(function() {
132 return that.set_state(msg.content.data.state);
132 return that.set_state(msg.content.data.state);
133 }).catch(utils.reject("Couldn't process update msg for model id '" + String(that.id) + "'", true))
133 }).catch(utils.reject("Couldn't process update msg for model id '" + String(that.id) + "'", true))
134 .then(function() {
134 .then(function() {
135 var parent_id = msg.parent_header.msg_id;
135 var parent_id = msg.parent_header.msg_id;
136 if (that._resolve_received_state[parent_id] !== undefined) {
136 if (that._resolve_received_state[parent_id] !== undefined) {
137 that._resolve_received_state[parent_id].call();
137 that._resolve_received_state[parent_id].call();
138 delete that._resolve_received_state[parent_id];
138 delete that._resolve_received_state[parent_id];
139 }
139 }
140 }).catch(utils.reject("Couldn't resolve state request promise", true));
140 }).catch(utils.reject("Couldn't resolve state request promise", true));
141 break;
141 break;
142 case 'custom':
142 case 'custom':
143 this.trigger('msg:custom', msg.content.data.content);
143 this.trigger('msg:custom', msg.content.data.content);
144 break;
144 break;
145 case 'display':
145 case 'display':
146 this.widget_manager.display_view(msg, this)
146 this.widget_manager.display_view(msg, this)
147 .catch(utils.reject('Could not process display view msg', true));
147 .catch(utils.reject('Could not process display view msg', true));
148 break;
148 break;
149 }
149 }
150 },
150 },
151
151
152 set_state: function (state) {
152 set_state: function (state) {
153 var that = this;
153 var that = this;
154 // Handle when a widget is updated via the python side.
154 // Handle when a widget is updated via the python side.
155 return this._unpack_models(state).then(function(state) {
155 return this._unpack_models(state).then(function(state) {
156 that.state_lock = state;
156 that.state_lock = state;
157 try {
157 try {
158 WidgetModel.__super__.set.call(that, state);
158 WidgetModel.__super__.set.call(that, state);
159 } finally {
159 } finally {
160 that.state_lock = null;
160 that.state_lock = null;
161 }
161 }
162 }).catch(utils.reject("Couldn't set model state", true));
162 }).catch(utils.reject("Couldn't set model state", true));
163 },
163 },
164
164
165 get_state: function() {
165 get_state: function() {
166 // Get the serializable state of the model.
166 // Get the serializable state of the model.
167 state = this.toJSON();
167 state = this.toJSON();
168 for (var key in state) {
168 for (var key in state) {
169 if (state.hasOwnProperty(key)) {
169 if (state.hasOwnProperty(key)) {
170 state[key] = this._pack_models(state[key]);
170 state[key] = this._pack_models(state[key]);
171 }
171 }
172 }
172 }
173 return state;
173 return state;
174 },
174 },
175
175
176 _handle_status: function (msg, callbacks) {
176 _handle_status: function (msg, callbacks) {
177 /**
177 /**
178 * Handle status msgs.
178 * Handle status msgs.
179 *
179 *
180 * execution_state : ('busy', 'idle', 'starting')
180 * execution_state : ('busy', 'idle', 'starting')
181 */
181 */
182 if (this.comm !== undefined) {
182 if (this.comm !== undefined) {
183 if (msg.content.execution_state ==='idle') {
183 if (msg.content.execution_state ==='idle') {
184 // Send buffer if this message caused another message to be
184 // Send buffer if this message caused another message to be
185 // throttled.
185 // throttled.
186 if (this.msg_buffer !== null &&
186 if (this.msg_buffer !== null &&
187 (this.get('msg_throttle') || 3) === this.pending_msgs) {
187 (this.get('msg_throttle') || 3) === this.pending_msgs) {
188 var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
188 var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
189 this.comm.send(data, callbacks);
189 this.comm.send(data, callbacks);
190 this.msg_buffer = null;
190 this.msg_buffer = null;
191 } else {
191 } else {
192 --this.pending_msgs;
192 --this.pending_msgs;
193 }
193 }
194 }
194 }
195 }
195 }
196 },
196 },
197
197
198 callbacks: function(view) {
198 callbacks: function(view) {
199 /**
199 /**
200 * Create msg callbacks for a comm msg.
200 * Create msg callbacks for a comm msg.
201 */
201 */
202 var callbacks = this.widget_manager.callbacks(view);
202 var callbacks = this.widget_manager.callbacks(view);
203
203
204 if (callbacks.iopub === undefined) {
204 if (callbacks.iopub === undefined) {
205 callbacks.iopub = {};
205 callbacks.iopub = {};
206 }
206 }
207
207
208 var that = this;
208 var that = this;
209 callbacks.iopub.status = function (msg) {
209 callbacks.iopub.status = function (msg) {
210 that._handle_status(msg, callbacks);
210 that._handle_status(msg, callbacks);
211 };
211 };
212 return callbacks;
212 return callbacks;
213 },
213 },
214
214
215 set: function(key, val, options) {
215 set: function(key, val, options) {
216 /**
216 /**
217 * Set a value.
217 * Set a value.
218 */
218 */
219 var return_value = WidgetModel.__super__.set.apply(this, arguments);
219 var return_value = WidgetModel.__super__.set.apply(this, arguments);
220
220
221 // Backbone only remembers the diff of the most recent set()
221 // Backbone only remembers the diff of the most recent set()
222 // operation. Calling set multiple times in a row results in a
222 // operation. Calling set multiple times in a row results in a
223 // loss of diff information. Here we keep our own running diff.
223 // loss of diff information. Here we keep our own running diff.
224 this._buffered_state_diff = $.extend(this._buffered_state_diff, this.changedAttributes() || {});
224 this._buffered_state_diff = $.extend(this._buffered_state_diff, this.changedAttributes() || {});
225 return return_value;
225 return return_value;
226 },
226 },
227
227
228 sync: function (method, model, options) {
228 sync: function (method, model, options) {
229 /**
229 /**
230 * Handle sync to the back-end. Called when a model.save() is called.
230 * Handle sync to the back-end. Called when a model.save() is called.
231 *
231 *
232 * Make sure a comm exists.
232 * Make sure a comm exists.
233 */
233 */
234 var error = options.error || function() {
234 var error = options.error || function() {
235 console.error('Backbone sync error:', arguments);
235 console.error('Backbone sync error:', arguments);
236 };
236 };
237 if (this.comm === undefined) {
237 if (this.comm === undefined) {
238 error();
238 error();
239 return false;
239 return false;
240 }
240 }
241
241
242 // Delete any key value pairs that the back-end already knows about.
242 // Delete any key value pairs that the back-end already knows about.
243 var attrs = (method === 'patch') ? options.attrs : model.toJSON(options);
243 var attrs = (method === 'patch') ? options.attrs : model.toJSON(options);
244 if (this.state_lock !== null) {
244 if (this.state_lock !== null) {
245 var keys = Object.keys(this.state_lock);
245 var keys = Object.keys(this.state_lock);
246 for (var i=0; i<keys.length; i++) {
246 for (var i=0; i<keys.length; i++) {
247 var key = keys[i];
247 var key = keys[i];
248 if (attrs[key] === this.state_lock[key]) {
248 if (attrs[key] === this.state_lock[key]) {
249 delete attrs[key];
249 delete attrs[key];
250 }
250 }
251 }
251 }
252 }
252 }
253
253
254 // Only sync if there are attributes to send to the back-end.
254 // Only sync if there are attributes to send to the back-end.
255 attrs = this._pack_models(attrs);
255 attrs = this._pack_models(attrs);
256 if (_.size(attrs) > 0) {
256 if (_.size(attrs) > 0) {
257
257
258 // If this message was sent via backbone itself, it will not
258 // If this message was sent via backbone itself, it will not
259 // have any callbacks. It's important that we create callbacks
259 // have any callbacks. It's important that we create callbacks
260 // so we can listen for status messages, etc...
260 // so we can listen for status messages, etc...
261 var callbacks = options.callbacks || this.callbacks();
261 var callbacks = options.callbacks || this.callbacks();
262
262
263 // Check throttle.
263 // Check throttle.
264 if (this.pending_msgs >= (this.get('msg_throttle') || 3)) {
264 if (this.pending_msgs >= (this.get('msg_throttle') || 3)) {
265 // The throttle has been exceeded, buffer the current msg so
265 // The throttle has been exceeded, buffer the current msg so
266 // it can be sent once the kernel has finished processing
266 // it can be sent once the kernel has finished processing
267 // some of the existing messages.
267 // some of the existing messages.
268
268
269 // Combine updates if it is a 'patch' sync, otherwise replace updates
269 // Combine updates if it is a 'patch' sync, otherwise replace updates
270 switch (method) {
270 switch (method) {
271 case 'patch':
271 case 'patch':
272 this.msg_buffer = $.extend(this.msg_buffer || {}, attrs);
272 this.msg_buffer = $.extend(this.msg_buffer || {}, attrs);
273 break;
273 break;
274 case 'update':
274 case 'update':
275 case 'create':
275 case 'create':
276 this.msg_buffer = attrs;
276 this.msg_buffer = attrs;
277 break;
277 break;
278 default:
278 default:
279 error();
279 error();
280 return false;
280 return false;
281 }
281 }
282 this.msg_buffer_callbacks = callbacks;
282 this.msg_buffer_callbacks = callbacks;
283
283
284 } else {
284 } else {
285 // We haven't exceeded the throttle, send the message like
285 // We haven't exceeded the throttle, send the message like
286 // normal.
286 // normal.
287 var data = {method: 'backbone', sync_data: attrs};
287 var data = {method: 'backbone', sync_data: attrs};
288 this.comm.send(data, callbacks);
288 this.comm.send(data, callbacks);
289 this.pending_msgs++;
289 this.pending_msgs++;
290 }
290 }
291 }
291 }
292 // Since the comm is a one-way communication, assume the message
292 // Since the comm is a one-way communication, assume the message
293 // arrived. Don't call success since we don't have a model back from the server
293 // arrived. Don't call success since we don't have a model back from the server
294 // this means we miss out on the 'sync' event.
294 // this means we miss out on the 'sync' event.
295 this._buffered_state_diff = {};
295 this._buffered_state_diff = {};
296 },
296 },
297
297
298 save_changes: function(callbacks) {
298 save_changes: function(callbacks) {
299 /**
299 /**
300 * Push this model's state to the back-end
300 * Push this model's state to the back-end
301 *
301 *
302 * This invokes a Backbone.Sync.
302 * This invokes a Backbone.Sync.
303 */
303 */
304 this.save(this._buffered_state_diff, {patch: true, callbacks: callbacks});
304 this.save(this._buffered_state_diff, {patch: true, callbacks: callbacks});
305 },
305 },
306
306
307 _pack_models: function(value) {
307 _pack_models: function(value) {
308 /**
308 /**
309 * Replace models with model ids recursively.
309 * Replace models with model ids recursively.
310 */
310 */
311 var that = this;
311 var that = this;
312 var packed;
312 var packed;
313 if (value instanceof Backbone.Model) {
313 if (value instanceof Backbone.Model) {
314 return "IPY_MODEL_" + value.id;
314 return "IPY_MODEL_" + value.id;
315
315
316 } else if ($.isArray(value)) {
316 } else if ($.isArray(value)) {
317 packed = [];
317 packed = [];
318 _.each(value, function(sub_value, key) {
318 _.each(value, function(sub_value, key) {
319 packed.push(that._pack_models(sub_value));
319 packed.push(that._pack_models(sub_value));
320 });
320 });
321 return packed;
321 return packed;
322 } else if (value instanceof Date || value instanceof String) {
322 } else if (value instanceof Date || value instanceof String) {
323 return value;
323 return value;
324 } else if (value instanceof Object) {
324 } else if (value instanceof Object) {
325 packed = {};
325 packed = {};
326 _.each(value, function(sub_value, key) {
326 _.each(value, function(sub_value, key) {
327 packed[key] = that._pack_models(sub_value);
327 packed[key] = that._pack_models(sub_value);
328 });
328 });
329 return packed;
329 return packed;
330
330
331 } else {
331 } else {
332 return value;
332 return value;
333 }
333 }
334 },
334 },
335
335
336 _unpack_models: function(value) {
336 _unpack_models: function(value) {
337 /**
337 /**
338 * Replace model ids with models recursively.
338 * Replace model ids with models recursively.
339 */
339 */
340 var that = this;
340 var that = this;
341 var unpacked;
341 var unpacked;
342 if ($.isArray(value)) {
342 if ($.isArray(value)) {
343 unpacked = [];
343 unpacked = [];
344 _.each(value, function(sub_value, key) {
344 _.each(value, function(sub_value, key) {
345 unpacked.push(that._unpack_models(sub_value));
345 unpacked.push(that._unpack_models(sub_value));
346 });
346 });
347 return Promise.all(unpacked);
347 return Promise.all(unpacked);
348 } else if (value instanceof Object) {
348 } else if (value instanceof Object) {
349 unpacked = {};
349 unpacked = {};
350 _.each(value, function(sub_value, key) {
350 _.each(value, function(sub_value, key) {
351 unpacked[key] = that._unpack_models(sub_value);
351 unpacked[key] = that._unpack_models(sub_value);
352 });
352 });
353 return utils.resolve_promises_dict(unpacked);
353 return utils.resolve_promises_dict(unpacked);
354 } else if (typeof value === 'string' && value.slice(0,10) === "IPY_MODEL_") {
354 } else if (typeof value === 'string' && value.slice(0,10) === "IPY_MODEL_") {
355 // get_model returns a promise already
355 // get_model returns a promise already
356 return this.widget_manager.get_model(value.slice(10, value.length));
356 return this.widget_manager.get_model(value.slice(10, value.length));
357 } else {
357 } else {
358 return Promise.resolve(value);
358 return Promise.resolve(value);
359 }
359 }
360 },
360 },
361
361
362 on_some_change: function(keys, callback, context) {
362 on_some_change: function(keys, callback, context) {
363 /**
363 /**
364 * on_some_change(["key1", "key2"], foo, context) differs from
364 * on_some_change(["key1", "key2"], foo, context) differs from
365 * on("change:key1 change:key2", foo, context).
365 * on("change:key1 change:key2", foo, context).
366 * If the widget attributes key1 and key2 are both modified,
366 * If the widget attributes key1 and key2 are both modified,
367 * the second form will result in foo being called twice
367 * the second form will result in foo being called twice
368 * while the first will call foo only once.
368 * while the first will call foo only once.
369 */
369 */
370 this.on('change', function() {
370 this.on('change', function() {
371 if (keys.some(this.hasChanged, this)) {
371 if (keys.some(this.hasChanged, this)) {
372 callback.apply(context);
372 callback.apply(context);
373 }
373 }
374 }, this);
374 }, this);
375
375
376 },
376 },
377 });
377 });
378 widgetmanager.WidgetManager.register_widget_model('WidgetModel', WidgetModel);
378 widgetmanager.WidgetManager.register_widget_model('WidgetModel', WidgetModel);
379
379
380
380
381 var WidgetView = Backbone.View.extend({
381 var WidgetView = Backbone.View.extend({
382 initialize: function(parameters) {
382 initialize: function(parameters) {
383 /**
383 /**
384 * Public constructor.
384 * Public constructor.
385 */
385 */
386 this.model.on('change',this.update,this);
386 this.model.on('change',this.update,this);
387 this.options = parameters.options;
387 this.options = parameters.options;
388 this.on('displayed', function() {
388 this.on('displayed', function() {
389 this.is_displayed = true;
389 this.is_displayed = true;
390 }, this);
390 }, this);
391 this.on('remove', function() {
391 this.on('remove', function() {
392 delete this.model.views[this.id];
392 delete this.model.views[this.id];
393 }, this);
393 }, this);
394 },
394 },
395
395
396 update: function(){
396 update: function(){
397 /**
397 /**
398 * Triggered on model change.
398 * Triggered on model change.
399 *
399 *
400 * Update view to be consistent with this.model
400 * Update view to be consistent with this.model
401 */
401 */
402 },
402 },
403
403
404 create_child_view: function(child_model, options) {
404 create_child_view: function(child_model, options) {
405 /**
405 /**
406 * Create and promise that resolves to a child view of a given model
406 * Create and promise that resolves to a child view of a given model
407 */
407 */
408 var that = this;
408 var that = this;
409 options = $.extend({ parent: this }, options || {});
409 options = $.extend({ parent: this }, options || {});
410 return this.model.widget_manager.create_view(child_model, options).catch(utils.reject("Couldn't create child view"), true);
410 return this.model.widget_manager.create_view(child_model, options).catch(utils.reject("Couldn't create child view"), true);
411 },
411 },
412
412
413 callbacks: function(){
413 callbacks: function(){
414 /**
414 /**
415 * Create msg callbacks for a comm msg.
415 * Create msg callbacks for a comm msg.
416 */
416 */
417 return this.model.callbacks(this);
417 return this.model.callbacks(this);
418 },
418 },
419
419
420 render: function(){
420 render: function(){
421 /**
421 /**
422 * Render the view.
422 * Render the view.
423 *
423 *
424 * By default, this is only called the first time the view is created
424 * By default, this is only called the first time the view is created
425 */
425 */
426 },
426 },
427
427
428 show: function(){
429 /**
430 * Show the widget-area
431 */
432 if (this.options && this.options.cell &&
433 this.options.cell.widget_area !== undefined) {
434 this.options.cell.widget_area.show();
435 }
436 },
437
438 send: function (content) {
428 send: function (content) {
439 /**
429 /**
440 * Send a custom msg associated with this view.
430 * Send a custom msg associated with this view.
441 */
431 */
442 this.model.send(content, this.callbacks());
432 this.model.send(content, this.callbacks());
443 },
433 },
444
434
445 touch: function () {
435 touch: function () {
446 this.model.save_changes(this.callbacks());
436 this.model.save_changes(this.callbacks());
447 },
437 },
448
438
449 after_displayed: function (callback, context) {
439 after_displayed: function (callback, context) {
450 /**
440 /**
451 * Calls the callback right away is the view is already displayed
441 * Calls the callback right away is the view is already displayed
452 * otherwise, register the callback to the 'displayed' event.
442 * otherwise, register the callback to the 'displayed' event.
453 */
443 */
454 if (this.is_displayed) {
444 if (this.is_displayed) {
455 callback.apply(context);
445 callback.apply(context);
456 } else {
446 } else {
457 this.on('displayed', callback, context);
447 this.on('displayed', callback, context);
458 }
448 }
459 },
449 },
460
450
461 remove: function () {
451 remove: function () {
462 // Raise a remove event when the view is removed.
452 // Raise a remove event when the view is removed.
463 WidgetView.__super__.remove.apply(this, arguments);
453 WidgetView.__super__.remove.apply(this, arguments);
464 this.trigger('remove');
454 this.trigger('remove');
465 }
455 }
466 });
456 });
467
457
468
458
469 var DOMWidgetView = WidgetView.extend({
459 var DOMWidgetView = WidgetView.extend({
470 initialize: function (parameters) {
460 initialize: function (parameters) {
471 /**
461 /**
472 * Public constructor
462 * Public constructor
473 */
463 */
474 DOMWidgetView.__super__.initialize.apply(this, [parameters]);
464 DOMWidgetView.__super__.initialize.apply(this, [parameters]);
475 this.on('displayed', this.show, this);
476 this.model.on('change:visible', this.update_visible, this);
465 this.model.on('change:visible', this.update_visible, this);
477 this.model.on('change:_css', this.update_css, this);
466 this.model.on('change:_css', this.update_css, this);
478
467
479 this.model.on('change:_dom_classes', function(model, new_classes) {
468 this.model.on('change:_dom_classes', function(model, new_classes) {
480 var old_classes = model.previous('_dom_classes');
469 var old_classes = model.previous('_dom_classes');
481 this.update_classes(old_classes, new_classes);
470 this.update_classes(old_classes, new_classes);
482 }, this);
471 }, this);
483
472
484 this.model.on('change:color', function (model, value) {
473 this.model.on('change:color', function (model, value) {
485 this.update_attr('color', value); }, this);
474 this.update_attr('color', value); }, this);
486
475
487 this.model.on('change:background_color', function (model, value) {
476 this.model.on('change:background_color', function (model, value) {
488 this.update_attr('background', value); }, this);
477 this.update_attr('background', value); }, this);
489
478
490 this.model.on('change:width', function (model, value) {
479 this.model.on('change:width', function (model, value) {
491 this.update_attr('width', value); }, this);
480 this.update_attr('width', value); }, this);
492
481
493 this.model.on('change:height', function (model, value) {
482 this.model.on('change:height', function (model, value) {
494 this.update_attr('height', value); }, this);
483 this.update_attr('height', value); }, this);
495
484
496 this.model.on('change:border_color', function (model, value) {
485 this.model.on('change:border_color', function (model, value) {
497 this.update_attr('border-color', value); }, this);
486 this.update_attr('border-color', value); }, this);
498
487
499 this.model.on('change:border_width', function (model, value) {
488 this.model.on('change:border_width', function (model, value) {
500 this.update_attr('border-width', value); }, this);
489 this.update_attr('border-width', value); }, this);
501
490
502 this.model.on('change:border_style', function (model, value) {
491 this.model.on('change:border_style', function (model, value) {
503 this.update_attr('border-style', value); }, this);
492 this.update_attr('border-style', value); }, this);
504
493
505 this.model.on('change:font_style', function (model, value) {
494 this.model.on('change:font_style', function (model, value) {
506 this.update_attr('font-style', value); }, this);
495 this.update_attr('font-style', value); }, this);
507
496
508 this.model.on('change:font_weight', function (model, value) {
497 this.model.on('change:font_weight', function (model, value) {
509 this.update_attr('font-weight', value); }, this);
498 this.update_attr('font-weight', value); }, this);
510
499
511 this.model.on('change:font_size', function (model, value) {
500 this.model.on('change:font_size', function (model, value) {
512 this.update_attr('font-size', this._default_px(value)); }, this);
501 this.update_attr('font-size', this._default_px(value)); }, this);
513
502
514 this.model.on('change:font_family', function (model, value) {
503 this.model.on('change:font_family', function (model, value) {
515 this.update_attr('font-family', value); }, this);
504 this.update_attr('font-family', value); }, this);
516
505
517 this.model.on('change:padding', function (model, value) {
506 this.model.on('change:padding', function (model, value) {
518 this.update_attr('padding', value); }, this);
507 this.update_attr('padding', value); }, this);
519
508
520 this.model.on('change:margin', function (model, value) {
509 this.model.on('change:margin', function (model, value) {
521 this.update_attr('margin', this._default_px(value)); }, this);
510 this.update_attr('margin', this._default_px(value)); }, this);
522
511
523 this.model.on('change:border_radius', function (model, value) {
512 this.model.on('change:border_radius', function (model, value) {
524 this.update_attr('border-radius', this._default_px(value)); }, this);
513 this.update_attr('border-radius', this._default_px(value)); }, this);
525
514
526 this.after_displayed(function() {
515 this.after_displayed(function() {
527 this.update_visible(this.model, this.model.get("visible"));
516 this.update_visible(this.model, this.model.get("visible"));
528 this.update_classes([], this.model.get('_dom_classes'));
517 this.update_classes([], this.model.get('_dom_classes'));
529
518
530 this.update_attr('color', this.model.get('color'));
519 this.update_attr('color', this.model.get('color'));
531 this.update_attr('background', this.model.get('background_color'));
520 this.update_attr('background', this.model.get('background_color'));
532 this.update_attr('width', this.model.get('width'));
521 this.update_attr('width', this.model.get('width'));
533 this.update_attr('height', this.model.get('height'));
522 this.update_attr('height', this.model.get('height'));
534 this.update_attr('border-color', this.model.get('border_color'));
523 this.update_attr('border-color', this.model.get('border_color'));
535 this.update_attr('border-width', this.model.get('border_width'));
524 this.update_attr('border-width', this.model.get('border_width'));
536 this.update_attr('border-style', this.model.get('border_style'));
525 this.update_attr('border-style', this.model.get('border_style'));
537 this.update_attr('font-style', this.model.get('font_style'));
526 this.update_attr('font-style', this.model.get('font_style'));
538 this.update_attr('font-weight', this.model.get('font_weight'));
527 this.update_attr('font-weight', this.model.get('font_weight'));
539 this.update_attr('font-size', this.model.get('font_size'));
528 this.update_attr('font-size', this.model.get('font_size'));
540 this.update_attr('font-family', this.model.get('font_family'));
529 this.update_attr('font-family', this.model.get('font_family'));
541 this.update_attr('padding', this.model.get('padding'));
530 this.update_attr('padding', this.model.get('padding'));
542 this.update_attr('margin', this.model.get('margin'));
531 this.update_attr('margin', this.model.get('margin'));
543 this.update_attr('border-radius', this.model.get('border_radius'));
532 this.update_attr('border-radius', this.model.get('border_radius'));
544
533
545 this.update_css(this.model, this.model.get("_css"));
534 this.update_css(this.model, this.model.get("_css"));
546 }, this);
535 }, this);
547 },
536 },
548
537
549 _default_px: function(value) {
538 _default_px: function(value) {
550 /**
539 /**
551 * Makes browser interpret a numerical string as a pixel value.
540 * Makes browser interpret a numerical string as a pixel value.
552 */
541 */
553 if (/^\d+\.?(\d+)?$/.test(value.trim())) {
542 if (/^\d+\.?(\d+)?$/.test(value.trim())) {
554 return value.trim() + 'px';
543 return value.trim() + 'px';
555 }
544 }
556 return value;
545 return value;
557 },
546 },
558
547
559 update_attr: function(name, value) {
548 update_attr: function(name, value) {
560 /**
549 /**
561 * Set a css attr of the widget view.
550 * Set a css attr of the widget view.
562 */
551 */
563 this.$el.css(name, value);
552 this.$el.css(name, value);
564 },
553 },
565
554
566 update_visible: function(model, value) {
555 update_visible: function(model, value) {
567 /**
556 /**
568 * Update visibility
557 * Update visibility
569 */
558 */
570 this.$el.toggle(value);
559 this.$el.toggle(value);
571 },
560 },
572
561
573 update_css: function (model, css) {
562 update_css: function (model, css) {
574 /**
563 /**
575 * Update the css styling of this view.
564 * Update the css styling of this view.
576 */
565 */
577 var e = this.$el;
566 var e = this.$el;
578 if (css === undefined) {return;}
567 if (css === undefined) {return;}
579 for (var i = 0; i < css.length; i++) {
568 for (var i = 0; i < css.length; i++) {
580 // Apply the css traits to all elements that match the selector.
569 // Apply the css traits to all elements that match the selector.
581 var selector = css[i][0];
570 var selector = css[i][0];
582 var elements = this._get_selector_element(selector);
571 var elements = this._get_selector_element(selector);
583 if (elements.length > 0) {
572 if (elements.length > 0) {
584 var trait_key = css[i][1];
573 var trait_key = css[i][1];
585 var trait_value = css[i][2];
574 var trait_value = css[i][2];
586 elements.css(trait_key ,trait_value);
575 elements.css(trait_key ,trait_value);
587 }
576 }
588 }
577 }
589 },
578 },
590
579
591 update_classes: function (old_classes, new_classes, $el) {
580 update_classes: function (old_classes, new_classes, $el) {
592 /**
581 /**
593 * Update the DOM classes applied to an element, default to this.$el.
582 * Update the DOM classes applied to an element, default to this.$el.
594 */
583 */
595 if ($el===undefined) {
584 if ($el===undefined) {
596 $el = this.$el;
585 $el = this.$el;
597 }
586 }
598 _.difference(old_classes, new_classes).map(function(c) {$el.removeClass(c);})
587 _.difference(old_classes, new_classes).map(function(c) {$el.removeClass(c);})
599 _.difference(new_classes, old_classes).map(function(c) {$el.addClass(c);})
588 _.difference(new_classes, old_classes).map(function(c) {$el.addClass(c);})
600 },
589 },
601
590
602 update_mapped_classes: function(class_map, trait_name, previous_trait_value, $el) {
591 update_mapped_classes: function(class_map, trait_name, previous_trait_value, $el) {
603 /**
592 /**
604 * Update the DOM classes applied to the widget based on a single
593 * Update the DOM classes applied to the widget based on a single
605 * trait's value.
594 * trait's value.
606 *
595 *
607 * Given a trait value classes map, this function automatically
596 * Given a trait value classes map, this function automatically
608 * handles applying the appropriate classes to the widget element
597 * handles applying the appropriate classes to the widget element
609 * and removing classes that are no longer valid.
598 * and removing classes that are no longer valid.
610 *
599 *
611 * Parameters
600 * Parameters
612 * ----------
601 * ----------
613 * class_map: dictionary
602 * class_map: dictionary
614 * Dictionary of trait values to class lists.
603 * Dictionary of trait values to class lists.
615 * Example:
604 * Example:
616 * {
605 * {
617 * success: ['alert', 'alert-success'],
606 * success: ['alert', 'alert-success'],
618 * info: ['alert', 'alert-info'],
607 * info: ['alert', 'alert-info'],
619 * warning: ['alert', 'alert-warning'],
608 * warning: ['alert', 'alert-warning'],
620 * danger: ['alert', 'alert-danger']
609 * danger: ['alert', 'alert-danger']
621 * };
610 * };
622 * trait_name: string
611 * trait_name: string
623 * Name of the trait to check the value of.
612 * Name of the trait to check the value of.
624 * previous_trait_value: optional string, default ''
613 * previous_trait_value: optional string, default ''
625 * Last trait value
614 * Last trait value
626 * $el: optional jQuery element handle, defaults to this.$el
615 * $el: optional jQuery element handle, defaults to this.$el
627 * Element that the classes are applied to.
616 * Element that the classes are applied to.
628 */
617 */
629 var key = previous_trait_value;
618 var key = previous_trait_value;
630 if (key === undefined) {
619 if (key === undefined) {
631 key = this.model.previous(trait_name);
620 key = this.model.previous(trait_name);
632 }
621 }
633 var old_classes = class_map[key] ? class_map[key] : [];
622 var old_classes = class_map[key] ? class_map[key] : [];
634 key = this.model.get(trait_name);
623 key = this.model.get(trait_name);
635 var new_classes = class_map[key] ? class_map[key] : [];
624 var new_classes = class_map[key] ? class_map[key] : [];
636
625
637 this.update_classes(old_classes, new_classes, $el || this.$el);
626 this.update_classes(old_classes, new_classes, $el || this.$el);
638 },
627 },
639
628
640 _get_selector_element: function (selector) {
629 _get_selector_element: function (selector) {
641 /**
630 /**
642 * Get the elements via the css selector.
631 * Get the elements via the css selector.
643 */
632 */
644 var elements;
633 var elements;
645 if (!selector) {
634 if (!selector) {
646 elements = this.$el;
635 elements = this.$el;
647 } else {
636 } else {
648 elements = this.$el.find(selector).addBack(selector);
637 elements = this.$el.find(selector).addBack(selector);
649 }
638 }
650 return elements;
639 return elements;
651 },
640 },
652
641
653 typeset: function(element, text){
642 typeset: function(element, text){
654 utils.typeset.apply(null, arguments);
643 utils.typeset.apply(null, arguments);
655 },
644 },
656 });
645 });
657
646
658
647
659 var ViewList = function(create_view, remove_view, context) {
648 var ViewList = function(create_view, remove_view, context) {
660 /**
649 /**
661 * - create_view and remove_view are default functions called when adding or removing views
650 * - create_view and remove_view are default functions called when adding or removing views
662 * - create_view takes a model and returns a view or a promise for a view for that model
651 * - create_view takes a model and returns a view or a promise for a view for that model
663 * - remove_view takes a view and destroys it (including calling `view.remove()`)
652 * - remove_view takes a view and destroys it (including calling `view.remove()`)
664 * - each time the update() function is called with a new list, the create and remove
653 * - each time the update() function is called with a new list, the create and remove
665 * callbacks will be called in an order so that if you append the views created in the
654 * callbacks will be called in an order so that if you append the views created in the
666 * create callback and remove the views in the remove callback, you will duplicate
655 * create callback and remove the views in the remove callback, you will duplicate
667 * the order of the list.
656 * the order of the list.
668 * - the remove callback defaults to just removing the view (e.g., pass in null for the second parameter)
657 * - the remove callback defaults to just removing the view (e.g., pass in null for the second parameter)
669 * - the context defaults to the created ViewList. If you pass another context, the create and remove
658 * - the context defaults to the created ViewList. If you pass another context, the create and remove
670 * will be called in that context.
659 * will be called in that context.
671 */
660 */
672
661
673 this.initialize.apply(this, arguments);
662 this.initialize.apply(this, arguments);
674 };
663 };
675
664
676 _.extend(ViewList.prototype, {
665 _.extend(ViewList.prototype, {
677 initialize: function(create_view, remove_view, context) {
666 initialize: function(create_view, remove_view, context) {
678 this.state_change = Promise.resolve();
667 this.state_change = Promise.resolve();
679 this._handler_context = context || this;
668 this._handler_context = context || this;
680 this._models = [];
669 this._models = [];
681 this.views = [];
670 this.views = [];
682 this._create_view = create_view;
671 this._create_view = create_view;
683 this._remove_view = remove_view || function(view) {view.remove();};
672 this._remove_view = remove_view || function(view) {view.remove();};
684 },
673 },
685
674
686 update: function(new_models, create_view, remove_view, context) {
675 update: function(new_models, create_view, remove_view, context) {
687 /**
676 /**
688 * the create_view, remove_view, and context arguments override the defaults
677 * the create_view, remove_view, and context arguments override the defaults
689 * specified when the list is created.
678 * specified when the list is created.
690 * returns a promise that resolves after this update is done
679 * returns a promise that resolves after this update is done
691 */
680 */
692 var remove = remove_view || this._remove_view;
681 var remove = remove_view || this._remove_view;
693 var create = create_view || this._create_view;
682 var create = create_view || this._create_view;
694 if (create === undefined || remove === undefined){
683 if (create === undefined || remove === undefined){
695 console.error("Must define a create a remove function");
684 console.error("Must define a create a remove function");
696 }
685 }
697 var context = context || this._handler_context;
686 var context = context || this._handler_context;
698 var added_views = [];
687 var added_views = [];
699 var that = this;
688 var that = this;
700 this.state_change = this.state_change.then(function() {
689 this.state_change = this.state_change.then(function() {
701 var i;
690 var i;
702 // first, skip past the beginning of the lists if they are identical
691 // first, skip past the beginning of the lists if they are identical
703 for (i = 0; i < new_models.length; i++) {
692 for (i = 0; i < new_models.length; i++) {
704 if (i >= that._models.length || new_models[i] !== that._models[i]) {
693 if (i >= that._models.length || new_models[i] !== that._models[i]) {
705 break;
694 break;
706 }
695 }
707 }
696 }
708 var first_removed = i;
697 var first_removed = i;
709 // Remove the non-matching items from the old list.
698 // Remove the non-matching items from the old list.
710 for (var j = first_removed; j < that._models.length; j++) {
699 for (var j = first_removed; j < that._models.length; j++) {
711 remove.call(context, that.views[j]);
700 remove.call(context, that.views[j]);
712 }
701 }
713
702
714 // Add the rest of the new list items.
703 // Add the rest of the new list items.
715 for (; i < new_models.length; i++) {
704 for (; i < new_models.length; i++) {
716 added_views.push(create.call(context, new_models[i]));
705 added_views.push(create.call(context, new_models[i]));
717 }
706 }
718 // make a copy of the input array
707 // make a copy of the input array
719 that._models = new_models.slice();
708 that._models = new_models.slice();
720 return Promise.all(added_views).then(function(added) {
709 return Promise.all(added_views).then(function(added) {
721 Array.prototype.splice.apply(that.views, [first_removed, that.views.length].concat(added));
710 Array.prototype.splice.apply(that.views, [first_removed, that.views.length].concat(added));
722 return that.views;
711 return that.views;
723 });
712 });
724 });
713 });
725 return this.state_change;
714 return this.state_change;
726 },
715 },
727
716
728 remove: function() {
717 remove: function() {
729 /**
718 /**
730 * removes every view in the list; convenience function for `.update([])`
719 * removes every view in the list; convenience function for `.update([])`
731 * that should be faster
720 * that should be faster
732 * returns a promise that resolves after this removal is done
721 * returns a promise that resolves after this removal is done
733 */
722 */
734 var that = this;
723 var that = this;
735 this.state_change = this.state_change.then(function() {
724 this.state_change = this.state_change.then(function() {
736 for (var i = 0; i < that.views.length; i++) {
725 for (var i = 0; i < that.views.length; i++) {
737 that._remove_view.call(that._handler_context, that.views[i]);
726 that._remove_view.call(that._handler_context, that.views[i]);
738 }
727 }
739 that._models = [];
728 that._models = [];
740 that.views = [];
729 that.views = [];
741 });
730 });
742 return this.state_change;
731 return this.state_change;
743 },
732 },
744 });
733 });
745
734
746 var widget = {
735 var widget = {
747 'WidgetModel': WidgetModel,
736 'WidgetModel': WidgetModel,
748 'WidgetView': WidgetView,
737 'WidgetView': WidgetView,
749 'DOMWidgetView': DOMWidgetView,
738 'DOMWidgetView': DOMWidgetView,
750 'ViewList': ViewList,
739 'ViewList': ViewList,
751 };
740 };
752
741
753 // For backwards compatability.
742 // For backwards compatability.
754 $.extend(IPython, widget);
743 $.extend(IPython, widget);
755
744
756 return widget;
745 return widget;
757 });
746 });
General Comments 0
You need to be logged in to leave comments. Login now