1 // ==========================================================================
  2 // Project:   SproutCore - JavaScript Application Framework
  3 // Copyright: ©2006-2011 Strobe Inc. and contributors.
  4 //            Portions ©2008-2011 Apple Inc. All rights reserved.
  5 // License:   Licensed under MIT license (see license.js)
  6 // ==========================================================================
  7 
  8 sc_require("views/toolbar");
  9 
 10 /** @class
 11   WorkspaceView manages a content view and two optional toolbars (top and bottom).
 12   You want to use WorkspaceView in one of two situations: iPhone apps where the toolbars
 13   need to change size automatically based on orientation (this does that, isn't that
 14   handy!) and iPad apps where you would like the masterIsHidden property to pass through.
 15 
 16   @since SproutCore 1.2
 17   @extends SC.View
 18   @author Alex Iskander
 19 */
 20 SC.WorkspaceView = SC.View.extend(
 21 /** @scope SC.WorkspaceView.prototype */ {
 22 
 23   /**
 24     @type Array
 25     @default ['sc-workspace-view']
 26     @see SC.View#classNames
 27   */
 28   classNames: ["sc-workspace-view"],
 29 
 30   /**
 31     @type Array
 32     @default "hasTopToolbar hasBottomToolbar".w()
 33     @see SC.View#displayProperties
 34   */
 35   displayProperties: ["hasTopToolbar", "hasBottomToolbar"],
 36 
 37   /**
 38     @type String
 39     @default 'workspaceRenderDelegate'
 40   */
 41   renderDelegateName: 'workspaceRenderDelegate',
 42 
 43   /**
 44     @type SC.View
 45     @default SC.ToolbarView
 46   */
 47   topToolbar: SC.ToolbarView.extend(),
 48 
 49   /**
 50     @type SC.View
 51     @default null
 52   */
 53   bottomToolbar: null,
 54 
 55   /**
 56     The content. Must NOT be null.
 57 
 58     @type SC.View
 59     @default SC.View
 60   */
 61   contentView: SC.View.extend(),
 62 
 63   /**
 64     If you want to automatically resize the toolbars like iPhone
 65     apps should, set to YES.
 66 
 67     @type Boolean
 68     @default NO
 69   */
 70   autoResizeToolbars: NO,
 71 
 72   /**
 73     @type Number
 74     @default 44
 75   */
 76   defaultToolbarSize: 44,
 77 
 78   /**
 79     @type Number
 80     @default 44
 81   */
 82   largeToolbarSize: 44,
 83 
 84   /**
 85     @type Number
 86     @default 30
 87   */
 88   smallToolbarSize: 30,
 89 
 90   /**
 91     @field
 92     @type Number
 93   */
 94   toolbarSize: function() {
 95     if (!this.get("autoResizeToolbars")) return this.get("defaultToolbarSize");
 96     if (this.get("orientation") === SC.HORIZONTAL_ORIENTATION) return this.get("smallToolbarSize");
 97     return this.get("largeToolbarSize");
 98   }.property("autoHideMaster", "orientation"),
 99 
100   /**
101     Tracks the orientation of the view. Possible values:
102 
103       - SC.HORIZONTAL_ORIENTATION
104       - SC.PORTRAIT_ORIENTATION
105 
106     @field
107     @type String
108     @default SC.HORIZONTAL_ORIENTATION
109   */
110   orientation: function() {
111     var f = this.get("frame");
112     if (f.width > f.height) return SC.HORIZONTAL_ORIENTATION;
113     else return SC.VERTICAL_ORIENTATION;
114   }.property("frame").cacheable(),
115 
116   /**
117     @type Boolean
118     @default NO
119   */
120   masterIsHidden: NO,
121 
122   /** @private */
123   masterIsHiddenDidChange: function() {
124     var t, mih = this.get("masterIsHidden");
125     if (t = this.get("topToolbar")) t.set("masterIsHidden", mih);
126     if (t = this.get("bottomToolbar")) t.set("masterIsHidden", mih);
127   }.observes("masterIsHidden"),
128 
129   /// INTERNAL CODE. HERE, THERE BE MONSTERS!
130 
131   /**
132     @private
133     Whenever something that affects the tiling changes (for now, just toolbarSize, but if
134     we allow dynamic changing of toolbars in future, this could include toolbars themselves),
135     we need to update the tiling.
136   */
137   _scmd_tilePropertyDidChange: function() {
138     this.invokeOnce("_scws_tile");
139   }.observes("toolbarSize"),
140 
141   /** @private
142     Creates the child views. Specifically, instantiates master and detail views.
143   */
144   createChildViews: function() {
145     sc_super();
146 
147     var topToolbar = this.get("topToolbar");
148     if (topToolbar) {
149       topToolbar = this.topToolbar = this.activeTopToolbar = this.createChildView(topToolbar);
150       this.appendChild(topToolbar);
151     }
152 
153     var bottomToolbar = this.get("bottomToolbar");
154     if (bottomToolbar) {
155       bottomToolbar = this.bottomToolbar = this.activeBottomToolbar = this.createChildView(bottomToolbar);
156       this.appendChild(bottomToolbar);
157     }
158 
159     var content = this.get("contentView");
160     content = this.contentView = this.activeContentView = this.createChildView(content);
161     this.appendChild(content);
162 
163     this.invokeOnce("_scws_tile");
164   },
165 
166   /**
167     @private
168     Tiles the views as necessary.
169   */
170   _scws_tile: function() {
171     var contentTop = 0, contentBottom = 0,
172         topToolbar = this.get("topToolbar"),
173         bottomToolbar = this.get("bottomToolbar"),
174         content = this.get("contentView"),
175         toolbarSize = this.get("toolbarSize");
176 
177       // basically, if there is a top toolbar, we position it and change contentTop.
178     if (topToolbar) {
179       topToolbar.set("layout", {
180         left: 0, right: 0, top: 0, height: toolbarSize
181       });
182       contentTop += toolbarSize;
183     }
184 
185     // same for bottom
186     if (bottomToolbar) {
187       bottomToolbar.set("layout", {
188         left: 0, right: 0, bottom: 0, height: toolbarSize
189       });
190       contentBottom += toolbarSize;
191     }
192 
193     // finally, position content
194     this.contentView.set("layout", {
195       left: 0, right: 0, top: contentTop, bottom: contentBottom
196     });
197   },
198 
199   /** @private
200     Returns YES if a top toolbar is present.
201   */
202   hasTopToolbar: function() {
203     if (this.get("topToolbar")) return YES;
204     return NO;
205   }.property("topToolbar").cacheable(),
206 
207   /** @private
208     Returns YES if a bottom toolbar is present.
209   */
210   hasBottomToolbar: function() {
211     if (this.get("bottomToolbar")) return YES;
212     return NO;
213   }.property("bottomToolbar").cacheable(),
214 
215   /** @private
216     Called by the individual toolbar/contentView observers at runloop end when the toolbars change.
217   */
218   childDidChange: function() {
219     this._scws_tile();
220   },
221 
222   /** @private
223     For subclassing, this is the currently displaying top toolbar.
224   */
225   activeTopToolbar: null,
226 
227   /** @private
228     For subclassing, this is the currently displaying bottom toolbar.
229   */
230   activeBottomToolbar: null,
231 
232   /** @private
233     For subclassing, this is the currently displaying content view.
234   */
235   activeContentView: null,
236 
237   /** @private
238     Called when the top toolbar changes. It appends it, removes any old ones, and calls toolbarsDidChange.
239 
240     You may want to override this if, for instance, you want to add transitions of some sort (should be trivial).
241   */
242   topToolbarDidChange: function() {
243     var active = this.activeTopToolbar, replacement = this.get("topToolbar");
244     if (active) {
245       if (active.createdByParent) {
246         container.removeChildAndDestroy(active);
247       } else {
248         container.removeChild(active);
249       }
250     }
251     if (replacement) {
252       this.appendChild(replacement);
253     }
254 
255     this.activeTopToolbar = replacement;
256     this.invokeLast("childDidChange");
257   }.observes("topToolbar"),
258 
259   /**
260     @private
261   */
262   bottomToolbarDidChange: function() {
263     var active = this.activeBottomToolbar, replacement = this.get("bottomToolbar");
264     if (active) {
265       if (active.createdByParent) {
266         container.removeChildAndDestroy(active);
267       } else {
268         container.removeChild(active);
269       }
270     }
271     if (replacement) {
272       this.appendChild(replacement);
273     }
274 
275     this.activeBottomToolbar = replacement;
276     this.invokeLast("childDidChange");
277   }.observes("bottomToolbar"),
278 
279   /** @private */
280   contentViewDidChange: function() {
281     var active = this.activeContentView, replacement = this.get("contentView");
282     if (active) {
283       if (active.createdByParent) {
284         container.removeChildAndDestroy(active);
285       } else {
286         container.removeChild(active);
287       }
288     }
289     if (replacement) {
290       this.appendChild(replacement);
291     }
292 
293     this.activeContentView = replacement;
294     this.invokeLast("childDidChange");
295   }.observes("contentView")
296 
297 });
298