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/workspace");
  9 
 10 /**
 11   @static
 12   @type String
 13   @constant
 14 */
 15 SC.TO_LEFT = "TOLEFT";
 16 
 17 /**
 18   @static
 19   @type String
 20   @constant
 21 */
 22 SC.TO_RIGHT = "TORIGHT";
 23 
 24 
 25 /** @class
 26 
 27   NavigationView is very loosely based on UINavigationController:
 28   that is, it implements a push/pop based API. 
 29   
 30   NavigationView checks if the view is NavigationBuildable--that is, if it has 
 31   
 32   Views may specify a topToolbar or bottomToolbar property. These will become the
 33   top or bottom toolbars of the NavigationView (which is, incidentally, a WorkspaceView).
 34   
 35   Of course, this process is animated...
 36   
 37   @author Alex Iskander
 38   @extends SC.WorkspaceView
 39   @since SproutCore 1.4
 40 */
 41 SC.NavigationView = SC.WorkspaceView.extend(
 42 /** @scope SC.NavigationView.prototype */ {
 43   
 44   /** @private */
 45   _views: null,
 46   
 47   /** @private */
 48   _current: null,
 49   
 50   /**
 51     @type SC.View
 52     @default SC.View
 53   */
 54   navigationContentView: SC.View,
 55   
 56   /** @private */
 57   init: function() {
 58     sc_super();
 59     this._views = [];
 60   },
 61   
 62   /** @private */
 63   createChildViews: function() {
 64     sc_super();
 65     
 66     // get the content
 67     var content = this.get("navigationContentView");
 68     
 69     // instantiate if needed
 70     if (content.isClass) content = this.createChildView(content);
 71     
 72     // set internal values
 73     this._defaultContent = this.navigationContentView = content;
 74     
 75     // append to the content view
 76     this.contentView.appendChild(content);
 77   },
 78   
 79   /** @private */
 80   changeNavigationContent: function(view) {
 81     var top = null, bottom = null;
 82     
 83     // find top and bottom toolbars if we are setting it to a view
 84     if (view) {
 85       top = view.get("topToolbar"); 
 86       bottom = view.get("bottomToolbar");
 87     }
 88     
 89     // instantiate top if needed
 90     if (top && top.isClass) {
 91       view.set("topToolbar", top = top.create());
 92     }
 93     
 94     // and now bottom
 95     if (bottom && bottom.isClass) {
 96       view.set("bottomToolbar", bottom = bottom.create());
 97     }
 98     
 99     
100     // batch property changes for efficiency
101     this.beginPropertyChanges();
102     
103     // update current, etc. etc.
104     this._current = view;
105     this.set("navigationContentView", view ? view : this._defaultContent);
106     
107     // set the top/bottom appropriately
108     this.set("topToolbar", top);
109     this.set("bottomToolbar", bottom);
110     
111     // and we are done
112     this.endPropertyChanges();
113   },
114   
115   /**
116     Pushes a view into the navigation view stack. The view may have topToolbar and bottomToolbar properties.
117     
118     @param {SC.View} view The view to display
119   */
120   push: function(view) {
121     this._currentDirection = this._current ? SC.TO_LEFT : null;
122     
123     // add current view to the stack (if needed)
124     if (this._current) this._views.push(this._current);
125     
126     // update content now...
127     this.changeNavigationContent(view);
128   },
129   
130   /**
131     Pops the current view off the navigation view stack.
132   */
133   pop: function() {
134     this._currentDirection = SC.TO_RIGHT;
135     
136     // pop the view
137     var view = this._views.pop();
138     
139     // set new (old) content view
140     this.changeNavigationContent(view);
141   },
142   
143   /**
144     Pops to the specified view on the navigation view stack; the view you pass will become the current view.
145     
146     @param {SC.View} toView The view to display
147   */
148   popToView: function(toView) {
149     this._currentDirection = SC.TO_RIGHT;
150     var views = this._views,
151         idx = views.length - 1, 
152         view = views[idx];
153     
154     // loop back from end
155     while (view && view !== toView) {
156       this._views.pop();
157       idx--;
158       view = views[idx];
159     }
160     
161     // and change the content
162     this.changeNavigationContent(view);
163   },
164   
165   /** @private */
166   topToolbarDidChange: function() {
167     var active = this.activeTopToolbar, replacement = this.get("topToolbar");
168     
169     // if we have an active toolbar, set the build direction and build out
170     if (active) {
171       if (this._currentDirection !== null) {
172         active.set("buildDirection", this._currentDirection);
173         this.buildOutChild(active);
174       } else {
175         this.removeChild(active);
176       }
177     }
178     
179     // if we have a new toolbar, set the build direction and build in
180     if (replacement) {
181       if (this._currentDirection !== null) {
182         replacement.set("buildDirection", this._currentDirection);
183         this.buildInChild(replacement);
184       } else {
185         this.appendChild(replacement);
186       }
187     }
188     
189     // update, and queue retiling
190     this.activeTopToolbar = replacement;
191     this.invokeOnce("childDidChange");
192   }.observes("topToolbar"),
193   
194   /** @private */
195   bottomToolbarDidChange: function() {
196     var active = this.activeBottomToolbar, replacement = this.get("bottomToolbar");
197     
198     if (active) {
199       if (this._currentDirection !== null) {
200         active.set("buildDirection", this._currentDirection);
201         this.buildOutChild(active);
202       } else {
203         this.removeChild(active);
204       }
205     }
206     if (replacement) {
207       if (this._currentDirection !== null) {
208         replacement.set("buildDirection", this._currentDirection);
209         this.buildInChild(replacement);
210       } else {
211         this.appendChild(replacement);
212       }
213     }
214     
215     this.activeBottomToolbar = replacement;
216     this.invokeOnce("childDidChange");
217   }.observes("topToolbar"),
218   
219   /** @private */
220   contentViewDidChange: function() {
221     var active = this.activeNavigationContentView, replacement = this.get("navigationContentView");
222     
223     // mix in navigationbuilder if needed
224     if (!replacement.isNavigationBuilder) {
225       replacement.mixin(SC.NavigationBuilder);
226     }
227     
228     // tiling really needs to happen _before_ animation
229     // so, we set "pending" and queue tiling.
230     this._pendingBuildOut = active;
231     this._pendingBuildIn = replacement;
232     
233     this.activeNavigationContentView = replacement;
234     this.invokeOnce("childDidChange");
235   }.observes("navigationContentView"),
236   
237   /** @private */
238   childDidChange: function() {
239     var replacement = this._pendingBuildIn, active = this._pendingBuildOut;
240     if (active) {
241       if (this._currentDirection !== null) {
242         active.set("buildDirection", this._currentDirection);
243         this.contentView.buildOutChild(active);
244       } else {
245         this.contentView.removeChild(active);
246       }
247     }
248 
249     this._scws_tile();
250     
251     if (replacement) {
252       if (this._currentDirection !== null) {
253         replacement.set("buildDirection", this._currentDirection);
254         this.contentView.buildInChild(replacement);
255       } else {
256         this.contentView.appendChild(replacement);
257       }
258     }
259   }
260   
261 });
262