[483] | 1 | define([ |
---|
| 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/_base/Deferred", |
---|
| 9 | "dojo/dom", |
---|
| 10 | "dojo/dom-class", |
---|
| 11 | "dojo/dom-construct", |
---|
| 12 | "dojo/on", |
---|
| 13 | "dojo/ready", |
---|
| 14 | "dijit/registry", |
---|
| 15 | "./ProgressIndicator", |
---|
| 16 | "./TransitionEvent", |
---|
| 17 | "./viewRegistry" |
---|
| 18 | ], function(dojo, array, connect, declare, lang, win, Deferred, dom, domClass, domConstruct, on, ready, registry, ProgressIndicator, TransitionEvent, viewRegistry){ |
---|
| 19 | |
---|
| 20 | // module: |
---|
| 21 | // dojox/mobile/ViewController |
---|
| 22 | |
---|
| 23 | var Controller = declare("dojox.mobile.ViewController", null, { |
---|
| 24 | // summary: |
---|
| 25 | // A singleton class that controls 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, the view content is |
---|
| 30 | // retrieved and parsed to create a new target view. |
---|
| 31 | |
---|
| 32 | // dataHandlerClass: Object |
---|
| 33 | // The data handler class used to load external views, |
---|
| 34 | // by default "dojox/mobile/dh/DataHandler" |
---|
| 35 | // (see the Data Handlers page in the reference documentation). |
---|
| 36 | dataHandlerClass: "dojox/mobile/dh/DataHandler", |
---|
| 37 | // dataSourceClass: Object |
---|
| 38 | // The data source class used to load external views, |
---|
| 39 | // by default "dojox/mobile/dh/UrlDataSource" |
---|
| 40 | // (see the Data Handlers page in the reference documentation). |
---|
| 41 | dataSourceClass: "dojox/mobile/dh/UrlDataSource", |
---|
| 42 | // fileTypeMapClass: Object |
---|
| 43 | // The file type map class used to load external views, |
---|
| 44 | // by default "dojox/mobile/dh/SuffixFileTypeMap" |
---|
| 45 | // (see the Data Handlers page in the reference documentation). |
---|
| 46 | fileTypeMapClass: "dojox/mobile/dh/SuffixFileTypeMap", |
---|
| 47 | |
---|
| 48 | constructor: function(){ |
---|
| 49 | // summary: |
---|
| 50 | // Creates a new instance of the class. |
---|
| 51 | // tags: |
---|
| 52 | // private |
---|
| 53 | this.viewMap = {}; |
---|
| 54 | ready(lang.hitch(this, function(){ |
---|
| 55 | on(win.body(), "startTransition", lang.hitch(this, "onStartTransition")); |
---|
| 56 | })); |
---|
| 57 | }, |
---|
| 58 | |
---|
| 59 | findTransitionViews: function(/*String*/moveTo){ |
---|
| 60 | // summary: |
---|
| 61 | // Parses the moveTo argument and determines a starting view and a destination view. |
---|
| 62 | // returns: Array |
---|
| 63 | // An array containing the currently showing view, the destination view |
---|
| 64 | // and the transition parameters, or an empty array if the moveTo argument |
---|
| 65 | // could not be parsed. |
---|
| 66 | if(!moveTo){ return []; } |
---|
| 67 | // removes a leading hash mark (#) and params if exists |
---|
| 68 | // ex. "#bar&myParam=0003" -> "bar" |
---|
| 69 | moveTo.match(/^#?([^&?]+)(.*)/); |
---|
| 70 | var params = RegExp.$2; |
---|
| 71 | var view = registry.byId(RegExp.$1); |
---|
| 72 | if(!view){ return []; } |
---|
| 73 | for(var v = view.getParent(); v; v = v.getParent()){ // search for the topmost invisible parent node |
---|
| 74 | if(v.isVisible && !v.isVisible()){ |
---|
| 75 | var sv = view.getShowingView(); |
---|
| 76 | if(sv && sv.id !== view.id){ |
---|
| 77 | view.show(); |
---|
| 78 | } |
---|
| 79 | view = v; |
---|
| 80 | } |
---|
| 81 | } |
---|
| 82 | return [view.getShowingView(), view, params]; // fromView, toView, params |
---|
| 83 | }, |
---|
| 84 | |
---|
| 85 | openExternalView: function(/*Object*/ transOpts, /*DomNode*/ target){ |
---|
| 86 | // summary: |
---|
| 87 | // Loads an external view and performs a transition to it. |
---|
| 88 | // returns: dojo/_base/Deferred |
---|
| 89 | // Deferred object that resolves when the external view is |
---|
| 90 | // ready and a transition starts. Note that it resolves before |
---|
| 91 | // the transition is complete. |
---|
| 92 | // description: |
---|
| 93 | // This method loads external view content through the |
---|
| 94 | // dojox/mobile data handlers, creates a new View instance with |
---|
| 95 | // the loaded content, and performs a view transition to the |
---|
| 96 | // new view. The external view content can be specified with |
---|
| 97 | // the url property of transOpts. The new view is created under |
---|
| 98 | // a DOM node specified by target. |
---|
| 99 | // |
---|
| 100 | // example: |
---|
| 101 | // This example loads view1.html, creates a new view under |
---|
| 102 | // `<body>`, and performs a transition to the new view with the |
---|
| 103 | // slide animation. |
---|
| 104 | // |
---|
| 105 | // | var vc = ViewController.getInstance(); |
---|
| 106 | // | vc.openExternalView({ |
---|
| 107 | // | url: "view1.html", |
---|
| 108 | // | transition: "slide" |
---|
| 109 | // | }, win.body()); |
---|
| 110 | // |
---|
| 111 | // |
---|
| 112 | // example: |
---|
| 113 | // If you want to perform a view transition without animation, |
---|
| 114 | // you can give transition:"none" to transOpts. |
---|
| 115 | // |
---|
| 116 | // | var vc = ViewController.getInstance(); |
---|
| 117 | // | vc.openExternalView({ |
---|
| 118 | // | url: "view1.html", |
---|
| 119 | // | transition: "none" |
---|
| 120 | // | }, win.body()); |
---|
| 121 | // |
---|
| 122 | // example: |
---|
| 123 | // If you want to dynamically create an external view, but do |
---|
| 124 | // not want to perform a view transition to it, you can give noTransition:true to transOpts. |
---|
| 125 | // This may be useful when you want to preload external views before the user starts using them. |
---|
| 126 | // |
---|
| 127 | // | var vc = ViewController.getInstance(); |
---|
| 128 | // | vc.openExternalView({ |
---|
| 129 | // | url: "view1.html", |
---|
| 130 | // | noTransition: true |
---|
| 131 | // | }, win.body()); |
---|
| 132 | // |
---|
| 133 | // example: |
---|
| 134 | // To do something when the external view is ready: |
---|
| 135 | // |
---|
| 136 | // | var vc = ViewController.getInstance(); |
---|
| 137 | // | Deferred.when(vc.openExternalView({...}, win.body()), function(){ |
---|
| 138 | // | doSomething(); |
---|
| 139 | // | }); |
---|
| 140 | |
---|
| 141 | var d = new Deferred(); |
---|
| 142 | var id = this.viewMap[transOpts.url]; |
---|
| 143 | if(id){ |
---|
| 144 | transOpts.moveTo = id; |
---|
| 145 | if(transOpts.noTransition){ |
---|
| 146 | registry.byId(id).hide(); |
---|
| 147 | }else{ |
---|
| 148 | new TransitionEvent(win.body(), transOpts).dispatch(); |
---|
| 149 | } |
---|
| 150 | d.resolve(true); |
---|
| 151 | return d; |
---|
| 152 | } |
---|
| 153 | |
---|
| 154 | // if a fixed bottom bar exists, a new view should be placed before it. |
---|
| 155 | var refNode = null; |
---|
| 156 | for(var i = target.childNodes.length - 1; i >= 0; i--){ |
---|
| 157 | var c = target.childNodes[i]; |
---|
| 158 | if(c.nodeType === 1){ |
---|
| 159 | var fixed = c.getAttribute("fixed") // TODO: Remove the non-HTML5-compliant attribute in 2.0 |
---|
| 160 | || c.getAttribute("data-mobile-fixed") |
---|
| 161 | || (registry.byNode(c) && registry.byNode(c).fixed); |
---|
| 162 | if(fixed === "bottom"){ |
---|
| 163 | refNode = c; |
---|
| 164 | break; |
---|
| 165 | } |
---|
| 166 | } |
---|
| 167 | } |
---|
| 168 | |
---|
| 169 | var dh = transOpts.dataHandlerClass || this.dataHandlerClass; |
---|
| 170 | var ds = transOpts.dataSourceClass || this.dataSourceClass; |
---|
| 171 | var ft = transOpts.fileTypeMapClass || this.fileTypeMapClass; |
---|
| 172 | require([dh, ds, ft], lang.hitch(this, function(DataHandler, DataSource, FileTypeMap){ |
---|
| 173 | var handler = new DataHandler(new DataSource(transOpts.data || transOpts.url), target, refNode); |
---|
| 174 | var contentType = transOpts.contentType || FileTypeMap.getContentType(transOpts.url) || "html"; |
---|
| 175 | handler.processData(contentType, lang.hitch(this, function(id){ |
---|
| 176 | if(id){ |
---|
| 177 | this.viewMap[transOpts.url] = transOpts.moveTo = id; |
---|
| 178 | if(transOpts.noTransition){ |
---|
| 179 | registry.byId(id).hide(); |
---|
| 180 | }else{ |
---|
| 181 | new TransitionEvent(win.body(), transOpts).dispatch(); |
---|
| 182 | } |
---|
| 183 | d.resolve(true); |
---|
| 184 | }else{ |
---|
| 185 | d.reject("Failed to load "+transOpts.url); |
---|
| 186 | } |
---|
| 187 | })); |
---|
| 188 | })); |
---|
| 189 | return d; |
---|
| 190 | }, |
---|
| 191 | |
---|
| 192 | onStartTransition: function(evt){ |
---|
| 193 | // summary: |
---|
| 194 | // A handler that performs view transition. |
---|
| 195 | evt.preventDefault(); |
---|
| 196 | if(!evt.detail){ return; } |
---|
| 197 | var detail = evt.detail; |
---|
| 198 | if(!detail.moveTo && !detail.href && !detail.url && !detail.scene){ return; } |
---|
| 199 | |
---|
| 200 | if(detail.url && !detail.moveTo){ |
---|
| 201 | var urlTarget = detail.urlTarget; |
---|
| 202 | var w = registry.byId(urlTarget); |
---|
| 203 | var target = w && w.containerNode || dom.byId(urlTarget); |
---|
| 204 | if(!target){ |
---|
| 205 | w = viewRegistry.getEnclosingView(evt.target); |
---|
| 206 | target = w && w.domNode.parentNode || win.body(); |
---|
| 207 | } |
---|
| 208 | this.openExternalView(detail, target); |
---|
| 209 | return; |
---|
| 210 | }else if(detail.href){ |
---|
| 211 | if(detail.hrefTarget && detail.hrefTarget != "_self"){ |
---|
| 212 | win.global.open(detail.href, detail.hrefTarget); |
---|
| 213 | }else{ |
---|
| 214 | var view; // find top level visible view |
---|
| 215 | for(var v = viewRegistry.getEnclosingView(evt.target); v; v = viewRegistry.getParentView(v)){ |
---|
| 216 | view = v; |
---|
| 217 | } |
---|
| 218 | if(view){ |
---|
| 219 | view.performTransition(null, detail.transitionDir, detail.transition, evt.target, function(){location.href = detail.href;}); |
---|
| 220 | } |
---|
| 221 | } |
---|
| 222 | return; |
---|
| 223 | }else if(detail.scene){ |
---|
| 224 | connect.publish("/dojox/mobile/app/pushScene", [detail.scene]); |
---|
| 225 | return; |
---|
| 226 | } |
---|
| 227 | |
---|
| 228 | var arr = this.findTransitionViews(detail.moveTo), |
---|
| 229 | fromView = arr[0], |
---|
| 230 | toView = arr[1], |
---|
| 231 | params = arr[2]; |
---|
| 232 | if(!location.hash && !detail.hashchange){ |
---|
| 233 | viewRegistry.initialView = fromView; |
---|
| 234 | } |
---|
| 235 | if(detail.moveTo && toView){ |
---|
| 236 | detail.moveTo = (detail.moveTo.charAt(0) === '#' ? '#' + toView.id : toView.id) + params; |
---|
| 237 | } |
---|
| 238 | if(!fromView || (detail.moveTo && fromView === registry.byId(detail.moveTo.replace(/^#?([^&?]+).*/, "$1")))){ return; } |
---|
| 239 | var src = registry.getEnclosingWidget(evt.target); |
---|
| 240 | if(src && src.callback){ |
---|
| 241 | detail.context = src; |
---|
| 242 | detail.method = src.callback; |
---|
| 243 | } |
---|
| 244 | fromView.performTransition(detail); |
---|
| 245 | } |
---|
| 246 | }); |
---|
| 247 | Controller._instance = new Controller(); // singleton |
---|
| 248 | Controller.getInstance = function(){ |
---|
| 249 | return Controller._instance; |
---|
| 250 | }; |
---|
| 251 | return Controller; |
---|
| 252 | }); |
---|
| 253 | |
---|