ViewController.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. define("dojox/mobile/ViewController", [
  2. "dojo/_base/kernel",
  3. "dojo/_base/array",
  4. "dojo/_base/connect",
  5. "dojo/_base/declare",
  6. "dojo/_base/lang",
  7. "dojo/_base/window",
  8. "dojo/dom",
  9. "dojo/dom-class",
  10. "dojo/dom-construct",
  11. // "dojo/hash", // optionally prereq'ed
  12. "dojo/on",
  13. "dojo/ready",
  14. "dijit/registry", // registry.byId
  15. "./ProgressIndicator",
  16. "./TransitionEvent"
  17. ], function(dojo, array, connect, declare, lang, win, dom, domClass, domConstruct, on, ready, registry, ProgressIndicator, TransitionEvent){
  18. // module:
  19. // dojox/mobile/ViewController
  20. // summary:
  21. // A singleton class that controlls view transition.
  22. var dm = lang.getObject("dojox.mobile", true);
  23. var Controller = declare("dojox.mobile.ViewController", null, {
  24. // summary:
  25. // A singleton class that controlls view transition.
  26. // description:
  27. // This class listens to the "startTransition" events and performs
  28. // view transitions. If the transition destination is an external
  29. // view specified with the url parameter, retrieves the view
  30. // content and parses it to create a new target view.
  31. constructor: function(){
  32. this.viewMap={};
  33. this.currentView=null;
  34. this.defaultView=null;
  35. ready(lang.hitch(this, function(){
  36. on(win.body(), "startTransition", lang.hitch(this, "onStartTransition"));
  37. }));
  38. },
  39. findCurrentView: function(moveTo,src){
  40. // summary:
  41. // Searches for the currently showing view.
  42. if(moveTo){
  43. var w = registry.byId(moveTo);
  44. if(w && w.getShowingView){ return w.getShowingView(); }
  45. }
  46. if(dm.currentView){
  47. return dm.currentView; //TODO:1.8 may not return an expected result especially when views are nested
  48. }
  49. //TODO:1.8 probably never reaches here
  50. w = src;
  51. while(true){
  52. w = w.getParent();
  53. if(!w){ return null; }
  54. if(domClass.contains(w.domNode, "mblView")){ break; }
  55. }
  56. return w;
  57. },
  58. onStartTransition: function(evt){
  59. // summary:
  60. // A handler that performs view transition.
  61. evt.preventDefault();
  62. if(!evt.detail || (evt.detail && !evt.detail.moveTo && !evt.detail.href && !evt.detail.url && !evt.detail.scene)){ return; }
  63. var w = this.findCurrentView(evt.detail.moveTo, (evt.target && evt.target.id)?registry.byId(evt.target.id):registry.byId(evt.target)); // the current view widget
  64. if(!w || (evt.detail && evt.detail.moveTo && w === registry.byId(evt.detail.moveTo))){ return; }
  65. if(evt.detail.href){
  66. var t = registry.byId(evt.target.id).hrefTarget;
  67. if(t){
  68. dm.openWindow(evt.detail.href, t);
  69. }else{
  70. w.performTransition(null, evt.detail.transitionDir, evt.detail.transition, evt.target, function(){location.href = evt.detail.href;});
  71. }
  72. return;
  73. } else if(evt.detail.scene){
  74. connect.publish("/dojox/mobile/app/pushScene", [evt.detail.scene]);
  75. return;
  76. }
  77. var moveTo = evt.detail.moveTo;
  78. if(evt.detail.url){
  79. var id;
  80. if(dm._viewMap && dm._viewMap[evt.detail.url]){
  81. // external view has already been loaded
  82. id = dm._viewMap[evt.detail.url];
  83. }else{
  84. // get the specified external view and append it to the <body>
  85. var text = this._text;
  86. if(!text){
  87. if(registry.byId(evt.target.id).sync){
  88. // We do not add explicit dependency on dojo/_base/xhr to this module
  89. // to be able to create a build that does not contain dojo/_base/xhr.
  90. // User applications that do sync loading here need to explicitly
  91. // require dojo/_base/xhr up front.
  92. dojo.xhrGet({url:evt.detail.url, sync:true, load:function(result){
  93. text = lang.trim(result);
  94. }});
  95. }else{
  96. var s = "dojo/_base/xhr"; // assign to a variable so as not to be picked up by the build tool
  97. require([s], lang.hitch(this, function(xhr){
  98. var prog = ProgressIndicator.getInstance();
  99. win.body().appendChild(prog.domNode);
  100. prog.start();
  101. var obj = xhr.get({
  102. url: evt.detail.url,
  103. handleAs: "text"
  104. });
  105. obj.addCallback(lang.hitch(this, function(response, ioArgs){
  106. prog.stop();
  107. if(response){
  108. this._text = response;
  109. new TransitionEvent(evt.target, {
  110. transition: evt.detail.transition,
  111. transitionDir: evt.detail.transitionDir,
  112. moveTo: moveTo,
  113. href: evt.detail.href,
  114. url: evt.detail.url,
  115. scene: evt.detail.scene},
  116. evt.detail)
  117. .dispatch();
  118. }
  119. }));
  120. obj.addErrback(function(error){
  121. prog.stop();
  122. console.log("Failed to load "+evt.detail.url+"\n"+(error.description||error));
  123. });
  124. }));
  125. return;
  126. }
  127. }
  128. this._text = null;
  129. id = this._parse(text, registry.byId(evt.target.id).urlTarget);
  130. if(!dm._viewMap){
  131. dm._viewMap = [];
  132. }
  133. dm._viewMap[evt.detail.url] = id;
  134. }
  135. moveTo = id;
  136. w = this.findCurrentView(moveTo,registry.byId(evt.target.id)) || w; // the current view widget
  137. }
  138. var src = registry.getEnclosingWidget(evt.target);
  139. var context, method;
  140. if(src && src.callback){
  141. context = src;
  142. method = src.callback;
  143. }
  144. w.performTransition(moveTo, evt.detail.transitionDir, evt.detail.transition, context, method);
  145. },
  146. _parse: function(text, id){
  147. // summary:
  148. // Parses the given view content.
  149. // description:
  150. // If the content is html fragment, constructs dom tree with it
  151. // and runs the parser. If the content is json data, passes it
  152. // to _instantiate().
  153. var container, view, i, j, len;
  154. var currentView = this.findCurrentView();
  155. var target = registry.byId(id) && registry.byId(id).containerNode
  156. || dom.byId(id)
  157. || currentView && currentView.domNode.parentNode
  158. || win.body();
  159. // if a fixed bottom bar exists, a new view should be placed before it.
  160. var refNode = null;
  161. for(j = target.childNodes.length - 1; j >= 0; j--){
  162. var c = target.childNodes[j];
  163. if(c.nodeType === 1){
  164. if(c.getAttribute("fixed") === "bottom"){
  165. refNode = c;
  166. break;
  167. }
  168. }
  169. }
  170. if(text.charAt(0) === "<"){ // html markup
  171. container = domConstruct.create("DIV", {innerHTML: text});
  172. for(i = 0; i < container.childNodes.length; i++){
  173. var n = container.childNodes[i];
  174. if(n.nodeType === 1){
  175. view = n; // expecting <div dojoType="dojox.mobile.View">
  176. break;
  177. }
  178. }
  179. if(!view){
  180. console.log("dojox.mobile.ViewController#_parse: invalid view content");
  181. return;
  182. }
  183. view.style.visibility = "hidden";
  184. target.insertBefore(container, refNode);
  185. var ws = dojo.parser.parse(container);
  186. array.forEach(ws, function(w){
  187. if(w && !w._started && w.startup){
  188. w.startup();
  189. }
  190. });
  191. // allows multiple root nodes in the fragment,
  192. // but transition will be performed to the 1st view.
  193. for(i = 0, len = container.childNodes.length; i < len; i++){
  194. target.insertBefore(container.firstChild, refNode); // reparent
  195. }
  196. target.removeChild(container);
  197. registry.byNode(view)._visible = true;
  198. }else if(text.charAt(0) === "{"){ // json
  199. container = domConstruct.create("DIV");
  200. target.insertBefore(container, refNode);
  201. this._ws = [];
  202. view = this._instantiate(eval('('+text+')'), container);
  203. for(i = 0; i < this._ws.length; i++){
  204. var w = this._ws[i];
  205. w.startup && !w._started && (!w.getParent || !w.getParent()) && w.startup();
  206. }
  207. this._ws = null;
  208. }
  209. view.style.display = "none";
  210. view.style.visibility = "visible";
  211. return dojo.hash ? "#" + view.id : view.id;
  212. },
  213. _instantiate: function(/*Object*/obj, /*DomNode*/node, /*Widget*/parent){
  214. // summary:
  215. // Given the evaluated json data, does the same thing as what
  216. // the parser does.
  217. var widget;
  218. for(var key in obj){
  219. if(key.charAt(0) == "@"){ continue; }
  220. var cls = lang.getObject(key);
  221. if(!cls){ continue; }
  222. var params = {};
  223. var proto = cls.prototype;
  224. var objs = lang.isArray(obj[key]) ? obj[key] : [obj[key]];
  225. for(var i = 0; i < objs.length; i++){
  226. for(var prop in objs[i]){
  227. if(prop.charAt(0) == "@"){
  228. var val = objs[i][prop];
  229. prop = prop.substring(1);
  230. if(typeof proto[prop] == "string"){
  231. params[prop] = val;
  232. }else if(typeof proto[prop] == "number"){
  233. params[prop] = val - 0;
  234. }else if(typeof proto[prop] == "boolean"){
  235. params[prop] = (val != "false");
  236. }else if(typeof proto[prop] == "object"){
  237. params[prop] = eval("(" + val + ")");
  238. }
  239. }
  240. }
  241. widget = new cls(params, node);
  242. if(node){ // to call View's startup()
  243. widget._visible = true;
  244. this._ws.push(widget);
  245. }
  246. if(parent && parent.addChild){
  247. parent.addChild(widget);
  248. }
  249. this._instantiate(objs[i], null, widget);
  250. }
  251. }
  252. return widget && widget.domNode;
  253. }
  254. });
  255. new Controller(); // singleton
  256. return Controller;
  257. });