//>>built define("dojox/mobile/View", [ "dojo/_base/kernel", // to test dojo.hash "dojo/_base/array", "dojo/_base/config", "dojo/_base/connect", "dojo/_base/declare", "dojo/_base/lang", "dojo/_base/sniff", "dojo/_base/window", "dojo/_base/Deferred", "dojo/dom", "dojo/dom-class", "dojo/dom-geometry", "dojo/dom-style", // "dojo/hash", // optionally prereq'ed "dijit/registry", // registry.byNode "dijit/_Contained", "dijit/_Container", "dijit/_WidgetBase", "./ViewController", // to load ViewController for you (no direct references) "./transition" ], function(dojo, array, config, connect, declare, lang, has, win, Deferred, dom, domClass, domGeometry, domStyle, registry, Contained, Container, WidgetBase, ViewController, transitDeferred){ /*===== var Contained = dijit._Contained; var Container = dijit._Container; var WidgetBase = dijit._WidgetBase; var ViewController = dojox.mobile.ViewController; =====*/ // module: // dojox/mobile/View // summary: // A widget that represents a view that occupies the full screen var dm = lang.getObject("dojox.mobile", true); return declare("dojox.mobile.View", [WidgetBase, Container, Contained], { // summary: // A widget that represents a view that occupies the full screen // description: // View acts as a container for any HTML and/or widgets. An entire // HTML page can have multiple View widgets and the user can // navigate through the views back and forth without page // transitions. // selected: Boolean // If true, the view is displayed at startup time. selected: false, // keepScrollPos: Boolean // If true, the scroll position is kept between views. keepScrollPos: true, constructor: function(params, node){ if(node){ dom.byId(node).style.visibility = "hidden"; } this._aw = has("android") >= 2.2 && has("android") < 3; // flag for android animation workaround }, buildRendering: function(){ this.domNode = this.containerNode = this.srcNodeRef || win.doc.createElement("DIV"); this.domNode.className = "mblView"; this.connect(this.domNode, "webkitAnimationEnd", "onAnimationEnd"); this.connect(this.domNode, "webkitAnimationStart", "onAnimationStart"); if(!config['mblCSS3Transition']){ this.connect(this.domNode, "webkitTransitionEnd", "onAnimationEnd"); } var id = location.href.match(/#(\w+)([^\w=]|$)/) ? RegExp.$1 : null; this._visible = this.selected && !id || this.id == id; if(this.selected){ dm._defaultView = this; } }, startup: function(){ if(this._started){ return; } var siblings = []; var children = this.domNode.parentNode.childNodes; var visible = false; // check if a visible view exists for(var i = 0; i < children.length; i++){ var c = children[i]; if(c.nodeType === 1 && domClass.contains(c, "mblView")){ siblings.push(c); visible = visible || registry.byNode(c)._visible; } } var _visible = this._visible; // if no visible view exists, make the first view visible if(siblings.length === 1 || (!visible && siblings[0] === this.domNode)){ _visible = true; } var _this = this; setTimeout(function(){ // necessary to render the view correctly if(!_visible){ _this.domNode.style.display = "none"; }else{ dm.currentView = _this; //TODO:1.8 reconsider this. currentView may not have a currently showing view when views are nested. _this.onStartView(); connect.publish("/dojox/mobile/startView", [_this]); } if(_this.domNode.style.visibility != "visible"){ // this check is to avoid screen flickers _this.domNode.style.visibility = "visible"; } var parent = _this.getParent && _this.getParent(); if(!parent || !parent.resize){ // top level widget _this.resize(); } }, has("ie") ? 100 : 0); // give IE a little time to complete drawing this.inherited(arguments); }, resize: function(){ // summary: // Calls resize() of each child widget. array.forEach(this.getChildren(), function(child){ if(child.resize){ child.resize(); } }); }, onStartView: function(){ // summary: // Stub function to connect to from your application. // description: // Called only when this view is shown at startup time. }, onBeforeTransitionIn: function(moveTo, dir, transition, context, method){ // summary: // Stub function to connect to from your application. // description: // Called before the arriving transition occurs. }, onAfterTransitionIn: function(moveTo, dir, transition, context, method){ // summary: // Stub function to connect to from your application. // description: // Called after the arriving transition occurs. }, onBeforeTransitionOut: function(moveTo, dir, transition, context, method){ // summary: // Stub function to connect to from your application. // description: // Called before the leaving transition occurs. }, onAfterTransitionOut: function(moveTo, dir, transition, context, method){ // summary: // Stub function to connect to from your application. // description: // Called after the leaving transition occurs. }, _saveState: function(moveTo, dir, transition, context, method){ this._context = context; this._method = method; if(transition == "none"){ transition = null; } this._moveTo = moveTo; this._dir = dir; this._transition = transition; this._arguments = lang._toArray(arguments); this._args = []; if(context || method){ for(var i = 5; i < arguments.length; i++){ this._args.push(arguments[i]); } } }, _fixViewState: function(/*DomNode*/toNode){ // summary: // Sanity check for view transition states. // description: // Sometimes uninitialization of Views fails after making view transition, // and that results in failure of subsequent view transitions. // This function does the uninitialization for all the sibling views. var nodes = this.domNode.parentNode.childNodes; for(var i = 0; i < nodes.length; i++){ var n = nodes[i]; if(n.nodeType === 1 && domClass.contains(n, "mblView")){ n.className = "mblView"; //TODO: Should remove classes one by one. This would clear user defined classes or even mblScrollableView. } } toNode.className = "mblView"; // just in case toNode is a sibling of an ancestor. }, convertToId: function(moveTo){ if(typeof(moveTo) == "string"){ // removes a leading hash mark (#) and params if exists // ex. "#bar&myParam=0003" -> "bar" moveTo.match(/^#?([^&?]+)/); return RegExp.$1; } return moveTo; }, performTransition: function(/*String*/moveTo, /*Number*/dir, /*String*/transition, /*Object|null*/context, /*String|Function*/method /*optional args*/){ // summary: // Function to perform the various types of view transitions, such as fade, slide, and flip. // moveTo: String // The id of the transition destination view which resides in // the current page. // If the value has a hash sign ('#') before the id // (e.g. #view1) and the dojo.hash module is loaded by the user // application, the view transition updates the hash in the // browser URL so that the user can bookmark the destination // view. In this case, the user can also use the browser's // back/forward button to navigate through the views in the // browser history. // If null, transitions to a blank view. // If '#', returns immediately without transition. // dir: Number // The transition direction. If 1, transition forward. If -1, transition backward. // For example, the slide transition slides the view from right to left when dir == 1, // and from left to right when dir == -1. // transition: String // A type of animated transition effect. You can choose from // the standard transition types, "slide", "fade", "flip", or // from the extended transition types, "cover", "coverv", // "dissolve", "reveal", "revealv", "scaleIn", // "scaleOut", "slidev", "swirl", "zoomIn", "zoomOut". If // "none" is specified, transition occurs immediately without // animation. // context: Object // The object that the callback function will receive as "this". // method: String|Function // A callback function that is called when the transition has been finished. // A function reference, or name of a function in context. // tags: // public // // example: // Transition backward to a view whose id is "foo" with the slide animation. // | performTransition("foo", -1, "slide"); // // example: // Transition forward to a blank view, and then open another page. // | performTransition(null, 1, "slide", null, function(){location.href = href;}); if(moveTo === "#"){ return; } if(dojo.hash){ if(typeof(moveTo) == "string" && moveTo.charAt(0) == '#' && !dm._params){ dm._params = []; for(var i = 0; i < arguments.length; i++){ dm._params.push(arguments[i]); } dojo.hash(moveTo); return; } } this._saveState.apply(this, arguments); var toNode; if(moveTo){ toNode = this.convertToId(moveTo); }else{ if(!this._dummyNode){ this._dummyNode = win.doc.createElement("DIV"); win.body().appendChild(this._dummyNode); } toNode = this._dummyNode; } var fromNode = this.domNode; var fromTop = fromNode.offsetTop; toNode = this.toNode = dom.byId(toNode); if(!toNode){ console.log("dojox.mobile.View#performTransition: destination view not found: "+moveTo); return; } toNode.style.visibility = this._aw ? "visible" : "hidden"; toNode.style.display = ""; this._fixViewState(toNode); var toWidget = registry.byNode(toNode); if(toWidget){ // Now that the target view became visible, it's time to run resize() if(config["mblAlwaysResizeOnTransition"] || !toWidget._resized){ dm.resizeAll(null, toWidget); toWidget._resized = true; } if(transition && transition != "none"){ // Temporarily add padding to align with the fromNode while transition toWidget.containerNode.style.paddingTop = fromTop + "px"; } toWidget.movedFrom = fromNode.id; } this.onBeforeTransitionOut.apply(this, arguments); connect.publish("/dojox/mobile/beforeTransitionOut", [this].concat(lang._toArray(arguments))); if(toWidget){ // perform view transition keeping the scroll position if(this.keepScrollPos && !this.getParent()){ var scrollTop = win.body().scrollTop || win.doc.documentElement.scrollTop || win.global.pageYOffset || 0; fromNode._scrollTop = scrollTop; var toTop = (dir == 1) ? 0 : (toNode._scrollTop || 0); toNode.style.top = "0px"; if(scrollTop > 1 || toTop !== 0){ fromNode.style.top = toTop - scrollTop + "px"; if(config["mblHideAddressBar"] !== false){ setTimeout(function(){ // iPhone needs setTimeout win.global.scrollTo(0, (toTop || 1)); }, 0); } } }else{ toNode.style.top = "0px"; } toWidget.onBeforeTransitionIn.apply(toWidget, arguments); connect.publish("/dojox/mobile/beforeTransitionIn", [toWidget].concat(lang._toArray(arguments))); } if(!this._aw){ toNode.style.display = "none"; toNode.style.visibility = "visible"; } if(dm._iw && dm.scrollable){ // Workaround for iPhone flicker issue (only when scrollable.js is loaded) var ss = dm.getScreenSize(); // Show cover behind the view. // cover's z-index is set to -10000, lower than z-index value specified in transition css. win.body().appendChild(dm._iwBgCover); domStyle.set(dm._iwBgCover, { position: "absolute", top: "0px", left: "0px", height: (ss.h + 1) + "px", // "+1" means the height of scrollTo(0,1) width: ss.w + "px", backgroundColor: domStyle.get(win.body(), "background-color"), zIndex: -10000, display: "" }); // Show toNode behind the cover. domStyle.set(toNode, { position: "absolute", zIndex: -10001, visibility: "visible", display: "" }); // setTimeout seems to be necessary to avoid flicker. // Also the duration of setTimeout should be long enough to avoid flicker. // 0 is not effective. 50 sometimes causes flicker. setTimeout(lang.hitch(this, function(){ this._doTransition(fromNode, toNode, transition, dir); }), 80); }else{ this._doTransition(fromNode, toNode, transition, dir); } }, _toCls: function(s){ // convert from transition name to corresponding class name // ex. "slide" -> "mblSlide" return "mbl"+s.charAt(0).toUpperCase() + s.substring(1); }, _doTransition: function(fromNode, toNode, transition, dir){ var rev = (dir == -1) ? " mblReverse" : ""; if(dm._iw && dm.scrollable){ // Workaround for iPhone flicker issue (only when scrollable.js is loaded) // Show toNode after flicker ends domStyle.set(toNode, { position: "", zIndex: "" }); // Remove cover win.body().removeChild(dm._iwBgCover); }else if(!this._aw){ toNode.style.display = ""; } if(!transition || transition == "none"){ this.domNode.style.display = "none"; this.invokeCallback(); }else if(config['mblCSS3Transition']){ //get dojox/css3/transit first Deferred.when(transitDeferred, lang.hitch(this, function(transit){ //follow the style of .mblView.mblIn in View.css //need to set the toNode to absolute position var toPosition = domStyle.get(toNode, "position"); domStyle.set(toNode, "position", "absolute"); Deferred.when(transit(fromNode, toNode, {transition: transition, reverse: (dir===-1)?true:false}),lang.hitch(this,function(){ domStyle.set(toNode, "position", toPosition); this.invokeCallback(); })); })); }else{ var s = this._toCls(transition); domClass.add(fromNode, s + " mblOut" + rev); domClass.add(toNode, s + " mblIn" + rev); setTimeout(function(){ domClass.add(fromNode, "mblTransition"); domClass.add(toNode, "mblTransition"); }, 100); // set transform origin var fromOrigin = "50% 50%"; var toOrigin = "50% 50%"; var scrollTop, posX, posY; if(transition.indexOf("swirl") != -1 || transition.indexOf("zoom") != -1){ if(this.keepScrollPos && !this.getParent()){ scrollTop = win.body().scrollTop || win.doc.documentElement.scrollTop || win.global.pageYOffset || 0; }else{ scrollTop = -domGeometry.position(fromNode, true).y; } posY = win.global.innerHeight / 2 + scrollTop; fromOrigin = "50% " + posY + "px"; toOrigin = "50% " + posY + "px"; }else if(transition.indexOf("scale") != -1){ var viewPos = domGeometry.position(fromNode, true); posX = ((this.clickedPosX !== undefined) ? this.clickedPosX : win.global.innerWidth / 2) - viewPos.x; if(this.keepScrollPos && !this.getParent()){ scrollTop = win.body().scrollTop || win.doc.documentElement.scrollTop || win.global.pageYOffset || 0; }else{ scrollTop = -viewPos.y; } posY = ((this.clickedPosY !== undefined) ? this.clickedPosY : win.global.innerHeight / 2) + scrollTop; fromOrigin = posX + "px " + posY + "px"; toOrigin = posX + "px " + posY + "px"; } domStyle.set(fromNode, {webkitTransformOrigin:fromOrigin}); domStyle.set(toNode, {webkitTransformOrigin:toOrigin}); } dm.currentView = registry.byNode(toNode); }, onAnimationStart: function(e){ }, onAnimationEnd: function(e){ var name = e.animationName || e.target.className; if(name.indexOf("Out") === -1 && name.indexOf("In") === -1 && name.indexOf("Shrink") === -1){ return; } var isOut = false; if(domClass.contains(this.domNode, "mblOut")){ isOut = true; this.domNode.style.display = "none"; domClass.remove(this.domNode, [this._toCls(this._transition), "mblIn", "mblOut", "mblReverse"]); }else{ // Reset the temporary padding this.containerNode.style.paddingTop = ""; } domStyle.set(this.domNode, {webkitTransformOrigin:""}); if(name.indexOf("Shrink") !== -1){ var li = e.target; li.style.display = "none"; domClass.remove(li, "mblCloseContent"); } if(isOut){ this.invokeCallback(); } // this.domNode may be destroyed as a result of invoking the callback, // so check for that before accessing it. this.domNode && (this.domNode.className = "mblView"); // clear the clicked position this.clickedPosX = this.clickedPosY = undefined; }, invokeCallback: function(){ this.onAfterTransitionOut.apply(this, this._arguments); connect.publish("/dojox/mobile/afterTransitionOut", [this].concat(this._arguments)); var toWidget = registry.byNode(this.toNode); if(toWidget){ toWidget.onAfterTransitionIn.apply(toWidget, this._arguments); connect.publish("/dojox/mobile/afterTransitionIn", [toWidget].concat(this._arguments)); toWidget.movedFrom = undefined; } var c = this._context, m = this._method; if(!c && !m){ return; } if(!m){ m = c; c = null; } c = c || win.global; if(typeof(m) == "string"){ c[m].apply(c, this._args); }else{ m.apply(c, this._args); } }, getShowingView: function(){ // summary: // Find the currently showing view from my sibling views. // description: // Note that dojox.mobile.currentView is the last shown view. // If the page consists of a splitter, there are multiple showing views. var nodes = this.domNode.parentNode.childNodes; for(var i = 0; i < nodes.length; i++){ var n = nodes[i]; if(n.nodeType === 1 && domClass.contains(n, "mblView") && domStyle.get(n, "display") !== "none"){ return registry.byNode(n); } } return null; }, show: function(){ // summary: // Shows this view without a transition animation. var view = this.getShowingView(); if(view){ view.domNode.style.display = "none"; // from-style } this.domNode.style.display = ""; // to-style dm.currentView = this; } }); });