##// END OF EJS Templates
Merge pull request #8027 from bollwyvl/fix-select-multiple...
Min RK -
r20695:883fe6f3 merge
parent child Browse files
Show More
@@ -1,583 +1,592 b''
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 "underscore",
9 9 "bootstrap",
10 10 ], function(widget, utils, $, _){
11 11
12 12 var DropdownView = widget.DOMWidgetView.extend({
13 13 render : function(){
14 14 /**
15 15 * Called when view is rendered.
16 16 */
17 17 this.$el
18 18 .addClass('widget-hbox widget-dropdown');
19 19 this.$label = $('<div />')
20 20 .appendTo(this.$el)
21 21 .addClass('widget-label')
22 22 .hide();
23 23 this.$buttongroup = $('<div />')
24 24 .addClass('widget_item')
25 25 .addClass('btn-group')
26 26 .appendTo(this.$el);
27 27 this.$droplabel = $('<button />')
28 28 .addClass('btn btn-default')
29 29 .addClass('widget-combo-btn')
30 30 .html("&nbsp;")
31 31 .appendTo(this.$buttongroup);
32 32 this.$dropbutton = $('<button />')
33 33 .addClass('btn btn-default')
34 34 .addClass('dropdown-toggle')
35 35 .addClass('widget-combo-carrot-btn')
36 36 .attr('data-toggle', 'dropdown')
37 37 .append($('<span />').addClass("caret"))
38 38 .appendTo(this.$buttongroup);
39 39 this.$droplist = $('<ul />')
40 40 .addClass('dropdown-menu')
41 41 .appendTo(this.$buttongroup);
42 42
43 43 this.model.on('change:button_style', function(model, value) {
44 44 this.update_button_style();
45 45 }, this);
46 46 this.update_button_style('');
47 47
48 48 // Set defaults.
49 49 this.update();
50 50 },
51 51
52 52 update : function(options){
53 53 /**
54 54 * Update the contents of this view
55 55 *
56 56 * Called when the model is changed. The model may have been
57 57 * changed by another view or by a state update from the back-end.
58 58 */
59 59
60 60 if (options === undefined || options.updated_view != this) {
61 61 var selected_item_text = this.model.get('selected_label');
62 62 if (selected_item_text.trim().length === 0) {
63 63 this.$droplabel.html("&nbsp;");
64 64 } else {
65 65 this.$droplabel.text(selected_item_text);
66 66 }
67 67
68 68 var items = this.model.get('_options_labels');
69 69 var $replace_droplist = $('<ul />')
70 70 .addClass('dropdown-menu');
71 71 // Copy the style
72 72 $replace_droplist.attr('style', this.$droplist.attr('style'));
73 73 var that = this;
74 74 _.each(items, function(item, i) {
75 75 var item_button = $('<a href="#"/>')
76 76 .text(item)
77 77 .on('click', $.proxy(that.handle_click, that));
78 78 $replace_droplist.append($('<li />').append(item_button));
79 79 });
80 80
81 81 this.$droplist.replaceWith($replace_droplist);
82 82 this.$droplist.remove();
83 83 this.$droplist = $replace_droplist;
84 84
85 85 if (this.model.get('disabled')) {
86 86 this.$buttongroup.attr('disabled','disabled');
87 87 this.$droplabel.attr('disabled','disabled');
88 88 this.$dropbutton.attr('disabled','disabled');
89 89 this.$droplist.attr('disabled','disabled');
90 90 } else {
91 91 this.$buttongroup.removeAttr('disabled');
92 92 this.$droplabel.removeAttr('disabled');
93 93 this.$dropbutton.removeAttr('disabled');
94 94 this.$droplist.removeAttr('disabled');
95 95 }
96 96
97 97 var description = this.model.get('description');
98 98 if (description.length === 0) {
99 99 this.$label.hide();
100 100 } else {
101 101 this.typeset(this.$label, description);
102 102 this.$label.show();
103 103 }
104 104 }
105 105 return DropdownView.__super__.update.apply(this);
106 106 },
107 107
108 108 update_button_style: function(previous_trait_value) {
109 109 var class_map = {
110 110 primary: ['btn-primary'],
111 111 success: ['btn-success'],
112 112 info: ['btn-info'],
113 113 warning: ['btn-warning'],
114 114 danger: ['btn-danger']
115 115 };
116 116 this.update_mapped_classes(class_map, 'button_style', previous_trait_value, this.$droplabel);
117 117 this.update_mapped_classes(class_map, 'button_style', previous_trait_value, this.$dropbutton);
118 118 },
119 119
120 120 update_attr: function(name, value) {
121 121 /**
122 122 * Set a css attr of the widget view.
123 123 */
124 124 if (name.substring(0, 6) == 'border' || name == 'background' || name == 'color') {
125 125 this.$droplabel.css(name, value);
126 126 this.$dropbutton.css(name, value);
127 127 this.$droplist.css(name, value);
128 128 } else if (name == 'width') {
129 129 this.$droplist.css(name, value);
130 130 this.$droplabel.css(name, value);
131 131 } else if (name == 'padding') {
132 132 this.$droplist.css(name, value);
133 133 this.$buttongroup.css(name, value);
134 134 } else if (name == 'margin') {
135 135 this.$buttongroup.css(name, value);
136 136 } else if (name == 'height') {
137 137 this.$droplabel.css(name, value);
138 138 this.$dropbutton.css(name, value);
139 139 } else if (name == 'padding' || name == 'margin') {
140 140 this.$el.css(name, value);
141 141 } else {
142 142 this.$droplist.css(name, value);
143 143 this.$droplabel.css(name, value);
144 144 }
145 145 },
146 146
147 147 handle_click: function (e) {
148 148 /**
149 149 * Handle when a value is clicked.
150 150 *
151 151 * Calling model.set will trigger all of the other views of the
152 152 * model to update.
153 153 */
154 154 this.model.set('selected_label', $(e.target).text(), {updated_view: this});
155 155 this.touch();
156 156
157 157 // Manually hide the droplist.
158 158 e.stopPropagation();
159 159 e.preventDefault();
160 160 this.$buttongroup.removeClass('open');
161 161 },
162 162
163 163 });
164 164
165 165
166 166 var RadioButtonsView = widget.DOMWidgetView.extend({
167 167 render : function(){
168 168 /**
169 169 * Called when view is rendered.
170 170 */
171 171 this.$el
172 172 .addClass('widget-hbox widget-radio');
173 173 this.$label = $('<div />')
174 174 .appendTo(this.$el)
175 175 .addClass('widget-label')
176 176 .hide();
177 177 this.$container = $('<div />')
178 178 .appendTo(this.$el)
179 179 .addClass('widget-radio-box');
180 180 this.update();
181 181 },
182 182
183 183 update : function(options){
184 184 /**
185 185 * Update the contents of this view
186 186 *
187 187 * Called when the model is changed. The model may have been
188 188 * changed by another view or by a state update from the back-end.
189 189 */
190 190 if (options === undefined || options.updated_view != this) {
191 191 // Add missing items to the DOM.
192 192 var items = this.model.get('_options_labels');
193 193 var disabled = this.model.get('disabled');
194 194 var that = this;
195 195 _.each(items, function(item, index) {
196 196 var item_query = ' :input[data-value="' + encodeURIComponent(item) + '"]';
197 197 if (that.$el.find(item_query).length === 0) {
198 198 var $label = $('<label />')
199 199 .addClass('radio')
200 200 .text(item)
201 201 .appendTo(that.$container);
202 202
203 203 $('<input />')
204 204 .attr('type', 'radio')
205 205 .addClass(that.model)
206 206 .val(item)
207 207 .attr('data-value', encodeURIComponent(item))
208 208 .prependTo($label)
209 209 .on('click', $.proxy(that.handle_click, that));
210 210 }
211 211
212 212 var $item_element = that.$container.find(item_query);
213 213 if (that.model.get('selected_label') == item) {
214 214 $item_element.prop('checked', true);
215 215 } else {
216 216 $item_element.prop('checked', false);
217 217 }
218 218 $item_element.prop('disabled', disabled);
219 219 });
220 220
221 221 // Remove items that no longer exist.
222 222 this.$container.find('input').each(function(i, obj) {
223 223 var value = $(obj).val();
224 224 var found = false;
225 225 _.each(items, function(item, index) {
226 226 if (item == value) {
227 227 found = true;
228 228 return false;
229 229 }
230 230 });
231 231
232 232 if (!found) {
233 233 $(obj).parent().remove();
234 234 }
235 235 });
236 236
237 237 var description = this.model.get('description');
238 238 if (description.length === 0) {
239 239 this.$label.hide();
240 240 } else {
241 241 this.$label.text(description);
242 242 this.typeset(this.$label, description);
243 243 this.$label.show();
244 244 }
245 245 }
246 246 return RadioButtonsView.__super__.update.apply(this);
247 247 },
248 248
249 249 update_attr: function(name, value) {
250 250 /**
251 251 * Set a css attr of the widget view.
252 252 */
253 253 if (name == 'padding' || name == 'margin') {
254 254 this.$el.css(name, value);
255 255 } else {
256 256 this.$container.css(name, value);
257 257 }
258 258 },
259 259
260 260 handle_click: function (e) {
261 261 /**
262 262 * Handle when a value is clicked.
263 263 *
264 264 * Calling model.set will trigger all of the other views of the
265 265 * model to update.
266 266 */
267 267 this.model.set('selected_label', $(e.target).val(), {updated_view: this});
268 268 this.touch();
269 269 },
270 270 });
271 271
272 272
273 273 var ToggleButtonsView = widget.DOMWidgetView.extend({
274 274 initialize: function() {
275 275 this._css_state = {};
276 276 ToggleButtonsView.__super__.initialize.apply(this, arguments);
277 277 },
278 278
279 279 render: function() {
280 280 /**
281 281 * Called when view is rendered.
282 282 */
283 283 this.$el
284 284 .addClass('widget-hbox widget-toggle-buttons');
285 285 this.$label = $('<div />')
286 286 .appendTo(this.$el)
287 287 .addClass('widget-label')
288 288 .hide();
289 289 this.$buttongroup = $('<div />')
290 290 .addClass('btn-group')
291 291 .appendTo(this.$el);
292 292
293 293 this.model.on('change:button_style', function(model, value) {
294 294 this.update_button_style();
295 295 }, this);
296 296 this.update_button_style('');
297 297 this.update();
298 298 },
299 299
300 300 update : function(options){
301 301 /**
302 302 * Update the contents of this view
303 303 *
304 304 * Called when the model is changed. The model may have been
305 305 * changed by another view or by a state update from the back-end.
306 306 */
307 307 if (options === undefined || options.updated_view != this) {
308 308 // Add missing items to the DOM.
309 309 var items = this.model.get('_options_labels');
310 310 var icons = this.model.get('icons');
311 311 var previous_icons = this.model.previous('icons') || [];
312 312 var disabled = this.model.get('disabled');
313 313 var that = this;
314 314 var item_html;
315 315 _.each(items, function(item, index) {
316 316 if (item.trim().length === 0 && (!icons[index] ||
317 317 icons[index].trim().length === 0)) {
318 318 item_html = "&nbsp;";
319 319 } else {
320 320 item_html = utils.escape_html(item);
321 321 }
322 322 var item_query = '[data-value="' + encodeURIComponent(item) + '"]';
323 323 var $item_element = that.$buttongroup.find(item_query);
324 324 var $icon_element = $item_element.find('.fa');
325 325 if (!$item_element.length) {
326 326 $item_element = $('<button/>')
327 327 .attr('type', 'button')
328 328 .addClass('btn btn-default')
329 329 .html(item_html)
330 330 .appendTo(that.$buttongroup)
331 331 .attr('data-value', encodeURIComponent(item))
332 332 .attr('data-toggle', 'tooltip')
333 333 .attr('value', item)
334 334 .on('click', $.proxy(that.handle_click, that));
335 335 that.update_style_traits($item_element);
336 336 $icon_element = $('<i class="fa"></i>').prependTo($item_element);
337 337 }
338 338 if (that.model.get('selected_label') == item) {
339 339 $item_element.addClass('active');
340 340 } else {
341 341 $item_element.removeClass('active');
342 342 }
343 343 $item_element.prop('disabled', disabled);
344 344 $item_element.attr('title', that.model.get('tooltips')[index]);
345 345 $icon_element
346 346 .removeClass(previous_icons[index])
347 347 .addClass(icons[index]);
348 348 });
349 349
350 350 // Remove items that no longer exist.
351 351 this.$buttongroup.find('button').each(function(i, obj) {
352 352 var value = $(obj).attr('value');
353 353 var found = false;
354 354 _.each(items, function(item, index) {
355 355 if (item == value) {
356 356 found = true;
357 357 return false;
358 358 }
359 359 });
360 360
361 361 if (!found) {
362 362 $(obj).remove();
363 363 }
364 364 });
365 365
366 366 var description = this.model.get('description');
367 367 if (description.length === 0) {
368 368 this.$label.hide();
369 369 } else {
370 370 this.$label.text();
371 371 this.typeset(this.$label, description);
372 372 this.$label.show();
373 373 }
374 374 }
375 375 return ToggleButtonsView.__super__.update.apply(this);
376 376 },
377 377
378 378 update_attr: function(name, value) {
379 379 /**
380 380 * Set a css attr of the widget view.
381 381 */
382 382 if (name == 'padding' || name == 'margin') {
383 383 this.$el.css(name, value);
384 384 } else {
385 385 this._css_state[name] = value;
386 386 this.update_style_traits();
387 387 }
388 388 },
389 389
390 390 update_style_traits: function(button) {
391 391 for (var name in this._css_state) {
392 392 if (this._css_state.hasOwnProperty(name)) {
393 393 if (name == 'margin') {
394 394 this.$buttongroup.css(name, this._css_state[name]);
395 395 } else if (name != 'width') {
396 396 if (button) {
397 397 button.css(name, this._css_state[name]);
398 398 } else {
399 399 this.$buttongroup.find('button').css(name, this._css_state[name]);
400 400 }
401 401 }
402 402 }
403 403 }
404 404 },
405 405
406 406 update_button_style: function(previous_trait_value) {
407 407 var class_map = {
408 408 primary: ['btn-primary'],
409 409 success: ['btn-success'],
410 410 info: ['btn-info'],
411 411 warning: ['btn-warning'],
412 412 danger: ['btn-danger']
413 413 };
414 414 this.update_mapped_classes(class_map, 'button_style', previous_trait_value, this.$buttongroup.find('button'));
415 415 },
416 416
417 417 handle_click: function (e) {
418 418 /**
419 419 * Handle when a value is clicked.
420 420 *
421 421 * Calling model.set will trigger all of the other views of the
422 422 * model to update.
423 423 */
424 424 this.model.set('selected_label', $(e.target).attr('value'), {updated_view: this});
425 425 this.touch();
426 426 },
427 427 });
428 428
429 429
430 430 var SelectView = widget.DOMWidgetView.extend({
431 431 render : function(){
432 432 /**
433 433 * Called when view is rendered.
434 434 */
435 435 this.$el
436 436 .addClass('widget-hbox widget-select');
437 437 this.$label = $('<div />')
438 438 .appendTo(this.$el)
439 439 .addClass('widget-label')
440 440 .hide();
441 441 this.$listbox = $('<select />')
442 442 .addClass('widget-listbox form-control')
443 443 .attr('size', 6)
444 444 .appendTo(this.$el)
445 445 .on('change', $.proxy(this.handle_change, this));
446 446 this.update();
447 447 },
448 448
449 449 update : function(options){
450 450 /**
451 451 * Update the contents of this view
452 452 *
453 453 * Called when the model is changed. The model may have been
454 454 * changed by another view or by a state update from the back-end.
455 455 */
456 456 if (options === undefined || options.updated_view != this) {
457 457 // Add missing items to the DOM.
458 458 var items = this.model.get('_options_labels');
459 459 var that = this;
460 460 _.each(items, function(item, index) {
461 461 var item_query = 'option[data-value="' + encodeURIComponent(item) + '"]';
462 462 if (that.$listbox.find(item_query).length === 0) {
463 463 $('<option />')
464 464 .text(item)
465 465 .attr('data-value', encodeURIComponent(item))
466 466 .attr('selected_label', item)
467 467 .on("click", $.proxy(that.handle_click, that))
468 468 .appendTo(that.$listbox);
469 469 }
470 470 });
471 471
472 472 // Select the correct element
473 473 this.$listbox.val(this.model.get('selected_label'));
474 474
475 475 // Disable listbox if needed
476 476 var disabled = this.model.get('disabled');
477 477 this.$listbox.prop('disabled', disabled);
478 478
479 479 // Remove items that no longer exist.
480 480 this.$listbox.find('option').each(function(i, obj) {
481 481 var value = $(obj).text();
482 482 var found = false;
483 483 _.each(items, function(item, index) {
484 484 if (item == value) {
485 485 found = true;
486 486 return false;
487 487 }
488 488 });
489 489
490 490 if (!found) {
491 491 $(obj).remove();
492 492 }
493 493 });
494 494
495 495 var description = this.model.get('description');
496 496 if (description.length === 0) {
497 497 this.$label.hide();
498 498 } else {
499 499 this.typeset(this.$label, description);
500 500 this.$label.show();
501 501 }
502 502 }
503 503 return SelectView.__super__.update.apply(this);
504 504 },
505 505
506 506 update_attr: function(name, value) {
507 507 /**
508 508 * Set a css attr of the widget view.
509 509 */
510 510 if (name == 'padding' || name == 'margin') {
511 511 this.$el.css(name, value);
512 512 } else {
513 513 this.$listbox.css(name, value);
514 514 }
515 515 },
516 516
517 517 handle_click: function (e) {
518 518 /**
519 519 * Handle when a new value is clicked.
520 520 */
521 521 this.$listbox.val($(e.target).val()).change();
522 522 },
523 523
524 524 handle_change: function (e) {
525 525 /**
526 526 * Handle when a new value is selected.
527 527 *
528 528 * Calling model.set will trigger all of the other views of the
529 529 * model to update.
530 530 */
531 531 this.model.set('selected_label', this.$listbox.val(), {updated_view: this});
532 532 this.touch();
533 533 },
534 534 });
535 535
536 536
537 537 var SelectMultipleView = SelectView.extend({
538 538 render: function(){
539 539 /**
540 540 * Called when view is rendered.
541 541 */
542 542 SelectMultipleView.__super__.render.apply(this);
543 543 this.$el.removeClass('widget-select')
544 544 .addClass('widget-select-multiple');
545 545 this.$listbox.attr('multiple', true)
546 546 .on('change', $.proxy(this.handle_change, this));
547 547 return this;
548 548 },
549 549
550 550 update: function(){
551 551 /**
552 552 * Update the contents of this view
553 553 *
554 554 * Called when the model is changed. The model may have been
555 555 * changed by another view or by a state update from the back-end.
556 556 */
557 557 SelectMultipleView.__super__.update.apply(this, arguments);
558 558 this.$listbox.val(this.model.get('selected_labels'));
559 559 },
560 560
561 handle_click: function(){
562 /**
563 * Overload click from select
564 *
565 * Apparently it's needed from there for testing purposes,
566 * but breaks behavior of this.
567 */
568 },
569
561 570 handle_change: function (e) {
562 571 /**
563 572 * Handle when a new value is selected.
564 573 *
565 574 * Calling model.set will trigger all of the other views of the
566 575 * model to update.
567 576 */
568 577 this.model.set('selected_labels',
569 578 (this.$listbox.val() || []).slice(),
570 579 {updated_view: this});
571 580 this.touch();
572 581 },
573 582 });
574 583
575 584
576 585 return {
577 586 'DropdownView': DropdownView,
578 587 'RadioButtonsView': RadioButtonsView,
579 588 'ToggleButtonsView': ToggleButtonsView,
580 589 'SelectView': SelectView,
581 590 'SelectMultipleView': SelectMultipleView,
582 591 };
583 592 });
General Comments 0
You need to be logged in to leave comments. Login now