514 lines
18 KiB
JavaScript
514 lines
18 KiB
JavaScript
|
//>>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;
|
||
|
}
|
||
|
});
|
||
|
});
|