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 sc_require('views/button'); 9 10 /** 11 @class 12 13 SelectView has a functionality similar to that of SelectField 14 15 Clicking the SelectView button displays a menu pane with a 16 list of items. The selected item will be displayed on the button. 17 User has the option of enabling checkbox for the selected menu item. 18 19 @extends SC.ButtonView 20 @version 1.0 21 @author Alex Iskander, Mohammed Ashik 22 */ 23 SC.SelectView = SC.ButtonView.extend( 24 /** @scope SC.SelectView.prototype */ { 25 26 /** 27 @field 28 @type Boolean 29 @default YES 30 */ 31 acceptsFirstResponder: function () { 32 return this.get('isEnabledInPane'); 33 }.property('isEnabledInPane'), 34 35 /** 36 If true, titles will be escaped to avoid scripting attacks. 37 38 @type Boolean 39 @default YES 40 */ 41 escapeHTML: YES, 42 escapeHTMLBindingDefault: SC.Binding.oneWay().bool(), 43 44 /** 45 An array of items that will be form the menu you want to show. 46 47 @type Array 48 @default [] 49 */ 50 items: [], 51 52 /** @private */ 53 itemsBindingDefault: SC.Binding.multiple(), 54 55 /** 56 If you set this to a non-null value, then the name shown for each 57 menu item will be pulled from the object using the named property. 58 if this is null, the collection items themselves will be used. 59 60 @type String 61 @default null 62 */ 63 itemTitleKey: null, 64 65 /** 66 If you set this to a non-null value, then the value of this key will 67 be used to sort the items. If this is not set, then itemTitleKey will 68 be used. 69 70 @type String 71 @default null 72 */ 73 itemSortKey: null, 74 75 /** 76 Set this to a non-null value to use a key from the passed set of items 77 as the value for the options popup. If you don't set this, then the 78 items themselves will be used as the value. 79 80 @type String 81 @default null 82 */ 83 itemValueKey: null, 84 85 /** 86 Key used to extract icons from the items array 87 88 @type String 89 @default null 90 */ 91 itemIconKey: null, 92 93 /** 94 Key to use to identify separators. 95 96 @type String 97 @default "isSeparator" 98 */ 99 itemSeparatorKey: "isSeparator", 100 101 /** 102 Key used to indicate if the item is to be enabled. 103 104 @type String 105 @default "isEnabled" 106 */ 107 itemIsEnabledKey: "isEnabled", 108 109 /** 110 @type Boolean 111 @default YES 112 */ 113 localize: YES, 114 115 /** 116 if true, it means that no sorting will occur, items will appear 117 in the same order as in the array 118 119 @type Boolean 120 @default YES 121 */ 122 disableSort: YES, 123 124 /** 125 @type Array 126 @default ['sc-select-button'] 127 @see SC.View#classNames 128 */ 129 classNames: ['sc-select-view'], 130 131 /** 132 Menu attached to the SelectView. 133 134 @type SC.View 135 @default SC.MenuView 136 */ 137 menu: null, 138 139 /** 140 List of actual menu items, handed off to the menu view. 141 142 @property 143 @private 144 @type:{Array} 145 */ 146 _itemList: [], 147 148 /** 149 Property to set the index of the selected menu item. This in turn 150 is used to calculate the preferMatrix. 151 152 @property 153 @type Number 154 @default null 155 @private 156 */ 157 _itemIdx: null, 158 159 /** 160 Current Value of the SelectView 161 162 @type Object 163 @default null 164 */ 165 value: null, 166 167 /** 168 if this property is set to 'YES', a checkbox is shown next to the 169 selected menu item. 170 171 @type Boolean 172 @default YES 173 */ 174 checkboxEnabled: YES, 175 176 /** 177 if this property is set to 'YES', a checkbox is shown next to the 178 selected menu item. 179 180 @type Boolean 181 @default YES 182 */ 183 showCheckbox: YES, 184 185 /** 186 Set this to non-null to place an empty option at the top of the menu. 187 188 @property 189 @type String 190 @default null 191 */ 192 emptyName: null, 193 194 /** 195 Default value of the select button. 196 This will be the first item from the menu item list. 197 198 @private 199 */ 200 _defaultVal: null, 201 202 /** 203 Default title of the select button. 204 This will be the title corresponding to the _defaultVal. 205 206 @private 207 */ 208 _defaultTitle: null, 209 210 /** 211 Default icon of the select button. 212 This will be the icon corresponding to the _defaultVal. 213 214 @private 215 */ 216 _defaultIcon: null, 217 218 /** 219 The button theme will be popup 220 221 @type String 222 @default 'popup' 223 @readOnly 224 */ 225 theme: 'popup', 226 227 /** 228 @type SC.Array 229 @default ['icon', 'value','controlSize'] 230 @see SC.View#displayProperties 231 */ 232 displayProperties: ['icon', 'value','controlSize', 'escapeHTML', 'emptyName'], 233 234 /** 235 Prefer matrix to position the select button menu such that the 236 selected item for the menu item will appear aligned to the 237 the button. The value at the second index(0) changes based on the 238 position(index) of the menu item in the menu pane. 239 240 @type Array 241 @default null 242 */ 243 preferMatrix: null, 244 245 /** 246 Property to set the menu item height. This in turn is used for 247 the calculation of prefMatrix. 248 249 @type Number 250 @default 20 251 */ 252 CUSTOM_MENU_ITEM_HEIGHT: 20, 253 254 /** 255 Binds the button's selection state to the menu's visibility. 256 257 @private 258 */ 259 isActiveBinding: '*menu.isVisibleInWindow', 260 261 /** 262 If this property is set to 'YES', the menu pane will be positioned 263 below the anchor. 264 265 @type Boolean 266 @default NO 267 */ 268 isDefaultPosition: NO, 269 270 /** 271 lastMenuWidth is the width of the last menu which was created from 272 the items of this select button. 273 274 @private 275 */ 276 lastMenuWidth: null, 277 278 /** 279 Example view used for menu items. 280 281 @type SC.View 282 @default null 283 */ 284 exampleView: null, 285 286 /** 287 customView menu offset width 288 289 @type Number 290 @default 0 291 */ 292 customViewMenuOffsetWidth: 0, 293 294 /** 295 This is a property for enabling/disabling ellipsis 296 297 @type Boolean 298 @default YES 299 */ 300 needsEllipsis: YES, 301 302 /** 303 This property allows you at add extra padding to the height 304 of the menu pane. 305 306 @type Number 307 @default 0 308 */ 309 menuPaneHeightPadding: 0, 310 311 /** 312 The amount of space to add to the calculated width of the menu item strings to 313 determine the width of the menu pane. 314 315 @type Number 316 @default 35 317 */ 318 menuItemPadding: 35, 319 320 /** 321 @type Boolean 322 @default NO 323 */ 324 isContextMenuEnabled: NO, 325 326 /** 327 This is a property to enable/disable focus rings in buttons. 328 For select_button, we are making it a default. 329 330 @type Boolean 331 @default YES 332 @see SC.ButtonView#supportFocusRing 333 */ 334 supportFocusRing: YES, 335 336 /** 337 * @private 338 * Overwritten to calculate the content corresponding to items configured at creation 339 */ 340 init: function() { 341 sc_super(); 342 343 this.itemsDidChange(); 344 }, 345 346 /** 347 Left Alignment based on the size of the button 348 349 @private 350 */ 351 leftAlign: function () { 352 switch (this.get('controlSize')) { 353 case SC.TINY_CONTROL_SIZE: 354 return SC.SelectView.TINY_OFFSET_X; 355 case SC.SMALL_CONTROL_SIZE: 356 return SC.SelectView.SMALL_OFFSET_X; 357 case SC.REGULAR_CONTROL_SIZE: 358 return SC.SelectView.REGULAR_OFFSET_X; 359 case SC.LARGE_CONTROL_SIZE: 360 return SC.SelectView.LARGE_OFFSET_X; 361 case SC.HUGE_CONTROL_SIZE: 362 return SC.SelectView.HUGE_OFFSET_X; 363 } 364 return 0; 365 }.property('controlSize'), 366 367 /** 368 override this method to implement your own sorting of the menu. By 369 default, menu items are sorted using the value shown or the sortKey 370 371 @param {SC.Array} objects the unsorted array of objects to display. 372 @returns {SC.Array} sorted array of objects 373 */ 374 sortObjects: function (objects) { 375 if (!this.get('disableSort')){ 376 var nameKey = this.get('itemSortKey') || this.get('itemTitleKey'); 377 objects = objects.sort(function (a,b) { 378 if (nameKey) { 379 a = a.get ? a.get(nameKey) : a[nameKey]; 380 b = b.get ? b.get(nameKey) : b[nameKey]; 381 } 382 return (a<b) ? -1 : ((a>b) ? 1 : 0); 383 }); 384 } 385 return objects; 386 }, 387 388 /** 389 Observer called whenever the items collection or an element of this collection changes 390 391 @private 392 */ 393 itemsDidChange: function() { 394 var escapeHTML, items, len, nameKey, iconKey, valueKey, separatorKey, showCheckbox, 395 currentSelectedVal, shouldLocalize, isSeparator, itemList, isChecked, 396 idx, name, icon, value, item, itemEnabled, isEnabledKey, emptyName, isSameRecord; 397 398 items = this.get('items') || []; 399 items = this.sortObjects(items); 400 len = items.length; 401 402 //Get the nameKey, iconKey and valueKey set by the user 403 nameKey = this.get('itemTitleKey'); 404 iconKey = this.get('itemIconKey'); 405 valueKey = this.get('itemValueKey'); 406 separatorKey = this.get('itemSeparatorKey'); 407 showCheckbox = this.get('showCheckbox'); 408 isEnabledKey = this.get('itemIsEnabledKey'); 409 escapeHTML = this.get('escapeHTML'); 410 411 //get the current selected value 412 currentSelectedVal = this.get('value'); 413 414 // get the localization flag. 415 shouldLocalize = this.get('localize'); 416 417 //itemList array to set the menu items 418 itemList = []; 419 420 //to set the 'checkbox' property of menu items 421 isChecked = YES; 422 423 //index for finding the first item in the list 424 idx = 0; 425 426 // Add the empty name to the list if applicable 427 emptyName = this.get('emptyName'); 428 429 if (!SC.none(emptyName)) { 430 emptyName = shouldLocalize ? SC.String.loc(emptyName) : emptyName; 431 emptyName = escapeHTML ? SC.RenderContext.escapeHTML(emptyName) : emptyName; 432 433 item = SC.Object.create({ 434 isSeparator: NO, 435 title: emptyName, 436 icon: null, 437 value: null, 438 isEnabled: YES, 439 checkbox: NO, 440 target: this, 441 action: 'displaySelectedItem' 442 }); 443 444 if (SC.none(currentSelectedVal)) { 445 this.set('title', emptyName); 446 } 447 448 //Set the items in the itemList array 449 itemList.push(item); 450 } 451 452 items.forEach(function (object) { 453 if (object || object === 0) { 454 //@if (debug) 455 // TODO: Remove in 1.11 and 2.0 456 // Help the developer if they were relying on the previously misleading 457 // default value of itemSeparatorKey. We need to ensure that the change 458 // is backwards compatible with apps prior to 1.10. 459 if (separatorKey === 'isSeparator') { 460 if ((object.get && SC.none(object.get('isSeparator')) && object.get('separator')) || (SC.none(object.isSeparator) && object.separator)) { 461 SC.warn("Developer Warning: the default value of itemSeparatorKey has been changed from 'separator' to 'isSeparator' to match the documentation. Please update your select item properties to 'isSeparator: YES' to remove this warning."); 462 if (object.set) { object.set('isSeparator', object.get('separator')); } 463 else { object.isSeparator = object.separator; } 464 } 465 } 466 //@endif 467 468 // get the separator 469 isSeparator = separatorKey ? (object.get ? object.get(separatorKey) : object[separatorKey]) : NO; 470 471 if (isSeparator) { 472 item = SC.Object.create({ 473 isSeparator: true, 474 value: '_sc_separator_item' 475 }); 476 } else { 477 //Get the name value. If value key is not specified convert obj 478 //to string 479 name = nameKey ? (object.get ? 480 object.get(nameKey) : object[nameKey]) : object.toString(); 481 482 //@if (debug) 483 // Help the developer if they don't define a matching itemTitleKey. 484 if (!name) { 485 SC.warn("Developer Warning: SC.SelectView: Every item, other than separator items, should have the '%@' property defined!".fmt(nameKey)); 486 name = ''; 487 } 488 //@endif 489 // localize name if specified. 490 name = shouldLocalize ? SC.String.loc(name) : name; 491 name = escapeHTML ? SC.RenderContext.escapeHTML(name) : name; 492 493 //Get the icon value 494 icon = iconKey ? (object.get ? 495 object.get(iconKey) : object[iconKey]) : null; 496 if (SC.none(object[iconKey])) icon = null; 497 498 // get the value using the valueKey or the object 499 value = valueKey ? (object.get ? 500 object.get(valueKey) : object[valueKey]) : object; 501 502 if (!SC.none(currentSelectedVal) && !SC.none(value)) { 503 504 // If the objects in question are records, we should just their storeKeys 505 isSameRecord = false; 506 if (SC.kindOf(currentSelectedVal, SC.Record) && SC.kindOf(value, SC.Record)) { 507 isSameRecord = currentSelectedVal.get('storeKey') === value.get('storeKey'); 508 } 509 510 if (currentSelectedVal === value || isSameRecord) { 511 this.set('title', name); 512 this.set('icon', icon); 513 } 514 } 515 516 //Check if the item is currentSelectedItem or not 517 if (value === this.get('value')) { 518 519 //set the _itemIdx - To change the prefMatrix accordingly. 520 this.set('_itemIdx', idx); 521 isChecked = !showCheckbox ? NO : YES; 522 } else { 523 isChecked = NO; 524 } 525 526 // Check if the item is enabled 527 itemEnabled = (object.get ? object.get(isEnabledKey) : object[isEnabledKey]); 528 if (NO !== itemEnabled) itemEnabled = YES; 529 530 // Set the first non-separator selectable item from the list as the 531 // default selected item 532 if (SC.none(this._defaultVal) && itemEnabled) { 533 this._defaultVal = value; 534 this._defaultTitle = name; 535 this._defaultIcon = icon; 536 } 537 538 item = SC.Object.create({ 539 action: 'displaySelectedItem', 540 title: name, 541 icon: icon, 542 value: value, 543 isEnabled: itemEnabled, 544 checkbox: isChecked, 545 target: this 546 }); 547 } 548 549 //Set the items in the itemList array 550 itemList.push(item); 551 552 } 553 554 idx += 1; 555 556 }, this ); 557 558 this.set('_itemList', itemList); 559 560 var value = this.get('value'); 561 if (SC.none(value)) { 562 if (SC.none(emptyName)) { 563 this.set('value', this._defaultVal); 564 this.set('title', this._defaultTitle); 565 this.set('icon', this._defaultIcon); 566 } 567 else this.set('title', emptyName); 568 } 569 570 //Set the preference matrix for the menu pane 571 this.changeSelectPreferMatrix(); 572 }.observes( '*items.[]' ), 573 574 /** 575 @private 576 @param {DOMMouseEvent} evt mouseup event that triggered the action 577 */ 578 _action: function (evt) { 579 var buttonLabel, menuWidth, scrollWidth, lastMenuWidth, offsetWidth, 580 items, elementOffsetWidth, largestMenuWidth, item, idx, 581 value, itemList, menuControlSize, menuHeightPadding, customView, 582 menu, itemsLength, itemIdx, escapeHTML; 583 584 buttonLabel = this.$('.sc-button-label')[0]; 585 586 var menuWidthOffset = SC.SelectView.MENU_WIDTH_OFFSET; 587 if (!this.get('isDefaultPosition')) { 588 switch (this.get('controlSize')) { 589 case SC.TINY_CONTROL_SIZE: 590 menuWidthOffset += SC.SelectView.TINY_POPUP_MENU_WIDTH_OFFSET; 591 break; 592 case SC.SMALL_CONTROL_SIZE: 593 menuWidthOffset += SC.SelectView.SMALL_POPUP_MENU_WIDTH_OFFSET; 594 break; 595 case SC.REGULAR_CONTROL_SIZE: 596 menuWidthOffset += SC.SelectView.REGULAR_POPUP_MENU_WIDTH_OFFSET; 597 break; 598 case SC.LARGE_CONTROL_SIZE: 599 menuWidthOffset += SC.SelectView.LARGE_POPUP_MENU_WIDTH_OFFSET; 600 break; 601 case SC.HUGE_CONTROL_SIZE: 602 menuWidthOffset += SC.SelectView.HUGE_POPUP_MENU_WIDTH_OFFSET; 603 break; 604 } 605 } 606 // Get the length of the text on the button in pixels 607 menuWidth = this.get('layer').offsetWidth + menuWidthOffset; 608 609 // Get the length of the text on the button in pixels 610 menuWidth = this.get('layer').offsetWidth; 611 scrollWidth = buttonLabel.scrollWidth; 612 lastMenuWidth = this.get('lastMenuWidth'); 613 if (scrollWidth) { 614 // Get the original width of the label in the button 615 offsetWidth = buttonLabel.offsetWidth; 616 if (scrollWidth && offsetWidth) { 617 menuWidth = menuWidth + scrollWidth - offsetWidth; 618 } 619 } 620 if (!lastMenuWidth || (menuWidth > lastMenuWidth)) { 621 lastMenuWidth = menuWidth; 622 } 623 624 items = this.get('_itemList'); 625 626 var customViewClassName = this.get('customViewClassName'); 627 // var customViewMenuOffsetWidth = this.get('customViewMenuOffsetWidth'); 628 var className = 'sc-view sc-pane sc-panel sc-palette sc-picker sc-menu select-button sc-scroll-view sc-menu-scroll-view sc-container-view menuContainer sc-button-view sc-menu-item sc-regular-size'; 629 className = customViewClassName ? (className + ' ' + customViewClassName) : className; 630 631 SC.prepareStringMeasurement("", className); 632 for (idx = 0, itemsLength = items.length; idx < itemsLength; ++idx) { 633 //getting the width of largest menu item 634 item = items.objectAt(idx); 635 elementOffsetWidth = SC.measureString(item.title).width; 636 637 if (!largestMenuWidth || (elementOffsetWidth > largestMenuWidth)) { 638 largestMenuWidth = elementOffsetWidth; 639 } 640 } 641 SC.teardownStringMeasurement(); 642 643 lastMenuWidth = (largestMenuWidth + this.menuItemPadding > lastMenuWidth) ? 644 largestMenuWidth + this.menuItemPadding : lastMenuWidth; 645 646 // Get the window size width and compare with the lastMenuWidth. 647 // If it is greater than windows width then reduce the maxwidth by 25px 648 // so that the ellipsis property is enabled by default 649 var maxWidth = SC.RootResponder.responder.get('currentWindowSize').width; 650 if (lastMenuWidth > maxWidth) { 651 lastMenuWidth = (maxWidth - 25); 652 } 653 654 this.set('lastMenuWidth',lastMenuWidth); 655 value = this.get('value'); 656 itemList = this.get('_itemList'); 657 menuControlSize = this.get('controlSize'); 658 menuHeightPadding = this.get('menuPaneHeightPadding'); 659 escapeHTML = this.get('escapeHTML'); 660 661 // get the user defined custom view 662 customView = this.get('exampleView') || SC.MenuItemView.extend({ escapeHTML: escapeHTML }); 663 664 menu = SC.MenuPane.create({ 665 666 /** 667 Class name - select-button-item 668 */ 669 classNames: ['select-button'], 670 671 /** 672 The menu items are set from the itemList property of SelectView 673 674 @property 675 */ 676 items: itemList, 677 678 /** 679 Example view which will be used to create the Menu Items 680 681 @default SC.MenuItemView 682 @type SC.View 683 */ 684 exampleView: customView, 685 686 /** 687 This property enables all the items and makes them selectable. 688 689 @property 690 */ 691 isEnabled: YES, 692 693 menuHeightPadding: menuHeightPadding, 694 695 preferType: SC.PICKER_MENU, 696 itemHeightKey: 'height', 697 layout: { width: lastMenuWidth }, 698 controlSize: menuControlSize, 699 itemWidth: lastMenuWidth 700 }); 701 702 // no menu to toggle... bail... 703 if (!menu) return NO; 704 menu.popup(this, this.preferMatrix); 705 this.set('menu', menu); 706 707 itemIdx = this._itemIdx; 708 if (!SC.empty(itemIdx) && itemIdx > -1) { 709 // if there is an item selected, make it the first responder 710 customView = menu.menuItemViewForContentIndex(itemIdx); 711 if (customView) { customView.becomeFirstResponder(); } 712 } 713 714 this.set('isActive', YES); 715 return YES; 716 }, 717 718 /** @private 719 Action method for the select button menu items 720 */ 721 displaySelectedItem: function (menuView) { 722 var currentItem = menuView.get("selectedItem"); 723 724 this.set("value", currentItem.get("value")); 725 }, 726 727 /** @private Each time the value changes, update each item's checkbox property and update our display properties. */ 728 valueDidChange: function () { 729 var itemList = this._itemList, 730 showCheckbox = this.get('showCheckbox'), 731 value = this.get('value'); 732 733 // Find the newly selected item (if any). 734 for (var i = 0, len = itemList.length; i < len; i++) { 735 var item = itemList[i], 736 isChecked = false; 737 738 if (value === item.get('value')) { 739 isChecked = showCheckbox; 740 this.set("title", item.get("title")); 741 this.set("icon", item.get("icon")); 742 this.set("_itemIdx", item.get("contentIndex")); 743 } 744 745 item.set('checkbox', isChecked); 746 } 747 748 }.observes('value'), 749 750 /** @private 751 Set the "top" attribute in the prefer matrix property which will 752 position menu such that the selected item in the menu will be 753 place aligned to the item on the button when menu is opened. 754 */ 755 changeSelectPreferMatrix: function () { 756 var controlSizeTuning = 0, customMenuItemHeight = 0; 757 switch (this.get('controlSize')) { 758 case SC.TINY_CONTROL_SIZE: 759 controlSizeTuning = SC.SelectView.TINY_OFFSET_Y; 760 customMenuItemHeight = SC.MenuPane.TINY_MENU_ITEM_HEIGHT; 761 break; 762 case SC.SMALL_CONTROL_SIZE: 763 controlSizeTuning = SC.SelectView.SMALL_OFFSET_Y; 764 customMenuItemHeight = SC.MenuPane.SMALL_MENU_ITEM_HEIGHT; 765 break; 766 case SC.REGULAR_CONTROL_SIZE: 767 controlSizeTuning = SC.SelectView.REGULAR_OFFSET_Y; 768 customMenuItemHeight = SC.MenuPane.REGULAR_MENU_ITEM_HEIGHT; 769 break; 770 case SC.LARGE_CONTROL_SIZE: 771 controlSizeTuning = SC.SelectView.LARGE_OFFSET_Y; 772 customMenuItemHeight = SC.MenuPane.LARGE_MENU_ITEM_HEIGHT; 773 break; 774 case SC.HUGE_CONTROL_SIZE: 775 controlSizeTuning = SC.SelectView.HUGE_OFFSET_Y; 776 customMenuItemHeight = SC.MenuPane.HUGE_MENU_ITEM_HEIGHT; 777 break; 778 } 779 780 var preferMatrixAttributeTop = controlSizeTuning, 781 itemIdx = this.get('_itemIdx'), 782 leftAlign = this.get('leftAlign'), defPreferMatrix, tempPreferMatrix; 783 784 if (this.get('isDefaultPosition')) { 785 defPreferMatrix = [1, 0, 3]; 786 this.set('preferMatrix', defPreferMatrix); 787 } else { 788 if (itemIdx) { 789 preferMatrixAttributeTop = itemIdx * customMenuItemHeight + 790 controlSizeTuning; 791 } 792 tempPreferMatrix = [leftAlign, -preferMatrixAttributeTop, 2]; 793 this.set('preferMatrix', tempPreferMatrix); 794 } 795 }, 796 797 /** 798 @private 799 Holding down the button should display the menu pane. 800 */ 801 mouseDown: function (evt) { 802 // Fast path, reject secondary clicks. 803 if (evt.which !== 1) return false; 804 805 if (!this.get('isEnabledInPane')) return YES; // handled event, but do nothing 806 this.set('isActive', YES); 807 this._isMouseDown = YES; 808 this.becomeFirstResponder(); 809 this._action(); 810 return YES; 811 }, 812 813 /** @private 814 Because we responded YES to the mouseDown event, we have responsibility 815 for handling the corresponding mouseUp event. 816 817 However, the user may click on this button, then drag the mouse down to a 818 menu item, and release the mouse over the menu item. We therefore need to 819 delegate any mouseUp events to the menu's menu item, if one is selected. 820 821 We also need to differentiate between a single click and a click and hold. 822 If the user clicks and holds, we want to close the menu when they release. 823 Otherwise, we should wait until they click on the menu's modal pane before 824 removing our active state. 825 826 @param {SC.Event} evt 827 @returns {Boolean} 828 */ 829 mouseUp: function (evt) { 830 var menu = this.get('menu'), targetMenuItem; 831 832 if (menu) { 833 targetMenuItem = menu.getPath('rootMenu.targetMenuItem'); 834 835 if (targetMenuItem && menu.get('mouseHasEntered')) { 836 // Have the menu item perform its action. 837 // If the menu returns NO, it had no action to 838 // perform, so we should close the menu immediately. 839 if (!targetMenuItem.performAction()) menu.remove(); 840 } else { 841 // If the user waits more than 200ms between mouseDown and mouseUp, 842 // we can assume that they are clicking and dragging to the menu item, 843 // and we should close the menu if they mouseup anywhere not inside 844 // the menu. 845 if (evt.timeStamp - this._mouseDownTimestamp > 400) { 846 menu.remove(); 847 } 848 } 849 } 850 851 // Reset state. 852 this._isMouseDown = NO; 853 this.set("isActive", NO); 854 return YES; 855 }, 856 857 /** @private 858 Override mouseExited to not remove the active state on mouseexit. 859 */ 860 mouseExited: function () { 861 return YES; 862 }, 863 864 /** 865 @private 866 Handle Key event - Down arrow key 867 */ 868 keyDown: function (event) { 869 if ( this.interpretKeyEvents(event) ) { 870 return YES; 871 } 872 else { 873 return sc_super(); 874 } 875 }, 876 877 /** 878 @private 879 Pressing the Up or Down arrow key should display the menu pane. Pressing escape should 880 resign first responder. 881 */ 882 moveUp: function(evt) { 883 this._action(); 884 return YES; 885 }, 886 /** @private */ 887 moveDown: function(evt) { 888 this._action(); 889 return YES; 890 }, 891 cancel: function(evt) { 892 this.resignFirstResponder(); 893 }, 894 895 /** @private 896 Override the button isSelectedDidChange function in order to not perform any action 897 on selecting the select_button 898 */ 899 _button_isSelectedDidChange: function () { 900 901 }.observes('isSelected') 902 903 }); 904 905 /** 906 @static 907 @type Number 908 @default 0 909 */ 910 SC.SelectView.TINY_OFFSET_X = 0; 911 912 /** 913 @static 914 @type Number 915 @default 0 916 */ 917 SC.SelectView.TINY_OFFSET_Y = 0; 918 919 /** 920 @static 921 @type Number 922 @default 0 923 */ 924 SC.SelectView.TINY_POPUP_MENU_WIDTH_OFFSET = 0; 925 926 927 /** 928 @static 929 @type Number 930 @default -18 931 */ 932 SC.SelectView.SMALL_OFFSET_X = -18; 933 934 /** 935 @static 936 @type Number 937 @default 3 938 */ 939 SC.SelectView.SMALL_OFFSET_Y = 3; 940 941 /** 942 @static 943 @type Number 944 @default 7 945 */ 946 SC.SelectView.SMALL_POPUP_MENU_WIDTH_OFFSET = 7; 947 948 949 /** 950 @static 951 @type Number 952 @default -17 953 */ 954 SC.SelectView.REGULAR_OFFSET_X = -17; 955 956 /** 957 @static 958 @type Number 959 @default 1 960 */ 961 SC.SelectView.REGULAR_OFFSET_Y = 0; 962 963 /** 964 @static 965 @type Number 966 @default 4 967 */ 968 SC.SelectView.REGULAR_POPUP_MENU_WIDTH_OFFSET = 4; 969 970 971 /** 972 @static 973 @type Number 974 @default -17 975 */ 976 SC.SelectView.LARGE_OFFSET_X = -17; 977 978 /** 979 @static 980 @type Number 981 @default 6 982 */ 983 SC.SelectView.LARGE_OFFSET_Y = 6; 984 985 /** 986 @static 987 @type Number 988 @default 3 989 */ 990 SC.SelectView.LARGE_POPUP_MENU_WIDTH_OFFSET = 3; 991 992 993 /** 994 @static 995 @type Number 996 @default 0 997 */ 998 SC.SelectView.HUGE_OFFSET_X = 0; 999 1000 /** 1001 @static 1002 @type Number 1003 @default 0 1004 */ 1005 SC.SelectView.HUGE_OFFSET_Y = 0; 1006 1007 /** 1008 @static 1009 @type Number 1010 @default 0 1011 */ 1012 SC.SelectView.HUGE_POPUP_MENU_WIDTH_OFFSET = 0; 1013 1014 1015 /** 1016 @static 1017 @type Number 1018 @default -2 1019 */ 1020 SC.SelectView.MENU_WIDTH_OFFSET = -2; 1021