Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * @fileoverview This implements a combobutton control. | 6 * @fileoverview This implements a combobutton control. |
| 7 */ | 7 */ |
| 8 | 8 |
| 9 cr.define('cr.ui', function() { | 9 cr.define('cr.ui', function() { |
| 10 /** | 10 /** |
| 11 * Sets minWidth for the target, so it's visually as large as source. | |
| 12 * @param {HTMLElement} target Element which min-width to tune. | |
| 13 * @param {HTMLElement} source Element, which width to use. | |
| 14 */ | |
| 15 function enlarge(target, source) { | |
| 16 var cs = target.ownerDocument.defaultView.getComputedStyle(target); | |
| 17 target.style.minWidth = (source.getBoundingClientRect().width - | |
| 18 parseFloat(cs.borderLeftWidth) - | |
| 19 parseFloat(cs.borderRightWidth)) + 'px'; | |
| 20 } | |
| 21 | |
| 22 /** | |
| 23 * Creates a new combobutton element. | 11 * Creates a new combobutton element. |
| 24 * @param {Object=} opt_propertyBag Optional properties. | 12 * @param {Object=} opt_propertyBag Optional properties. |
| 25 * @constructor | 13 * @constructor |
| 26 * @extends {HTMLUListElement} | 14 * @extends {HTMLUListElement} |
| 27 */ | 15 */ |
| 28 var ComboButton = cr.ui.define('div'); | 16 var ComboButton = cr.ui.define(cr.ui.MenuButton); |
| 17 | |
| 29 | 18 |
| 30 ComboButton.prototype = { | 19 ComboButton.prototype = { |
| 31 __proto__: HTMLDivElement.prototype, | 20 __proto__: cr.ui.MenuButton.prototype, |
| 21 | |
| 22 defaultItem_: null, | |
| 32 | 23 |
| 33 /** | 24 /** |
| 34 * The items list. | 25 * Truncates drop-down list. |
| 35 */ | 26 */ |
| 36 items_: null, | |
| 37 | |
| 38 clear: function() { | 27 clear: function() { |
| 39 if (this.items_.length > 0) | 28 this.menu.clear(); |
| 40 // Remove default combobox item if we have added it at addItem. | |
| 41 this.removeChild(this.firstChild); | |
| 42 | |
| 43 this.items_ = []; | |
| 44 this.popup_.textContent = ''; | |
| 45 this.multiple = false; | 29 this.multiple = false; |
| 46 this.popup_.style.minWidth = ''; | |
| 47 }, | 30 }, |
| 48 | 31 |
| 49 addItem: function(item) { | 32 addItem: function(item) { |
|
dgozman
2012/05/18 13:15:14
This method does not add the initially shown item
Dmitry Zvorygin
2012/05/21 10:51:01
Done.
| |
| 50 this.items_.push(item); | 33 this.multiple = true; |
| 51 if (this.items_.length == 1) { | 34 this.menu.addMenuItem(item).data = item; |
| 52 // Set first added item as default on combobox. | |
| 53 // First item should be the first element to prepend drop-down arrow and | |
| 54 // popup layer. | |
| 55 this.insertBefore(item, this.firstChild); | |
| 56 } else { | |
| 57 this.multiple = true; | |
| 58 if (this.popup_.hasChildNodes()) | |
| 59 this.popup_.insertBefore(item, this.popup_.firstChild); | |
| 60 else | |
| 61 this.popup_.appendChild(item); | |
| 62 if (this.visible) | |
| 63 this.setPopupSize_(); | |
| 64 } | |
| 65 }, | 35 }, |
| 66 | 36 |
| 67 setPopupSize_: function() { | 37 /** |
| 68 this.popup_.style.bottom = (this.clientHeight + 1) + 'px'; | 38 * Default item to fire on combobox click |
| 69 enlarge(this.popup_, this); | 39 */ |
| 40 get defaultItem() { | |
| 41 return this.defaultItem_; | |
| 42 }, | |
| 43 set defaultItem(defaultItem) { | |
| 44 this.defaultItem_ = defaultItem; | |
| 45 if (defaultItem.label) { | |
| 46 this.labelNode_.textContent = defaultItem.label; | |
| 47 } else { | |
| 48 this.labelNode_.textContent = ''; | |
| 49 } | |
| 50 | |
| 51 if (defaultItem.iconUrl) { | |
| 52 this.iconNode_.src = defaultItem.iconUrl; | |
| 53 } else { | |
| 54 this.iconNode_.src = ''; | |
| 55 } | |
| 70 }, | 56 }, |
| 71 | 57 |
| 72 /** | 58 /** |
| 73 * Initializes the element. | 59 * Initializes the element. |
| 74 */ | 60 */ |
| 75 decorate: function() { | 61 decorate: function() { |
| 76 this.items_ = []; | 62 cr.ui.MenuButton.prototype.decorate.call(this); |
| 77 | 63 |
| 78 this.classList.add('combobutton'); | 64 this.classList.add('combobutton'); |
| 79 | 65 |
| 66 this.iconNode_ = this.ownerDocument.createElement('img'); | |
| 67 this.appendChild(this.iconNode_); | |
| 68 | |
| 69 this.labelNode_ = this.ownerDocument.createElement('span'); | |
| 70 this.appendChild(this.labelNode_); | |
| 71 | |
| 80 var triggerIcon = this.ownerDocument.createElement('span'); | 72 var triggerIcon = this.ownerDocument.createElement('span'); |
| 81 triggerIcon.className = 'disclosureindicator'; | 73 triggerIcon.className = 'disclosureindicator'; |
| 82 this.trigger_ = this.ownerDocument.createElement('div'); | 74 this.trigger_ = this.ownerDocument.createElement('div'); |
| 83 this.trigger_.appendChild(triggerIcon); | 75 this.trigger_.appendChild(triggerIcon); |
| 84 | 76 |
| 85 this.popup_ = this.ownerDocument.createElement('div'); | 77 this.appendChild(this.trigger_); |
| 86 this.popup_.className = 'popup'; | |
| 87 | 78 |
| 88 this.appendChild(this.trigger_); | 79 this.addEventListener('click', this.handleButtonClick_.bind(this)); |
| 89 this.appendChild(this.popup_); | |
| 90 | 80 |
| 91 this.addEventListener('click', | |
| 92 this.handleButtonClick_.bind(this)); | |
| 93 this.popup_.addEventListener('click', | |
| 94 this.handlePopupClick_.bind(this)); | |
| 95 this.trigger_.addEventListener('click', | 81 this.trigger_.addEventListener('click', |
| 96 this.handleTriggerClicked_.bind(this)); | 82 this.handleTriggerClicked_.bind(this)); |
| 97 this.addEventListener('mouseout', this.handleMouseOut_.bind(this)); | |
| 98 | 83 |
| 99 this.visible = true; | 84 this.menu.addEventListener('activate', |
| 85 this.handleMenuActivate_.bind(this)); | |
| 86 | |
| 87 // Remove mousedown event listener created by MenuButton::decorate, | |
|
dgozman
2012/05/18 13:15:14
I don't like this dependency on internals of MenuB
Dmitry Zvorygin
2012/05/21 10:51:01
I don't like this complex logic too, but if we mak
| |
| 88 // and move it down to trigger_. | |
| 89 this.removeEventListener('mousedown', this); | |
| 90 this.trigger_.addEventListener('mousedown', this); | |
| 91 }, | |
| 92 | |
| 93 /** | |
| 94 * Handles the keydown event for the menu button. | |
| 95 */ | |
| 96 handleKeyDown: function(e) { | |
| 97 switch (e.keyIdentifier) { | |
| 98 case 'Down': | |
| 99 case 'Up': | |
| 100 if (!this.isMenuShown()) | |
| 101 this.showMenu(); | |
| 102 e.preventDefault(); | |
| 103 break; | |
| 104 case 'Esc': | |
| 105 case 'U+001B': // Maybe this is remote desktop playing a prank? | |
| 106 this.hideMenu(); | |
| 107 break; | |
| 108 } | |
| 100 }, | 109 }, |
| 101 | 110 |
| 102 handleTriggerClicked_: function(event) { | 111 handleTriggerClicked_: function(event) { |
| 103 this.open = !this.open; | |
| 104 event.stopPropagation(); | 112 event.stopPropagation(); |
| 105 }, | 113 }, |
| 106 | 114 |
| 107 handleMouseOut_: function(event) { | 115 handleMenuActivate_: function(event) { |
| 108 var x = event.x; | 116 this.dispatchSelectEvent(event.target.data); |
| 109 var y = event.y; | |
| 110 | |
| 111 var children = this.childNodes; | |
| 112 for (var i = 0; i < children.length; i++) | |
| 113 { | |
| 114 var r = this.children[i].getBoundingClientRect(); | |
| 115 if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) | |
| 116 return; | |
| 117 } | |
| 118 | |
| 119 this.open = false; | |
| 120 }, | 117 }, |
| 121 | 118 |
| 122 handleButtonClick_: function(event) { | 119 handleButtonClick_: function() { |
| 123 this.dispatchSelectEvent(this.items_[0]); | 120 this.dispatchSelectEvent(this.defaultItem_); |
| 124 }, | |
| 125 | |
| 126 handlePopupClick_: function(event) { | |
| 127 var item = event.target; | |
| 128 while (item && item.parentNode != this.popup_) | |
| 129 item = item.parentNode; | |
| 130 if (!item) | |
| 131 return; | |
| 132 | |
| 133 this.open = false; | |
| 134 this.dispatchSelectEvent(item); | |
| 135 event.stopPropagation(); | |
| 136 }, | 121 }, |
| 137 | 122 |
| 138 dispatchSelectEvent: function(item) { | 123 dispatchSelectEvent: function(item) { |
| 139 var selectEvent = new Event('select'); | 124 var selectEvent = new Event('select'); |
| 140 selectEvent.item = item; | 125 selectEvent.item = item; |
| 141 this.dispatchEvent(selectEvent); | 126 this.dispatchEvent(selectEvent); |
| 142 }, | |
| 143 | |
| 144 get visible() { | |
| 145 return this.hasAttribute('visible'); | |
| 146 }, | |
| 147 set visible(value) { | |
| 148 if (value) { | |
| 149 this.setAttribute('visible', 'visible'); | |
| 150 setTimeout(this.setPopupSize_.bind(this), 0); | |
| 151 } else { | |
| 152 this.removeAttribute('visible'); | |
| 153 } | |
| 154 } | 127 } |
| 155 }; | 128 }; |
| 156 | 129 |
| 157 cr.defineProperty(ComboButton, 'disabled', cr.PropertyKind.BOOL_ATTR); | 130 cr.defineProperty(ComboButton, 'disabled', cr.PropertyKind.BOOL_ATTR); |
| 158 cr.defineProperty(ComboButton, 'open', cr.PropertyKind.BOOL_ATTR); | |
| 159 cr.defineProperty(ComboButton, 'multiple', cr.PropertyKind.BOOL_ATTR); | 131 cr.defineProperty(ComboButton, 'multiple', cr.PropertyKind.BOOL_ATTR); |
| 160 | 132 |
| 133 cr.defineProperty(ComboButton, 'label', cr.PropertyKind.ATTR); | |
|
dgozman
2012/05/18 13:15:14
Why do you need this?
Dmitry Zvorygin
2012/05/21 10:51:01
Removed.
Just some stuff from research has been le
| |
| 134 cr.defineProperty(ComboButton, 'iconUrl', cr.PropertyKind.ATTR); | |
| 135 | |
| 161 return { | 136 return { |
| 162 ComboButton: ComboButton | 137 ComboButton: ComboButton |
| 163 }; | 138 }; |
| 164 }); | 139 }); |
| OLD | NEW |