##// END OF EJS Templates
Fix a bunch of bugs with the accordion and tab widgets
Jonathan Frederic -
Show More
@@ -1,301 +1,325
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_view, this.remove_child_view, 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 /**
25 25 * Called when view is rendered.
26 26 */
27 27 var guid = 'panel-group' + utils.uuid();
28 28 this.$el
29 29 .attr('id', guid)
30 30 .addClass('panel-group');
31 31 this.model.on('change:selected_index', function(model, value, options) {
32 this.update_selected_index(model.previous('selected_index'), value, options);
32 this.update_selected_index(options);
33 33 }, this);
34 34 this.model.on('change:_titles', function(model, value, options) {
35 this.update_titles(value);
35 this.update_titles(options);
36 36 }, this);
37 37 this.on('displayed', function() {
38 38 this.update_titles();
39 39 }, this);
40 40 this.children_views.update(this.model.get('children'));
41 41 },
42 42
43 update_titles: function(titles) {
43 /**
44 * Update the contents of this view
45 *
46 * Called when the model is changed. The model may have been
47 * changed by another view or by a state update from the back-end.
48 */
49 update: function(options) {
50 this.update_titles();
51 this.update_selected_index(options);
52 return TabView.__super__.update.apply(this);
53 },
54
55 update_titles: function() {
44 56 /**
45 57 * Set tab titles
46 58 */
47 if (!titles) {
48 titles = this.model.get('_titles');
49 }
50
59 var titles = this.model.get('_titles');
51 60 var that = this;
52 61 _.each(titles, function(title, page_index) {
53 62 var accordian = that.containers[page_index];
54 63 if (accordian !== undefined) {
55 64 accordian
56 65 .find('.panel-heading')
57 66 .find('.accordion-toggle')
58 67 .text(title);
59 68 }
60 69 });
61 70 },
62 71
63 update_selected_index: function(old_index, new_index, options) {
72 update_selected_index: function(options) {
64 73 /**
65 74 * Only update the selection if the selection wasn't triggered
66 75 * by the front-end. It must be triggered by the back-end.
67 76 */
68 77 if (options === undefined || options.updated_view != this) {
78 var old_index = this.model.previous('selected_index');
79 var new_index = this.model.get('selected_index');
69 80 this.containers[old_index].find('.panel-collapse').collapse('hide');
70 81 if (0 <= new_index && new_index < this.containers.length) {
71 82 this.containers[new_index].find('.panel-collapse').collapse('show');
72 83 }
73 84 }
74 85 },
75 86
76 87 remove_child_view: function(view) {
77 88 /**
78 89 * Called when a child is removed from children list.
79 90 * TODO: does this handle two different views of the same model as children?
80 91 */
81 92 var model = view.model;
82 93 var accordion_group = this.model_containers[model.id];
83 94 this.containers.splice(accordion_group.container_index, 1);
84 95 delete this.model_containers[model.id];
85 96 accordion_group.remove();
86 97 },
87 98
88 99 add_child_view: function(model) {
89 100 /**
90 101 * Called when a child is added to children list.
91 102 */
92 103 var index = this.containers.length;
93 104 var uuid = utils.uuid();
94 105 var accordion_group = $('<div />')
95 106 .addClass('panel panel-default')
96 107 .appendTo(this.$el);
97 108 var accordion_heading = $('<div />')
98 109 .addClass('panel-heading')
99 110 .appendTo(accordion_group);
100 111 var that = this;
101 112 var accordion_toggle = $('<a />')
102 113 .addClass('accordion-toggle')
103 114 .attr('data-toggle', 'collapse')
104 115 .attr('data-parent', '#' + this.$el.attr('id'))
105 116 .attr('href', '#' + uuid)
106 117 .click(function(evt){
107 118
108 119 // Calling model.set will trigger all of the other views of the
109 120 // model to update.
110 121 that.model.set("selected_index", index, {updated_view: that});
111 122 that.touch();
112 123 })
113 124 .text('Page ' + index)
114 125 .appendTo(accordion_heading);
115 126 var accordion_body = $('<div />', {id: uuid})
116 127 .addClass('panel-collapse collapse')
117 128 .appendTo(accordion_group);
118 129 var accordion_inner = $('<div />')
119 130 .addClass('panel-body')
120 131 .appendTo(accordion_body);
121 132 var container_index = this.containers.push(accordion_group) - 1;
122 133 accordion_group.container_index = container_index;
123 134 this.model_containers[model.id] = accordion_group;
124 135
125 136 var dummy = $('<div/>');
126 137 accordion_inner.append(dummy);
127 138 return this.create_child_view(model).then(function(view) {
128 139 dummy.replaceWith(view.$el);
129 140 that.update();
130 141 that.update_titles();
131 142
132 143 // Trigger the displayed event of the child view.
133 144 that.after_displayed(function() {
134 145 view.trigger('displayed');
135 146 });
136 147 return view;
137 148 }).catch(utils.reject("Couldn't add child view to box", true));
138 149 },
139 150
140 151 remove: function() {
141 152 /**
142 153 * We remove this widget before removing the children as an optimization
143 154 * we want to remove the entire container from the DOM first before
144 155 * removing each individual child separately.
145 156 */
146 157 AccordionView.__super__.remove.apply(this, arguments);
147 158 this.children_views.remove();
148 159 },
149 160 });
150 161
151 162
152 163 var TabView = widget.DOMWidgetView.extend({
153 164 initialize: function() {
154 165 /**
155 166 * Public constructor.
156 167 */
157 168 TabView.__super__.initialize.apply(this, arguments);
158 169
159 170 this.containers = [];
160 171 this.children_views = new widget.ViewList(this.add_child_view, this.remove_child_view, this);
161 172 this.listenTo(this.model, 'change:children', function(model, value) {
162 173 this.children_views.update(value);
163 174 }, this);
164 175 },
165 176
166 177 render: function(){
167 178 /**
168 179 * Called when view is rendered.
169 180 */
170 181 var uuid = 'tabs'+utils.uuid();
171 182 var that = this;
172 183 this.$tabs = $('<div />', {id: uuid})
173 184 .addClass('nav')
174 185 .addClass('nav-tabs')
175 186 .appendTo(this.$el);
176 187 this.$tab_contents = $('<div />', {id: uuid + 'Content'})
177 188 .addClass('tab-content')
178 189 .appendTo(this.$el);
179 190 this.children_views.update(this.model.get('children'));
180 191 },
181 192
182 193 update_attr: function(name, value) {
183 194 /**
184 195 * Set a css attr of the widget view.
185 196 */
186 197 if (name == 'padding' || name == 'margin') {
187 198 this.$el.css(name, value);
188 199 } else {
189 200 this.$tabs.css(name, value);
190 201 }
191 202 },
192 203
193 204 remove_child_view: function(view) {
194 205 /**
195 206 * Called when a child is removed from children list.
196 207 */
197 208 this.containers.splice(view.parent_tab.tab_text_index, 1);
198 209 view.parent_tab.remove();
199 210 view.parent_container.remove();
200 211 view.remove();
201 212 },
202 213
203 214 add_child_view: function(model) {
204 215 /**
205 216 * Called when a child is added to children list.
206 217 */
207 218 var index = this.containers.length;
208 219 var uuid = utils.uuid();
209 220
210 221 var that = this;
211 222 var tab = $('<li />')
212 223 .css('list-style-type', 'none')
213 224 .appendTo(this.$tabs);
214
215 225
216 226 var tab_text = $('<a />')
217 227 .attr('href', '#' + uuid)
218 228 .attr('data-toggle', 'tab')
219 229 .text('Page ' + index)
220 230 .appendTo(tab)
221 231 .click(function (e) {
222 232
223 233 // Calling model.set will trigger all of the other views of the
224 234 // model to update.
225 235 that.model.set("selected_index", index, {updated_view: that});
226 236 that.touch();
227 237 that.select_page(index);
228 238 });
229 239 tab.tab_text_index = that.containers.push(tab_text) - 1;
230 240
231 241 var dummy = $('<div />');
232 242 var contents_div = $('<div />', {id: uuid})
233 243 .addClass('tab-pane')
234 244 .addClass('fade')
235 245 .append(dummy)
236 246 .appendTo(that.$tab_contents);
237 247
248 this.update();
238 249 return this.create_child_view(model).then(function(view) {
239 250 dummy.replaceWith(view.$el);
240 251 view.parent_tab = tab;
241 252 view.parent_container = contents_div;
242 253
243 254 // Trigger the displayed event of the child view.
244 255 that.after_displayed(function() {
245 256 view.trigger('displayed');
257 that.update();
246 258 });
247 259 return view;
248 260 }).catch(utils.reject("Couldn't add child view to box", true));
249 261 },
250 262
251 263 update: function(options) {
252 264 /**
253 265 * Update the contents of this view
254 266 *
255 267 * Called when the model is changed. The model may have been
256 268 * changed by another view or by a state update from the back-end.
257 269 */
258 if (options === undefined || options.updated_view != this) {
259 // Set tab titles
260 var titles = this.model.get('_titles');
261 var that = this;
262 _.each(titles, function(title, page_index) {
263 var tab_text = that.containers[page_index];
264 if (tab_text !== undefined) {
265 tab_text.text(title);
266 }
267 });
270 this.update_titles();
271 this.update_selected_index(options);
272 return TabView.__super__.update.apply(this);
273 },
268 274
275 /**
276 * Updates the tab page titles.
277 */
278 update_titles: function() {
279 var titles = this.model.get('_titles');
280 var that = this;
281 _.each(titles, function(title, page_index) {
282 var tab_text = that.containers[page_index];
283 if (tab_text !== undefined) {
284 tab_text.text(title);
285 }
286 });
287 },
288
289 /**
290 * Updates the tab page titles.
291 */
292 update_selected_index: function(options) {
293 if (options === undefined || options.updated_view != this) {
269 294 var selected_index = this.model.get('selected_index');
270 295 if (0 <= selected_index && selected_index < this.containers.length) {
271 296 this.select_page(selected_index);
272 297 }
273 298 }
274 return TabView.__super__.update.apply(this);
275 299 },
276 300
277 301 select_page: function(index) {
278 302 /**
279 303 * Select a page.
280 304 */
281 305 this.$tabs.find('li')
282 306 .removeClass('active');
283 307 this.containers[index].tab('show');
284 308 },
285 309
286 310 remove: function() {
287 311 /**
288 312 * We remove this widget before removing the children as an optimization
289 313 * we want to remove the entire container from the DOM first before
290 314 * removing each individual child separately.
291 315 */
292 316 TabView.__super__.remove.apply(this, arguments);
293 317 this.children_views.remove();
294 318 },
295 319 });
296 320
297 321 return {
298 322 'AccordionView': AccordionView,
299 323 'TabView': TabView,
300 324 };
301 325 });
General Comments 0
You need to be logged in to leave comments. Login now