Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Avatar for KuCalc : devops.
Download
50659 views
1
//----------------------------------------------------------------------------
2
// Copyright (C) 2013 The IPython Development Team
3
//
4
// Distributed under the terms of the BSD License. The full license is in
5
// the file COPYING, distributed as part of this software.
6
//----------------------------------------------------------------------------
7
8
//============================================================================
9
// Base Widget Model and View classes
10
//============================================================================
11
12
/**
13
* @module IPython
14
* @namespace IPython
15
**/
16
17
define(["widgets/js/manager",
18
"underscore",
19
"backbone"],
20
function(WidgetManager, _, Backbone){
21
22
var WidgetModel = Backbone.Model.extend({
23
constructor: function (widget_manager, model_id, comm) {
24
// Constructor
25
//
26
// Creates a WidgetModel instance.
27
//
28
// Parameters
29
// ----------
30
// widget_manager : WidgetManager instance
31
// model_id : string
32
// An ID unique to this model.
33
// comm : Comm instance (optional)
34
this.widget_manager = widget_manager;
35
this._buffered_state_diff = {};
36
this.pending_msgs = 0;
37
this.msg_buffer = null;
38
this.key_value_lock = null;
39
this.id = model_id;
40
this.views = [];
41
42
if (comm !== undefined) {
43
// Remember comm associated with the model.
44
this.comm = comm;
45
comm.model = this;
46
47
// Hook comm messages up to model.
48
comm.on_close($.proxy(this._handle_comm_closed, this));
49
comm.on_msg($.proxy(this._handle_comm_msg, this));
50
}
51
return Backbone.Model.apply(this);
52
},
53
54
send: function (content, callbacks) {
55
// Send a custom msg over the comm.
56
if (this.comm !== undefined) {
57
var data = {method: 'custom', content: content};
58
this.comm.send(data, callbacks);
59
this.pending_msgs++;
60
}
61
},
62
63
_handle_comm_closed: function (msg) {
64
// Handle when a widget is closed.
65
this.trigger('comm:close');
66
delete this.comm.model; // Delete ref so GC will collect widget model.
67
delete this.comm;
68
delete this.model_id; // Delete id from model so widget manager cleans up.
69
_.each(this.views, function(view, i) {
70
view.remove();
71
});
72
},
73
74
_handle_comm_msg: function (msg) {
75
// Handle incoming comm msg.
76
var method = msg.content.data.method;
77
switch (method) {
78
case 'update':
79
this.apply_update(msg.content.data.state);
80
break;
81
case 'custom':
82
this.trigger('msg:custom', msg.content.data.content);
83
break;
84
case 'display':
85
this.widget_manager.display_view(msg, this);
86
this.trigger('displayed');
87
break;
88
}
89
},
90
91
apply_update: function (state) {
92
// Handle when a widget is updated via the python side.
93
var that = this;
94
_.each(state, function(value, key) {
95
that.key_value_lock = [key, value];
96
try {
97
WidgetModel.__super__.set.apply(that, [key, that._unpack_models(value)]);
98
} finally {
99
that.key_value_lock = null;
100
}
101
});
102
},
103
104
_handle_status: function (msg, callbacks) {
105
// Handle status msgs.
106
107
// execution_state : ('busy', 'idle', 'starting')
108
if (this.comm !== undefined) {
109
if (msg.content.execution_state ==='idle') {
110
// Send buffer if this message caused another message to be
111
// throttled.
112
if (this.msg_buffer !== null &&
113
(this.get('msg_throttle') || 3) === this.pending_msgs) {
114
var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
115
this.comm.send(data, callbacks);
116
this.msg_buffer = null;
117
} else {
118
--this.pending_msgs;
119
}
120
}
121
}
122
},
123
124
callbacks: function(view) {
125
// Create msg callbacks for a comm msg.
126
var callbacks = this.widget_manager.callbacks(view);
127
128
if (callbacks.iopub === undefined) {
129
callbacks.iopub = {};
130
}
131
132
var that = this;
133
callbacks.iopub.status = function (msg) {
134
that._handle_status(msg, callbacks);
135
};
136
return callbacks;
137
},
138
139
set: function(key, val, options) {
140
// Set a value.
141
var return_value = WidgetModel.__super__.set.apply(this, arguments);
142
143
// Backbone only remembers the diff of the most recent set()
144
// operation. Calling set multiple times in a row results in a
145
// loss of diff information. Here we keep our own running diff.
146
this._buffered_state_diff = $.extend(this._buffered_state_diff, this.changedAttributes() || {});
147
return return_value;
148
},
149
150
sync: function (method, model, options) {
151
// Handle sync to the back-end. Called when a model.save() is called.
152
153
// Make sure a comm exists.
154
var error = options.error || function() {
155
console.error('Backbone sync error:', arguments);
156
};
157
if (this.comm === undefined) {
158
error();
159
return false;
160
}
161
162
// Delete any key value pairs that the back-end already knows about.
163
var attrs = (method === 'patch') ? options.attrs : model.toJSON(options);
164
if (this.key_value_lock !== null) {
165
var key = this.key_value_lock[0];
166
var value = this.key_value_lock[1];
167
if (attrs[key] === value) {
168
delete attrs[key];
169
}
170
}
171
172
// Only sync if there are attributes to send to the back-end.
173
attrs = this._pack_models(attrs);
174
if (_.size(attrs) > 0) {
175
176
// If this message was sent via backbone itself, it will not
177
// have any callbacks. It's important that we create callbacks
178
// so we can listen for status messages, etc...
179
var callbacks = options.callbacks || this.callbacks();
180
181
// Check throttle.
182
if (this.pending_msgs >= (this.get('msg_throttle') || 3)) {
183
// The throttle has been exceeded, buffer the current msg so
184
// it can be sent once the kernel has finished processing
185
// some of the existing messages.
186
187
// Combine updates if it is a 'patch' sync, otherwise replace updates
188
switch (method) {
189
case 'patch':
190
this.msg_buffer = $.extend(this.msg_buffer || {}, attrs);
191
break;
192
case 'update':
193
case 'create':
194
this.msg_buffer = attrs;
195
break;
196
default:
197
error();
198
return false;
199
}
200
this.msg_buffer_callbacks = callbacks;
201
202
} else {
203
// We haven't exceeded the throttle, send the message like
204
// normal.
205
var data = {method: 'backbone', sync_data: attrs};
206
this.comm.send(data, callbacks);
207
this.pending_msgs++;
208
}
209
}
210
// Since the comm is a one-way communication, assume the message
211
// arrived. Don't call success since we don't have a model back from the server
212
// this means we miss out on the 'sync' event.
213
this._buffered_state_diff = {};
214
},
215
216
save_changes: function(callbacks) {
217
// Push this model's state to the back-end
218
//
219
// This invokes a Backbone.Sync.
220
this.save(this._buffered_state_diff, {patch: true, callbacks: callbacks});
221
},
222
223
_pack_models: function(value) {
224
// Replace models with model ids recursively.
225
if (value instanceof Backbone.Model) {
226
return value.id;
227
228
} else if ($.isArray(value)) {
229
var packed = [];
230
var that = this;
231
_.each(value, function(sub_value, key) {
232
packed.push(that._pack_models(sub_value));
233
});
234
return packed;
235
236
} else if (value instanceof Object) {
237
var packed = {};
238
var that = this;
239
_.each(value, function(sub_value, key) {
240
packed[key] = that._pack_models(sub_value);
241
});
242
return packed;
243
244
} else {
245
return value;
246
}
247
},
248
249
_unpack_models: function(value) {
250
// Replace model ids with models recursively.
251
if ($.isArray(value)) {
252
var unpacked = [];
253
var that = this;
254
_.each(value, function(sub_value, key) {
255
unpacked.push(that._unpack_models(sub_value));
256
});
257
return unpacked;
258
259
} else if (value instanceof Object) {
260
var unpacked = {};
261
var that = this;
262
_.each(value, function(sub_value, key) {
263
unpacked[key] = that._unpack_models(sub_value);
264
});
265
return unpacked;
266
267
} else {
268
var model = this.widget_manager.get_model(value);
269
if (model) {
270
return model;
271
} else {
272
return value;
273
}
274
}
275
},
276
277
});
278
WidgetManager.register_widget_model('WidgetModel', WidgetModel);
279
280
281
var WidgetView = Backbone.View.extend({
282
initialize: function(parameters) {
283
// Public constructor.
284
this.model.on('change',this.update,this);
285
this.options = parameters.options;
286
this.child_views = [];
287
this.model.views.push(this);
288
},
289
290
update: function(){
291
// Triggered on model change.
292
//
293
// Update view to be consistent with this.model
294
},
295
296
create_child_view: function(child_model, options) {
297
// Create and return a child view.
298
//
299
// -given a model and (optionally) a view name if the view name is
300
// not given, it defaults to the model's default view attribute.
301
302
// TODO: this is hacky, and makes the view depend on this cell attribute and widget manager behavior
303
// it would be great to have the widget manager add the cell metadata
304
// to the subview without having to add it here.
305
var child_view = this.model.widget_manager.create_view(child_model, options || {}, this);
306
this.child_views[child_model.id] = child_view;
307
return child_view;
308
},
309
310
delete_child_view: function(child_model, options) {
311
// Delete a child view that was previously created using create_child_view.
312
var view = this.child_views[child_model.id];
313
if (view !== undefined) {
314
delete this.child_views[child_model.id];
315
view.remove();
316
}
317
},
318
319
do_diff: function(old_list, new_list, removed_callback, added_callback) {
320
// Difference a changed list and call remove and add callbacks for
321
// each removed and added item in the new list.
322
//
323
// Parameters
324
// ----------
325
// old_list : array
326
// new_list : array
327
// removed_callback : Callback(item)
328
// Callback that is called for each item removed.
329
// added_callback : Callback(item)
330
// Callback that is called for each item added.
331
332
333
// removed items
334
_.each(_.difference(old_list, new_list), function(item, index, list) {
335
removed_callback(item);
336
}, this);
337
338
// added items
339
_.each(_.difference(new_list, old_list), function(item, index, list) {
340
added_callback(item);
341
}, this);
342
},
343
344
callbacks: function(){
345
// Create msg callbacks for a comm msg.
346
return this.model.callbacks(this);
347
},
348
349
render: function(){
350
// Render the view.
351
//
352
// By default, this is only called the first time the view is created
353
},
354
355
send: function (content) {
356
// Send a custom msg associated with this view.
357
this.model.send(content, this.callbacks());
358
},
359
360
touch: function () {
361
this.model.save_changes(this.callbacks());
362
},
363
});
364
365
366
var DOMWidgetView = WidgetView.extend({
367
initialize: function (options) {
368
// Public constructor
369
370
// In the future we may want to make changes more granular
371
// (e.g., trigger on visible:change).
372
this.model.on('change', this.update, this);
373
this.model.on('msg:custom', this.on_msg, this);
374
DOMWidgetView.__super__.initialize.apply(this, arguments);
375
},
376
377
on_msg: function(msg) {
378
// Handle DOM specific msgs.
379
switch(msg.msg_type) {
380
case 'add_class':
381
this.add_class(msg.selector, msg.class_list);
382
break;
383
case 'remove_class':
384
this.remove_class(msg.selector, msg.class_list);
385
break;
386
}
387
},
388
389
add_class: function (selector, class_list) {
390
// Add a DOM class to an element.
391
this._get_selector_element(selector).addClass(class_list);
392
},
393
394
remove_class: function (selector, class_list) {
395
// Remove a DOM class from an element.
396
this._get_selector_element(selector).removeClass(class_list);
397
},
398
399
update: function () {
400
// Update the contents of this view
401
//
402
// Called when the model is changed. The model may have been
403
// changed by another view or by a state update from the back-end.
404
// The very first update seems to happen before the element is
405
// finished rendering so we use setTimeout to give the element time
406
// to render
407
var e = this.$el;
408
var visible = this.model.get('visible');
409
setTimeout(function() {e.toggle(visible);},0);
410
411
var css = this.model.get('_css');
412
if (css === undefined) {return;}
413
var that = this;
414
_.each(css, function(css_traits, selector){
415
// Apply the css traits to all elements that match the selector.
416
var elements = that._get_selector_element(selector);
417
if (elements.length > 0) {
418
_.each(css_traits, function(css_value, css_key){
419
elements.css(css_key, css_value);
420
});
421
}
422
});
423
},
424
425
_get_selector_element: function (selector) {
426
// Get the elements via the css selector.
427
428
// If the selector is blank, apply the style to the $el_to_style
429
// element. If the $el_to_style element is not defined, use apply
430
// the style to the view's element.
431
var elements;
432
if (!selector) {
433
if (this.$el_to_style === undefined) {
434
elements = this.$el;
435
} else {
436
elements = this.$el_to_style;
437
}
438
} else {
439
elements = this.$el.find(selector);
440
}
441
return elements;
442
},
443
});
444
445
IPython.WidgetModel = WidgetModel;
446
IPython.WidgetView = WidgetView;
447
IPython.DOMWidgetView = DOMWidgetView;
448
449
// Pass through WidgetManager namespace.
450
return WidgetManager;
451
});
452
453