1 // ==========================================================================
  2 // Project:   SproutCore
  3 // Copyright: @2012 7x7 Software, Inc.
  4 // License:   Licensed under MIT license (see license.js)
  5 // ==========================================================================
  6 sc_require("views/view");
  7 
  8 // When in debug mode, developers can log the design mode.
  9 //@if (debug)
 10 SC.LOG_DESIGN_MODE = false;
 11 //@endif
 12 
 13 // The class names assigned to view elements depending on the current design
 14 // mode.
 15 SC.DESIGN_MODE_CLASS_NAMES = {
 16   s: 'sc-small',
 17   m: 'sc-medium',
 18   l: 'sc-large',
 19   xl: 'sc-xlarge'
 20 };
 21 
 22 /** @private This adds design modes support to SC.View. */
 23 SC.View.reopen(
 24   /** @scope SC.View.prototype */ {
 25 
 26   // ------------------------------------------------------------------------
 27   // Properties
 28   //
 29 
 30   /**
 31     The current design mode of the application and this view.
 32 
 33     If the application has designModes specified, this property will be set
 34     automatically when the view is created and as the window size changes
 35     across the design mode boundaries.
 36 
 37     @property {String}
 38     @default null
 39   */
 40   designMode: null,
 41 
 42   /**
 43     The dynamic adjustments to apply to this view depending on the current
 44     design mode.
 45 
 46     If you specify designModes on the application, this hash will be checked
 47     for a matching adjustment to apply for the current design mode.
 48 
 49     @property {Object}
 50     @default null
 51   */
 52   modeAdjust: null,
 53 
 54   // ------------------------------------------------------------------------
 55   // Methods
 56   //
 57 
 58   /** @private Recursively set the designMode on each child view. */
 59   adjustChildDesignModes: function (lastDesignMode, designMode) {
 60     var childViews = this.get('childViews');
 61 
 62     var i, len = childViews.get('length');
 63     for (i = 0; i < len; i++) {
 64       var childView = childViews.objectAt(i);
 65 
 66       childView.updateDesignMode(lastDesignMode, designMode);
 67     }
 68   },
 69 
 70   _sc_assignProperty: function (key, value) {
 71     if (key === 'layout') {
 72       var newExplicitLayout = this._sc_computeExplicitLayout(value), // Convert the layout to an explicit layout.
 73           layoutDiff = {},
 74           explicitLayout = this.get('explicitLayout');
 75       for (var layoutKey in newExplicitLayout) {
 76         var currentValue = explicitLayout[layoutKey];
 77 
 78         layoutDiff[layoutKey] = currentValue === undefined ? null : currentValue;
 79 
 80         if (layoutKey === 'centerX') {
 81           layoutDiff.left = explicitLayout.left;
 82           layoutDiff.right = explicitLayout.right;
 83         }
 84 
 85         if (layoutKey === 'centerY') {
 86           layoutDiff.top = explicitLayout.top;
 87           layoutDiff.bottom = explicitLayout.bottom;
 88         }
 89       }
 90 
 91       this._originalProperties.layout = layoutDiff;
 92     } else {
 93       // Get the original value of the property for reset.
 94       this._originalProperties[key] = this.get(key);
 95     }
 96 
 97     // Apply the override.
 98     if (key === 'layout') {
 99       //@if(debug)
100       if (SC.LOG_DESIGN_MODE || this.SC_LOG_DESIGN_MODE) {
101         SC.Logger.log('  - Adjusting %@: %@ (cached as %@)'.fmt(key, SC.inspect(value), SC.inspect(this._originalProperties[key])));
102       }
103       //@endif
104       this.adjust(value);
105     } else {
106       //@if(debug)
107       if (SC.LOG_DESIGN_MODE || this.SC_LOG_DESIGN_MODE) {
108         SC.Logger.log('  - Setting %@: %@ (cached as %@)'.fmt(key, SC.inspect(value), SC.inspect(this._originalProperties[key])));
109       }
110       //@endif
111       this.set(key,value);
112     }
113   },
114 
115   _sc_revertProperty: function (key, oldValue) {
116     //@if(debug)
117     if (SC.LOG_DESIGN_MODE || this.SC_LOG_DESIGN_MODE) {
118       SC.Logger.log('  - Resetting %@ to %@'.fmt(key, SC.inspect(oldValue)));
119     }
120     //@endif
121 
122     if (key === 'layout') {
123       this.adjust(oldValue);
124     } else {
125       this.set(key, oldValue);
126     }
127   },
128 
129   /**
130     Updates the design mode for this view.
131 
132     This method is called automatically by the view's pane whenever the pane
133     determines that the design mode, as specified in the pane's designModes
134     property, has changed.  You should likely never need to call it manually.
135 
136     This method updates the designMode property of the view, adjusts
137     the layout if a matching design adjustment in the view's designAdjustments
138     property is found and adds a class name to the view for the current
139     design mode.
140 
141     Note that updating the design mode also updates all child views of this
142     view.
143 
144     @param {String} lastDesignMode the previously applied design mode
145     @param {String} [designMode] the name of the design mode
146    */
147   updateDesignMode: function (lastDesignMode, designMode) {
148     // Fast path.
149     if (lastDesignMode === designMode) { return; }
150 
151     var classNames = this.get('classNames'),
152       modeAdjust,
153       elem,
154       key,
155       layer,
156       newProperties,
157       prevProperties,
158       size;
159 
160     this.set('designMode', designMode);
161 
162     // Get the size name portion of the mode.
163     if (designMode) {
164       size = designMode.split('_')[0];
165     }
166 
167     modeAdjust = this.get('modeAdjust');
168     if (modeAdjust) {
169       // Stop observing changes for a moment.
170       this.beginPropertyChanges();
171 
172       // Unset any previous properties.
173       prevProperties = this._originalProperties;
174       if (prevProperties) {
175         //@if(debug)
176         if (SC.LOG_DESIGN_MODE || this.SC_LOG_DESIGN_MODE) {
177           SC.Logger.log('%@ — Removing previous design property overrides set by "%@":'.fmt(this, lastDesignMode));
178         }
179         //@endif
180 
181         for (key in prevProperties) {
182           this._sc_revertProperty(key, prevProperties[key]);
183         }
184 
185         // Remove the cache.
186         this._originalProperties = null;
187       }
188 
189       if (designMode) {
190         // Apply new properties. The orientation specific properties override the size properties.
191         if (modeAdjust[size] || modeAdjust[designMode]) {
192           newProperties = SC.merge(modeAdjust[size], modeAdjust[designMode]);
193 
194           //@if(debug)
195           if (SC.LOG_DESIGN_MODE || this.SC_LOG_DESIGN_MODE) {
196             SC.Logger.log('%@ — Applying design properties for "%@":'.fmt(this, designMode));
197           }
198           //@endif
199 
200           // Cache the original properties for reset.
201           this._originalProperties = {};
202           for (key in newProperties) {
203             this._sc_assignProperty(key, newProperties[key]);
204           }
205         }
206       }
207 
208       // Resume observing.
209       this.endPropertyChanges();
210     }
211 
212     // Apply the design mode as a class name.
213     // This is done here rather than through classNameBindings, because we can
214     // do it here without needing to setup a designMode observer for each view.
215     var designClass;
216     layer = this.get('layer');
217     if (layer) {
218       elem = this.$();
219 
220       // If we had previously added a class to the element, remove it.
221       if (lastDesignMode) {
222         designClass = SC.DESIGN_MODE_CLASS_NAMES[lastDesignMode.split('_')[0]];
223         elem.removeClass(designClass);
224         classNames.removeObject(designClass);
225       }
226 
227       // If necessary, add a new class.
228       if (designMode) {
229         designClass = SC.DESIGN_MODE_CLASS_NAMES[size];
230         elem.addClass(designClass);
231         classNames.push(designClass);
232       }
233     } else {
234       if (designMode) {
235         designClass = SC.DESIGN_MODE_CLASS_NAMES[size];
236         // Ensure that it gets into the classNames array
237         // so it is displayed when we render.
238         classNames.push(designClass);
239       }
240     }
241 
242     // Set the designMode on each child view (may be null).
243     this.adjustChildDesignModes(lastDesignMode, designMode);
244   }
245 
246 });
247