1 // ==========================================================================
  2 // Project:   SproutCore - JavaScript Application Framework
  3 // Copyright: ©2008-2011 Apple Inc. All rights reserved.
  4 // License:   Licensed under MIT license (see license.js)
  5 // ==========================================================================
  6 
  7 /**
  8   @namespace 
  9   A view is empty if all of its children are empty. A view is automatically 
 10   counted as empty if it is not visible, and not empty if it is being edited.
 11 
 12   Any field that does not mix in CalculatesEmptiness will be considered empty.
 13 */
 14 SC.CalculatesEmptiness = {
 15   
 16   hasCalculatesEmptiness: YES,
 17   
 18   /**
 19   YES if the value of the field is empty. Defaults to yes so if you don't override this, it will only consider child fields in emptiness calculation (this is the desired behavior for forms).
 20   */
 21   isValueEmpty: YES,
 22   
 23   /**
 24     Defaults to YES so that a field with no children will act properly.
 25   */
 26   _scce_childrenAreEmpty: YES,
 27   
 28   /**
 29     If YES, all visible fields are considered non-empty when editing.
 30     @type Boolean
 31     @default YES
 32   */
 33   isEditingAffectsIsEmpty: YES,
 34 
 35 
 36   _scce_isEditingDidChange: function() {
 37     if(this.get('isEditingAffectsIsEmpty')) {
 38       this.notifyPropertyChange('isEmpty');
 39     }
 40   }.observes('isEditing'),
 41   
 42   /**
 43     YES if the field itself is empty. Even if the value is non-empty, the field can be empty due to isVisible.
 44   */
 45   isEmpty: function() {
 46     // When not visible, it is empty. Period.
 47     if (!this.get('isVisible')) {
 48       return YES;
 49     }
 50 
 51     // if it is editing and edit mode affects emptiness, it is NOT empty.
 52     if (this.get('isEditingAffectsIsEmpty') && this.get('isEditing')) {
 53       return NO;
 54     }
 55 
 56     // otherwise, it is empty if its value AND all children are empty.
 57     return this.get('isValueEmpty') && this.get('_scce_childrenAreEmpty');
 58   }.property('isValueEmpty', 'isVisible', '_scce_childrenAreEmpty', 'isEditingAffectsIsEmpty').cacheable(),
 59 
 60   /**
 61     When emptiness changes tell the parent to re-check its own emptiness.
 62   */
 63   _scce_isEmptyDidChange: function() {
 64     var parentView = this.get('parentView');
 65 
 66     if (parentView && parentView._scce_emptinessDidChangeFor) {
 67       parentView._scce_emptinessDidChangeFor(this);
 68     }
 69   }.observes('isEmpty'),
 70 
 71   initMixin: function() {
 72     this._scce_emptinessDidChangeFor();
 73   },
 74 
 75   /**
 76   Called by fields when their emptiness changes.
 77 
 78   Always triggers (at end of run loop) a relayout of fields.
 79   */
 80   _scce_emptinessDidChangeFor: function(child) {
 81     this.invokeOnce('_scce_recalculateChildrensEmptiness');
 82   },
 83 
 84   /**
 85   By default, a view will check all of its fields to determine if it is empty. It is only empty if all of its value fields are.
 86   */
 87   _scce_recalculateChildrensEmptiness: function()
 88   {
 89     // in short, we get the value fields, if we come across one that is not empty
 90     // we cannot be empty.
 91     var views = this.get('childViews');
 92 
 93     var empty = YES,
 94     len = views.length,
 95     field;
 96 
 97     for (var i = 0; i < len; i++)
 98     {
 99       field = views[i];
100 
101       if (!field.get('isEmpty') && field.hasCalculatesEmptiness) {
102         empty = NO;
103         break;
104       }
105     }
106     
107     this.setIfChanged('_scce_childrenAreEmpty', empty);
108   }
109 };
110 
111