View.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. define("dojox/mobile/View", [
  2. "dojo/_base/kernel", // to test dojo.hash
  3. "dojo/_base/array",
  4. "dojo/_base/config",
  5. "dojo/_base/connect",
  6. "dojo/_base/declare",
  7. "dojo/_base/lang",
  8. "dojo/_base/sniff",
  9. "dojo/_base/window",
  10. "dojo/_base/Deferred",
  11. "dojo/dom",
  12. "dojo/dom-class",
  13. "dojo/dom-geometry",
  14. "dojo/dom-style",
  15. // "dojo/hash", // optionally prereq'ed
  16. "dijit/registry", // registry.byNode
  17. "dijit/_Contained",
  18. "dijit/_Container",
  19. "dijit/_WidgetBase",
  20. "./ViewController", // to load ViewController for you (no direct references)
  21. "./transition"
  22. ], function(dojo, array, config, connect, declare, lang, has, win, Deferred, dom, domClass, domGeometry, domStyle, registry, Contained, Container, WidgetBase, ViewController, transitDeferred){
  23. /*=====
  24. var Contained = dijit._Contained;
  25. var Container = dijit._Container;
  26. var WidgetBase = dijit._WidgetBase;
  27. var ViewController = dojox.mobile.ViewController;
  28. =====*/
  29. // module:
  30. // dojox/mobile/View
  31. // summary:
  32. // A widget that represents a view that occupies the full screen
  33. var dm = lang.getObject("dojox.mobile", true);
  34. return declare("dojox.mobile.View", [WidgetBase, Container, Contained], {
  35. // summary:
  36. // A widget that represents a view that occupies the full screen
  37. // description:
  38. // View acts as a container for any HTML and/or widgets. An entire
  39. // HTML page can have multiple View widgets and the user can
  40. // navigate through the views back and forth without page
  41. // transitions.
  42. // selected: Boolean
  43. // If true, the view is displayed at startup time.
  44. selected: false,
  45. // keepScrollPos: Boolean
  46. // If true, the scroll position is kept between views.
  47. keepScrollPos: true,
  48. constructor: function(params, node){
  49. if(node){
  50. dom.byId(node).style.visibility = "hidden";
  51. }
  52. this._aw = has("android") >= 2.2 && has("android") < 3; // flag for android animation workaround
  53. },
  54. buildRendering: function(){
  55. this.domNode = this.containerNode = this.srcNodeRef || win.doc.createElement("DIV");
  56. this.domNode.className = "mblView";
  57. this.connect(this.domNode, "webkitAnimationEnd", "onAnimationEnd");
  58. this.connect(this.domNode, "webkitAnimationStart", "onAnimationStart");
  59. if(!config['mblCSS3Transition']){
  60. this.connect(this.domNode, "webkitTransitionEnd", "onAnimationEnd");
  61. }
  62. var id = location.href.match(/#(\w+)([^\w=]|$)/) ? RegExp.$1 : null;
  63. this._visible = this.selected && !id || this.id == id;
  64. if(this.selected){
  65. dm._defaultView = this;
  66. }
  67. },
  68. startup: function(){
  69. if(this._started){ return; }
  70. var siblings = [];
  71. var children = this.domNode.parentNode.childNodes;
  72. var visible = false;
  73. // check if a visible view exists
  74. for(var i = 0; i < children.length; i++){
  75. var c = children[i];
  76. if(c.nodeType === 1 && domClass.contains(c, "mblView")){
  77. siblings.push(c);
  78. visible = visible || registry.byNode(c)._visible;
  79. }
  80. }
  81. var _visible = this._visible;
  82. // if no visible view exists, make the first view visible
  83. if(siblings.length === 1 || (!visible && siblings[0] === this.domNode)){
  84. _visible = true;
  85. }
  86. var _this = this;
  87. setTimeout(function(){ // necessary to render the view correctly
  88. if(!_visible){
  89. _this.domNode.style.display = "none";
  90. }else{
  91. dm.currentView = _this; //TODO:1.8 reconsider this. currentView may not have a currently showing view when views are nested.
  92. _this.onStartView();
  93. connect.publish("/dojox/mobile/startView", [_this]);
  94. }
  95. if(_this.domNode.style.visibility != "visible"){ // this check is to avoid screen flickers
  96. _this.domNode.style.visibility = "visible";
  97. }
  98. var parent = _this.getParent && _this.getParent();
  99. if(!parent || !parent.resize){ // top level widget
  100. _this.resize();
  101. }
  102. }, has("ie") ? 100 : 0); // give IE a little time to complete drawing
  103. this.inherited(arguments);
  104. },
  105. resize: function(){
  106. // summary:
  107. // Calls resize() of each child widget.
  108. array.forEach(this.getChildren(), function(child){
  109. if(child.resize){ child.resize(); }
  110. });
  111. },
  112. onStartView: function(){
  113. // summary:
  114. // Stub function to connect to from your application.
  115. // description:
  116. // Called only when this view is shown at startup time.
  117. },
  118. onBeforeTransitionIn: function(moveTo, dir, transition, context, method){
  119. // summary:
  120. // Stub function to connect to from your application.
  121. // description:
  122. // Called before the arriving transition occurs.
  123. },
  124. onAfterTransitionIn: function(moveTo, dir, transition, context, method){
  125. // summary:
  126. // Stub function to connect to from your application.
  127. // description:
  128. // Called after the arriving transition occurs.
  129. },
  130. onBeforeTransitionOut: function(moveTo, dir, transition, context, method){
  131. // summary:
  132. // Stub function to connect to from your application.
  133. // description:
  134. // Called before the leaving transition occurs.
  135. },
  136. onAfterTransitionOut: function(moveTo, dir, transition, context, method){
  137. // summary:
  138. // Stub function to connect to from your application.
  139. // description:
  140. // Called after the leaving transition occurs.
  141. },
  142. _saveState: function(moveTo, dir, transition, context, method){
  143. this._context = context;
  144. this._method = method;
  145. if(transition == "none"){
  146. transition = null;
  147. }
  148. this._moveTo = moveTo;
  149. this._dir = dir;
  150. this._transition = transition;
  151. this._arguments = lang._toArray(arguments);
  152. this._args = [];
  153. if(context || method){
  154. for(var i = 5; i < arguments.length; i++){
  155. this._args.push(arguments[i]);
  156. }
  157. }
  158. },
  159. _fixViewState: function(/*DomNode*/toNode){
  160. // summary:
  161. // Sanity check for view transition states.
  162. // description:
  163. // Sometimes uninitialization of Views fails after making view transition,
  164. // and that results in failure of subsequent view transitions.
  165. // This function does the uninitialization for all the sibling views.
  166. var nodes = this.domNode.parentNode.childNodes;
  167. for(var i = 0; i < nodes.length; i++){
  168. var n = nodes[i];
  169. if(n.nodeType === 1 && domClass.contains(n, "mblView")){
  170. n.className = "mblView"; //TODO: Should remove classes one by one. This would clear user defined classes or even mblScrollableView.
  171. }
  172. }
  173. toNode.className = "mblView"; // just in case toNode is a sibling of an ancestor.
  174. },
  175. convertToId: function(moveTo){
  176. if(typeof(moveTo) == "string"){
  177. // removes a leading hash mark (#) and params if exists
  178. // ex. "#bar&myParam=0003" -> "bar"
  179. moveTo.match(/^#?([^&?]+)/);
  180. return RegExp.$1;
  181. }
  182. return moveTo;
  183. },
  184. performTransition: function(/*String*/moveTo, /*Number*/dir, /*String*/transition,
  185. /*Object|null*/context, /*String|Function*/method /*optional args*/){
  186. // summary:
  187. // Function to perform the various types of view transitions, such as fade, slide, and flip.
  188. // moveTo: String
  189. // The id of the transition destination view which resides in
  190. // the current page.
  191. // If the value has a hash sign ('#') before the id
  192. // (e.g. #view1) and the dojo.hash module is loaded by the user
  193. // application, the view transition updates the hash in the
  194. // browser URL so that the user can bookmark the destination
  195. // view. In this case, the user can also use the browser's
  196. // back/forward button to navigate through the views in the
  197. // browser history.
  198. // If null, transitions to a blank view.
  199. // If '#', returns immediately without transition.
  200. // dir: Number
  201. // The transition direction. If 1, transition forward. If -1, transition backward.
  202. // For example, the slide transition slides the view from right to left when dir == 1,
  203. // and from left to right when dir == -1.
  204. // transition: String
  205. // A type of animated transition effect. You can choose from
  206. // the standard transition types, "slide", "fade", "flip", or
  207. // from the extended transition types, "cover", "coverv",
  208. // "dissolve", "reveal", "revealv", "scaleIn",
  209. // "scaleOut", "slidev", "swirl", "zoomIn", "zoomOut". If
  210. // "none" is specified, transition occurs immediately without
  211. // animation.
  212. // context: Object
  213. // The object that the callback function will receive as "this".
  214. // method: String|Function
  215. // A callback function that is called when the transition has been finished.
  216. // A function reference, or name of a function in context.
  217. // tags:
  218. // public
  219. //
  220. // example:
  221. // Transition backward to a view whose id is "foo" with the slide animation.
  222. // | performTransition("foo", -1, "slide");
  223. //
  224. // example:
  225. // Transition forward to a blank view, and then open another page.
  226. // | performTransition(null, 1, "slide", null, function(){location.href = href;});
  227. if(moveTo === "#"){ return; }
  228. if(dojo.hash){
  229. if(typeof(moveTo) == "string" && moveTo.charAt(0) == '#' && !dm._params){
  230. dm._params = [];
  231. for(var i = 0; i < arguments.length; i++){
  232. dm._params.push(arguments[i]);
  233. }
  234. dojo.hash(moveTo);
  235. return;
  236. }
  237. }
  238. this._saveState.apply(this, arguments);
  239. var toNode;
  240. if(moveTo){
  241. toNode = this.convertToId(moveTo);
  242. }else{
  243. if(!this._dummyNode){
  244. this._dummyNode = win.doc.createElement("DIV");
  245. win.body().appendChild(this._dummyNode);
  246. }
  247. toNode = this._dummyNode;
  248. }
  249. var fromNode = this.domNode;
  250. var fromTop = fromNode.offsetTop;
  251. toNode = this.toNode = dom.byId(toNode);
  252. if(!toNode){ console.log("dojox.mobile.View#performTransition: destination view not found: "+moveTo); return; }
  253. toNode.style.visibility = this._aw ? "visible" : "hidden";
  254. toNode.style.display = "";
  255. this._fixViewState(toNode);
  256. var toWidget = registry.byNode(toNode);
  257. if(toWidget){
  258. // Now that the target view became visible, it's time to run resize()
  259. if(config["mblAlwaysResizeOnTransition"] || !toWidget._resized){
  260. dm.resizeAll(null, toWidget);
  261. toWidget._resized = true;
  262. }
  263. if(transition && transition != "none"){
  264. // Temporarily add padding to align with the fromNode while transition
  265. toWidget.containerNode.style.paddingTop = fromTop + "px";
  266. }
  267. toWidget.movedFrom = fromNode.id;
  268. }
  269. this.onBeforeTransitionOut.apply(this, arguments);
  270. connect.publish("/dojox/mobile/beforeTransitionOut", [this].concat(lang._toArray(arguments)));
  271. if(toWidget){
  272. // perform view transition keeping the scroll position
  273. if(this.keepScrollPos && !this.getParent()){
  274. var scrollTop = win.body().scrollTop || win.doc.documentElement.scrollTop || win.global.pageYOffset || 0;
  275. fromNode._scrollTop = scrollTop;
  276. var toTop = (dir == 1) ? 0 : (toNode._scrollTop || 0);
  277. toNode.style.top = "0px";
  278. if(scrollTop > 1 || toTop !== 0){
  279. fromNode.style.top = toTop - scrollTop + "px";
  280. if(config["mblHideAddressBar"] !== false){
  281. setTimeout(function(){ // iPhone needs setTimeout
  282. win.global.scrollTo(0, (toTop || 1));
  283. }, 0);
  284. }
  285. }
  286. }else{
  287. toNode.style.top = "0px";
  288. }
  289. toWidget.onBeforeTransitionIn.apply(toWidget, arguments);
  290. connect.publish("/dojox/mobile/beforeTransitionIn", [toWidget].concat(lang._toArray(arguments)));
  291. }
  292. if(!this._aw){
  293. toNode.style.display = "none";
  294. toNode.style.visibility = "visible";
  295. }
  296. if(dm._iw && dm.scrollable){ // Workaround for iPhone flicker issue (only when scrollable.js is loaded)
  297. var ss = dm.getScreenSize();
  298. // Show cover behind the view.
  299. // cover's z-index is set to -10000, lower than z-index value specified in transition css.
  300. win.body().appendChild(dm._iwBgCover);
  301. domStyle.set(dm._iwBgCover, {
  302. position: "absolute",
  303. top: "0px",
  304. left: "0px",
  305. height: (ss.h + 1) + "px", // "+1" means the height of scrollTo(0,1)
  306. width: ss.w + "px",
  307. backgroundColor: domStyle.get(win.body(), "background-color"),
  308. zIndex: -10000,
  309. display: ""
  310. });
  311. // Show toNode behind the cover.
  312. domStyle.set(toNode, {
  313. position: "absolute",
  314. zIndex: -10001,
  315. visibility: "visible",
  316. display: ""
  317. });
  318. // setTimeout seems to be necessary to avoid flicker.
  319. // Also the duration of setTimeout should be long enough to avoid flicker.
  320. // 0 is not effective. 50 sometimes causes flicker.
  321. setTimeout(lang.hitch(this, function(){
  322. this._doTransition(fromNode, toNode, transition, dir);
  323. }), 80);
  324. }else{
  325. this._doTransition(fromNode, toNode, transition, dir);
  326. }
  327. },
  328. _toCls: function(s){
  329. // convert from transition name to corresponding class name
  330. // ex. "slide" -> "mblSlide"
  331. return "mbl"+s.charAt(0).toUpperCase() + s.substring(1);
  332. },
  333. _doTransition: function(fromNode, toNode, transition, dir){
  334. var rev = (dir == -1) ? " mblReverse" : "";
  335. if(dm._iw && dm.scrollable){ // Workaround for iPhone flicker issue (only when scrollable.js is loaded)
  336. // Show toNode after flicker ends
  337. domStyle.set(toNode, {
  338. position: "",
  339. zIndex: ""
  340. });
  341. // Remove cover
  342. win.body().removeChild(dm._iwBgCover);
  343. }else if(!this._aw){
  344. toNode.style.display = "";
  345. }
  346. if(!transition || transition == "none"){
  347. this.domNode.style.display = "none";
  348. this.invokeCallback();
  349. }else if(config['mblCSS3Transition']){
  350. //get dojox/css3/transit first
  351. Deferred.when(transitDeferred, lang.hitch(this, function(transit){
  352. //follow the style of .mblView.mblIn in View.css
  353. //need to set the toNode to absolute position
  354. var toPosition = domStyle.get(toNode, "position");
  355. domStyle.set(toNode, "position", "absolute");
  356. Deferred.when(transit(fromNode, toNode, {transition: transition, reverse: (dir===-1)?true:false}),lang.hitch(this,function(){
  357. domStyle.set(toNode, "position", toPosition);
  358. this.invokeCallback();
  359. }));
  360. }));
  361. }else{
  362. var s = this._toCls(transition);
  363. domClass.add(fromNode, s + " mblOut" + rev);
  364. domClass.add(toNode, s + " mblIn" + rev);
  365. setTimeout(function(){
  366. domClass.add(fromNode, "mblTransition");
  367. domClass.add(toNode, "mblTransition");
  368. }, 100);
  369. // set transform origin
  370. var fromOrigin = "50% 50%";
  371. var toOrigin = "50% 50%";
  372. var scrollTop, posX, posY;
  373. if(transition.indexOf("swirl") != -1 || transition.indexOf("zoom") != -1){
  374. if(this.keepScrollPos && !this.getParent()){
  375. scrollTop = win.body().scrollTop || win.doc.documentElement.scrollTop || win.global.pageYOffset || 0;
  376. }else{
  377. scrollTop = -domGeometry.position(fromNode, true).y;
  378. }
  379. posY = win.global.innerHeight / 2 + scrollTop;
  380. fromOrigin = "50% " + posY + "px";
  381. toOrigin = "50% " + posY + "px";
  382. }else if(transition.indexOf("scale") != -1){
  383. var viewPos = domGeometry.position(fromNode, true);
  384. posX = ((this.clickedPosX !== undefined) ? this.clickedPosX : win.global.innerWidth / 2) - viewPos.x;
  385. if(this.keepScrollPos && !this.getParent()){
  386. scrollTop = win.body().scrollTop || win.doc.documentElement.scrollTop || win.global.pageYOffset || 0;
  387. }else{
  388. scrollTop = -viewPos.y;
  389. }
  390. posY = ((this.clickedPosY !== undefined) ? this.clickedPosY : win.global.innerHeight / 2) + scrollTop;
  391. fromOrigin = posX + "px " + posY + "px";
  392. toOrigin = posX + "px " + posY + "px";
  393. }
  394. domStyle.set(fromNode, {webkitTransformOrigin:fromOrigin});
  395. domStyle.set(toNode, {webkitTransformOrigin:toOrigin});
  396. }
  397. dm.currentView = registry.byNode(toNode);
  398. },
  399. onAnimationStart: function(e){
  400. },
  401. onAnimationEnd: function(e){
  402. var name = e.animationName || e.target.className;
  403. if(name.indexOf("Out") === -1 &&
  404. name.indexOf("In") === -1 &&
  405. name.indexOf("Shrink") === -1){ return; }
  406. var isOut = false;
  407. if(domClass.contains(this.domNode, "mblOut")){
  408. isOut = true;
  409. this.domNode.style.display = "none";
  410. domClass.remove(this.domNode, [this._toCls(this._transition), "mblIn", "mblOut", "mblReverse"]);
  411. }else{
  412. // Reset the temporary padding
  413. this.containerNode.style.paddingTop = "";
  414. }
  415. domStyle.set(this.domNode, {webkitTransformOrigin:""});
  416. if(name.indexOf("Shrink") !== -1){
  417. var li = e.target;
  418. li.style.display = "none";
  419. domClass.remove(li, "mblCloseContent");
  420. }
  421. if(isOut){
  422. this.invokeCallback();
  423. }
  424. // this.domNode may be destroyed as a result of invoking the callback,
  425. // so check for that before accessing it.
  426. this.domNode && (this.domNode.className = "mblView");
  427. // clear the clicked position
  428. this.clickedPosX = this.clickedPosY = undefined;
  429. },
  430. invokeCallback: function(){
  431. this.onAfterTransitionOut.apply(this, this._arguments);
  432. connect.publish("/dojox/mobile/afterTransitionOut", [this].concat(this._arguments));
  433. var toWidget = registry.byNode(this.toNode);
  434. if(toWidget){
  435. toWidget.onAfterTransitionIn.apply(toWidget, this._arguments);
  436. connect.publish("/dojox/mobile/afterTransitionIn", [toWidget].concat(this._arguments));
  437. toWidget.movedFrom = undefined;
  438. }
  439. var c = this._context, m = this._method;
  440. if(!c && !m){ return; }
  441. if(!m){
  442. m = c;
  443. c = null;
  444. }
  445. c = c || win.global;
  446. if(typeof(m) == "string"){
  447. c[m].apply(c, this._args);
  448. }else{
  449. m.apply(c, this._args);
  450. }
  451. },
  452. getShowingView: function(){
  453. // summary:
  454. // Find the currently showing view from my sibling views.
  455. // description:
  456. // Note that dojox.mobile.currentView is the last shown view.
  457. // If the page consists of a splitter, there are multiple showing views.
  458. var nodes = this.domNode.parentNode.childNodes;
  459. for(var i = 0; i < nodes.length; i++){
  460. var n = nodes[i];
  461. if(n.nodeType === 1 && domClass.contains(n, "mblView") && domStyle.get(n, "display") !== "none"){
  462. return registry.byNode(n);
  463. }
  464. }
  465. return null;
  466. },
  467. show: function(){
  468. // summary:
  469. // Shows this view without a transition animation.
  470. var view = this.getShowingView();
  471. if(view){
  472. view.domNode.style.display = "none"; // from-style
  473. }
  474. this.domNode.style.display = ""; // to-style
  475. dm.currentView = this;
  476. }
  477. });
  478. });