##// END OF EJS Templates
Add comments emphasizing the order of steps in widget box remove() methods
Jason Grout -
Show More
@@ -1,341 +1,344
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 "widgets/js/widget",
6 6 "jqueryui",
7 7 "base/js/utils",
8 8 "bootstrap",
9 9 ], function(widget, $, utils){
10 10
11 11 var BoxView = widget.DOMWidgetView.extend({
12 12 initialize: function(){
13 13 // Public constructor
14 14 BoxView.__super__.initialize.apply(this, arguments);
15 15 this.children_views = new widget.ViewList(this.add_child_model, null, this);
16 16 this.listenTo(this.model, 'change:children', function(model, value) {
17 17 this.children_views.update(value);
18 18 }, this);
19 19 this.listenTo(this.model, 'change:overflow_x', function(model, value) {
20 20 this.update_overflow_x();
21 21 }, this);
22 22 this.listenTo(this.model, 'change:overflow_y', function(model, value) {
23 23 this.update_overflow_y();
24 24 }, this);
25 25 this.listenTo(this.model, 'change:box_style', function(model, value) {
26 26 this.update_box_style();
27 27 }, this);
28 28 },
29 29
30 30 update_attr: function(name, value) {
31 31 // Set a css attr of the widget view.
32 32 this.$box.css(name, value);
33 33 },
34 34
35 35 render: function(){
36 36 // Called when view is rendered.
37 37 this.$box = this.$el;
38 38 this.$box.addClass('widget-box');
39 39 this.children_views.update(this.model.get('children'));
40 40 this.update_overflow_x();
41 41 this.update_overflow_y();
42 42 this.update_box_style('');
43 43 },
44 44
45 45 update_overflow_x: function() {
46 46 // Called when the x-axis overflow setting is changed.
47 47 this.$box.css('overflow-x', this.model.get('overflow_x'));
48 48 },
49 49
50 50 update_overflow_y: function() {
51 51 // Called when the y-axis overflow setting is changed.
52 52 this.$box.css('overflow-y', this.model.get('overflow_y'));
53 53 },
54 54
55 55 update_box_style: function(previous_trait_value) {
56 56 var class_map = {
57 57 success: ['alert', 'alert-success'],
58 58 info: ['alert', 'alert-info'],
59 59 warning: ['alert', 'alert-warning'],
60 60 danger: ['alert', 'alert-danger']
61 61 };
62 62 this.update_mapped_classes(class_map, 'box_style', previous_trait_value, this.$box);
63 63 },
64 64
65 65 add_child_model: function(model) {
66 66 // Called when a model is added to the children list.
67 67 var that = this;
68 68 var dummy = $('<div/>');
69 69 that.$box.append(dummy);
70 70 return this.create_child_view(model).then(function(view) {
71 71 dummy.replaceWith(view.el);
72 72
73 73 // Trigger the displayed event of the child view.
74 74 that.after_displayed(function() {
75 75 view.trigger('displayed');
76 76 });
77 77 return view;
78 78 }, utils.reject("Couldn't add child view to box", true));
79 79 },
80 80
81 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.
82 85 BoxView.__super__.remove.apply(this, arguments);
83 86 this.children_views.remove();
84 87 },
85 88 });
86 89
87 90
88 91 var FlexBoxView = BoxView.extend({
89 92 render: function(){
90 93 FlexBoxView.__super__.render.apply(this);
91 94 this.listenTo(this.model, 'change:orientation', this.update_orientation, this);
92 95 this.listenTo(this.model, 'change:flex', this._flex_changed, this);
93 96 this.listenTo(this.model, 'change:pack', this._pack_changed, this);
94 97 this.listenTo(this.model, 'change:align', this._align_changed, this);
95 98 this._flex_changed();
96 99 this._pack_changed();
97 100 this._align_changed();
98 101 this.update_orientation();
99 102 },
100 103
101 104 update_orientation: function(){
102 105 var orientation = this.model.get("orientation");
103 106 if (orientation == "vertical") {
104 107 this.$box.removeClass("hbox").addClass("vbox");
105 108 } else {
106 109 this.$box.removeClass("vbox").addClass("hbox");
107 110 }
108 111 },
109 112
110 113 _flex_changed: function(){
111 114 if (this.model.previous('flex')) {
112 115 this.$box.removeClass('box-flex' + this.model.previous('flex'));
113 116 }
114 117 this.$box.addClass('box-flex' + this.model.get('flex'));
115 118 },
116 119
117 120 _pack_changed: function(){
118 121 if (this.model.previous('pack')) {
119 122 this.$box.removeClass(this.model.previous('pack'));
120 123 }
121 124 this.$box.addClass(this.model.get('pack'));
122 125 },
123 126
124 127 _align_changed: function(){
125 128 if (this.model.previous('align')) {
126 129 this.$box.removeClass('align-' + this.model.previous('align'));
127 130 }
128 131 this.$box.addClass('align-' + this.model.get('align'));
129 132 },
130 133 });
131 134
132 135 var PopupView = BoxView.extend({
133 136
134 137 render: function(){
135 138 // Called when view is rendered.
136 139 var that = this;
137 140
138 141 this.$el.on("remove", function(){
139 142 that.$backdrop.remove();
140 143 });
141 144 this.$backdrop = $('<div />')
142 145 .appendTo($('#notebook-container'))
143 146 .addClass('modal-dialog')
144 147 .css('position', 'absolute')
145 148 .css('left', '0px')
146 149 .css('top', '0px');
147 150 this.$window = $('<div />')
148 151 .appendTo(this.$backdrop)
149 152 .addClass('modal-content widget-modal')
150 153 .mousedown(function(){
151 154 that.bring_to_front();
152 155 });
153 156
154 157 // Set the elements array since the this.$window element is not child
155 158 // of this.$el and the parent widget manager or other widgets may
156 159 // need to know about all of the top-level widgets. The IPython
157 160 // widget manager uses this to register the elements with the
158 161 // keyboard manager.
159 162 this.additional_elements = [this.$window];
160 163
161 164 this.$title_bar = $('<div />')
162 165 .addClass('popover-title')
163 166 .appendTo(this.$window)
164 167 .mousedown(function(){
165 168 that.bring_to_front();
166 169 });
167 170 this.$close = $('<button />')
168 171 .addClass('close fa fa-remove')
169 172 .css('margin-left', '5px')
170 173 .appendTo(this.$title_bar)
171 174 .click(function(){
172 175 that.hide();
173 176 event.stopPropagation();
174 177 });
175 178 this.$minimize = $('<button />')
176 179 .addClass('close fa fa-arrow-down')
177 180 .appendTo(this.$title_bar)
178 181 .click(function(){
179 182 that.popped_out = !that.popped_out;
180 183 if (!that.popped_out) {
181 184 that.$minimize
182 185 .removeClass('fa-arrow-down')
183 186 .addClass('fa-arrow-up');
184 187
185 188 that.$window
186 189 .draggable('destroy')
187 190 .resizable('destroy')
188 191 .removeClass('widget-modal modal-content')
189 192 .addClass('docked-widget-modal')
190 193 .detach()
191 194 .insertBefore(that.$show_button);
192 195 that.$show_button.hide();
193 196 that.$close.hide();
194 197 } else {
195 198 that.$minimize
196 199 .addClass('fa-arrow-down')
197 200 .removeClass('fa-arrow-up');
198 201
199 202 that.$window
200 203 .removeClass('docked-widget-modal')
201 204 .addClass('widget-modal modal-content')
202 205 .detach()
203 206 .appendTo(that.$backdrop)
204 207 .draggable({handle: '.popover-title', snap: '#notebook, .modal', snapMode: 'both'})
205 208 .resizable()
206 209 .children('.ui-resizable-handle').show();
207 210 that.show();
208 211 that.$show_button.show();
209 212 that.$close.show();
210 213 }
211 214 event.stopPropagation();
212 215 });
213 216 this.$title = $('<div />')
214 217 .addClass('widget-modal-title')
215 218 .html("&nbsp;")
216 219 .appendTo(this.$title_bar);
217 220 this.$box = $('<div />')
218 221 .addClass('modal-body')
219 222 .addClass('widget-modal-body')
220 223 .addClass('widget-box')
221 224 .addClass('vbox')
222 225 .appendTo(this.$window);
223 226
224 227 this.$show_button = $('<button />')
225 228 .html("&nbsp;")
226 229 .addClass('btn btn-info widget-modal-show')
227 230 .appendTo(this.$el)
228 231 .click(function(){
229 232 that.show();
230 233 });
231 234
232 235 this.$window.draggable({handle: '.popover-title', snap: '#notebook, .modal', snapMode: 'both'});
233 236 this.$window.resizable();
234 237 this.$window.on('resize', function(){
235 238 that.$box.outerHeight(that.$window.innerHeight() - that.$title_bar.outerHeight());
236 239 });
237 240
238 241 this._shown_once = false;
239 242 this.popped_out = true;
240 243
241 244 this.children_views.update(this.model.get('children'))
242 245 },
243 246
244 247 hide: function() {
245 248 // Called when the modal hide button is clicked.
246 249 this.$window.hide();
247 250 this.$show_button.removeClass('btn-info');
248 251 },
249 252
250 253 show: function() {
251 254 // Called when the modal show button is clicked.
252 255 this.$show_button.addClass('btn-info');
253 256 this.$window.show();
254 257 if (this.popped_out) {
255 258 this.$window.css("positon", "absolute");
256 259 this.$window.css("top", "0px");
257 260 this.$window.css("left", Math.max(0, (($('body').outerWidth() - this.$window.outerWidth()) / 2) +
258 261 $(window).scrollLeft()) + "px");
259 262 this.bring_to_front();
260 263 }
261 264 },
262 265
263 266 bring_to_front: function() {
264 267 // Make the modal top-most, z-ordered about the other modals.
265 268 var $widget_modals = $(".widget-modal");
266 269 var max_zindex = 0;
267 270 $widget_modals.each(function (index, el){
268 271 var zindex = parseInt($(el).css('z-index'));
269 272 if (!isNaN(zindex)) {
270 273 max_zindex = Math.max(max_zindex, zindex);
271 274 }
272 275 });
273 276
274 277 // Start z-index of widget modals at 2000
275 278 max_zindex = Math.max(max_zindex, 2000);
276 279
277 280 $widget_modals.each(function (index, el){
278 281 $el = $(el);
279 282 if (max_zindex == parseInt($el.css('z-index'))) {
280 283 $el.css('z-index', max_zindex - 1);
281 284 }
282 285 });
283 286 this.$window.css('z-index', max_zindex);
284 287 },
285 288
286 289 update: function(){
287 290 // Update the contents of this view
288 291 //
289 292 // Called when the model is changed. The model may have been
290 293 // changed by another view or by a state update from the back-end.
291 294 var description = this.model.get('description');
292 295 if (description.trim().length === 0) {
293 296 this.$title.html("&nbsp;"); // Preserve title height
294 297 } else {
295 298 this.$title.text(description);
296 299 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$title.get(0)]);
297 300 }
298 301
299 302 var button_text = this.model.get('button_text');
300 303 if (button_text.trim().length === 0) {
301 304 this.$show_button.html("&nbsp;"); // Preserve button height
302 305 } else {
303 306 this.$show_button.text(button_text);
304 307 }
305 308
306 309 if (!this._shown_once) {
307 310 this._shown_once = true;
308 311 this.show();
309 312 }
310 313
311 314 return PopupView.__super__.update.apply(this);
312 315 },
313 316
314 317 _get_selector_element: function(selector) {
315 318 // Get an element view a 'special' jquery selector. (see widget.js)
316 319 //
317 320 // Since the modal actually isn't within the $el in the DOM, we need to extend
318 321 // the selector logic to allow the user to set css on the modal if need be.
319 322 // The convention used is:
320 323 // "modal" - select the modal div
321 324 // "modal [selector]" - select element(s) within the modal div.
322 325 // "[selector]" - select elements within $el
323 326 // "" - select the $el
324 327 if (selector.substring(0, 5) == 'modal') {
325 328 if (selector == 'modal') {
326 329 return this.$window;
327 330 } else {
328 331 return this.$window.find(selector.substring(6));
329 332 }
330 333 } else {
331 334 return PopupView.__super__._get_selector_element.apply(this, [selector]);
332 335 }
333 336 },
334 337 });
335 338
336 339 return {
337 340 'BoxView': BoxView,
338 341 'PopupView': PopupView,
339 342 'FlexBoxView': FlexBoxView,
340 343 };
341 344 });
@@ -1,264 +1,270
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 "widgets/js/widget",
6 6 "base/js/utils",
7 7 "jquery",
8 8 "bootstrap",
9 9 ], function(widget, utils, $){
10 10
11 11 var AccordionView = widget.DOMWidgetView.extend({
12 12 initialize: function(){
13 13 AccordionView.__super__.initialize.apply(this, arguments);
14 14
15 15 this.containers = [];
16 16 this.model_containers = {};
17 17 this.children_views = new widget.ViewList(this.add_child_model, this.remove_child_model, this);
18 18 this.listenTo(this.model, 'change:children', function(model, value) {
19 19 this.children_views.update(value);
20 20 }, this);
21 21 },
22 22
23 23 render: function(){
24 24 // Called when view is rendered.
25 25 var guid = 'panel-group' + utils.uuid();
26 26 this.$el
27 27 .attr('id', guid)
28 28 .addClass('panel-group');
29 29 this.model.on('change:selected_index', function(model, value, options) {
30 30 this.update_selected_index(model.previous('selected_index'), value, options);
31 31 }, this);
32 32 this.model.on('change:_titles', function(model, value, options) {
33 33 this.update_titles(value);
34 34 }, this);
35 35 var that = this;
36 36 this.on('displayed', function() {
37 37 this.update_titles();
38 38 }, this);
39 39 this.children_views.update(this.model.get('children'));
40 40 },
41 41
42 42 update_titles: function(titles) {
43 43 // Set tab titles
44 44 if (!titles) {
45 45 titles = this.model.get('_titles');
46 46 }
47 47
48 48 var that = this;
49 49 _.each(titles, function(title, page_index) {
50 50 var accordian = that.containers[page_index];
51 51 if (accordian !== undefined) {
52 52 accordian
53 53 .find('.panel-heading')
54 54 .find('.accordion-toggle')
55 55 .text(title);
56 56 }
57 57 });
58 58 },
59 59
60 60 update_selected_index: function(old_index, new_index, options) {
61 61 // Only update the selection if the selection wasn't triggered
62 62 // by the front-end. It must be triggered by the back-end.
63 63 if (options === undefined || options.updated_view != this) {
64 64 this.containers[old_index].find('.panel-collapse').collapse('hide');
65 65 if (0 <= new_index && new_index < this.containers.length) {
66 66 this.containers[new_index].find('.panel-collapse').collapse('show');
67 67 }
68 68 }
69 69 },
70 70
71 71 remove_child_model: function(model) {
72 72 // Called when a child is removed from children list.
73 73 var accordion_group = this.model_containers[model.id];
74 74 this.containers.splice(accordion_group.container_index, 1);
75 75 delete this.model_containers[model.id];
76 76 accordion_group.remove();
77 77 this.pop_child_view(model);
78 78 },
79 79
80 80 add_child_model: function(model) {
81 81 // Called when a child is added to children list.
82 82 var index = this.containers.length;
83 83 var uuid = utils.uuid();
84 84 var accordion_group = $('<div />')
85 85 .addClass('panel panel-default')
86 86 .appendTo(this.$el);
87 87 var accordion_heading = $('<div />')
88 88 .addClass('panel-heading')
89 89 .appendTo(accordion_group);
90 90 var that = this;
91 91 var accordion_toggle = $('<a />')
92 92 .addClass('accordion-toggle')
93 93 .attr('data-toggle', 'collapse')
94 94 .attr('data-parent', '#' + this.$el.attr('id'))
95 95 .attr('href', '#' + uuid)
96 96 .click(function(evt){
97 97
98 98 // Calling model.set will trigger all of the other views of the
99 99 // model to update.
100 100 that.model.set("selected_index", index, {updated_view: that});
101 101 that.touch();
102 102 })
103 103 .text('Page ' + index)
104 104 .appendTo(accordion_heading);
105 105 var accordion_body = $('<div />', {id: uuid})
106 106 .addClass('panel-collapse collapse')
107 107 .appendTo(accordion_group);
108 108 var accordion_inner = $('<div />')
109 109 .addClass('panel-body')
110 110 .appendTo(accordion_body);
111 111 var container_index = this.containers.push(accordion_group) - 1;
112 112 accordion_group.container_index = container_index;
113 113 this.model_containers[model.id] = accordion_group;
114 114
115 115 var dummy = $('<div/>');
116 116 accordion_inner.append(dummy);
117 117 return this.create_child_view(model).then(function(view) {
118 118 dummy.replaceWith(view.$el);
119 119 that.update();
120 120 that.update_titles();
121 121
122 122 // Trigger the displayed event of the child view.
123 123 that.after_displayed(function() {
124 124 view.trigger('displayed');
125 125 });
126 126 return view;
127 127 }, utils.reject("Couldn't add child view to box", true));
128 128 },
129 129
130 130 remove: function() {
131 // We remove this widget before removing the children as an optimization
132 // we want to remove the entire container from the DOM first before
133 // removing each individual child separately.
131 134 AccordionView.__super__.remove.apply(this, arguments);
132 135 this.children_views.remove();
133 136 },
134 137 });
135 138
136 139
137 140 var TabView = widget.DOMWidgetView.extend({
138 141 initialize: function() {
139 142 // Public constructor.
140 143 TabView.__super__.initialize.apply(this, arguments);
141 144
142 145 this.containers = [];
143 146 this.children_views = new widget.ViewList(this.add_child_model, this.remove_child_model, this);
144 147 this.listenTo(this.model, 'change:children', function(model, value) {
145 148 this.children_views.update(value);
146 149 }, this);
147 150 },
148 151
149 152 render: function(){
150 153 // Called when view is rendered.
151 154 var uuid = 'tabs'+utils.uuid();
152 155 var that = this;
153 156 this.$tabs = $('<div />', {id: uuid})
154 157 .addClass('nav')
155 158 .addClass('nav-tabs')
156 159 .appendTo(this.$el);
157 160 this.$tab_contents = $('<div />', {id: uuid + 'Content'})
158 161 .addClass('tab-content')
159 162 .appendTo(this.$el);
160 163 this.children_views.update(this.model.get('children'));
161 164 },
162 165
163 166 update_attr: function(name, value) {
164 167 // Set a css attr of the widget view.
165 168 this.$tabs.css(name, value);
166 169 },
167 170
168 171 remove_child_model: function(model) {
169 172 // Called when a child is removed from children list.
170 173 var view = this.pop_child_view(model);
171 174 this.containers.splice(view.parent_tab.tab_text_index, 1);
172 175 view.parent_tab.remove();
173 176 view.parent_container.remove();
174 177 view.remove();
175 178 },
176 179
177 180 add_child_model: function(model) {
178 181 // Called when a child is added to children list.
179 182 var index = this.containers.length;
180 183 var uuid = utils.uuid();
181 184
182 185 var that = this;
183 186 var tab = $('<li />')
184 187 .css('list-style-type', 'none')
185 188 .appendTo(this.$tabs);
186 189
187 190
188 191 var tab_text = $('<a />')
189 192 .attr('href', '#' + uuid)
190 193 .attr('data-toggle', 'tab')
191 194 .text('Page ' + index)
192 195 .appendTo(tab)
193 196 .click(function (e) {
194 197
195 198 // Calling model.set will trigger all of the other views of the
196 199 // model to update.
197 200 that.model.set("selected_index", index, {updated_view: that});
198 201 that.touch();
199 202 that.select_page(index);
200 203 });
201 204 tab.tab_text_index = that.containers.push(tab_text) - 1;
202 205
203 206 var dummy = $('<div />');
204 207 var contents_div = $('<div />', {id: uuid})
205 208 .addClass('tab-pane')
206 209 .addClass('fade')
207 210 .append(dummy)
208 211 .appendTo(that.$tab_contents);
209 212
210 213 return this.create_child_view(model).then(function(view) {
211 214 dummy.replaceWith(view.$el);
212 215 view.parent_tab = tab;
213 216 view.parent_container = contents_div;
214 217
215 218 // Trigger the displayed event of the child view.
216 219 that.after_displayed(function() {
217 220 view.trigger('displayed');
218 221 });
219 222 return view;
220 223 }, utils.reject("Couldn't add child view to box", true));
221 224 },
222 225
223 226 update: function(options) {
224 227 // Update the contents of this view
225 228 //
226 229 // Called when the model is changed. The model may have been
227 230 // changed by another view or by a state update from the back-end.
228 231 if (options === undefined || options.updated_view != this) {
229 232 // Set tab titles
230 233 var titles = this.model.get('_titles');
231 234 var that = this;
232 235 _.each(titles, function(title, page_index) {
233 236 var tab_text = that.containers[page_index];
234 237 if (tab_text !== undefined) {
235 238 tab_text.text(title);
236 239 }
237 240 });
238 241
239 242 var selected_index = this.model.get('selected_index');
240 243 if (0 <= selected_index && selected_index < this.containers.length) {
241 244 this.select_page(selected_index);
242 245 }
243 246 }
244 247 return TabView.__super__.update.apply(this);
245 248 },
246 249
247 250 select_page: function(index) {
248 251 // Select a page.
249 252 this.$tabs.find('li')
250 253 .removeClass('active');
251 254 this.containers[index].tab('show');
252 255 },
253 256
254 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.
255 261 TabView.__super__.remove.apply(this, arguments);
256 262 this.children_views.remove();
257 263 },
258 264 });
259 265
260 266 return {
261 267 'AccordionView': AccordionView,
262 268 'TabView': TabView,
263 269 };
264 270 });
General Comments 0
You need to be logged in to leave comments. Login now