##// END OF EJS Templates
Merge pull request #6990 from jasongrout/viewlists...
Matthias Bussonnier -
r19103:d54dc3a9 merge
parent child Browse files
Show More
@@ -62,7 +62,7 b' define(['
62 dummy.replaceWith(view.$el);
62 dummy.replaceWith(view.$el);
63 view.trigger('displayed');
63 view.trigger('displayed');
64 return view;
64 return view;
65 }, utils.reject('Could not display view'));
65 }).catch(utils.reject('Could not display view', true));
66 }
66 }
67 };
67 };
68
68
@@ -103,7 +103,7 b' define(['
103 view.listenTo(model, 'destroy', view.remove);
103 view.listenTo(model, 'destroy', view.remove);
104 view.render();
104 view.render();
105 return view;
105 return view;
106 }, utils.reject("Couldn't create a view for model id '" + String(model.id) + "'"));
106 }).catch(utils.reject("Couldn't create a view for model id '" + String(model.id) + "'", true));
107 });
107 });
108 return model.state_change;
108 return model.state_change;
109 };
109 };
@@ -178,7 +178,7 b' define(['
178 return this.create_model({
178 return this.create_model({
179 model_name: msg.content.data.model_name,
179 model_name: msg.content.data.model_name,
180 model_module: msg.content.data.model_module,
180 model_module: msg.content.data.model_module,
181 comm: comm}).catch(utils.reject("Couldn't create a model."));
181 comm: comm}).catch(utils.reject("Couldn't create a model.", true));
182 };
182 };
183
183
184 WidgetManager.prototype.create_model = function (options) {
184 WidgetManager.prototype.create_model = function (options) {
@@ -95,7 +95,7 b' define(["widgets/js/manager",'
95 } finally {
95 } finally {
96 that.state_lock = null;
96 that.state_lock = null;
97 }
97 }
98 }, utils.reject("Couldn't set model state", true));
98 }).catch(utils.reject("Couldn't set model state", true));
99 },
99 },
100
100
101 _handle_status: function (msg, callbacks) {
101 _handle_status: function (msg, callbacks) {
@@ -292,8 +292,6 b' define(["widgets/js/manager",'
292 // Public constructor.
292 // Public constructor.
293 this.model.on('change',this.update,this);
293 this.model.on('change',this.update,this);
294 this.options = parameters.options;
294 this.options = parameters.options;
295 this.child_model_views = {};
296 this.child_views = {};
297 this.id = this.id || utils.uuid();
295 this.id = this.id || utils.uuid();
298 this.model.views[this.id] = this;
296 this.model.views[this.id] = this;
299 this.on('displayed', function() {
297 this.on('displayed', function() {
@@ -311,69 +309,7 b' define(["widgets/js/manager",'
311 // Create and promise that resolves to a child view of a given model
309 // Create and promise that resolves to a child view of a given model
312 var that = this;
310 var that = this;
313 options = $.extend({ parent: this }, options || {});
311 options = $.extend({ parent: this }, options || {});
314 return this.model.widget_manager.create_view(child_model, options).then(function(child_view) {
312 return this.model.widget_manager.create_view(child_model, options).catch(utils.reject("Couldn't create child view"), true);
315 // Associate the view id with the model id.
316 if (that.child_model_views[child_model.id] === undefined) {
317 that.child_model_views[child_model.id] = [];
318 }
319 that.child_model_views[child_model.id].push(child_view.id);
320 // Remember the view by id.
321 that.child_views[child_view.id] = child_view;
322 return child_view;
323 }, utils.reject("Couldn't create child view"));
324 },
325
326 pop_child_view: function(child_model) {
327 // Delete a child view that was previously created using create_child_view.
328 var view_ids = this.child_model_views[child_model.id];
329 if (view_ids !== undefined) {
330
331 // Only delete the first view in the list.
332 var view_id = view_ids[0];
333 var view = this.child_views[view_id];
334 delete this.child_views[view_id];
335 view_ids.splice(0,1);
336 delete child_model.views[view_id];
337
338 // Remove the view list specific to this model if it is empty.
339 if (view_ids.length === 0) {
340 delete this.child_model_views[child_model.id];
341 }
342 return view;
343 }
344 return null;
345 },
346
347 do_diff: function(old_list, new_list, removed_callback, added_callback) {
348 // Difference a changed list and call remove and add callbacks for
349 // each removed and added item in the new list.
350 //
351 // Parameters
352 // ----------
353 // old_list : array
354 // new_list : array
355 // removed_callback : Callback(item)
356 // Callback that is called for each item removed.
357 // added_callback : Callback(item)
358 // Callback that is called for each item added.
359
360 // Walk the lists until an unequal entry is found.
361 var i;
362 for (i = 0; i < new_list.length; i++) {
363 if (i >= old_list.length || new_list[i] !== old_list[i]) {
364 break;
365 }
366 }
367
368 // Remove the non-matching items from the old list.
369 for (var j = i; j < old_list.length; j++) {
370 removed_callback(old_list[j]);
371 }
372
373 // Add the rest of the new list items.
374 for (; i < new_list.length; i++) {
375 added_callback(new_list[i]);
376 }
377 },
313 },
378
314
379 callbacks: function(){
315 callbacks: function(){
@@ -533,11 +469,8 b' define(["widgets/js/manager",'
533 if ($el===undefined) {
469 if ($el===undefined) {
534 $el = this.$el;
470 $el = this.$el;
535 }
471 }
536 this.do_diff(old_classes, new_classes, function(removed) {
472 _.difference(old_classes, new_classes).map(function(c) {$el.removeClass(c);})
537 $el.removeClass(removed);
473 _.difference(new_classes, old_classes).map(function(c) {$el.addClass(c);})
538 }, function(added) {
539 $el.addClass(added);
540 });
541 },
474 },
542
475
543 update_mapped_classes: function(class_map, trait_name, previous_trait_value, $el) {
476 update_mapped_classes: function(class_map, trait_name, previous_trait_value, $el) {
@@ -588,11 +521,93 b' define(["widgets/js/manager",'
588 },
521 },
589 });
522 });
590
523
591
524
525 var ViewList = function(create_view, remove_view, context) {
526 // * create_view and remove_view are default functions called when adding or removing views
527 // * create_view takes a model and returns a view or a promise for a view for that model
528 // * remove_view takes a view and destroys it (including calling `view.remove()`)
529 // * each time the update() function is called with a new list, the create and remove
530 // callbacks will be called in an order so that if you append the views created in the
531 // create callback and remove the views in the remove callback, you will duplicate
532 // the order of the list.
533 // * the remove callback defaults to just removing the view (e.g., pass in null for the second parameter)
534 // * the context defaults to the created ViewList. If you pass another context, the create and remove
535 // will be called in that context.
536
537 this.initialize.apply(this, arguments);
538 };
539
540 _.extend(ViewList.prototype, {
541 initialize: function(create_view, remove_view, context) {
542 this.state_change = Promise.resolve();
543 this._handler_context = context || this;
544 this._models = [];
545 this.views = [];
546 this._create_view = create_view;
547 this._remove_view = remove_view || function(view) {view.remove();};
548 },
549
550 update: function(new_models, create_view, remove_view, context) {
551 // the create_view, remove_view, and context arguments override the defaults
552 // specified when the list is created.
553 // returns a promise that resolves after this update is done
554 var remove = remove_view || this._remove_view;
555 var create = create_view || this._create_view;
556 if (create === undefined || remove === undefined){
557 console.error("Must define a create a remove function");
558 }
559 var context = context || this._handler_context;
560 var added_views = [];
561 var that = this;
562 this.state_change = this.state_change.then(function() {
563 var i;
564 // first, skip past the beginning of the lists if they are identical
565 for (i = 0; i < new_models.length; i++) {
566 if (i >= that._models.length || new_models[i] !== that._models[i]) {
567 break;
568 }
569 }
570 var first_removed = i;
571 // Remove the non-matching items from the old list.
572 for (var j = first_removed; j < that._models.length; j++) {
573 remove.call(context, that.views[j]);
574 }
575
576 // Add the rest of the new list items.
577 for (; i < new_models.length; i++) {
578 added_views.push(create.call(context, new_models[i]));
579 }
580 // make a copy of the input array
581 that._models = new_models.slice();
582 return Promise.all(added_views).then(function(added) {
583 Array.prototype.splice.apply(that.views, [first_removed, that.views.length].concat(added));
584 return that.views;
585 });
586 });
587 return this.state_change;
588 },
589
590 remove: function() {
591 // removes every view in the list; convenience function for `.update([])`
592 // that should be faster
593 // returns a promise that resolves after this removal is done
594 var that = this;
595 this.state_change = this.state_change.then(function() {
596 for (var i = 0; i < that.views.length; i++) {
597 that._remove_view.call(that._handler_context, that.views[i]);
598 }
599 that._models = [];
600 that.views = [];
601 });
602 return this.state_change;
603 },
604 });
605
592 var widget = {
606 var widget = {
593 'WidgetModel': WidgetModel,
607 'WidgetModel': WidgetModel,
594 'WidgetView': WidgetView,
608 'WidgetView': WidgetView,
595 'DOMWidgetView': DOMWidgetView,
609 'DOMWidgetView': DOMWidgetView,
610 'ViewList': ViewList,
596 };
611 };
597
612
598 // For backwards compatability.
613 // For backwards compatability.
@@ -12,16 +12,17 b' define(['
12 initialize: function(){
12 initialize: function(){
13 // Public constructor
13 // Public constructor
14 BoxView.__super__.initialize.apply(this, arguments);
14 BoxView.__super__.initialize.apply(this, arguments);
15 this.model.on('change:children', function(model, value) {
15 this.children_views = new widget.ViewList(this.add_child_model, null, this);
16 this.update_children(model.previous('children'), value);
16 this.listenTo(this.model, 'change:children', function(model, value) {
17 this.children_views.update(value);
17 }, this);
18 }, this);
18 this.model.on('change:overflow_x', function(model, value) {
19 this.listenTo(this.model, 'change:overflow_x', function(model, value) {
19 this.update_overflow_x();
20 this.update_overflow_x();
20 }, this);
21 }, this);
21 this.model.on('change:overflow_y', function(model, value) {
22 this.listenTo(this.model, 'change:overflow_y', function(model, value) {
22 this.update_overflow_y();
23 this.update_overflow_y();
23 }, this);
24 }, this);
24 this.model.on('change:box_style', function(model, value) {
25 this.listenTo(this.model, 'change:box_style', function(model, value) {
25 this.update_box_style();
26 this.update_box_style();
26 }, this);
27 }, this);
27 },
28 },
@@ -35,7 +36,7 b' define(['
35 // Called when view is rendered.
36 // Called when view is rendered.
36 this.$box = this.$el;
37 this.$box = this.$el;
37 this.$box.addClass('widget-box');
38 this.$box.addClass('widget-box');
38 this.update_children([], this.model.get('children'));
39 this.children_views.update(this.model.get('children'));
39 this.update_overflow_x();
40 this.update_overflow_x();
40 this.update_overflow_y();
41 this.update_overflow_y();
41 this.update_box_style('');
42 this.update_box_style('');
@@ -61,18 +62,6 b' define(['
61 this.update_mapped_classes(class_map, 'box_style', previous_trait_value, this.$box);
62 this.update_mapped_classes(class_map, 'box_style', previous_trait_value, this.$box);
62 },
63 },
63
64
64 update_children: function(old_list, new_list) {
65 // Called when the children list changes.
66 this.do_diff(old_list, new_list,
67 $.proxy(this.remove_child_model, this),
68 $.proxy(this.add_child_model, this));
69 },
70
71 remove_child_model: function(model) {
72 // Called when a model is removed from the children list.
73 this.pop_child_view(model).remove();
74 },
75
76 add_child_model: function(model) {
65 add_child_model: function(model) {
77 // Called when a model is added to the children list.
66 // Called when a model is added to the children list.
78 var that = this;
67 var that = this;
@@ -86,7 +75,15 b' define(['
86 view.trigger('displayed');
75 view.trigger('displayed');
87 });
76 });
88 return view;
77 return view;
89 }, utils.reject("Couldn't add child view to box", true));
78 }).catch(utils.reject("Couldn't add child view to box", true));
79 },
80
81 remove: function() {
82 // We remove this widget before removing the children as an optimization
83 // we want to remove the entire container from the DOM first before
84 // removing each individual child separately.
85 BoxView.__super__.remove.apply(this, arguments);
86 this.children_views.remove();
90 },
87 },
91 });
88 });
92
89
@@ -94,10 +91,10 b' define(['
94 var FlexBoxView = BoxView.extend({
91 var FlexBoxView = BoxView.extend({
95 render: function(){
92 render: function(){
96 FlexBoxView.__super__.render.apply(this);
93 FlexBoxView.__super__.render.apply(this);
97 this.model.on('change:orientation', this.update_orientation, this);
94 this.listenTo(this.model, 'change:orientation', this.update_orientation, this);
98 this.model.on('change:flex', this._flex_changed, this);
95 this.listenTo(this.model, 'change:flex', this._flex_changed, this);
99 this.model.on('change:pack', this._pack_changed, this);
96 this.listenTo(this.model, 'change:pack', this._pack_changed, this);
100 this.model.on('change:align', this._align_changed, this);
97 this.listenTo(this.model, 'change:align', this._align_changed, this);
101 this._flex_changed();
98 this._flex_changed();
102 this._pack_changed();
99 this._pack_changed();
103 this._align_changed();
100 this._align_changed();
@@ -244,10 +241,7 b' define(['
244 this._shown_once = false;
241 this._shown_once = false;
245 this.popped_out = true;
242 this.popped_out = true;
246
243
247 this.update_children([], this.model.get('children'));
244 this.children_views.update(this.model.get('children'))
248 this.model.on('change:children', function(model, value) {
249 this.update_children(model.previous('children'), value);
250 }, this);
251 },
245 },
252
246
253 hide: function() {
247 hide: function() {
@@ -9,18 +9,23 b' define(['
9 ], function(widget, utils, $){
9 ], function(widget, utils, $){
10
10
11 var AccordionView = widget.DOMWidgetView.extend({
11 var AccordionView = widget.DOMWidgetView.extend({
12 initialize: function(){
13 AccordionView.__super__.initialize.apply(this, arguments);
14
15 this.containers = [];
16 this.model_containers = {};
17 this.children_views = new widget.ViewList(this.add_child_view, this.remove_child_view, this);
18 this.listenTo(this.model, 'change:children', function(model, value) {
19 this.children_views.update(value);
20 }, this);
21 },
22
12 render: function(){
23 render: function(){
13 // Called when view is rendered.
24 // Called when view is rendered.
14 var guid = 'panel-group' + utils.uuid();
25 var guid = 'panel-group' + utils.uuid();
15 this.$el
26 this.$el
16 .attr('id', guid)
27 .attr('id', guid)
17 .addClass('panel-group');
28 .addClass('panel-group');
18 this.containers = [];
19 this.model_containers = {};
20 this.update_children([], this.model.get('children'));
21 this.model.on('change:children', function(model, value, options) {
22 this.update_children(model.previous('children'), value);
23 }, this);
24 this.model.on('change:selected_index', function(model, value, options) {
29 this.model.on('change:selected_index', function(model, value, options) {
25 this.update_selected_index(model.previous('selected_index'), value, options);
30 this.update_selected_index(model.previous('selected_index'), value, options);
26 }, this);
31 }, this);
@@ -31,6 +36,7 b' define(['
31 this.on('displayed', function() {
36 this.on('displayed', function() {
32 this.update_titles();
37 this.update_titles();
33 }, this);
38 }, this);
39 this.children_views.update(this.model.get('children'));
34 },
40 },
35
41
36 update_titles: function(titles) {
42 update_titles: function(titles) {
@@ -61,25 +67,18 b' define(['
61 }
67 }
62 }
68 }
63 },
69 },
64
65 update_children: function(old_list, new_list) {
66 // Called when the children list is modified.
67 this.do_diff(old_list,
68 new_list,
69 $.proxy(this.remove_child_model, this),
70 $.proxy(this.add_child_model, this));
71 },
72
70
73 remove_child_model: function(model) {
71 remove_child_view: function(view) {
74 // Called when a child is removed from children list.
72 // Called when a child is removed from children list.
73 // TODO: does this handle two different views of the same model as children?
74 var model = view.model;
75 var accordion_group = this.model_containers[model.id];
75 var accordion_group = this.model_containers[model.id];
76 this.containers.splice(accordion_group.container_index, 1);
76 this.containers.splice(accordion_group.container_index, 1);
77 delete this.model_containers[model.id];
77 delete this.model_containers[model.id];
78 accordion_group.remove();
78 accordion_group.remove();
79 this.pop_child_view(model);
80 },
79 },
81
80
82 add_child_model: function(model) {
81 add_child_view: function(model) {
83 // Called when a child is added to children list.
82 // Called when a child is added to children list.
84 var index = this.containers.length;
83 var index = this.containers.length;
85 var uuid = utils.uuid();
84 var uuid = utils.uuid();
@@ -126,7 +125,15 b' define(['
126 view.trigger('displayed');
125 view.trigger('displayed');
127 });
126 });
128 return view;
127 return view;
129 }, utils.reject("Couldn't add child view to box", true));
128 }).catch(utils.reject("Couldn't add child view to box", true));
129 },
130
131 remove: function() {
132 // We remove this widget before removing the children as an optimization
133 // we want to remove the entire container from the DOM first before
134 // removing each individual child separately.
135 AccordionView.__super__.remove.apply(this, arguments);
136 this.children_views.remove();
130 },
137 },
131 });
138 });
132
139
@@ -134,8 +141,13 b' define(['
134 var TabView = widget.DOMWidgetView.extend({
141 var TabView = widget.DOMWidgetView.extend({
135 initialize: function() {
142 initialize: function() {
136 // Public constructor.
143 // Public constructor.
137 this.containers = [];
138 TabView.__super__.initialize.apply(this, arguments);
144 TabView.__super__.initialize.apply(this, arguments);
145
146 this.containers = [];
147 this.children_views = new widget.ViewList(this.add_child_view, this.remove_child_view, this);
148 this.listenTo(this.model, 'change:children', function(model, value) {
149 this.children_views.update(value);
150 }, this);
139 },
151 },
140
152
141 render: function(){
153 render: function(){
@@ -149,11 +161,7 b' define(['
149 this.$tab_contents = $('<div />', {id: uuid + 'Content'})
161 this.$tab_contents = $('<div />', {id: uuid + 'Content'})
150 .addClass('tab-content')
162 .addClass('tab-content')
151 .appendTo(this.$el);
163 .appendTo(this.$el);
152 this.containers = [];
164 this.children_views.update(this.model.get('children'));
153 this.update_children([], this.model.get('children'));
154 this.model.on('change:children', function(model, value, options) {
155 this.update_children(model.previous('children'), value);
156 }, this);
157 },
165 },
158
166
159 update_attr: function(name, value) {
167 update_attr: function(name, value) {
@@ -161,24 +169,15 b' define(['
161 this.$tabs.css(name, value);
169 this.$tabs.css(name, value);
162 },
170 },
163
171
164 update_children: function(old_list, new_list) {
172 remove_child_view: function(view) {
165 // Called when the children list is modified.
166 this.do_diff(old_list,
167 new_list,
168 $.proxy(this.remove_child_model, this),
169 $.proxy(this.add_child_model, this));
170 },
171
172 remove_child_model: function(model) {
173 // Called when a child is removed from children list.
173 // Called when a child is removed from children list.
174 var view = this.pop_child_view(model);
175 this.containers.splice(view.parent_tab.tab_text_index, 1);
174 this.containers.splice(view.parent_tab.tab_text_index, 1);
176 view.parent_tab.remove();
175 view.parent_tab.remove();
177 view.parent_container.remove();
176 view.parent_container.remove();
178 view.remove();
177 view.remove();
179 },
178 },
180
179
181 add_child_model: function(model) {
180 add_child_view: function(model) {
182 // Called when a child is added to children list.
181 // Called when a child is added to children list.
183 var index = this.containers.length;
182 var index = this.containers.length;
184 var uuid = utils.uuid();
183 var uuid = utils.uuid();
@@ -221,7 +220,7 b' define(['
221 view.trigger('displayed');
220 view.trigger('displayed');
222 });
221 });
223 return view;
222 return view;
224 }, utils.reject("Couldn't add child view to box", true));
223 }).catch(utils.reject("Couldn't add child view to box", true));
225 },
224 },
226
225
227 update: function(options) {
226 update: function(options) {
@@ -254,6 +253,14 b' define(['
254 .removeClass('active');
253 .removeClass('active');
255 this.containers[index].tab('show');
254 this.containers[index].tab('show');
256 },
255 },
256
257 remove: function() {
258 // We remove this widget before removing the children as an optimization
259 // we want to remove the entire container from the DOM first before
260 // removing each individual child separately.
261 TabView.__super__.remove.apply(this, arguments);
262 this.children_views.remove();
263 },
257 });
264 });
258
265
259 return {
266 return {
General Comments 0
You need to be logged in to leave comments. Login now