1// Copyright (c) 2011 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5cr.define('cr.ui', function() { 6 /** 7 * Creates a new button element. The repeating button behaves like a 8 * keyboard button, which auto-repeats if held. This button is designed 9 * for use with controls such as brightness and volume adjustment buttons. 10 * @constructor 11 * @extends {HTMLButtonElement} 12 */ 13 var RepeatingButton = cr.ui.define('button'); 14 15 /** 16 * DOM Events that may be fired by the Repeating button. 17 */ 18 RepeatingButton.Event = { 19 BUTTON_HELD: 'buttonHeld' 20 }; 21 22 RepeatingButton.prototype = { 23 __proto__: HTMLButtonElement.prototype, 24 25 /** 26 * Delay in milliseconds before the first repeat trigger of the button 27 * held action. 28 * @type {number} 29 * @private 30 */ 31 holdDelayTime_: 500, 32 33 /** 34 * Delay in milliseconds between triggers of the button held action. 35 * @type {number} 36 * @private 37 */ 38 holdRepeatIntervalTime_: 50, 39 40 /** 41 * Callback function ID when repeated intervals trigger. Initialized when 42 * the button is held for an initial delay period and cleared when the 43 * button is released. 44 * @type {number|undefined} 45 * @private 46 */ 47 intervalCallbackId_: undefined, 48 49 /** 50 * Callback function to arm the repeat timer. Initialized when the button 51 * is pressed and cleared when the interval timer is set or the button is 52 * released. 53 * @type {number|undefined} 54 * @private 55 */ 56 armRepeaterCallbackId_: undefined, 57 58 /** 59 * Initializes the button. 60 */ 61 decorate: function() { 62 this.addEventListener('mousedown', this.buttonDown_.bind(this)); 63 this.addEventListener('mouseup', this.buttonUp_.bind(this)); 64 this.addEventListener('mouseout', this.buttonUp_.bind(this)); 65 this.addEventListener('touchstart', this.touchStart_.bind(this)); 66 this.addEventListener('touchend', this.buttonUp_.bind(this)); 67 this.addEventListener('touchcancel', this.buttonUp_.bind(this)); 68 }, 69 70 /** 71 * Called when the user initiates a touch gesture. 72 * @param {!Event} e The triggered event. 73 * @private 74 */ 75 touchStart_: function(e) { 76 // Block system level gestures to prevent double tap to zoom. Also, 77 // block following mouse event to prevent double firing of the button 78 // held action in the case of a tap. Otherwise, a single tap action in 79 // webkit generates the following event sequence: touchstart, touchend, 80 // mouseover, mousemove, mousedown, mouseup and click. 81 e.preventDefault(); 82 this.buttonDown_(e); 83 }, 84 85 /** 86 * Called when the user presses this button. 87 * @param {!Event} e The triggered event. 88 * @private 89 */ 90 buttonDown_: function(e) { 91 this.clearTimeout_(); 92 // Trigger the button held action immediately, after an initial delay and 93 // then repeated based on a fixed time increment. The time intervals are 94 // in agreement with the defaults for the ChromeOS keyboard and virtual 95 // keyboard. 96 // TODO(kevers): Consider adding a common location for picking up the 97 // initial delay and repeat interval. 98 this.buttonHeld_(); 99 var self = this; 100 var armRepeaterCallback = function() { 101 // In the event of a click/tap operation, this button has already been 102 // released by the time this timeout triggers. Test to ensure that the 103 // button is still being held (i.e. clearTimeout has not been called). 104 if (typeof self.armRepeaterCallbackId_ != 'undefined') { 105 self.armRepeaterCallbackId_ = undefined; 106 self.buttonHeld_(); 107 self.intervalCallbackId_ = setInterval(self.buttonHeld_.bind(self), 108 self.holdRepeatIntervalTime_); 109 } 110 }; 111 this.armRepeaterCallbackId_ = setTimeout(armRepeaterCallback, 112 this.holdDelayTime_); 113 }, 114 115 /** 116 * Called when the user releases this button. 117 * @param {!Event} e The triggered event. 118 * @private 119 */ 120 buttonUp_: function(e) { 121 this.clearTimeout_(); 122 }, 123 124 /** 125 * Resets the interval callback. 126 * @private 127 */ 128 clearTimeout_: function() { 129 if (typeof this.armRepeaterCallbackId_ != 'undefined') { 130 clearTimeout(this.armRepeaterCallbackId_); 131 this.armRepeaterCallbackId_ = undefined; 132 } 133 if (typeof this.intervalCallbackId_ != 'undefined') { 134 clearInterval(this.intervalCallbackId_); 135 this.intervalCallbackId_ = undefined; 136 } 137 }, 138 139 /** 140 * Dispatches the action associated with keeping this button pressed. 141 * @private 142 */ 143 buttonHeld_: function() { 144 cr.dispatchSimpleEvent(this, RepeatingButton.Event.BUTTON_HELD); 145 }, 146 147 /** 148 * Getter for the initial delay before repeating. 149 * @type {number} The delay in milliseconds. 150 */ 151 get repeatDelay() { 152 return this.holdDelayTime_; 153 }, 154 155 /** 156 * Setter for the initial delay before repeating. 157 * @type {number} The delay in milliseconds. 158 */ 159 set repeatDelay(delay) { 160 this.holdDelayTime_ = delay; 161 }, 162 163 /** 164 * Getter for the repeat interval. 165 * @type {number} The repeat interval in milliseconds. 166 */ 167 get repeatInterval() { 168 return this.holdRepeatIntervalTime_; 169 }, 170 171 /** 172 * Setter for the repeat interval. 173 * @type {number} The interval in milliseconds. 174 */ 175 set repeatInterval(delay) { 176 this.holdRepeatIntervalTime_ = delay; 177 } 178 }; 179 180 return { 181 RepeatingButton: RepeatingButton 182 }; 183}); 184 185