1 // ==========================================================================
  2 // Project:   The M-Project - Mobile HTML5 Application Framework
  3 // Copyright: (c) 2010 M-Way Solutions GmbH. All rights reserved.
  4 //            (c) 2011 panacoda GmbH. All rights reserved.
  5 // Creator:   Sebastian
  6 // Date:      02.11.2010
  7 // License:   Dual licensed under the MIT or GPL Version 2 licenses.
  8 //            http://github.com/mwaylabs/The-M-Project/blob/master/MIT-LICENSE
  9 //            http://github.com/mwaylabs/The-M-Project/blob/master/GPL-LICENSE
 10 // ==========================================================================
 11 
 12 /**
 13  * @class
 14  *
 15  * M.PageView is the prototype of any page. It is the seconds 'highest' view, right after
 16  * M.Application. A page is the container view for all other views.
 17  *
 18  * @extends M.View
 19  */
 20 M.PageView = M.View.extend(
 21 /** @scope M.PageView.prototype */ {
 22 
 23     /**
 24      * The type of this object.
 25      *
 26      * @type String
 27      */
 28     type: 'M.PageView',
 29 
 30     /**
 31      * States whether a page is loaded the first time or not. It is automatically set to NO
 32      * once the page was first loaded.
 33      *
 34      * @type Boolean
 35      */
 36     isFirstLoad: YES,
 37 
 38     /**
 39      * Indicates whether the page has a tab bar or not.
 40      *
 41      * @type Boolean
 42      */
 43     hasTabBarView: NO,
 44 
 45     /**
 46      * The page's tab bar.
 47      *
 48      * @type M.TabBarView
 49      */
 50     tabBarView: null,
 51 
 52     /**
 53      * This property specifies the recommended events for this type of view.
 54      *
 55      * @type Array
 56      */
 57     recommendedEvents: ['pagebeforeshow', 'pageshow', 'pagebeforehide', 'pagehide', 'orientationchange'],
 58 
 59     /**
 60      * This property is used to specify a view's internal events and their corresponding actions. If
 61      * there are external handlers specified for the same event, the internal handler is called first.
 62      *
 63      * @type Object
 64      */
 65     internalEvents: null,
 66 
 67     /**
 68      * An associative array containing all list views used in this page. The key for a list view is
 69      * its id. We do this to have direct access to a list view, so we can reset its selected item
 70      * once the page was hidden.
 71      *
 72      * @type Object
 73      */
 74     listList: null,
 75 
 76     /**
 77      * This property contains the page's current orientation. This property is only used internally!
 78      *
 79      * @private
 80      * @type Number
 81      */
 82     orientation: null,
 83 
 84     /**
 85      * Renders in three steps:
 86      * 1. Rendering Opening div tag with corresponding data-role
 87      * 2. Triggering render process of child views
 88      * 3. Rendering closing tag
 89      *
 90      * @private
 91      * @returns {String} The page view's html representation.
 92      */
 93     render: function() {
 94         /* store the currently rendered page as a reference for use in child views */
 95         M.ViewManager.currentlyRenderedPage = this;
 96         
 97         this.html += '<div id="' + this.id + '" data-role="page"' + this.style() + '>';
 98 
 99         this.renderChildViews();
100 
101         this.html += '</div>';
102 
103         this.writeToDOM();
104         this.theme();
105         this.registerEvents();
106     },
107 
108     /**
109      * This method is responsible for registering events for view elements and its child views. It
110      * basically passes the view's event-property to M.EventDispatcher to bind the appropriate
111      * events.
112      *
113      * It extend M.View's registerEvents method with some special stuff for page views and its
114      * internal events.
115      */
116     registerEvents: function() {
117         this.internalEvents = {
118             pagebeforeshow: {
119                 target: this,
120                 action: 'pageWillLoad'
121             },
122             pageshow: {
123                 target: this,
124                 action: 'pageDidLoad'
125             },
126             pagebeforehide: {
127                 target: this,
128                 action: 'pageWillHide'
129             },
130             pagehide: {
131                 target: this,
132                 action: 'pageDidHide'
133             },
134             orientationchange: {
135                 target: this,
136                 action: 'orientationDidChange'
137             }
138         }
139         this.bindToCaller(this, M.View.registerEvents)();
140     },
141 
142     /**
143      * This method writes the view's html string into the DOM. M.Page is the only view that does
144      * that. All other views just deliver their html representation to a page view.
145      */
146     writeToDOM: function() {
147         document.write(this.html);
148     },
149 
150     /**
151      * This method is called right before the page is loaded. If a beforeLoad-action is defined
152      * for the page, it is now called.
153      *
154      * @param {String} id The DOM id of the event target.
155      * @param {Object} event The DOM event.
156      * @param {Object} nextEvent The next event (external event), if specified.
157      */
158     pageWillLoad: function(id, event, nextEvent) {
159         /* initialize the tabbar */
160         if(M.Application.isFirstLoad) {
161             M.Application.isFirstLoad = NO;
162             var currentPage = M.ViewManager.getCurrentPage();
163             if(currentPage && currentPage.hasTabBarView) {
164                 var tabBarView = currentPage.tabBarView;
165 
166                 if(tabBarView.childViews) {
167                     var childViews = tabBarView.getChildViewsAsArray();
168                     for(var i in childViews) {
169                         if(M.ViewManager.getPage(tabBarView[childViews[i]].page).id === currentPage.id) {
170                             tabBarView.setActiveTab(tabBarView[childViews[i]]);
171                         }
172                     }
173                 }
174             }
175         }
176 
177         /* initialize the loader for later use (if not already done) */
178         if(M.LoaderView) {
179             M.LoaderView.initialize();
180         }
181 
182         /* reset the page's title */
183         document.title = M.Application.name;
184 
185         /* delegate event to external handler, if specified */
186         if(nextEvent) {
187             M.EventDispatcher.callHandler(nextEvent, event, NO, [this.isFirstLoad]);
188         }
189     },
190 
191     /**
192      * This method is called right after the page was loaded. If a onLoad-action is defined
193      * for the page, it is now called.
194      *
195      * @param {String} id The DOM id of the event target.
196      * @param {Object} event The DOM event.
197      * @param {Object} nextEvent The next event (external event), if specified.
198      */
199     pageDidLoad: function(id, event, nextEvent) {
200         /* delegate event to external handler, if specified */
201         if(nextEvent) {
202             M.EventDispatcher.callHandler(nextEvent, event, NO, [this.isFirstLoad]);
203         }
204 
205         /* call jqm to fix header/footer */
206         $.mobile.fixedToolbars.show();
207 
208         this.isFirstLoad = NO;
209     },
210 
211     /**
212      * This method is called right before the page is hidden. If a beforeHide-action is defined
213      * for the page, it is now called.
214      *
215      * @param {String} id The DOM id of the event target.
216      * @param {Object} event The DOM event.
217      * @param {Object} nextEvent The next event (external event), if specified.
218      */
219     pageWillHide: function(id, event, nextEvent) {
220         /* delegate event to external handler, if specified */
221         if(nextEvent) {
222             M.EventDispatcher.callHandler(nextEvent, event, NO, [this.isFirstLoad]);
223         }
224     },
225 
226     /**
227      * This method is called right after the page was hidden. If a onHide-action is defined
228      * for the page, it is now called.
229      *
230      * @param {String} id The DOM id of the event target.
231      * @param {Object} event The DOM event.
232      * @param {Object} nextEvent The next event (external event), if specified.
233      */
234     pageDidHide: function(id, event, nextEvent) {
235         /* if there is a list on the page, reset it: deactivate possible active list items */
236         if(this.listList) {
237             _.each(this.listList, function(list) {
238                 list.resetActiveListItem();
239             });
240         }
241 
242         /* delegate event to external handler, if specified */
243         if(nextEvent) {
244             M.EventDispatcher.callHandler(nextEvent, event, NO, [this.isFirstLoad]);
245         }
246     },
247 
248     /**
249      * This method is called right after the device's orientation did change. If a action for
250      * orientationchange is defined for the page, it is now called.
251      *
252      * @param {String} id The DOM id of the event target.
253      * @param {Object} event The DOM event.
254      * @param {Object} nextEvent The next event (external event), if specified.
255      */
256     orientationDidChange: function(id, event, nextEvent) {
257         /* get the orientation */
258         var orientation = M.Environment.getOrientation();
259         
260         /* filter event duplicates (can happen due to event delegation in bootstraping.js) */
261         if(orientation === this.orientation) {
262             return;
263         }
264 
265         /* auto-reposition opened dialogs */
266         $('.tmp-dialog').each(function() {
267             var id = $(this).attr('id');
268             var dialog = M.ViewManager.getViewById(id);
269             var dialogDOM = $(this);
270             window.setTimeout(function() {
271                 dialog.positionDialog(dialogDOM);
272                 dialog.positionBackground($('.tmp-dialog-background'));
273             }, 500);
274         });
275 
276         /* set the current orientation */
277         this.orientation = orientation;
278 
279         /* delegate event to external handler, if specified */
280         if(nextEvent) {
281             M.EventDispatcher.callHandler(nextEvent, event, NO, [M.Environment.getOrientation()]);
282         }
283     },
284 
285     /**
286      * Triggers the rendering engine, jQuery mobile, to style the page and call the theme() of
287      * its child views.
288      *
289      * @private
290      */
291     theme: function() {
292         $('#' + this.id).page();
293         this.themeChildViews();
294     },
295 
296     /**
297      * Applies some style-attributes to the page.
298      *
299      * @private
300      * @returns {String} The page's styling as html representation.
301      */
302     style: function() {
303         var html = '';
304         if(this.cssClass) {
305             if(!html) {
306                 html += ' class="';
307             }
308             html += this.cssClass;
309         }
310         if(html) {
311             html += '"';
312         }
313         return html;
314     }
315     
316 });