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 /*global jQuery*/ 8 9 sc_require('system/browser'); 10 sc_require('system/event'); 11 sc_require('system/cursor'); 12 sc_require('system/responder'); 13 sc_require('system/theme'); 14 15 sc_require('system/string'); 16 sc_require('views/view/statechart'); 17 18 19 /** 20 Default property to disable or enable by default the contextMenu 21 */ 22 SC.CONTEXT_MENU_ENABLED = YES; 23 24 /** 25 Default property to disable or enable if the focus can jump to the address 26 bar or not. 27 */ 28 SC.TABBING_ONLY_INSIDE_DOCUMENT = NO; 29 30 /** 31 Tells the property (when fetched with themed()) to get its value from the renderer (if any). 32 */ 33 SC.FROM_THEME = "__FROM_THEME__"; // doesn't really matter what it is, so long as it is unique. Readability is a plus. 34 35 /** @private - custom array used for child views */ 36 SC.EMPTY_CHILD_VIEWS_ARRAY = []; 37 SC.EMPTY_CHILD_VIEWS_ARRAY.needsClone = YES; 38 39 /** 40 @class 41 42 */ 43 SC.CoreView.reopen( 44 /** @scope SC.View.prototype */ { 45 46 /** 47 An array of the properties of this class that will be concatenated when 48 also present on subclasses. 49 50 @type Array 51 @default ['outlets', 'displayProperties', 'classNames', 'renderMixin', 'didCreateLayerMixin', 'willDestroyLayerMixin', 'classNameBindings', 'attributeBindings'] 52 */ 53 concatenatedProperties: ['outlets', 'displayProperties', 'classNames', 'renderMixin', 'didCreateLayerMixin', 'willDestroyLayerMixin', 'classNameBindings', 'attributeBindings'], 54 55 /** 56 The WAI-ARIA role of the control represented by this view. For example, a 57 button may have a role of type 'button', or a pane may have a role of 58 type 'alertdialog'. This property is used by assistive software to help 59 visually challenged users navigate rich web applications. 60 61 The full list of valid WAI-ARIA roles is available at: 62 http://www.w3.org/TR/wai-aria/roles#roles_categorization 63 64 @type String 65 @default null 66 */ 67 ariaRole: null, 68 69 /** 70 The aria-hidden role is managed appropriately by the internal view's 71 statechart. When the view is not currently displayed the aria-hidden 72 attribute will be set to true. 73 74 @type String 75 @default null 76 @deprecated Version 1.10 77 */ 78 ariaHidden: null, 79 80 /** 81 Whether this view was created by its parent view or not. 82 83 Several views are given child view classes or instances to automatically 84 append and remove. In the case that the view was provided an instance, 85 when it removes the instance and no longer needs it, it should not destroy 86 the instance because it was created by someone else. 87 88 On the other hand if the view was given a class that it creates internal 89 instances from, then it should destroy those instances properly to avoid 90 memory leaks. 91 92 This property should be set by any view that is creating internal child 93 views so that it can properly remove them later. Note that if you use 94 `createChildView`, this property is set automatically for you. 95 96 @type Boolean 97 @see SC.View#createChildView 98 @default false 99 */ 100 createdByParent: false, 101 102 /** @deprecated Version 1.11.0 Please use parentView instead. */ 103 owner: function () { 104 //@if(debug) 105 SC.warn("Developer Warning: The `owner` property of SC.View has been deprecated in favor of the `parentView`, which is the same value. Please use `parentView`."); 106 //@endif 107 return this.get('parentView'); 108 }.property('parentView').cacheable(), 109 110 /** 111 The current pane. 112 113 @field 114 @type SC.Pane 115 @default null 116 */ 117 pane: function () { 118 var view = this; 119 120 while (view && !view.isPane) { view = view.get('parentView'); } 121 122 return view; 123 }.property('parentView').cacheable(), 124 125 /** 126 The page this view was instantiated from. This is set by the page object 127 during instantiation. 128 129 @type SC.Page 130 @default null 131 */ 132 page: null, 133 134 /** 135 If the view is currently inserted into the DOM of a parent view, this 136 property will point to the parent of the view. 137 138 @type SC.View 139 @default null 140 */ 141 parentView: null, 142 143 /** 144 The isVisible property determines if the view should be displayed or not. 145 146 If you also set a transitionShow or transitionHide plugin, then when 147 isVisible changes, the appropriate transition will execute as the view's 148 visibility changes. 149 150 Note that isVisible can be set to true and the view may still not be 151 "visible" in the window. This can occur if: 152 153 1. the view is not attached to the document. 154 2. the view has a view ancestor with isVisible set to false. 155 156 @type Boolean 157 @see SC.View#viewState 158 @default true 159 */ 160 isVisible: true, 161 isVisibleBindingDefault: SC.Binding.bool(), 162 163 // .......................................................... 164 // CHILD VIEW SUPPORT 165 // 166 167 /** 168 Array of child views. You should never edit this array directly unless 169 you are implementing createChildViews(). Most of the time, you should 170 use the accessor methods such as appendChild(), insertBefore() and 171 removeChild(). 172 173 @type Array 174 @default [] 175 */ 176 childViews: SC.EMPTY_CHILD_VIEWS_ARRAY, 177 178 /** 179 Use this property to automatically mix in a collection of mixins into all 180 child views created by the view. This collection is applied during createChildView 181 @property 182 183 @type Array 184 @default null 185 */ 186 autoMixins: null, 187 188 // .......................................................... 189 // LAYER SUPPORT 190 // 191 192 /** 193 Returns the current layer for the view. The layer for a view is only 194 generated when the view first becomes visible in the window and even 195 then it will not be computed until you request this layer property. 196 197 If the layer is not actually set on the view itself, then the layer will 198 be found by calling this.findLayerInParentLayer(). 199 200 You can also set the layer by calling set on this property. 201 202 @type DOMElement the layer 203 */ 204 layer: function (key, value) { 205 if (value !== undefined) { 206 this._view_layer = value; 207 208 // no layer...attempt to discover it... 209 } else { 210 value = this._view_layer; 211 if (!value) { 212 var parent = this.get('parentView'); 213 if (parent) { parent = parent.get('layer'); } 214 this._view_layer = value = this.findLayerInParentLayer(parent); 215 } 216 } 217 return value; 218 }.property('isVisibleInWindow').cacheable(), 219 220 /** 221 Get a CoreQuery object for this view's layer, or pass in a selector string 222 to get a CoreQuery object for a DOM node nested within this layer. 223 224 @param {String} sel a CoreQuery-compatible selector string 225 @returns {SC.CoreQuery} the CoreQuery object for the DOM node 226 */ 227 $: function (sel) { 228 var layer = this.get('layer'); 229 230 if (!layer) { return SC.$(); } 231 else if (sel === undefined) { return SC.$(layer); } 232 else { return SC.$(sel, layer); } 233 }, 234 235 /** 236 Returns the DOM element that should be used to hold child views when they 237 are added/remove via DOM manipulation. The default implementation simply 238 returns the layer itself. You can override this to return a DOM element 239 within the layer. 240 241 @type DOMElement the container layer 242 */ 243 containerLayer: function () { 244 return this.get('layer'); 245 }.property('layer').cacheable(), 246 247 /** 248 The ID to use when trying to locate the layer in the DOM. If you do not 249 set the layerId explicitly, then the view's GUID will be used instead. 250 This ID must be set at the time the view is created. 251 252 @type String 253 @readOnly 254 */ 255 layerId: function (key, value) { 256 if (value) { this._layerId = value; } 257 if (this._layerId) { return this._layerId; } 258 return SC.guidFor(this); 259 }.property().cacheable(), 260 261 /** 262 Attempts to discover the layer in the parent layer. The default 263 implementation looks for an element with an ID of layerId (or the view's 264 guid if layerId is null). You can override this method to provide your 265 own form of lookup. For example, if you want to discover your layer using 266 a CSS class name instead of an ID. 267 268 @param {DOMElement} parentLayer the parent's DOM layer 269 @returns {DOMElement} the discovered layer 270 */ 271 findLayerInParentLayer: function (parentLayer) { 272 var id = "#" + this.get('layerId').escapeCssIdForSelector(); 273 return jQuery(id, parentLayer)[0] || jQuery(id)[0]; 274 }, 275 276 /** 277 Returns YES if the receiver is a subview of a given view or if it's 278 identical to that view. Otherwise, it returns NO. 279 280 @property {SC.View} view 281 */ 282 isDescendantOf: function (view) { 283 var parentView = this.get('parentView'); 284 285 if (this === view) { return YES; } 286 else if (parentView) { return parentView.isDescendantOf(view); } 287 else { return NO; } 288 }, 289 290 /** 291 This method is invoked whenever a display property changes and updates 292 the view's content once at the end of the run loop before any invokeLast 293 functions run. 294 295 To cause the view to be updated you can call this method directly and 296 if you need to perform additional setup whenever the display changes, you 297 can override this method as well. 298 299 @returns {SC.View} receiver 300 */ 301 displayDidChange: function () { 302 //@if (debug) 303 if (SC.LOG_VIEW_STATES) { 304 SC.Logger.log('%c%@:%@ — displayDidChange()'.fmt(this, this.get('viewState')), SC.LOG_VIEW_STATES_STYLE[this.get('viewState')]); 305 } 306 //@endif 307 308 // Don't run _doUpdateContent needlessly, because the view may render 309 // before it is invoked, which would result in a needless update. 310 if (this.get('_isRendered')) { 311 // Legacy. 312 this.set('layerNeedsUpdate', true); 313 314 this.invokeOnce(this._doUpdateContent); 315 } 316 317 return this; 318 }, 319 320 /** 321 This property has no effect and is deprecated. 322 323 To cause a view to update immediately, you should just call updateLayer or 324 updateLayerIfNeeded. To cause a view to update at the end of the run loop 325 before any invokeLast functions run, you should call displayDidChange. 326 327 @deprecated Version 1.10 328 @type Boolean 329 @test in updateLayer 330 */ 331 layerNeedsUpdate: NO, 332 333 /** 334 Updates the view's layer if the view is in a shown state. Otherwise, the 335 view will be updated the next time it enters a shown state. 336 337 This is the same behavior as `displayDidChange` except that calling 338 `updateLayerIfNeeded` will attempt to update each time it is called, 339 while `displayDidChange` will only attempt to update the layer once per run 340 loop. 341 342 @returns {SC.View} receiver 343 @test in updateLayer 344 */ 345 updateLayerIfNeeded: function (skipIsVisibleInWindowCheck) { 346 //@if(debug) 347 if (skipIsVisibleInWindowCheck) { 348 SC.warn("Developer Warning: The `skipIsVisibleInWindowCheck` argument of updateLayerIfNeeded is not supported and will be ignored."); 349 } 350 //@endif 351 this._doUpdateContent(false); 352 353 return this; 354 }, 355 356 /** 357 This is the core method invoked to update a view layer whenever it has 358 changed. This method simply creates a render context focused on the 359 layer element and then calls your render() method. 360 361 You will not usually call or override this method directly. Instead you 362 should set the layerNeedsUpdate property to YES to cause this method to 363 run at the end of the run loop, or you can call updateLayerIfNeeded() 364 to force the layer to update immediately. 365 366 Instead of overriding this method, consider overriding the render() method 367 instead, which is called both when creating and updating a layer. If you 368 do not want your render() method called when updating a layer, then you 369 should override this method instead. 370 371 @returns {SC.View} receiver 372 */ 373 updateLayer: function () { 374 this._doUpdateContent(true); 375 376 return this; 377 }, 378 379 /** @private */ 380 parentViewDidResize: function () { 381 if (!this.get('hasLayout')) { this.notifyPropertyChange('frame'); } 382 this.viewDidResize(); 383 }, 384 385 /** 386 Override this in a child class to define behavior that should be invoked 387 when a parent's view was resized. 388 */ 389 viewDidResize: function () {}, 390 391 /** 392 Creates a new renderContext with the passed tagName or element. You 393 can override this method to provide further customization to the context 394 if needed. Normally you will not need to call or override this method. 395 396 @returns {SC.RenderContext} 397 */ 398 renderContext: function (tagNameOrElement) { 399 return SC.RenderContext(tagNameOrElement); 400 }, 401 402 /** 403 Creates the layer by creating a renderContext and invoking the view's 404 render() method. This will only create the layer if the layer does not 405 already exist. 406 407 When you create a layer, it is expected that your render() method will 408 also render the HTML for all child views as well. This method will 409 notify the view along with any of its childViews that its layer has been 410 created. 411 412 @returns {SC.View} receiver 413 */ 414 createLayer: function () { 415 if (!this.get('_isRendered')) { 416 this._doRender(); 417 } 418 419 return this; 420 }, 421 422 /** 423 Destroys any existing layer along with the layer for any child views as 424 well. If the view does not currently have a layer, then this method will 425 do nothing. 426 427 If you implement willDestroyLayer() on your view or if any mixins 428 implement willDestroLayerMixin(), then this method will be invoked on your 429 view before your layer is destroyed to give you a chance to clean up any 430 event handlers, etc. 431 432 If you write a willDestroyLayer() handler, you can assume that your 433 didCreateLayer() handler was called earlier for the same layer. 434 435 Normally you will not call or override this method yourself, but you may 436 want to implement the above callbacks when it is run. 437 438 @returns {SC.View} receiver 439 */ 440 destroyLayer: function () { 441 // We allow you to call destroy layer, but you should really detach first. 442 if (this.get('isAttached')) { 443 this._doDetach(); 444 } 445 446 if (this.get('_isRendered')) { 447 this._doDestroyLayer(); 448 } 449 450 return this; 451 }, 452 453 /** 454 Destroys and recreates the current layer. Doing this on a parent view can 455 be more efficient than modifying individual child views independently. 456 457 @returns {SC.View} receiver 458 */ 459 replaceLayer: function () { 460 var layer, parentNode; 461 462 // If attached, detach and track our parent node so we can re-attach. 463 if (this.get('isAttached')) { 464 layer = this.get('layer'); 465 parentNode = layer.parentNode; 466 467 this._doDetach(); 468 } 469 470 this.destroyLayer().createLayer(); 471 472 // Reattach our layer (if we have a parentView this is done automatically). 473 if (parentNode && !this.get('isAttached')) { this._doAttach(parentNode); } 474 475 return this; 476 }, 477 478 /** 479 If the parent view has changed, we need to insert this 480 view's layer into the layer of the new parent view. 481 */ 482 parentViewDidChange: function () { 483 //@if(debug) 484 SC.warn("Developer Warning: parentViewDidChange has been deprecated. Please use the notification methods willAddChild, didAddChild, willRemoveChild or didRemoveChild on the parent or willAddToParent, didAddToParent, willRemoveFromParent or didRemoveFromParent on the child to perform updates when the parent/child status changes."); 485 //@endif 486 }, 487 488 /** 489 Set to YES when the view's layer location is dirty. You can call 490 updateLayerLocationIfNeeded() to clear this flag if it is set. 491 492 @deprecated Version 1.10 493 @type Boolean 494 */ 495 layerLocationNeedsUpdate: NO, 496 497 /** 498 Calls updateLayerLocation(), but only if the view's layer location 499 currently needs to be updated. 500 501 @deprecated Version 1.10 502 @returns {SC.View} receiver 503 @test in updateLayerLocation 504 */ 505 updateLayerLocationIfNeeded: function () { 506 //@if(debug) 507 SC.warn("SC.View.prototype.updateLayerLocationIfNeeded is no longer used and has been deprecated. See the SC.View statechart code for more details on attaching and detaching layers."); 508 //@endif 509 510 return this; 511 }, 512 513 /** 514 This method is called when a view changes its location in the view 515 hierarchy. This method will update the underlying DOM-location of the 516 layer so that it reflects the new location. 517 518 @deprecated Version 1.10 519 @returns {SC.View} receiver 520 */ 521 updateLayerLocation: function () { 522 //@if(debug) 523 SC.warn("SC.View.prototype.updateLayerLocation is no longer used and has been deprecated. See the SC.View statechart code for more details on attaching and detaching layers."); 524 //@endif 525 526 return this; 527 }, 528 529 /** 530 @private 531 532 Renders to a context. 533 Rendering only happens for the initial rendering. Further updates happen in updateLayer, 534 and are not done to contexts, but to layers. 535 Note: You should not generally override nor directly call this method. This method is only 536 called by createLayer to set up the layer initially, and by renderChildViews, to write to 537 a context. 538 539 @param {SC.RenderContext} context the render context. 540 */ 541 renderToContext: function (context) { 542 var mixins, idx, len; 543 544 this.beginPropertyChanges(); 545 546 context.id(this.get('layerId')); 547 context.setAttr('role', this.get('ariaRole')); 548 549 // Set up the classNameBindings and attributeBindings observers. 550 // TODO: CLEAN UP!! 551 this._applyClassNameBindings(); 552 this._applyAttributeBindings(context); 553 554 context.addClass(this.get('classNames')); 555 556 if (this.get('isTextSelectable')) { context.addClass('allow-select'); } 557 558 if (!this.get('isVisible')) { 559 context.addClass('sc-hidden'); 560 context.setAttr('aria-hidden', 'true'); 561 } 562 563 // Call applyAttributesToContext so that subclasses that override it can 564 // insert further attributes. 565 this.applyAttributesToContext(context); 566 567 // We pass true for the second argument to support the old style of render. 568 this.render(context, true); 569 570 // If we've made it this far and renderChildViews() was never called, 571 // render any child views now. 572 if (!this._didRenderChildViews) { this.renderChildViews(context); } 573 // Reset the flag so that if the layer is recreated we re-render the child views. 574 this._didRenderChildViews = false; 575 576 if (mixins = this.renderMixin) { 577 len = mixins.length; 578 for (idx = 0; idx < len; ++idx) { mixins[idx].call(this, context, true); } 579 } 580 581 this.endPropertyChanges(); 582 }, 583 584 /** Apply the attributes to the context. */ 585 applyAttributesToContext: function (context) { 586 587 }, 588 589 /** 590 @private 591 592 Iterates over the view's `classNameBindings` array, inserts the value 593 of the specified property into the `classNames` array, then creates an 594 observer to update the view's element if the bound property ever changes 595 in the future. 596 */ 597 _applyClassNameBindings: function () { 598 var classBindings = this.get('classNameBindings'), 599 classNames = this.get('classNames'), 600 dasherizedClass; 601 602 if (!classBindings) { return; } 603 604 // Loop through all of the configured bindings. These will be either 605 // property names ('isUrgent') or property paths relative to the view 606 // ('content.isUrgent') 607 classBindings.forEach(function (property) { 608 609 // Variable in which the old class value is saved. The observer function 610 // closes over this variable, so it knows which string to remove when 611 // the property changes. 612 var oldClass; 613 614 // Set up an observer on the context. If the property changes, toggle the 615 // class name. 616 var observer = function () { 617 // Get the current value of the property 618 var newClass = this._classStringForProperty(property); 619 var elem = this.$(); 620 621 // If we had previously added a class to the element, remove it. 622 if (oldClass) { 623 elem.removeClass(oldClass); 624 classNames.removeObject(oldClass); 625 } 626 627 // If necessary, add a new class. Make sure we keep track of it so 628 // it can be removed in the future. 629 if (newClass) { 630 elem.addClass(newClass); 631 classNames.push(newClass); 632 oldClass = newClass; 633 } else { 634 oldClass = null; 635 } 636 }; 637 638 this.addObserver(property.split(':')[0], this, observer); 639 640 // Get the class name for the property at its current value 641 dasherizedClass = this._classStringForProperty(property); 642 643 if (dasherizedClass) { 644 // Ensure that it gets into the classNames array 645 // so it is displayed when we render. 646 classNames.push(dasherizedClass); 647 648 // Save a reference to the class name so we can remove it 649 // if the observer fires. Remember that this variable has 650 // been closed over by the observer. 651 oldClass = dasherizedClass; 652 } 653 654 }, this); 655 }, 656 657 /** 658 Iterates through the view's attribute bindings, sets up observers for each, 659 then applies the current value of the attributes to the passed render buffer. 660 661 @param {SC.RenderBuffer} buffer 662 */ 663 _applyAttributeBindings: function (context) { 664 var attributeBindings = this.get('attributeBindings'), 665 attributeValue, elem, type; 666 667 if (!attributeBindings) { return; } 668 669 attributeBindings.forEach(function (attribute) { 670 // Create an observer to add/remove/change the attribute if the 671 // JavaScript property changes. 672 var observer = function () { 673 elem = this.$(); 674 var currentValue = elem.attr(attribute); 675 attributeValue = this.get(attribute); 676 677 type = typeof attributeValue; 678 679 if ((type === 'string' || type === 'number') && attributeValue !== currentValue) { 680 elem.attr(attribute, attributeValue); 681 } else if (attributeValue && type === 'boolean') { 682 elem.attr(attribute, attribute); 683 } else if (attributeValue === NO) { 684 elem.removeAttr(attribute); 685 } 686 }; 687 688 this.addObserver(attribute, this, observer); 689 690 // Determine the current value and add it to the render buffer 691 // if necessary. 692 attributeValue = this.get(attribute); 693 type = typeof attributeValue; 694 695 if (type === 'string' || type === 'number') { 696 context.setAttr(attribute, attributeValue); 697 } else if (attributeValue && type === 'boolean') { 698 // Apply boolean attributes in the form attribute="attribute" 699 context.setAttr(attribute, attribute); 700 } 701 }, this); 702 }, 703 704 /** 705 @private 706 707 Given a property name, returns a dasherized version of that 708 property name if the property evaluates to a non-falsy value. 709 710 For example, if the view has property `isUrgent` that evaluates to true, 711 passing `isUrgent` to this method will return `"is-urgent"`. 712 */ 713 _classStringForProperty: function (property) { 714 var split = property.split(':'), className = split[1]; 715 property = split[0]; 716 717 var val = SC.getPath(this, property); 718 719 // If value is a Boolean and true, return the dasherized property 720 // name. 721 if (val === YES) { 722 if (className) { return className; } 723 724 // Normalize property path to be suitable for use 725 // as a class name. For exaple, content.foo.barBaz 726 // becomes bar-baz. 727 return SC.String.dasherize(property.split('.').get('lastObject')); 728 729 // If the value is not NO, undefined, or null, return the current 730 // value of the property. 731 } else if (val !== NO && val !== undefined && val !== null) { 732 return val; 733 734 // Nothing to display. Return null so that the old class is removed 735 // but no new class is added. 736 } else { 737 return null; 738 } 739 }, 740 741 /** 742 Your render method should invoke this method to render any child views, 743 especially if this is the first time the view will be rendered. This will 744 walk down the childView chain, rendering all of the children in a nested 745 way. 746 747 @param {SC.RenderContext} context the context 748 @returns {SC.RenderContext} the render context 749 @test in render 750 */ 751 renderChildViews: function (context) { 752 var cv = this.get('childViews'), len = cv.length, idx, view; 753 for (idx = 0; idx < len; ++idx) { 754 view = cv[idx]; 755 if (!view) { continue; } 756 context = context.begin(view.get('tagName')); 757 view.renderToContext(context); 758 context = context.end(); 759 } 760 761 // Track that renderChildViews was called in case it was called directly 762 // in a render method. 763 this._didRenderChildViews = true; 764 765 return context; 766 }, 767 768 /** @private - 769 override to add support for theming or in your view 770 */ 771 render: function () { }, 772 773 // .......................................................... 774 // STANDARD RENDER PROPERTIES 775 // 776 777 /** 778 A list of properties on the view to translate dynamically into attributes on 779 the view's layer (element). 780 781 When the view is rendered, the value of each property listed in 782 attributeBindings will be inserted in the element. If the value is a 783 Boolean, the attribute name itself will be inserted. As well, as the 784 value of any of these properties changes, the layer will update itself 785 automatically. 786 787 This is an easy way to set custom attributes on the View without 788 implementing it through a render or update function. 789 790 For example, 791 792 // ... MyApp.MyView 793 794 attributeBindings: ['aria-valuenow', 'disabled'], 795 796 'aria-valuenow': function () { 797 return this.get('value'); 798 }.property('value').cacheable(), // adds 'aria-valuenow="{value}"' attribute 799 800 disabled: YES, // adds 'disabled="disabled"' attribute 801 802 // ... 803 804 @type Array 805 @default null 806 */ 807 attributeBindings: null, 808 809 810 /** 811 Tag name for the view's outer element. The tag name is only used when 812 a layer is first created. If you change the tagName for an element, you 813 must destroy and recreate the view layer. 814 815 @type String 816 @default 'div' 817 */ 818 tagName: 'div', 819 820 /** 821 Standard CSS class names to apply to the view's outer element. These class 822 names are used in addition to any defined on the view's superclass. 823 824 @type Array 825 @default [] 826 */ 827 classNames: [], 828 829 /** 830 A list of local property names to translate dynamically into standard 831 CSS class names on your view's layer (element). 832 833 Each entry in the array should take the form "propertyName:css-class". 834 For example, "isRed:my-red-view" will cause the class "my-red-view" to 835 be appended if the property "isRed" is (or becomes) true, and removed 836 if it later becomes false (or null/undefined). 837 838 Optionally, you may provide just the property name, in which case it will 839 be dasherized and used as the class name. For example, including 840 "isUpsideDown" will cause the view's isUpsideDown property to mediate the 841 class "is-upside-down". 842 843 Instead of a boolean value, your property may return a string, which will 844 be used as the class name for that entry. Use caution when returning other 845 values; numbers will be appended verbatim and objects will be stringified, 846 leading to unintended results such as class="4" or class="Object object". 847 848 Class names mediated by these bindings are used in addition to any that 849 you've listed in the classNames property. 850 851 @type Array 852 */ 853 classNameBindings: null, 854 855 /** 856 Tool tip property that will be set to the title attribute on the HTML 857 rendered element. 858 859 @type String 860 */ 861 toolTip: null, 862 863 /** 864 The computed tooltip. This is generated by localizing the toolTip 865 property if necessary. 866 867 @type String 868 */ 869 displayToolTip: function () { 870 var ret = this.get('toolTip'); 871 return (ret && this.get('localize')) ? SC.String.loc(ret) : (ret || ''); 872 }.property('toolTip', 'localize').cacheable(), 873 874 /** 875 Determines if the user can select text within the view. Normally this is 876 set to NO to disable text selection. You should set this to YES if you 877 are creating a view that includes editable text. Otherwise, settings this 878 to YES will probably make your controls harder to use and it is not 879 recommended. 880 881 @type Boolean 882 @readOnly 883 */ 884 isTextSelectable: NO, 885 886 /** 887 You can set this array to include any properties that should immediately 888 invalidate the display. The display will be automatically invalidated 889 when one of these properties change. 890 891 These are the properties that will be visible to any Render Delegate. 892 When the RenderDelegate asks for a property it needs, the view checks the 893 displayProperties array. It first looks for the property name prefixed 894 by 'display'; for instance, if the render delegate needs a 'title', 895 the view will attempt to find 'displayTitle'. If there is no 'displayTitle' 896 in displayProperties, the view will then try 'title'. If 'title' is not 897 in displayProperties either, an error will be thrown. 898 899 This allows you to avoid collisions between your view's API and the Render 900 Delegate's API. 901 902 Implementation note: 'isVisible' is also effectively a display property, 903 but it is not declared as such because it is observed separately in 904 order to manage the view's internal state. 905 906 @type Array 907 @readOnly 908 */ 909 displayProperties: [], 910 911 // ....................................................... 912 // SC.RESPONDER SUPPORT 913 // 914 915 /** @property 916 The nextResponder is usually the parentView. 917 */ 918 nextResponder: function () { 919 return this.get('parentView'); 920 }.property('parentView').cacheable(), 921 922 923 /** @property 924 Set to YES if your view is willing to accept first responder status. This 925 is used when calculating key responder loop. 926 */ 927 acceptsFirstResponder: NO, 928 929 // ....................................................... 930 // CORE DISPLAY METHODS 931 // 932 933 /** @private 934 Caches the layerId to detect when it changes. 935 */ 936 _lastLayerId: null, 937 938 /** @private 939 Setup a view, but do not finish waking it up. 940 941 - configure childViews 942 - Determine the view's theme 943 - Fetch a render delegate from the theme, if necessary 944 - register the view with the global views hash, which is used for event 945 dispatch 946 */ 947 init: function () { 948 var childViews, layerId; 949 950 sc_super(); 951 952 layerId = this._lastLayerId = this.get('layerId'); 953 954 // Register the view for event handling. This hash is used by 955 // SC.RootResponder to dispatch incoming events. 956 //@if (debug) 957 if (SC.View.views[layerId]) { 958 throw new Error("Developer Error: A view with layerId, '%@', already exists. Each view must have a unique layerId.".fmt(this.get('layerId'))); 959 } 960 //@endif 961 SC.View.views[layerId] = this; 962 963 // setup classNames 964 this.classNames = this.get('classNames').slice(); 965 966 // setup child views. be sure to clone the child views array first 967 childViews = this.childViews = this.get('childViews').slice(); 968 this.createChildViews(); // setup child Views 969 }, 970 971 /** 972 Frame describes this view's current bounding rect, relative to its parent view. You 973 can use this, for example, to reliably access a width for a view whose layout is 974 defined with left and right. (Note that width and height values are calculated in 975 the parent view's frame of reference as well, which has consequences for scaled 976 views.) 977 978 @type Rect 979 @test in layoutStyle 980 */ 981 frame: function () { 982 return this.computeFrameWithParentFrame(null); 983 }.property('useStaticLayout').cacheable(), // We depend on the layout, but layoutDidChange will call viewDidResize to check the frame for us 984 985 /** 986 Computes the frame of the view by examining the view's DOM representation. 987 If no representation exists, returns null. 988 989 If the view has a parent view, the parent's bounds will be taken into account when 990 calculating the frame. 991 992 @returns {Rect} the computed frame 993 */ 994 computeFrameWithParentFrame: function () { 995 var layer, // The view's layer 996 pv = this.get('parentView'), // The view's parent view (if it exists) 997 f; // The layer's coordinates in the document 998 999 // need layer to be able to compute rect 1000 if (layer = this.get('layer')) { 1001 f = SC.offset(layer); // x,y 1002 if (pv) { f = pv.convertFrameFromView(f, null); } 1003 1004 /* 1005 TODO Can probably have some better width/height values - CC 1006 FIXME This will probably not work right with borders - PW 1007 */ 1008 f.width = layer.offsetWidth; 1009 f.height = layer.offsetHeight; 1010 1011 return f; 1012 } 1013 1014 // Unable to compute yet 1015 if (this.get('hasLayout')) { 1016 return null; 1017 } else { 1018 return { x: 0, y: 0, width: 0, height: 0 }; 1019 } 1020 }, 1021 1022 /** @private Call the method recursively on all child views. */ 1023 _callOnChildViews: function (methodName, isTopDown, context) { 1024 var childView, 1025 childViews = this.get('childViews'), 1026 method, 1027 shouldContinue; 1028 1029 for (var i = childViews.length - 1; i >= 0; i--) { 1030 childView = childViews[i]; 1031 1032 // We allow missing childViews in the array so ignore them. 1033 if (!childView) { continue; } 1034 1035 // Look up the method on the child. 1036 method = childView[methodName]; 1037 1038 // Call the method on this view *before* its children. 1039 if (isTopDown === undefined || isTopDown) { 1040 shouldContinue = method.call(childView, context); 1041 } 1042 1043 // Recurse. 1044 if (shouldContinue === undefined || shouldContinue) { 1045 childView._callOnChildViews(methodName, isTopDown, context); 1046 } 1047 1048 // Call the method on this view *after* its children. 1049 if (isTopDown === false) { 1050 method.call(childView, context); 1051 } 1052 } 1053 }, 1054 1055 /** 1056 The clipping frame returns the visible portion of the view, taking into 1057 account the clippingFrame of the parent view. (Note that, in contrast 1058 to `frame`, `clippingFrame` is in the context of the view itself, not 1059 its parent view.) 1060 1061 Normally this will be calculated based on the intersection of your own 1062 clippingFrame and your parentView's clippingFrame. 1063 1064 @type Rect 1065 */ 1066 clippingFrame: function () { 1067 var f = this.get('frame'); 1068 1069 // FAST PATH: No frame, no clipping frame. 1070 if (!f) return null; 1071 1072 /*jshint eqnull:true */ 1073 var scale = (f.scale == null) ? 1 : f.scale, 1074 pv = this.get('parentView'), 1075 pcf = pv ? pv.get('clippingFrame') : null, 1076 ret; 1077 1078 // FAST PATH: No parent clipping frame, no change. (The origin and scale are reset from parent view's 1079 // context to our own.) 1080 if (!pcf) return { x: 0, y: 0, width: f.width / scale, height: f.height / scale}; 1081 1082 // Get the intersection. 1083 ret = SC.intersectRects(pcf, f); 1084 1085 // Reorient the top-left from the parent's origin to ours. 1086 ret.x -= f.x; 1087 ret.y -= f.y; 1088 1089 // If we're scaled, we have to scale the intersected rectangle from our parent's frame of reference 1090 // to our own. 1091 if (scale !== 1) { 1092 var scaleX, scaleY; 1093 // We're scaling from parent space into our space, so the scale is reversed. (Layout scale may be an array.) 1094 if (SC.typeOf(scale) === SC.T_ARRAY) { 1095 scaleX = 1 / scale[0]; 1096 scaleY = 1 / scale[1]; 1097 } else { 1098 scaleX = scaleY = 1 / scale; 1099 } 1100 1101 // Convert the entire rectangle into our scale. 1102 ret.x *= scaleX; 1103 ret.width *= scaleX; 1104 ret.y *= scaleY; 1105 ret.height *= scaleY; 1106 } 1107 1108 return ret; 1109 }.property('parentView', 'frame').cacheable(), 1110 1111 /** @private 1112 This method is invoked whenever the clippingFrame changes, notifying 1113 each child view that its clippingFrame has also changed. 1114 */ 1115 _sc_clippingFrameDidChange: function () { 1116 this.notifyPropertyChange('clippingFrame'); 1117 }, 1118 1119 /** 1120 Removes the child view from the parent view *and* detaches it from the 1121 document. 1122 1123 This does *not* remove the child view's layer (i.e. the node still exists, 1124 but is no longer in the document) and does *not* destroy the child view 1125 (i.e. it can still be re-attached to the document). 1126 1127 Note that if the child view uses a transitionOut plugin, it will not be 1128 fully detached until the transition completes. To force the view to detach 1129 immediately you can pass true for the optional `immediately` argument. 1130 1131 If you wish to remove the child and discard it, use `removeChildAndDestroy`. 1132 1133 @param {SC.View} view The view to remove as a child view. 1134 @param {Boolean} [immediately=false] Forces the child view to be removed immediately regardless if it uses a transitionOut plugin. 1135 @see SC.View#removeChildAndDestroy 1136 @returns {SC.View} receiver 1137 */ 1138 removeChild: function (view, immediately) { 1139 if (view.get('isAttached')) { 1140 view._doDetach(immediately); 1141 } 1142 1143 // If the view will transition out, wait for the transition to complete 1144 // before orphaning the view entirely. 1145 if (!immediately && view.get('viewState') === SC.CoreView.ATTACHED_BUILDING_OUT) { 1146 view.addObserver('isAttached', this, this._orphanChildView); 1147 } else { 1148 view._doOrphan(); 1149 } 1150 1151 return this; 1152 }, 1153 1154 /** 1155 Removes the child view from the parent view, detaches it from the document 1156 *and* destroys the view and its layer. 1157 1158 Note that if the child view uses a transitionOut plugin, it will not be 1159 fully detached and destroyed until the transition completes. To force the 1160 view to detach immediately you can pass true for the optional `immediately` 1161 argument. 1162 1163 If you wish to remove the child and keep it for further re-use, use 1164 `removeChild`. 1165 1166 @param {SC.View} view The view to remove as a child view and destroy. 1167 @param {Boolean} [immediately=false] Forces the child view to be removed and destroyed immediately regardless if it uses a transitionOut plugin. 1168 @see SC.View#removeChild 1169 @returns {SC.View} receiver 1170 */ 1171 removeChildAndDestroy: function (view, immediately) { 1172 view._doDetach(immediately); 1173 1174 // If the view will transition out, wait for the transition to complete 1175 // before destroying the view entirely. 1176 if (view.get('transitionOut') && !immediately) { 1177 view.addObserver('isAttached', this, this._destroyChildView); 1178 } else { 1179 view.destroy(); // Destroys the layer and the view. 1180 } 1181 1182 return this; 1183 }, 1184 1185 /** 1186 Removes all children from the parentView *and* destroys them and their 1187 layers. 1188 1189 Note that if any child view uses a transitionOut plugin, it will not be 1190 fully removed until the transition completes. To force all child views to 1191 remove immediately you can pass true as the optional `immediately` argument. 1192 1193 Tip: If you know that there are no transitions for the child views, 1194 you should pass true to optimize the document removal. 1195 1196 @param {Boolean} [immediately=false] Forces all child views to be removed immediately regardless if any uses a transitionOut plugin. 1197 @returns {SC.View} receiver 1198 */ 1199 removeAllChildren: function (immediately) { 1200 var childViews = this.get('childViews'), 1201 len = childViews.get('length'), 1202 i; 1203 1204 // OPTIMIZATION! 1205 // If we know that we're removing all children and we are rendered, lets do the document cleanup in one sweep. 1206 // if (immediately && this.get('_isRendered')) { 1207 // var layer, 1208 // parentNode; 1209 1210 // // If attached, detach and track our parent node so we can re-attach. 1211 // if (this.get('isAttached')) { 1212 // layer = this.get('layer'); 1213 // parentNode = layer.parentNode; 1214 1215 // this._doDetach(); 1216 // } 1217 1218 // // Destroy our layer and thus all the children's layers in one move. 1219 // this.destroyLayer(); 1220 1221 // // Remove all the children. 1222 // for (i = len - 1; i >= 0; i--) { 1223 // this.removeChildAndDestroy(childViews.objectAt(i), immediately); 1224 // } 1225 1226 // // Recreate our layer (now empty). 1227 // this.createLayer(); 1228 1229 // // Reattach our layer. 1230 // if (parentNode && !this.get('isAttached')) { this._doAttach(parentNode); } 1231 // } else { 1232 for (i = len - 1; i >= 0; i--) { 1233 this.removeChildAndDestroy(childViews.objectAt(i), immediately); 1234 } 1235 // } 1236 1237 return this; 1238 }, 1239 1240 /** 1241 Removes the view from its parentView, if one is found. Otherwise 1242 does nothing. 1243 1244 @returns {SC.View} receiver 1245 */ 1246 removeFromParent: function () { 1247 var parent = this.get('parentView'); 1248 if (parent) { parent.removeChild(this); } 1249 1250 return this; 1251 }, 1252 1253 /** @private Observer for child views that are being discarded after transitioning out. */ 1254 _destroyChildView: function (view) { 1255 // Commence destroying of the view once it is detached. 1256 if (!view.get('isAttached')) { 1257 view.removeObserver('isAttached', this, this._destroyChildView); 1258 view.destroy(); 1259 } 1260 }, 1261 1262 /** @private Observer for child views that are being orphaned after transitioning out. */ 1263 _orphanChildView: function (view) { 1264 // Commence orphaning of the view once it is detached. 1265 if (!view.get('isAttached')) { 1266 view.removeObserver('isAttached', this, this._orphanChildView); 1267 view._doOrphan(); 1268 } 1269 }, 1270 1271 /** 1272 Completely destroys a view instance so that it may be garbage collected. 1273 1274 You must call this method on a view to destroy the view (and all of its 1275 child views). This will remove the view from any parent, detach the 1276 view's layer from the DOM if it is attached and clear the view's layer 1277 if it is rendered. 1278 1279 Once a view is destroyed it can *not* be reused. 1280 1281 @returns {SC.View} receiver 1282 */ 1283 destroy: function () { 1284 // Fast path! 1285 if (this.get('isDestroyed')) { return this; } 1286 1287 // Do generic destroy. It takes care of mixins and sets isDestroyed to YES. 1288 // Do this first, since it cleans up bindings that may apply to parentView 1289 // (which we will soon null out). 1290 var ret = sc_super(); 1291 1292 // If our parent is already destroyed, then we can defer destroying ourself 1293 // and our own child views momentarily. 1294 if (this.getPath('parentView.isDestroyed')) { 1295 // Complete the destroy in a bit. 1296 this.invokeNext(this._destroy); 1297 } else { 1298 // Immediately remove the layer if attached (ignores transitionOut). This 1299 // will detach the layer for all child views as well. 1300 if (this.get('isAttached')) { 1301 this._doDetach(true); 1302 } 1303 1304 // Clear the layer if rendered. This will clear all child views layer 1305 // references as well. 1306 if (this.get('_isRendered')) { 1307 this._doDestroyLayer(); 1308 } 1309 1310 // Complete the destroy. 1311 this._destroy(); 1312 } 1313 1314 // Remove the view from the global hash. 1315 delete SC.View.views[this.get('layerId')]; 1316 1317 // Destroy any children. Loop backwards since childViews will shrink. 1318 var childViews = this.get('childViews'); 1319 for (var i = childViews.length - 1; i >= 0; i--) { 1320 childViews[i].destroy(); 1321 } 1322 1323 return ret; 1324 }, 1325 1326 /** @private */ 1327 _destroy: function () { 1328 // Orphan the view if adopted. 1329 this._doOrphan(); 1330 1331 delete this.page; 1332 }, 1333 1334 /** 1335 This method is called when your view is first created to setup any child 1336 views that are already defined on your class. If any are found, it will 1337 instantiate them for you. 1338 1339 The default implementation of this method simply steps through your 1340 childViews array, which is expects to either be empty or to contain View 1341 designs that can be instantiated 1342 1343 Alternatively, you can implement this method yourself in your own 1344 subclasses to look for views defined on specific properties and then build 1345 a childViews array yourself. 1346 1347 Note that when you implement this method yourself, you should never 1348 instantiate views directly. Instead, you should use 1349 this.createChildView() method instead. This method can be much faster in 1350 a production environment than creating views yourself. 1351 1352 @returns {SC.View} receiver 1353 */ 1354 createChildViews: function () { 1355 var childViews = this.get('childViews'), 1356 len = childViews.length, 1357 isNoLongerValid = false, 1358 idx, key, view; 1359 1360 this.beginPropertyChanges(); 1361 1362 // swap the array 1363 for (idx = 0; idx < len; ++idx) { 1364 key = view = childViews[idx]; 1365 1366 // is this is a key name, lookup view class 1367 if (typeof key === SC.T_STRING) { 1368 view = this[key]; 1369 } else { 1370 key = null; 1371 } 1372 1373 if (!view) { 1374 //@if (debug) 1375 SC.warn("Developer Warning: The child view named '%@' was not found in the view, %@. This child view will be ignored.".fmt(key, this)); 1376 //@endif 1377 1378 // skip this one. 1379 isNoLongerValid = true; 1380 childViews[idx] = null; 1381 continue; 1382 } 1383 1384 // createChildView creates the view if necessary, but also sets 1385 // important properties, such as parentView 1386 view = this.createChildView(view); 1387 if (key) { this[key] = view; } // save on key name if passed 1388 1389 childViews[idx] = view; 1390 } 1391 1392 // Set childViews to be only the valid array. 1393 if (isNoLongerValid) { this.set('childViews', childViews.compact()); } 1394 1395 this.endPropertyChanges(); 1396 return this; 1397 }, 1398 1399 /** 1400 Instantiates a view to be added to the childViews array during view 1401 initialization. You generally will not call this method directly unless 1402 you are overriding createChildViews(). Note that this method will 1403 automatically configure the correct settings on the new view instance to 1404 act as a child of the parent. 1405 1406 If the given view is a class, then createdByParent will be set to true on 1407 the returned instance. 1408 1409 @param {Class} view A view class to create or view instance to prepare. 1410 @param {Object} [attrs={}] attributes to add 1411 @returns {SC.View} new instance 1412 @test in createChildViews 1413 */ 1414 createChildView: function (view, attrs) { 1415 // Create the view if it is a class. 1416 if (view.isClass) { 1417 // attrs should always exist... 1418 if (!attrs) { attrs = {}; } 1419 1420 // clone the hash that was given so we do not pollute it if it's being reused 1421 else { attrs = SC.clone(attrs); } 1422 1423 // Assign the parentView & page to ourself. 1424 attrs.parentView = this; 1425 if (!attrs.page) { attrs.page = this.page; } 1426 1427 // Track that we created this view. 1428 attrs.createdByParent = true; 1429 1430 // Insert the autoMixins if defined 1431 var applyMixins = this.autoMixins; 1432 if (!!applyMixins) { 1433 applyMixins = SC.clone(applyMixins); 1434 applyMixins.push(attrs); 1435 view = view.create.apply(view, applyMixins); 1436 } else { 1437 view = view.create(attrs); 1438 } 1439 // Assign the parentView if the view is an instance. 1440 // TODO: This should not be accepting view instances, for the purpose of lazy code elsewhere in the framework. 1441 // We should ensure users of `createChildViews` are using appendChild and other manipulation methods. 1442 } else { 1443 view.set('parentView', this); 1444 view._adopted(); 1445 1446 if (!view.get('page')) { view.set('page', this.page); } 1447 } 1448 1449 return view; 1450 }, 1451 1452 /** walk like a duck */ 1453 isView: YES, 1454 1455 /** 1456 Default method called when a selectstart event is triggered. This event is 1457 only supported by IE. Used in sproutcore to disable text selection and 1458 IE8 accelerators. The accelerators will be enabled only in 1459 text selectable views. In FF and Safari we use the css style 'allow-select'. 1460 1461 If you want to enable text selection in certain controls is recommended 1462 to override this function to always return YES , instead of setting 1463 isTextSelectable to true. 1464 1465 For example in textfield you do not want to enable textSelection on the text 1466 hint only on the actual text you are entering. You can achieve that by 1467 only overriding this method. 1468 1469 @param evt {SC.Event} the selectstart event 1470 @returns YES if selectable 1471 */ 1472 selectStart: function (evt) { 1473 return this.get('isTextSelectable'); 1474 }, 1475 1476 /** 1477 Used to block the contextMenu per view. 1478 1479 @param evt {SC.Event} the contextmenu event 1480 @returns YES if the contextmenu will be allowed to show up 1481 */ 1482 contextMenu: function (evt) { 1483 if (this.get('isContextMenuEnabled')) { 1484 evt.allowDefault(); 1485 return YES; 1486 } 1487 }, 1488 1489 // ------------------------------------------------------------------------ 1490 // Transitions 1491 // 1492 1493 /** 1494 The transition plugin to use when this view is appended to the DOM. 1495 1496 SC.CoreView uses a pluggable transition architecture where the transition 1497 setup, execution and cleanup can be handled by a specified transition 1498 plugin. 1499 1500 There are a number of pre-built transition plugins available in the 1501 foundation framework: 1502 1503 SC.View.BOUNCE_IN 1504 SC.View.FADE_IN 1505 SC.View.SLIDE_IN 1506 SC.View.SCALE_IN 1507 SC.View.SPRING_IN 1508 1509 You can even provide your own custom transition plugins. Just create a 1510 transition object that conforms to the SC.ViewTransitionProtocol protocol. 1511 1512 @type Object (SC.ViewTransitionProtocol) 1513 @default null 1514 @since Version 1.10 1515 */ 1516 transitionIn: null, 1517 1518 /** 1519 The options for the given transition in plugin. 1520 1521 These options are specific to the current transition plugin used and are 1522 used to modify the transition animation. To determine what options 1523 may be used for a given plugin and to see what the default options are, 1524 see the documentation for the transition plugin being used. 1525 1526 Most transitions will accept a duration and timing option, but may 1527 also use other options. For example, SC.View.SLIDE_IN accepts options 1528 like: 1529 1530 transitionInOptions: { 1531 direction: 'left', 1532 duration: 0.25, 1533 timing: 'ease-in-out' 1534 } 1535 1536 @type Object 1537 @default null 1538 @since Version 1.10 1539 */ 1540 transitionInOptions: null, 1541 1542 /** 1543 The transition plugin to use when this view is removed from the DOM. 1544 1545 SC.View uses a pluggable transition architecture where the transition setup, 1546 execution and cleanup can be handled by a specified transition plugin. 1547 1548 There are a number of pre-built transition plugins available in the 1549 foundation framework: 1550 1551 SC.View.BOUNCE_OUT 1552 SC.View.FADE_OUT 1553 SC.View.SLIDE_OUT 1554 SC.View.SCALE_OUT 1555 SC.View.SPRING_OUT 1556 1557 You can even provide your own custom transition plugins. Just create a 1558 transition object that conforms to the SC.ViewTransitionProtocol protocol. 1559 1560 @type Object (SC.ViewTransitionProtocol) 1561 @default null 1562 @since Version 1.10 1563 */ 1564 transitionOut: null, 1565 1566 /** 1567 The options for the given transition out plugin. 1568 1569 These options are specific to the current transition plugin used and are 1570 used to modify the transition animation. To determine what options 1571 may be used for a given plugin and to see what the default options are, 1572 see the documentation for the transition plugin being used. 1573 1574 Most transitions will accept a duration and timing option, but may 1575 also use other options. For example, SC.View.SLIDE accepts options 1576 like: 1577 1578 transitionOutOptions: { 1579 direction: 'right', 1580 duration: 0.15, 1581 timing: 'ease-in' 1582 } 1583 1584 @type Object 1585 @default null 1586 @since Version 1.10 1587 */ 1588 transitionOutOptions: null, 1589 1590 /** 1591 The transition plugin to use when this view is made shown from being 1592 hidden. 1593 1594 SC.CoreView uses a pluggable transition architecture where the transition setup, 1595 execution and cleanup can be handled by a specified transition plugin. 1596 1597 There are a number of pre-built transition plugins available in the 1598 foundation framework: 1599 1600 SC.View.BOUNCE_IN 1601 SC.View.FADE_IN 1602 SC.View.SLIDE_IN 1603 SC.View.SCALE_IN 1604 SC.View.SPRING_IN 1605 1606 You can even provide your own custom transition plugins. Just create a 1607 transition object that conforms to the SC.ViewTransitionProtocol protocol. 1608 1609 @type Object (SC.ViewTransitionProtocol) 1610 @default null 1611 @since Version 1.10 1612 */ 1613 transitionShow: null, 1614 1615 /** 1616 The options for the given transition show plugin. 1617 1618 These options are specific to the current transition plugin used and are 1619 used to modify the transition animation. To determine what options 1620 may be used for a given plugin and to see what the default options are, 1621 see the documentation for the transition plugin being used. 1622 1623 Most transitions will accept a duration and timing option, but may 1624 also use other options. For example, SC.View.SLIDE accepts options 1625 like: 1626 1627 transitionShowOptions: { 1628 direction: 'left', 1629 duration: 0.25, 1630 timing: 'ease-in-out' 1631 } 1632 1633 @type Object 1634 @default null 1635 @since Version 1.10 1636 */ 1637 transitionShowOptions: null, 1638 1639 /** 1640 The transition plugin to use when this view is hidden after being shown. 1641 1642 SC.View uses a pluggable transition architecture where the transition setup, 1643 execution and cleanup can be handled by a specified transition plugin. 1644 1645 There are a number of pre-built transition plugins available in the 1646 foundation framework: 1647 1648 SC.View.BOUNCE_OUT 1649 SC.View.FADE_OUT 1650 SC.View.SLIDE_OUT 1651 SC.View.SCALE_OUT 1652 SC.View.SPRING_OUT 1653 1654 You can even provide your own custom transition plugins. Just create a 1655 transition object that conforms to the SC.ViewTransitionProtocol protocol. 1656 1657 @type Object (SC.ViewTransitionProtocol) 1658 @default null 1659 @since Version 1.10 1660 */ 1661 transitionHide: null, 1662 1663 /** 1664 The options for the given transition hide plugin. 1665 1666 These options are specific to the current transition plugin used and are 1667 used to modify the transition animation. To determine what options 1668 may be used for a given plugin and to see what the default options are, 1669 see the documentation for the transition plugin being used. 1670 1671 Most transitions will accept a duration and timing option, but may 1672 also use other options. For example, SC.View.SLIDE accepts options 1673 like: 1674 1675 transitionHideOptions: { 1676 direction: 'right', 1677 duration: 0.15, 1678 timing: 'ease-in' 1679 } 1680 1681 @type Object 1682 @default null 1683 @since Version 1.10 1684 */ 1685 transitionHideOptions: null, 1686 1687 // ............................................ 1688 // Patches 1689 // 1690 1691 /** @private 1692 Override this method to apply design modes to this view and 1693 its children. 1694 @see SC.View 1695 */ 1696 updateDesignMode: function (lastDesignMode, designMode) {} 1697 }); 1698 1699 SC.CoreView.mixin( 1700 /** @scope SC.CoreView */ { 1701 1702 /** @private walk like a duck -- used by SC.Page */ 1703 isViewClass: YES, 1704 1705 /** 1706 This method works just like extend() except that it will also preserve 1707 the passed attributes in case you want to use a view builder later, if 1708 needed. 1709 1710 @param {Hash} attrs Attributes to add to view 1711 @returns {Class} SC.View subclass to create 1712 @function 1713 */ 1714 design: function () { 1715 if (this.isDesign) { 1716 // @if (debug) 1717 SC.Logger.warn("Developer Warning: .design() was called twice for %@.".fmt(this)); 1718 // @endif 1719 return this; 1720 } 1721 1722 var ret = this.extend.apply(this, arguments); 1723 ret.isDesign = YES; 1724 if (SC.ViewDesigner) { 1725 SC.ViewDesigner.didLoadDesign(ret, this, SC.A(arguments)); 1726 } 1727 return ret; 1728 }, 1729 1730 extend: function () { 1731 var last = arguments[arguments.length - 1]; 1732 1733 if (last && !SC.none(last.theme)) { 1734 last.themeName = last.theme; 1735 delete last.theme; 1736 } 1737 1738 return SC.Object.extend.apply(this, arguments); 1739 }, 1740 1741 /** 1742 Helper applies the layout to the prototype. 1743 */ 1744 layout: function (layout) { 1745 this.prototype.layout = layout; 1746 return this; 1747 }, 1748 1749 /** 1750 Helper applies the classNames to the prototype 1751 */ 1752 classNames: function (sc) { 1753 sc = (this.prototype.classNames || []).concat(sc); 1754 this.prototype.classNames = sc; 1755 return this; 1756 }, 1757 1758 /** 1759 Help applies the tagName 1760 */ 1761 tagName: function (tg) { 1762 this.prototype.tagName = tg; 1763 return this; 1764 }, 1765 1766 /** 1767 Helper adds the childView 1768 */ 1769 childView: function (cv) { 1770 var childViews = this.prototype.childViews || []; 1771 if (childViews === this.superclass.prototype.childViews) { 1772 childViews = childViews.slice(); 1773 } 1774 childViews.push(cv); 1775 this.prototype.childViews = childViews; 1776 return this; 1777 }, 1778 1779 /** 1780 Helper adds a binding to a design 1781 */ 1782 bind: function (keyName, path) { 1783 var p = this.prototype, s = this.superclass.prototype; 1784 var bindings = p._bindings; 1785 if (!bindings || bindings === s._bindings) { 1786 bindings = p._bindings = (bindings || []).slice(); 1787 } 1788 1789 keyName = keyName + "Binding"; 1790 p[keyName] = path; 1791 bindings.push(keyName); 1792 1793 return this; 1794 }, 1795 1796 /** 1797 Helper sets a generic property on a design. 1798 */ 1799 prop: function (keyName, value) { 1800 this.prototype[keyName] = value; 1801 return this; 1802 }, 1803 1804 /** 1805 Used to construct a localization for a view. The default implementation 1806 will simply return the passed attributes. 1807 */ 1808 localization: function (attrs, rootElement) { 1809 // add rootElement 1810 if (rootElement) attrs.rootElement = SC.$(rootElement)[0]; 1811 return attrs; 1812 }, 1813 1814 /** 1815 Creates a view instance, first finding the DOM element you name and then 1816 using that as the root element. You should not use this method very 1817 often, but it is sometimes useful if you want to attach to already 1818 existing HTML. 1819 1820 @param {String|Element} element 1821 @param {Hash} attrs 1822 @returns {SC.View} instance 1823 */ 1824 viewFor: function (element, attrs) { 1825 var args = SC.$A(arguments); // prepare to edit 1826 if (SC.none(element)) { 1827 args.shift(); // remove if no element passed 1828 } else args[0] = { rootElement: SC.$(element)[0] }; 1829 var ret = this.create.apply(this, arguments); 1830 args = args[0] = null; 1831 return ret; 1832 }, 1833 1834 /** 1835 Create a new view with the passed attributes hash. If you have the 1836 Designer module loaded, this will also create a peer designer if needed. 1837 */ 1838 create: function () { 1839 var last = arguments[arguments.length - 1]; 1840 1841 if (last && last.theme) { 1842 last.themeName = last.theme; 1843 delete last.theme; 1844 } 1845 1846 var C = this, ret = new C(arguments); 1847 if (SC.ViewDesigner) { 1848 SC.ViewDesigner.didCreateView(ret, SC.$A(arguments)); 1849 } 1850 return ret; 1851 }, 1852 1853 /** 1854 Applies the passed localization hash to the component views. Call this 1855 method before you call create(). Returns the receiver. Typically you 1856 will do something like this: 1857 1858 view = SC.View.design({...}).loc(localizationHash).create(); 1859 1860 @param {Hash} loc 1861 @param rootElement {String} optional rootElement with prepped HTML 1862 @returns {SC.View} receiver 1863 */ 1864 loc: function (loc) { 1865 var childLocs = loc.childViews; 1866 delete loc.childViews; // clear out child views before applying to attrs 1867 1868 this.applyLocalizedAttributes(loc); 1869 if (SC.ViewDesigner) { 1870 SC.ViewDesigner.didLoadLocalization(this, SC.$A(arguments)); 1871 } 1872 1873 // apply localization recursively to childViews 1874 var childViews = this.prototype.childViews, idx = childViews.length, 1875 viewClass; 1876 while (--idx >= 0) { 1877 viewClass = childViews[idx]; 1878 loc = childLocs[idx]; 1879 if (loc && viewClass && typeof viewClass === SC.T_STRING) SC.String.loc(viewClass, loc); 1880 } 1881 1882 return this; // done! 1883 }, 1884 1885 /** 1886 Internal method actually updates the localized attributes on the view 1887 class. This is overloaded in design mode to also save the attributes. 1888 */ 1889 applyLocalizedAttributes: function (loc) { 1890 SC.mixin(this.prototype, loc); 1891 }, 1892 1893 views: {} 1894 1895 }); 1896 1897 // ....................................................... 1898 // OUTLET BUILDER 1899 // 1900 1901 /** 1902 Generates a computed property that will look up the passed property path 1903 the first time you try to get the value. Use this whenever you want to 1904 define an outlet that points to another view or object. The root object 1905 used for the path will be the receiver. 1906 */ 1907 SC.outlet = function (path, root) { 1908 return function (key) { 1909 return (this[key] = SC.objectForPropertyPath(path, (root !== undefined) ? root : this)); 1910 }.property(); 1911 }; 1912 1913 /** @private on unload clear cached divs. */ 1914 SC.CoreView.unload = function () { 1915 // delete view items this way to ensure the views are cleared. The hash 1916 // itself may be owned by multiple view subclasses. 1917 var views = SC.View.views; 1918 if (views) { 1919 for (var key in views) { 1920 if (!views.hasOwnProperty(key)) continue; 1921 delete views[key]; 1922 } 1923 } 1924 }; 1925 1926 /** 1927 @class 1928 1929 Base class for managing a view. Views provide two functions: 1930 1931 1. They display – translating your application's state into drawing 1932 instructions for the web browser, and 1933 2. They react – acting as responders for incoming keyboard, mouse, and touch 1934 events. 1935 1936 View Basics 1937 ==== 1938 1939 SproutCore's view layer is made up of a tree of SC.View instances, nested 1940 using the `childViews` list – usually an array of local property names. You 1941 position each view by specifying a set of layout keys, like 'left', 'right', 1942 'width', or 'centerX', in a hash on the layout property. (See the 'layout' 1943 documentation for more.) 1944 1945 Other than positioning, SproutCore relies on CSS for all your styling needs. 1946 Set an array of CSS classes on the `classNames` property, then style them with 1947 standard CSS. (SproutCore's build tools come with Sass support built in, too.) 1948 If you have a class that you want automatically added and removed as another 1949 property changes, take a look at `classNameBindings`. 1950 1951 Different view classes do different things. The so-called "Big Five" view 1952 classes are SC.LabelView, for displaying (optionally editable, optionally 1953 localizable) text; SC.ButtonView, for the user to poke; SC.CollectionView 1954 (most often as its subclass SC.ListView) for displaying an array of content; 1955 SC.ContainerView, for easily swapping child views in and out; and SC.ScrollView, 1956 for containing larger views and allowing them to be scrolled. 1957 1958 All views live in panes (subclasses of SC.Pane, like SC.MainPane and SC.PanelPane), 1959 which are parentless views that know how to append themselves directly to the document. 1960 Panes also serve as routers for events, like mouse, touch and keyboard events, that are 1961 bound for their views. (See "View Events" below for more.) 1962 1963 For best performance, you should define your view and pane instances with `extend()` 1964 inside an SC.Page instance, getting them as needed with `get`. As its name suggests, 1965 SC.Page's only job is to instantiate views once when first requested, deferring the 1966 expensive view creation process until each view is needed. Correctly using SC.Page is 1967 considered an important best practice for high-performance applications. 1968 1969 View Initialization 1970 ==== 1971 1972 When a view is setup, there are several methods you can override that 1973 will be called at different times depending on how your view is created. 1974 Here is a guide to which method you want to override and when: 1975 1976 - `init` -- override this method for any general object setup (such as 1977 observers, starting timers and animations, etc) that you need to happen 1978 every time the view is created, regardless of whether or not its layer 1979 exists yet. 1980 - `render` -- override this method to generate or update your HTML to reflect 1981 the current state of your view. This method is called both when your view 1982 is first created and later anytime it needs to be updated. 1983 - `update` -- Normally, when a view needs to update its content, it will 1984 re-render the view using the render() method. If you would like to 1985 override this behavior with your own custom updating code, you can 1986 replace update() with your own implementation instead. 1987 - `didCreateLayer` -- the render() method is used to generate new HTML. 1988 Override this method to perform any additional setup on the DOM you might 1989 need to do after creating the view. For example, if you need to listen 1990 for events. 1991 - `willDestroyLayer` -- if you implement didCreateLayer() to setup event 1992 listeners, you should implement this method as well to remove the same 1993 just before the DOM for your view is destroyed. 1994 - `didAppendToDocument` -- in theory all DOM setup could be done 1995 in didCreateLayer() as you already have a DOM element instantiated. 1996 However there is cases where the element has to be first appended to the 1997 Document because there is either a bug on the browser or you are using 1998 plugins which objects are not instantiated until you actually append the 1999 element to the DOM. This will allow you to do things like registering 2000 DOM events on flash or quicktime objects. 2001 - `willRemoveFromDocument` -- This method is called on the view immediately 2002 before its layer is removed from the DOM. You can use this to reverse any 2003 setup that is performed in `didAppendToDocument`. 2004 2005 View Events 2006 ==== 2007 2008 One of SproutCore's optimizations is application-wide event delegation: SproutCore 2009 handles and standardizes events for you before sending them through your view layer's 2010 chain of responding views. You should never need to attach event listeners to elements; 2011 instead, just implement methods like `click`, `doubleClick`, `mouseEntered` and 2012 `dataDragHover` on your views. 2013 2014 Note that events generally bubble up an event's responder chain, which is made up of the 2015 targeted view (i.e. the view whose DOM element received the event), and its chain of 2016 parentViews up to its pane. (In certain rare cases, you may wish to manipulate the responder 2017 chain to bypass certain views; you can do so by overriding a view's `nextResponder` property.) 2018 2019 Simple mouse click events 2020 ---- 2021 In many situations, all you need are clicks - in which case, just implement `click` or 2022 `doubleClick` on your views. Note that these events bubble up the responder chain until 2023 they encounter a view which implements the event method. For example, if a view and its 2024 parent both implement `click`, the parent will not be notified of the click. (If you want a 2025 view to handle the event AND allow the event to keep bubbling to its parent views, no 2026 problem: just be sure to return NO from the event method.) 2027 - `click` -- Called on a view when the user clicks the mouse on a view. (Note that the view 2028 on which the user lifts the mouse button will receive the `click` event, regardless of 2029 whether the user depressed the mouse button elsewhere. If you need finer-grained control 2030 than this, see "Granular mouse click events" below.) 2031 - `doubleClick` -- Called on a view when a user has double-clicked it. Double-clicks are 2032 triggered when two clicks of the same button happen within eight pixels and 250ms of each 2033 other. (If you need finer-grained control than this, see "Granular mouse click events" 2034 below.) The same view may receive both `click` and `doubleClick` events. 2035 2036 Note that defining application behavior directly in event handlers is usually a bad idea; you 2037 should follow the target/action pattern when possible. See SC.ButtonView and SC.ActionSupport. 2038 Also note that you will not need to implement event handling yourself on most built-in 2039 SproutCore controls. 2040 2041 Note that `click` and `doubleClick` event handlers on your views will not be notified of touch 2042 events; you must also implement touch handling. See "Touch events" below. 2043 2044 Mouse movement events 2045 ---- 2046 SproutCore normalizes (and brings sanity to) mouse movement events by calculating when 2047 the mouse has entered and exited views, and sending the correct event to each view in 2048 the responder chain. For example, if a mouse moves within a parent view but crosses from 2049 one child view to another, the parent view will receive a mouseMoved event while the child 2050 views will receive mouseEntered and mouseExit events. 2051 2052 In contrast to mouse click events, mouse movement events are called on the entire responder 2053 chain regardless of how you handle it along the way - a view and its parent, both implementing 2054 event methods, will both be notified of the event. 2055 2056 - `mouseEntered` -- Called when the cursor first enters a view. Called on every view that has 2057 just entered the responder chain. 2058 - `mouseMoved` -- Called when the cursor moves over a view. 2059 - `mouseExited` -- Called when the cursor leaves a view. Called on every view that has 2060 just exited the responder chain. 2061 2062 Granular mouse click events 2063 ---- 2064 If you need more granular handling of mouse click events than what is provided by `click` 2065 and `doubleClick`, you can handle their atomic components `mouseDown`, `mouseDrag` and 2066 `mouseUp`. Like the compound events, these events bubble up their responder chain towards 2067 the pane until they find an event which implements the event handler method. (Again, to 2068 handle an event but allow it to continue bubbling, just return NO.) 2069 2070 It bears emphasizing that `mouseDrag` and `mouseUp` events for a given mouse click sequence 2071 are *only ever called* on the view which successfully responded to the `mouseDown` event. This 2072 gives `mouseDown` control over which view responder-chain is allowed to handle the entire 2073 click sequence. 2074 2075 (Note that because of how events bubble up the responder chain, if a child view implements 2076 `mouseDown` but not `mouseDrag` or `mouseUp`, those events will bubble to its parent. This 2077 may cause unexpected behavior if similar events are handled at different parts of your view 2078 hierarchy, for example if you handle `mouseDown` in a child and a parent, and only handle 2079 `mouseUp` in the parent.) 2080 2081 - `mouseDown` -- Called on the target view and responder chain when the user depresses a 2082 button. A view must implement `mouseDown` (and not return NO) in order to be notified 2083 of the subsequent drag and up events. 2084 - `mouseDrag` -- Called on the target view if it handled mouseDown. A view must implement 2085 mouseDown (and not return NO) in order to receive mouseDrag; only the view which handled a 2086 given click sequence's mouseDown will receive `mouseDrag` events (and will continue to 2087 receive them even if the user drags the mouse off of it). 2088 - `mouseUp` -- Called on the target view when the user lifts a mouse button. A view must 2089 implement mouseDown (and not return NO) in order to receive mouseUp. 2090 2091 SproutCore implements a higher-level API for handling in-application dragging and dropping. 2092 See `SC.Drag`, `SC.DragSourceProtocol`, `SC.DragDataSourceProtocol`, and `SC.DropTargetProtocol` 2093 for more. 2094 2095 Data-drag events 2096 ---- 2097 Browsers implement a parallel system of events for drags which bring something with them: for 2098 example, dragging text, an image, a URL or (in modern browsers) a file. They behave differently, 2099 and require different responses from the developer, so SproutCore implements them as a separate 2100 set of "data drag" events. These behave much like mouse events; the data-drag movement events 2101 bubble indiscriminately, and the data-drag drop event bubbles until it finds a view which handles 2102 it (and doesn't return NO). 2103 2104 By default, SproutCore cancels the default behavior of any data drag event which carries URLs 2105 or files, as by default these would quit the app and open the dragged item in the browser. If 2106 you wish to implement data drag-and-drop support in your application, you should set the event's 2107 dataTransfer.dropEffect property to 'copy' in a `dataDragHovered` event handler. 2108 2109 - `dataDragEntered` -- Triggered when a data drag enters a view. You can use this handler to 2110 update the view to visually signal that a drop is possible. 2111 - `dataDragHovered` -- Triggered when the browser sends a dragover event to a view. If you want 2112 to support dropping data on your view, you must set the event's `dataTransfer.dropEffect` 2113 property to 'copy' (or related). Note that `dataDragHovered` is given access to dragenter 2114 events as well, so you do not need to worry about this in your `dataDragEntered` methods. 2115 - `dataDragDropped` -- If the last hover event's dropEffect was set correctly, this event will 2116 give the view access to the data that was dropped. This event bubbles up the responder chain 2117 until it finds a view which handles it (and doesn't return NO). 2118 - `dataDragExited` -- Triggered when a data drag leaves a view. You can use this handler to 2119 update the view to remove the visual drop signal. This event is fired regardless of whether 2120 a drop occurred. 2121 2122 2123 Touch events 2124 ---- 2125 Touch events can be much more complicated than mouse events: multiple touches may be in flight 2126 at once, and views may wish to handle average touches rather than individual touches. 2127 2128 Basic support for touch events is required to make your application touch-aware. (You will not 2129 need to implement touch support for built-in SproutCore controls, which are touch-aware out of 2130 the box.) The basic touch event handlers are `touchStart` and `touchEnd`; if all you need is 2131 basic support then you can simply proxy these events to their mouse counterparts. 2132 2133 The counterpart to `mouseDragged` is `touchesDragged`, which is passed two arguments: a special 2134 multitouch event object which includes methods for accessing information about all currently 2135 in-flight touches, and a list of touches active on the current view. If you need to check the 2136 status of touches currently being handled by other views, the special multitouch event object 2137 exposes the `touchesForView` method. It also exposes the convenient `averagedTouchesForView` 2138 method, which gives you easy access to an average touch center and distance. Unlike `mouseDragged`, 2139 `touchesDragged` does not bubble, being only called on views whic handled `touchStart` for touches 2140 which have moved. 2141 2142 To facilitate intuitive behavior in situations like scroll views with touch handlers inside them, 2143 you may capture a touch from part way up its responder chain before it has a chance to bubble 2144 up from the target. To capture a touch, expose a method on your view called `captureTouch` which 2145 accepts the touch as its only argument, and which returns YES if you would like to capture that 2146 touch. A captured touch will not bubble as normal, instead bubbling up from the capture point. Any 2147 child views will not have the opportunity to handle the captured event unless you implement custom 2148 responder swapping yourself. 2149 2150 Touch events bubble differently than mouse and keyboard events. The initial reverse `captureTouch` 2151 bubbling is followed by regular `touchStart` bubbling; however, once this process has found a view 2152 that's willing to respond to the touch, further events are applied only to that view. If a view 2153 wishes to assign respondership for a touch to a different view, it can call one of several methods 2154 on the touch object. For a fuller discussion of touch events, touch responder behavior, and the touch 2155 object itself, see the documentation for SC.Touch. 2156 2157 Keyboard events 2158 ---- 2159 The basic key events are `keyDown` and `keyUp`. In order to be notified of keyboard events, 2160 a view must set `acceptsFirstResponder` to `YES`, and be on an active pane with 2161 `acceptsKeyPane` set to YES. (You may also need to call `becomeFirstResponder` on your view 2162 on a `mouseDown`, for example, to focus it. You can verify whether your view has successfully 2163 received first responder status by checking `isFirstResponder`.) 2164 2165 Note that key events bubble similarly to mouse click events: they will stop bubbling if they 2166 encounter a view which handles the event and does not return NO. 2167 2168 SproutCore implements a set of very convenient, higher-level keyboard events for action keys 2169 such as *tab*, *enter*, and the arrow keys. These are not triggered automatically, but you 2170 can gain access to them by proxying the keyboard event of your choice to `interpretKeyEvent`. 2171 For example: 2172 2173 // Proxy the keyboard event to SC's built-in interpreter. 2174 keyDown: function(evt) { 2175 return this.interpretKeyEvents(evt); 2176 }, 2177 // The interpreter will trigger the view's `cancel` event if the escape key was pressed. 2178 cancel: function(evt) { 2179 console.log('The escape key was pressed.''); 2180 } 2181 2182 This will analyze the key press and fire an appropriate event. These events include, but are 2183 not limited to: 2184 2185 - `moveUp`, `moveDown`, `moveLeft`, `moveRight` -- The arrow keys 2186 - `insertNewline` -- The enter key (note the lower-case 'line') 2187 - `cancel` -- The escape key 2188 - `insertTab` -- The tab key 2189 - `insertBacktab` -- Shift + the tab key 2190 - `moveToBeginningOfDocument` -- The *home* key 2191 - `moveToEndOfDocument` -- The *end* key 2192 - `pageUp` and `pageDown` 2193 - `moveLeftAndModifySelection` -- Shift + the left arrow 2194 - `selectAll` -- Ctrl + A / Cmd + A 2195 2196 For a full list of available methods, see the key values on SC.BASE_KEY_BINDINGS and 2197 SC.MODIFIED_KEY_BINDINGS. 2198 2199 @extends SC.Responder 2200 @extends SC.DelegateSupport 2201 @since SproutCore 1.0 2202 2203 */ 2204 SC.View = SC.CoreView.extend(/** @scope SC.View.prototype */{ 2205 classNames: ['sc-view'], 2206 2207 displayProperties: [], 2208 2209 /** @private Enhance. */ 2210 _executeQueuedUpdates: function () { 2211 sc_super(); 2212 2213 // Enabled 2214 // Update the layout style of the layer if necessary. 2215 if (this._enabledStyleNeedsUpdate) { 2216 this._doUpdateEnabledStyle(); 2217 } 2218 2219 // Layout 2220 // Update the layout style of the layer if necessary. 2221 if (this._layoutStyleNeedsUpdate) { 2222 this._doUpdateLayoutStyle(); 2223 } 2224 }, 2225 2226 /** Apply the attributes to the context. */ 2227 applyAttributesToContext: function (context) { 2228 // Cursor 2229 var cursor = this.get('cursor'); 2230 if (cursor) { context.addClass(cursor.get('className')); } 2231 2232 // Enabled 2233 if (!this.get('isEnabled')) { 2234 context.addClass('disabled'); 2235 context.setAttr('aria-disabled', 'true'); 2236 } 2237 2238 // Layout 2239 // Have to pass 'true' for second argument for legacy. 2240 this.renderLayout(context, true); 2241 2242 if (this.get('useStaticLayout')) { context.addClass('sc-static-layout'); } 2243 2244 // Background color defaults to null; for performance reasons we should ignore it 2245 // unless it's ever been non-null. 2246 var backgroundColor = this.get('backgroundColor'); 2247 if (!SC.none(backgroundColor) || this._scv_hasBackgroundColor) { 2248 this._scv_hasBackgroundColor = YES; 2249 if (backgroundColor) context.setStyle('backgroundColor', backgroundColor); 2250 else context.removeStyle('backgroundColor'); 2251 } 2252 2253 // Theming 2254 var theme = this.get('theme'); 2255 var themeClassNames = theme.classNames, idx, len = themeClassNames.length; 2256 2257 for (idx = 0; idx < len; idx++) { 2258 context.addClass(themeClassNames[idx]); 2259 } 2260 2261 sc_super(); 2262 2263 var renderDelegate = this.get('renderDelegate'); 2264 if (renderDelegate && renderDelegate.className) { 2265 context.addClass(renderDelegate.className); 2266 } 2267 2268 // @if(debug) 2269 if (renderDelegate && renderDelegate.name) { 2270 SC.Logger.error("Render delegates now use 'className' instead of 'name'."); 2271 SC.Logger.error("Name '%@' will be ignored.", renderDelegate.name); 2272 } 2273 // @endif 2274 }, 2275 2276 /** 2277 Computes what the frame of this view would be if the parent were resized 2278 to the passed dimensions. You can use this method to project the size of 2279 a frame based on the resize behavior of the parent. 2280 2281 This method is used especially by the scroll view to automatically 2282 calculate when scrollviews should be visible. 2283 2284 Passing null for the parent dimensions will use the actual current 2285 parent dimensions. This is the same method used to calculate the current 2286 frame when it changes. 2287 2288 @param {Rect} pdim the projected parent dimensions (optional) 2289 @returns {Rect} the computed frame 2290 */ 2291 computeFrameWithParentFrame: function (pdim) { 2292 // Layout. 2293 var layout = this.get('layout'), 2294 f; 2295 2296 // We can't predict the frame for static layout, so just return the view's 2297 // current frame (see original computeFrameWithParentFrame in views/view.js) 2298 if (this.get('useStaticLayout')) { 2299 f = sc_super(); 2300 f = f ? this._sc_adjustForBorder(f, layout) : null; 2301 f = f ? this._sc_adjustForScale(f, layout) : null; 2302 return f; 2303 } 2304 2305 f = {}; 2306 2307 var error, layer, AUTO = SC.LAYOUT_AUTO, 2308 dH, dW, //shortHand for parentDimensions 2309 lR = layout.right, 2310 lL = layout.left, 2311 lT = layout.top, 2312 lB = layout.bottom, 2313 lW = layout.width, 2314 lH = layout.height, 2315 lcX = layout.centerX, 2316 lcY = layout.centerY; 2317 2318 if (lW === AUTO) { 2319 SC.throw(("%@.layout() cannot use width:auto if staticLayout is disabled").fmt(this), "%@".fmt(this), -1); 2320 } 2321 2322 if (lH === AUTO) { 2323 SC.throw(("%@.layout() cannot use height:auto if staticLayout is disabled").fmt(this), "%@".fmt(this), -1); 2324 } 2325 2326 if (!pdim) { pdim = this.computeParentDimensions(layout); } 2327 dH = pdim.height; 2328 dW = pdim.width; 2329 2330 // handle left aligned and left/right 2331 if (!SC.none(lL)) { 2332 if (SC.isPercentage(lL)) { 2333 f.x = dW * lL; 2334 } else { 2335 f.x = lL; 2336 } 2337 2338 if (lW !== undefined) { 2339 if (lW === AUTO) { f.width = AUTO; } 2340 else if (SC.isPercentage(lW)) { f.width = dW * lW; } 2341 else { f.width = lW; } 2342 } else { // better have lR! 2343 f.width = dW - f.x; 2344 if (lR && SC.isPercentage(lR)) { f.width = f.width - (lR * dW); } 2345 else { f.width = f.width - (lR || 0); } 2346 } 2347 2348 // handle right aligned 2349 } else if (!SC.none(lR)) { 2350 if (SC.none(lW)) { 2351 if (SC.isPercentage(lR)) { 2352 f.width = dW - (dW * lR); 2353 } 2354 else f.width = dW - lR; 2355 f.x = 0; 2356 } else { 2357 if (lW === AUTO) f.width = AUTO; 2358 else if (SC.isPercentage(lW)) f.width = dW * lW; 2359 else f.width = (lW || 0); 2360 if (SC.isPercentage(lW)) f.x = dW - (lR * dW) - f.width; 2361 else f.x = dW - lR - f.width; 2362 } 2363 2364 // handle centered 2365 } else if (!SC.none(lcX)) { 2366 if (lW === AUTO) f.width = AUTO; 2367 else if (SC.isPercentage(lW)) f.width = lW * dW; 2368 else f.width = (lW || 0); 2369 if (SC.isPercentage(lcX)) f.x = (dW - f.width) / 2 + (lcX * dW); 2370 else f.x = (dW - f.width) / 2 + lcX; 2371 } else { 2372 f.x = 0; // fallback 2373 if (SC.none(lW)) { 2374 f.width = dW; 2375 } else { 2376 if (lW === AUTO) f.width = AUTO; 2377 if (SC.isPercentage(lW)) f.width = lW * dW; 2378 else f.width = (lW || 0); 2379 } 2380 } 2381 2382 // handle top aligned and top/bottom 2383 if (!SC.none(lT)) { 2384 if (SC.isPercentage(lT)) f.y = lT * dH; 2385 else f.y = lT; 2386 if (lH !== undefined) { 2387 if (lH === AUTO) f.height = AUTO; 2388 else if (SC.isPercentage(lH)) f.height = lH * dH; 2389 else f.height = lH; 2390 } else { // better have lB! 2391 if (lB && SC.isPercentage(lB)) f.height = dH - f.y - (lB * dH); 2392 else f.height = dH - f.y - (lB || 0); 2393 } 2394 2395 // handle bottom aligned 2396 } else if (!SC.none(lB)) { 2397 if (SC.none(lH)) { 2398 if (SC.isPercentage(lB)) f.height = dH - (lB * dH); 2399 else f.height = dH - lB; 2400 f.y = 0; 2401 } else { 2402 if (lH === AUTO) f.height = AUTO; 2403 if (lH && SC.isPercentage(lH)) f.height = lH * dH; 2404 else f.height = (lH || 0); 2405 if (SC.isPercentage(lB)) f.y = dH - (lB * dH) - f.height; 2406 else f.y = dH - lB - f.height; 2407 } 2408 2409 // handle centered 2410 } else if (!SC.none(lcY)) { 2411 if (lH === AUTO) f.height = AUTO; 2412 if (lH && SC.isPercentage(lH)) f.height = lH * dH; 2413 else f.height = (lH || 0); 2414 if (SC.isPercentage(lcY)) f.y = (dH - f.height) / 2 + (lcY * dH); 2415 else f.y = (dH - f.height) / 2 + lcY; 2416 2417 // fallback 2418 } else { 2419 f.y = 0; // fallback 2420 if (SC.none(lH)) { 2421 f.height = dH; 2422 } else { 2423 if (lH === AUTO) f.height = AUTO; 2424 if (SC.isPercentage(lH)) f.height = lH * dH; 2425 else f.height = lH || 0; 2426 } 2427 } 2428 2429 f.x = Math.floor(f.x); 2430 f.y = Math.floor(f.y); 2431 if (f.height !== AUTO) f.height = Math.floor(f.height); 2432 if (f.width !== AUTO) f.width = Math.floor(f.width); 2433 2434 // if width or height were set to auto and we have a layer, try lookup 2435 if (f.height === AUTO || f.width === AUTO) { 2436 layer = this.get('layer'); 2437 if (f.height === AUTO) f.height = layer ? layer.clientHeight : 0; 2438 if (f.width === AUTO) f.width = layer ? layer.clientWidth : 0; 2439 } 2440 2441 // Okay we have all our numbers. Let's adjust them for things. 2442 2443 // First, adjust for border. 2444 f = this._sc_adjustForBorder(f, layout); 2445 2446 // Make sure the width/height fix their min/max (note the inlining of SC.none for performance)... 2447 /*jshint eqnull:true */ 2448 if ((layout.maxHeight != null) && (f.height > layout.maxHeight)) f.height = layout.maxHeight; 2449 if ((layout.minHeight != null) && (f.height < layout.minHeight)) f.height = layout.minHeight; 2450 if ((layout.maxWidth != null) && (f.width > layout.maxWidth)) f.width = layout.maxWidth; 2451 if ((layout.minWidth != null) && (f.width < layout.minWidth)) f.width = layout.minWidth; 2452 2453 // Finally, adjust for scale. 2454 f = this._sc_adjustForScale(f, layout); 2455 2456 return f; 2457 }, 2458 2459 init: function () { 2460 sc_super(); 2461 2462 // Enabled. 2463 // If the view is pre-configured as disabled, then go to the proper initial state. 2464 if (!this.get('isEnabled')) { this._doDisable(); } 2465 2466 // Layout 2467 this._previousLayout = this.get('layout'); 2468 2469 // Apply the automatic child view layout if it is defined. 2470 var childViewLayout = this.childViewLayout; 2471 if (childViewLayout) { 2472 // Layout the child views once. 2473 this.set('childViewsNeedLayout', true); 2474 this.layoutChildViewsIfNeeded(); 2475 2476 // If the child view layout is live, start observing affecting properties. 2477 if (this.get('isChildViewLayoutLive')) { 2478 this.addObserver('childViews.[]', this, this._cvl_childViewsDidChange); 2479 // DISABLED. this.addObserver('childViewLayout', this, this._cvl_childViewLayoutDidChange); 2480 this.addObserver('childViewLayoutOptions', this, this._cvl_childViewLayoutDidChange); 2481 2482 // Initialize the child views. 2483 this._cvl_setupChildViewsLiveLayout(); 2484 2485 // Initialize our own frame observer. 2486 if (!this.get('isFixedSize') && childViewLayout.layoutDependsOnSize && childViewLayout.layoutDependsOnSize(this)) { 2487 this.addObserver('frame', this, this._cvl_childViewLayoutDidChange); 2488 } 2489 } 2490 } 2491 2492 // Theming 2493 this._lastTheme = this.get('theme'); 2494 2495 }, 2496 2497 /** @private */ 2498 destroy: function () { 2499 // Clean up. 2500 this._previousLayout = null; 2501 2502 return sc_super(); 2503 }, 2504 2505 /** SC.CoreView.prototype. */ 2506 removeChild: function(view) { 2507 // Manipulation 2508 if (!view) { return this; } // nothing to do 2509 if (view.parentView !== this) { 2510 throw new Error("%@.removeChild(%@) must belong to parent".fmt(this, view)); 2511 } 2512 2513 // notify views 2514 // TODO: Deprecate these notifications. 2515 if (view.willRemoveFromParent) { view.willRemoveFromParent() ; } 2516 if (this.willRemoveChild) { this.willRemoveChild(view) ; } 2517 2518 sc_super(); 2519 2520 return this; 2521 } 2522 2523 }); 2524 2525 //unload views for IE, trying to collect memory. 2526 if (SC.browser.isIE) SC.Event.add(window, 'unload', SC.View, SC.View.unload); 2527 2528 2529