1 // ========================================================================== 2 // Project: SproutCore - JavaScript Application Framework 3 // Copyright: ©2006-2011 Strobe Inc. and contributors. 4 // Portions ©2008-2011 Apple Inc. All rights reserved. 5 // License: Licensed under MIT license (see license.js) 6 // ========================================================================== 7 8 /** 9 @static 10 @constant 11 @type String 12 */ 13 SC.TOGGLE_BEHAVIOR = 'toggle'; 14 15 /** 16 @static 17 @constant 18 @type String 19 */ 20 SC.PUSH_BEHAVIOR = 'push'; 21 22 /** 23 @static 24 @constant 25 @type String 26 */ 27 SC.TOGGLE_ON_BEHAVIOR = 'on'; 28 29 /** 30 @static 31 @constant 32 @type String 33 */ 34 SC.TOGGLE_OFF_BEHAVIOR = 'off'; 35 36 /** 37 @static 38 @constant 39 @type String 40 */ 41 SC.HOLD_BEHAVIOR = 'hold'; 42 43 /** @class 44 45 Implements a push-button-style button. This class is used to implement 46 both standard push buttons and tab-style controls. See also SC.CheckboxView 47 and SC.RadioView which are implemented as field views, but can also be 48 treated as buttons. 49 50 By default, a button uses the SC.Control mixin which will apply CSS 51 classnames when the state of the button changes: 52 53 - `active` -- when button is active 54 - `sel` -- when button is toggled to a selected state 55 56 @extends SC.View 57 @extends SC.Control 58 @since SproutCore 1.0 59 */ 60 SC.ButtonView = SC.View.extend(SC.ActionSupport, SC.Control, 61 /** @scope SC.ButtonView.prototype */ { 62 63 /** 64 Tied to the isEnabledInPane state 65 66 @type Boolean 67 @default YES 68 */ 69 acceptsFirstResponder: function() { 70 if (SC.FOCUS_ALL_CONTROLS) { return this.get('isEnabledInPane'); } 71 return NO; 72 }.property('isEnabledInPane'), 73 74 /** 75 The name of the method to call when the button is pressed. 76 77 This property is used in conjunction with the `target` property to execute a method when a 78 regular button is pressed. If you do not set a target, then pressing the button will cause a 79 search of the responder chain for a view that implements the action named. If you do set a 80 target, then the button will only try to call the method on that target. 81 82 The action method of the target should implement the following signature: 83 84 action: function (sender) { 85 // Return value is ignored by SC.ButtonView. 86 } 87 88 Therefore, if a target needs to know which button called its action, it should look to the 89 `sender` argument. 90 91 *NOTE:* This property is not relevant when the button is used in toggle mode. Toggle mode only 92 modifies the `value` of the button without triggering actions. 93 94 @type String 95 @default null 96 @see SC.ActionSupport 97 */ 98 action: null, 99 100 /** 101 @type Array 102 @default ['sc-button-view'] 103 @see SC.View#classNames 104 */ 105 classNames: ['sc-button-view'], 106 107 /** 108 Whether the title and toolTip will be escaped to avoid HTML injection attacks 109 or not. 110 111 You should only disable this option if you are sure you are displaying 112 non-user generated text. 113 114 Note: this is not an observed display property. If you change it after 115 rendering, you should call `displayDidChange` on the view to update the layer. 116 117 @type Boolean 118 @default true 119 */ 120 escapeHTML: true, 121 122 /** 123 The target to invoke the action on when the button is pressed. 124 125 If you set this target, the action will be called on the target object directly when the button 126 is clicked. If you leave this property set to `null`, then the responder chain will be 127 searched for a view that implements the action when the button is pressed. 128 129 The action method of the target should implement the following signature: 130 131 action: function (sender) { 132 // Return value is ignored by SC.ButtonView. 133 } 134 135 Therefore, if a target needs to know which button called its action, it should look to the 136 `sender` argument. 137 138 *NOTE:* This property is not relevant when the button is used in toggle mode. Toggle mode only 139 modifies the `value` of the button without triggering actions. 140 141 @type Object 142 @default null 143 @see SC.ActionSupport 144 */ 145 target: null, 146 147 /** 148 The theme to apply to the button. By default, a subtheme with the name of 149 'square' is created for backwards-compatibility. 150 151 @type String 152 @default 'square' 153 */ 154 themeName: 'square', 155 156 157 // .......................................................... 158 // Value Handling 159 // 160 161 /** 162 Used to automatically update the state of the button view for toggle style 163 buttons. 164 165 For toggle style buttons, you can set the value and it will be used to 166 update the isSelected state of the button view. The value will also 167 change as the user selects or deselects. You can control which values 168 the button will treat as `isSelected` by setting the `toggleOnValue` and 169 `toggleOffValue`. Alternatively, if you leave these properties set to 170 `YES` or `NO`, the button will do its best to convert a value to an 171 appropriate state: 172 173 - `null`, `false`, `0` -- `isSelected = false` 174 - any other single value -- `isSelected = true` 175 - array -- if all values are the same state, that state; otherwise `MIXED`. 176 177 @type Object 178 @default null 179 */ 180 value: null, 181 182 /** 183 Value of a selected toggle button. 184 185 For a toggle button, set this to any object value you want. The button 186 will be selected if the value property equals the targetValue. If the 187 value is an array of multiple items that contains the targetValue, then 188 the button will be set to a mixed state. 189 190 default is YES 191 192 @type Boolean|Object 193 @default YES 194 */ 195 toggleOnValue: YES, 196 197 /** 198 Value of an unselected toggle button. 199 200 For a toggle button, set this to any object value you want. When the 201 user toggle's the button off, the value of the button will be set to this 202 value. 203 204 @type Boolean|Object 205 @default NO 206 */ 207 toggleOffValue: NO, 208 209 210 // .......................................................... 211 // Title Handling 212 // 213 214 /** 215 If YES, then the title will be localized. 216 217 @type Boolean 218 @default NO 219 */ 220 localize: NO, 221 222 /** @private */ 223 localizeBindingDefault: SC.Binding.bool(), 224 225 /** 226 The button title. If localize is `YES`, then this should be the 227 localization key to display. Otherwise, this will be the actual string 228 displayed in the title. This property is observable and bindable. 229 230 @type String 231 @default "" 232 */ 233 title: "", 234 235 /** 236 If set, the title property will be updated automatically 237 from the content using the key you specify. 238 239 @type String 240 @default null 241 */ 242 contentTitleKey: null, 243 244 /** 245 The button icon. Set this to either a URL or a CSS class name (for 246 spriting). Note that if you pass a URL, it must contain at 247 least one slash to be detected as such. 248 249 @type String 250 @default null 251 */ 252 icon: null, 253 254 /** 255 If you set this property, the icon will be updated automatically from the 256 content using the key you specify. 257 258 @type String 259 @default null 260 */ 261 contentIconKey: null, 262 263 /** 264 If YES, button will attempt to display an ellipsis if the title cannot 265 fit inside of the visible area. This feature is not available on all 266 browsers. 267 268 Note: this is not an observed display property. If you change it after 269 rendering, you should call `displayDidChange` on the view to update the layer. 270 271 @type Boolean 272 @default YES 273 */ 274 needsEllipsis: YES, 275 276 /** 277 This is generated by localizing the title property if necessary. 278 279 @type String 280 @observes 'title' 281 @observes 'localize' 282 */ 283 displayTitle: function() { 284 var ret = this.get('title'); 285 return (ret && this.get('localize')) ? SC.String.loc(ret) : (ret || ''); 286 }.property('title','localize').cacheable(), 287 288 /** 289 The key equivalent that should trigger this button on the page. 290 291 @type String 292 @default null 293 */ 294 keyEquivalent: null, 295 296 297 // .......................................................... 298 // BEHAVIOR 299 // 300 301 /** 302 The behavioral mode of this button. 303 304 Possible values are: 305 306 - `SC.PUSH_BEHAVIOR` -- Pressing the button will trigger an action tied to the 307 button. Does not change the value of the button. 308 - `SC.TOGGLE_BEHAVIOR` -- Pressing the button will invert the current value of 309 the button. If the button has a mixed value, it will be set to true. 310 - `SC.TOGGLE_ON_BEHAVIOR` -- Pressing the button will set the current state to 311 true no matter the previous value. 312 - `SC.TOGGLE_OFF_BEHAVIOR` -- Pressing the button will set the current state to 313 false no matter the previous value. 314 - `SC.HOLD_BEHAVIOR` -- Pressing the button will cause the action to repeat at a 315 regular interval specified by 'holdInterval' 316 317 @type String 318 @default SC.PUSH_BEHAVIOR 319 */ 320 buttonBehavior: SC.PUSH_BEHAVIOR, 321 322 /* 323 If buttonBehavior is `SC.HOLD_BEHAVIOR`, this specifies, in milliseconds, 324 how often to trigger the action. Ignored for other behaviors. 325 326 @type Number 327 @default 100 328 */ 329 holdInterval: 100, 330 331 /** 332 If YES, then this button will be triggered when you hit return. 333 334 This is the same as setting the `keyEquivalent` to 'return'. This will also 335 apply the "def" classname to the button. 336 337 @type Boolean 338 @default NO 339 */ 340 isDefault: NO, 341 isDefaultBindingDefault: SC.Binding.oneWay().bool(), 342 343 /** 344 If YES, then this button will be triggered when you hit escape. 345 This is the same as setting the keyEquivalent to 'escape'. 346 347 @type Boolean 348 @default NO 349 */ 350 isCancel: NO, 351 isCancelBindingDefault: SC.Binding.oneWay().bool(), 352 353 /* 354 TODO When is this property ever changed? Is this redundant with 355 render delegates since it can now be turned on on a theme-by-theme 356 basis? --TD 357 */ 358 /** 359 If YES, use a focus ring. 360 361 @type Boolean 362 @default NO 363 */ 364 supportFocusRing: NO, 365 366 // .......................................................... 367 // Auto Resize Support 368 // 369 // 370 // These properties are provided so that SC.AutoResize can be mixed in 371 // to enable automatic resizing of the button. 372 // 373 374 /** @private */ 375 supportsAutoResize: YES, 376 377 /* 378 TODO get this from the render delegate so other elements may be used. 379 */ 380 /** @private */ 381 autoResizeLayer: function() { 382 var ret = this.invokeRenderDelegateMethod('getRenderedAutoResizeLayer', this.$()); 383 return ret || this.get('layer'); 384 }.property('layer').cacheable(), 385 386 /** @private */ 387 autoResizeText: function() { 388 return this.get('displayTitle'); 389 }.property('displayTitle').cacheable(), 390 391 /** 392 The padding to add to the measured size of the text to arrive at the measured 393 size for the view. 394 395 `SC.ButtonView` gets this from its render delegate, but if not supplied, defaults 396 to 10. 397 398 @default 10 399 @type Number 400 */ 401 autoResizePadding: SC.propertyFromRenderDelegate('autoResizePadding', 10), 402 403 404 // TODO: What the hell is this? --TD 405 _labelMinWidthIE7: 0, 406 407 /** 408 Called when the user presses a shortcut key, such as return or cancel, 409 associated with this button. 410 411 Highlights the button to show that it is being triggered, then, after a 412 delay, performs the button's action. 413 414 Does nothing if the button is disabled. 415 416 @param {Event} evt 417 @returns {Boolean} YES if successful, NO otherwise 418 */ 419 triggerActionAfterDelay: function(evt) { 420 // If this button is disabled, we have nothing to do 421 if (!this.get('isEnabledInPane')) return NO; 422 423 // Set active state of the button so it appears highlighted 424 this.set('isActive', YES); 425 426 // Invoke the actual action method after a small delay to give the user a 427 // chance to see the highlight. This is especially important if the button 428 // closes a pane, for example. 429 this.invokeLater('triggerAction', SC.ButtonView.TRIGGER_DELAY, evt); 430 return YES; 431 }, 432 433 /** @private 434 Called by triggerActionAfterDelay; this method actually 435 performs the action and restores the button's state. 436 437 @param {Event} evt 438 */ 439 triggerAction: function(evt) { 440 this._action(evt, YES); 441 this.didTriggerAction(); 442 this.set('isActive', NO); 443 }, 444 445 /** 446 Callback called anytime the button's action is triggered. You can 447 implement this method in your own subclass to perform any cleanup needed 448 after an action is performed. 449 */ 450 didTriggerAction: function() {}, 451 452 453 // ................................................................ 454 // INTERNAL SUPPORT 455 // 456 457 /** @private - save keyEquivalent for later use */ 458 init: function() { 459 sc_super(); 460 461 var keyEquivalent = this.get('keyEquivalent'); 462 // Cache the key equivalent. The key equivalent is saved so that if, 463 // for example, isDefault is changed from YES to NO, the old key 464 // equivalent can be restored. 465 if (keyEquivalent) { 466 this._defaultKeyEquivalent = keyEquivalent; 467 } 468 469 // if value is not null, update isSelected to match value. If value is 470 // null, we assume you may be using isSelected only. 471 if (!SC.none(this.get('value'))) this._button_valueDidChange(); 472 }, 473 474 /** 475 The WAI-ARIA role of the button. 476 477 @type String 478 @default 'button' 479 @readOnly 480 */ 481 ariaRole: 'button', 482 483 /** 484 The following properties affect how `SC.ButtonView` is rendered, and will 485 cause the view to be rerendered if they change. 486 487 Note: 'value', 'isDefault', 'isCancel' are also display properties, but are 488 observed separately. 489 490 @type Array 491 @default ['icon', 'displayTitle', 'displayToolTip', 'supportFocusRing', 'buttonBehavior'] 492 */ 493 displayProperties: ['icon', 'displayTitle', 'displayToolTip', 'supportFocusRing', 'buttonBehavior'], 494 495 /** 496 The name of the render delegate in the theme that should be used to 497 render the button. 498 499 In this case, the 'button' property will be retrieved from the theme and 500 set to the render delegate of this view. 501 502 @type String 503 @default 'buttonRenderDelegate' 504 */ 505 renderDelegateName: 'buttonRenderDelegate', 506 507 contentKeys: { 508 'contentValueKey': 'value', 509 'contentTitleKey': 'title', 510 'contentIconKey': 'icon' 511 }, 512 513 /** 514 Handle a key equivalent if set. Trigger the default action for the 515 button. Depending on the implementation this may vary. 516 517 @param {String} keystring 518 @param {SC.Event} evt 519 @returns {Boolean} YES if handled, NO otherwise 520 */ 521 performKeyEquivalent: function(keystring, evt) { 522 //If this is not visible 523 if (!this.get('isVisibleInWindow')) return NO; 524 525 if (!this.get('isEnabledInPane')) return NO; 526 var equiv = this.get('keyEquivalent'); 527 528 // button has defined a keyEquivalent and it matches! 529 // if triggering succeeded, true will be returned and the operation will 530 // be handled (i.e performKeyEquivalent will cease crawling the view 531 // tree) 532 if (equiv) { 533 if (equiv === keystring) return this.triggerAction(evt); 534 535 // should fire if isDefault OR isCancel. This way if isDefault AND 536 // isCancel, responds to both return and escape 537 } else if ((this.get('isDefault') && (keystring === 'return')) || 538 (this.get('isCancel') && (keystring === 'escape'))) { 539 return this.triggerAction(evt); 540 } 541 542 return NO; // did not handle it; keep searching 543 }, 544 545 // .......................................................... 546 // VALUE <-> isSelected STATE MANAGEMENT 547 // 548 549 /** 550 This is the standard logic to compute a proposed isSelected state for a 551 new value. This takes into account the `toggleOnValue`/`toggleOffValue` 552 properties, among other things. It may return `YES`, `NO`, or 553 `SC.MIXED_STATE`. 554 555 @param {Object} value 556 @returns {Boolean} return state 557 */ 558 computeIsSelectedForValue: function(value) { 559 var targetValue = this.get('toggleOnValue'), state, next ; 560 561 if (SC.typeOf(value) === SC.T_ARRAY) { 562 563 // treat a single item array like a single value 564 if (value.length === 1) { 565 state = (value[0] == targetValue) ; 566 567 // for a multiple item array, check the states of all items. 568 } else { 569 state = null; 570 value.find(function(x) { 571 next = (x == targetValue) ; 572 if (state === null) { 573 state = next ; 574 } else if (next !== state) state = SC.MIXED_STATE ; 575 return state === SC.MIXED_STATE ; // stop when we hit a mixed state. 576 }); 577 } 578 579 // for single values, just compare to the toggleOnValue...use truthiness 580 } else { 581 if(value === SC.MIXED_STATE) state = SC.MIXED_STATE; 582 else state = (value === targetValue) ; 583 } 584 return state ; 585 }, 586 587 /** @private 588 Whenever the button value changes, update the selected state to match. 589 */ 590 _button_valueDidChange: function() { 591 var value = this.get('value'), 592 state = this.computeIsSelectedForValue(value); 593 this.set('isSelected', state) ; // set new state... 594 595 // value acts as a display property 596 this.displayDidChange(); 597 }.observes('value'), 598 599 /** @private 600 Whenever the selected state is changed, make sure the button value is 601 also updated. Note that this may be called because the value has just 602 changed. In that case this should do nothing. 603 */ 604 _button_isSelectedDidChange: function() { 605 var newState = this.get('isSelected'), 606 curState = this.computeIsSelectedForValue(this.get('value')); 607 608 // fix up the value, but only if computed state does not match. 609 // never fix up value if isSelected is set to MIXED_STATE since this can 610 // only come from the value. 611 if ((newState !== SC.MIXED_STATE) && (curState !== newState)) { 612 var valueKey = (newState) ? 'toggleOnValue' : 'toggleOffValue' ; 613 this.set('value', this.get(valueKey)); 614 } 615 }.observes('isSelected'), 616 617 618 /** @private 619 Used to store the keyboard equivalent. 620 621 Setting the isDefault property to YES, for example, will cause the 622 `keyEquivalent` property to 'return'. This cached value is used to restore 623 the `keyEquivalent` property if isDefault is set back to NO. 624 625 @type String 626 */ 627 _defaultKeyEquivalent: null, 628 629 /** @private 630 631 Whenever the isDefault or isCancel property changes, re-render and change 632 the keyEquivalent property so that we respond to the return or escape key. 633 */ 634 _isDefaultOrCancelDidChange: function() { 635 var isDefault = !!this.get('isDefault'), 636 isCancel = !isDefault && this.get('isCancel') ; 637 638 if (isDefault) { 639 this.set('keyEquivalent', 'return'); // change the key equivalent 640 } else if (isCancel) { 641 this.set('keyEquivalent', 'escape') ; 642 } else { 643 // Restore the default key equivalent 644 this.set('keyEquivalent', this._defaultKeyEquivalent); 645 } 646 647 // isDefault and isCancel act as display properties 648 this.displayDidChange(); 649 }.observes('isDefault', 'isCancel'), 650 651 /** @private 652 On mouse down, set active only if enabled. 653 */ 654 mouseDown: function(evt) { 655 // Fast path, reject secondary clicks. 656 if (evt.which !== 1) return false; 657 658 var buttonBehavior = this.get('buttonBehavior'); 659 660 if (!this.get('isEnabledInPane')) return YES ; // handled event, but do nothing 661 this.set('isActive', YES); 662 this._isMouseDown = YES; 663 664 if (buttonBehavior === SC.HOLD_BEHAVIOR) { 665 this._action(evt); 666 } else if (!this._isFocused && (buttonBehavior!==SC.PUSH_BEHAVIOR)) { 667 this._isFocused = YES ; 668 this.becomeFirstResponder(); 669 } 670 671 return YES; 672 }, 673 674 /** @private 675 Remove the active class on mouseExited if mouse is down. 676 */ 677 mouseExited: function(evt) { 678 if (this._isMouseDown) { 679 this.set('isActive', NO); 680 } 681 return YES; 682 }, 683 684 /** @private 685 If mouse was down and we renter the button area, set the active state again. 686 */ 687 mouseEntered: function(evt) { 688 if (this._isMouseDown) { 689 this.set('isActive', YES); 690 } 691 return YES; 692 }, 693 694 /** @private 695 ON mouse up, trigger the action only if we are enabled and the mouse was released inside of the view. 696 */ 697 mouseUp: function(evt) { 698 if (this._isMouseDown) this.set('isActive', NO); // track independently in case isEnabledInPane has changed 699 this._isMouseDown = false; 700 701 if (this.get('buttonBehavior') !== SC.HOLD_BEHAVIOR) { 702 var inside = this.$().within(evt.target); 703 if (inside && this.get('isEnabledInPane')) this._action(evt) ; 704 } 705 706 return YES ; 707 }, 708 709 /** @private */ 710 touchStart: function(touch){ 711 var buttonBehavior = this.get('buttonBehavior'); 712 713 if (!this.get('isEnabledInPane')) return YES ; // handled event, but do nothing 714 this.set('isActive', YES); 715 716 if (buttonBehavior === SC.HOLD_BEHAVIOR) { 717 this._action(touch); 718 } else if (!this._isFocused && (buttonBehavior!==SC.PUSH_BEHAVIOR)) { 719 this._isFocused = YES ; 720 this.becomeFirstResponder(); 721 } 722 723 // don't want to do whatever default is... 724 touch.preventDefault(); 725 726 return YES; 727 }, 728 729 /** @private */ 730 touchesDragged: function(evt, touches) { 731 if (!this.touchIsInBoundary(evt)) { 732 if (!this._touch_exited) this.set('isActive', NO); 733 this._touch_exited = YES; 734 } else { 735 if (this._touch_exited) this.set('isActive', YES); 736 this._touch_exited = NO; 737 } 738 739 evt.preventDefault(); 740 return YES; 741 }, 742 743 /** @private */ 744 touchEnd: function(touch){ 745 this._touch_exited = NO; 746 this.set('isActive', NO); // track independently in case isEnabledInPane has changed 747 748 if (this.get('buttonBehavior') !== SC.HOLD_BEHAVIOR) { 749 if (this.touchIsInBoundary(touch) && this.get('isEnabledInPane')) { 750 this._action(); 751 } 752 } 753 754 touch.preventDefault(); 755 return YES ; 756 }, 757 758 /** @private */ 759 keyDown: function(evt) { 760 // handle tab key 761 if(!this.get('isEnabledInPane')) return YES; 762 if (evt.which === 9 || evt.keyCode === 9) { 763 var view = evt.shiftKey ? this.get('previousValidKeyView') : this.get('nextValidKeyView'); 764 if(view) view.becomeFirstResponder(); 765 else evt.allowDefault(); 766 return YES ; // handled 767 } 768 if (evt.which === 13 || evt.which === 32) { 769 this.triggerActionAfterDelay(evt); 770 return YES ; // handled 771 } 772 773 // let other keys through to browser 774 evt.allowDefault(); 775 776 return NO; 777 }, 778 779 /** @private 780 Perform an action based on the behavior of the button. 781 782 - toggle behavior: switch to on/off state 783 - on behavior: turn on. 784 - off behavior: turn off. 785 - otherwise: invoke target/action 786 */ 787 _action: function(evt, skipHoldRepeat) { 788 switch(this.get('buttonBehavior')) { 789 790 // When toggling, try to invert like values. i.e. 1 => 0, etc. 791 case SC.TOGGLE_BEHAVIOR: 792 var sel = this.get('isSelected') ; 793 if (sel) { 794 this.set('value', this.get('toggleOffValue')) ; 795 } else { 796 this.set('value', this.get('toggleOnValue')) ; 797 } 798 break ; 799 800 // set value to on. change 0 => 1. 801 case SC.TOGGLE_ON_BEHAVIOR: 802 this.set('value', this.get('toggleOnValue')) ; 803 break ; 804 805 // set the value to false. change 1 => 0 806 case SC.TOGGLE_OFF_BEHAVIOR: 807 this.set('value', this.get('toggleOffValue')) ; 808 break ; 809 810 case SC.HOLD_BEHAVIOR: 811 this._runHoldAction(evt, skipHoldRepeat); 812 break ; 813 814 // otherwise, just trigger an action if there is one. 815 default: 816 //if (this.action) this.action(evt); 817 this._runAction(evt); 818 } 819 }, 820 821 /** @private */ 822 _runAction: function(evt) { 823 var action = this.get('action'); 824 825 if (action) { 826 // Legacy support for action functions. 827 if (action && (SC.typeOf(action) === SC.T_FUNCTION)) { 828 this.action(evt); 829 830 // Use SC.ActionSupport. 831 } else { 832 this.fireAction(); 833 } 834 } 835 }, 836 837 /** @private */ 838 _runHoldAction: function(evt, skipRepeat) { 839 if (this.get('isActive')) { 840 this._runAction(); 841 842 if (!skipRepeat) { 843 // This run loop appears to only be necessary for testing 844 SC.RunLoop.begin(); 845 this.invokeLater('_runHoldAction', this.get('holdInterval'), evt); 846 SC.RunLoop.end(); 847 } 848 } 849 }, 850 851 852 /** @private */ 853 didBecomeKeyResponderFrom: function(keyView) { 854 // focus the text field. 855 if (!this._isFocused) { 856 this._isFocused = YES ; 857 this.becomeFirstResponder(); 858 if (this.get('isVisibleInWindow')) { 859 this.$().focus(); 860 } 861 } 862 }, 863 864 /** @private */ 865 willLoseKeyResponderTo: function(responder) { 866 if (this._isFocused) this._isFocused = NO ; 867 }, 868 869 /** @private */ 870 didAppendToDocument: function() { 871 if(SC.browser.isIE && 872 SC.browser.compare(SC.browser.version, '7') === 0 && 873 this.get('useStaticLayout')){ 874 var layout = this.get('layout'), 875 elem = this.$(), w=0; 876 if(elem && elem[0] && (w=elem[0].clientWidth) && w!==0 && this._labelMinWidthIE7===0){ 877 var label = this.$('.sc-button-label'), 878 paddingRight = parseInt(label.css('paddingRight'),0), 879 paddingLeft = parseInt(label.css('paddingLeft'),0), 880 marginRight = parseInt(label.css('marginRight'),0), 881 marginLeft = parseInt(label.css('marginLeft'),0); 882 if(marginRight=='auto') SC.Logger.log(marginRight+","+marginLeft+","+paddingRight+","+paddingLeft); 883 if(!paddingRight && isNaN(paddingRight)) paddingRight = 0; 884 if(!paddingLeft && isNaN(paddingLeft)) paddingLeft = 0; 885 if(!marginRight && isNaN(marginRight)) marginRight = 0; 886 if(!marginLeft && isNaN(marginLeft)) marginLeft = 0; 887 888 this._labelMinWidthIE7 = w-(paddingRight + paddingLeft)-(marginRight + marginLeft); 889 label.css('minWidth', this._labelMinWidthIE7+'px'); 890 }else{ 891 this.invokeLater(this.didAppendToDocument, 1); 892 } 893 } 894 } 895 896 }) ; 897 898 /** 899 How long to wait before triggering the action. 900 901 @constant 902 @type {Number} 903 */ 904 SC.ButtonView.TRIGGER_DELAY = 200; 905 906 /** 907 The delay after which "click" behavior should transition to "click and hold" 908 behavior. This is used by subclasses such as PopupButtonView and 909 SelectButtonView. 910 911 @constant 912 @type Number 913 */ 914 SC.ButtonView.CLICK_AND_HOLD_DELAY = SC.browser.isIE ? 600 : 300; 915 916 SC.REGULAR_BUTTON_HEIGHT=24; 917 918 919