scene.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588
  1. define("dojox/app/scene", ["dojo/_base/kernel",
  2. "dojo/_base/declare",
  3. "dojo/_base/connect",
  4. "dojo/_base/array",
  5. "dojo/_base/Deferred",
  6. "dojo/_base/lang",
  7. "dojo/_base/sniff",
  8. "dojo/dom-style",
  9. "dojo/dom-geometry",
  10. "dojo/dom-class",
  11. "dojo/dom-construct",
  12. "dojo/dom-attr",
  13. "dojo/query",
  14. "dijit",
  15. "dojox",
  16. "dijit/_WidgetBase",
  17. "dijit/_TemplatedMixin",
  18. "dijit/_WidgetsInTemplateMixin",
  19. "dojox/css3/transit",
  20. "./animation",
  21. "./model",
  22. "./view",
  23. "./bind"],
  24. function(dojo,declare,connect, array,deferred,dlang,has,dstyle,dgeometry,cls,dconstruct,dattr,query,dijit,dojox,WidgetBase,Templated,WidgetsInTemplate,transit, anim, model, baseView, bind){
  25. var marginBox2contentBox = function(/*DomNode*/ node, /*Object*/ mb){
  26. // summary:
  27. // Given the margin-box size of a node, return its content box size.
  28. // Functions like dojo.contentBox() but is more reliable since it doesn't have
  29. // to wait for the browser to compute sizes.
  30. var cs = dstyle.getComputedStyle(node);
  31. var me = dgeometry.getMarginExtents(node, cs);
  32. var pb = dgeometry.getPadBorderExtents(node, cs);
  33. return {
  34. l: dstyle.toPixelValue(node, cs.paddingLeft),
  35. t: dstyle.toPixelValue(node, cs.paddingTop),
  36. w: mb.w - (me.w + pb.w),
  37. h: mb.h - (me.h + pb.h)
  38. };
  39. };
  40. var capitalize = function(word){
  41. return word.substring(0,1).toUpperCase() + word.substring(1);
  42. };
  43. var size = function(widget, dim){
  44. // size the child
  45. var newSize = widget.resize ? widget.resize(dim) : dgeometry.setMarginBox(widget.domNode, dim);
  46. // record child's size
  47. if(newSize){
  48. // if the child returned it's new size then use that
  49. dojo.mixin(widget, newSize);
  50. }else{
  51. // otherwise, call marginBox(), but favor our own numbers when we have them.
  52. // the browser lies sometimes
  53. dojo.mixin(widget, dgeometry.getMarginBox(widget.domNode));
  54. dojo.mixin(widget, dim);
  55. }
  56. };
  57. return declare("dojox.app.scene", [dijit._WidgetBase, dijit._TemplatedMixin, dijit._WidgetsInTemplateMixin], {
  58. isContainer: true,
  59. widgetsInTemplate: true,
  60. defaultView: "default",
  61. selectedChild: null,
  62. baseClass: "scene mblView",
  63. isFullScreen: false,
  64. defaultViewType: baseView,
  65. //Temporary work around for getting a null when calling getParent
  66. getParent: function(){return null;},
  67. constructor: function(params,node){
  68. this.children={};
  69. if(params.parent){
  70. this.parent=params.parent
  71. }
  72. if(params.app){
  73. this.app = params.app;
  74. }
  75. },
  76. buildRendering: function(){
  77. this.inherited(arguments);
  78. dstyle.set(this.domNode, {width: "100%", "height": "100%"});
  79. cls.add(this.domNode,"dijitContainer");
  80. },
  81. splitChildRef: function(childId){
  82. var id = childId.split(",");
  83. if (id.length>0){
  84. var to = id.shift();
  85. }else{
  86. console.warn("invalid child id passed to splitChildRef(): ", childId);
  87. }
  88. return {
  89. id:to || this.defaultView,
  90. next: id.join(',')
  91. }
  92. },
  93. loadChild: function(childId,subIds){
  94. // if no childId, load the default view
  95. if (!childId) {
  96. var parts = this.defaultView ? this.defaultView.split(",") : "default";
  97. childId = parts.shift();
  98. subIds = parts.join(',');
  99. }
  100. var cid = this.id+"_" + childId;
  101. if (this.children[cid]){
  102. return this.children[cid];
  103. }
  104. if (this.views&& this.views[childId]){
  105. var conf = this.views[childId];
  106. if (!conf.dependencies){conf.dependencies=[];}
  107. var deps = conf.template? conf.dependencies.concat(["dojo/text!app/"+conf.template]) :
  108. conf.dependencies.concat([]);
  109. var def = new deferred();
  110. if (deps.length>0) {
  111. require(deps,function(){
  112. def.resolve.call(def, arguments);
  113. });
  114. }else{
  115. def.resolve(true);
  116. }
  117. var loadChildDeferred = new deferred();
  118. var self = this;
  119. deferred.when(def, function(){
  120. var ctor;
  121. if (conf.type){
  122. ctor=dojo.getObject(conf.type);
  123. }else if (self.defaultViewType){
  124. ctor=self.defaultViewType;
  125. }else{
  126. throw Error("Unable to find appropriate ctor for the base child class");
  127. }
  128. var params = dojo.mixin({}, conf, {
  129. id: self.id + "_" + childId,
  130. templateString: conf.template?arguments[0][arguments[0].length-1]:"<div></div>",
  131. parent: self,
  132. app: self.app
  133. })
  134. if (subIds){
  135. params.defaultView=subIds;
  136. }
  137. var child = new ctor(params);
  138. //load child's model if it is not loaded before
  139. if(!child.loadedModels){
  140. child.loadedModels = model(conf.models, self.loadedModels)
  141. //TODO need to find out a better way to get all bindable controls in a view
  142. bind([child], child.loadedModels);
  143. }
  144. var addResult = self.addChild(child);
  145. //publish /app/loadchild event
  146. //application can subscript this event to do user define operation like select TabBarButton, add dynamic script text etc.
  147. connect.publish("/app/loadchild", [child]);
  148. var promise;
  149. subIds = subIds.split(',');
  150. if ((subIds[0].length > 0) && (subIds.length > 1)) {//TODO join subIds
  151. promise = child.loadChild(subIds[0], subIds[1]);
  152. }
  153. else
  154. if (subIds[0].length > 0) {
  155. promise = child.loadChild(subIds[0], "");
  156. }
  157. dojo.when(promise, function(){
  158. loadChildDeferred.resolve(addResult)
  159. });
  160. });
  161. return loadChildDeferred;
  162. }
  163. throw Error("Child '" + childId + "' not found.");
  164. },
  165. resize: function(changeSize,resultSize){
  166. var node = this.domNode;
  167. // set margin box size, unless it wasn't specified, in which case use current size
  168. if(changeSize){
  169. dgeometry.setMarginBox(node, changeSize);
  170. // set offset of the node
  171. if(changeSize.t){ node.style.top = changeSize.t + "px"; }
  172. if(changeSize.l){ node.style.left = changeSize.l + "px"; }
  173. }
  174. // If either height or width wasn't specified by the user, then query node for it.
  175. // But note that setting the margin box and then immediately querying dimensions may return
  176. // inaccurate results, so try not to depend on it.
  177. var mb = resultSize || {};
  178. dojo.mixin(mb, changeSize || {}); // changeSize overrides resultSize
  179. if( !("h" in mb) || !("w" in mb) ){
  180. mb = dojo.mixin(dgeometry.getMarginBox(node), mb); // just use dojo.marginBox() to fill in missing values
  181. }
  182. // Compute and save the size of my border box and content box
  183. // (w/out calling dojo.contentBox() since that may fail if size was recently set)
  184. var cs = dstyle.getComputedStyle(node);
  185. var me = dgeometry.getMarginExtents(node, cs);
  186. var be = dgeometry.getBorderExtents(node, cs);
  187. var bb = (this._borderBox = {
  188. w: mb.w - (me.w + be.w),
  189. h: mb.h - (me.h + be.h)
  190. });
  191. var pe = dgeometry.getPadExtents(node, cs);
  192. this._contentBox = {
  193. l: dstyle.toPixelValue(node, cs.paddingLeft),
  194. t: dstyle.toPixelValue(node, cs.paddingTop),
  195. w: bb.w - pe.w,
  196. h: bb.h - pe.h
  197. };
  198. // Callback for widget to adjust size of its children
  199. this.layout();
  200. },
  201. layout: function(){
  202. var fullScreenScene,children,hasCenter;
  203. //console.log("fullscreen: ", this.selectedChild && this.selectedChild.isFullScreen);
  204. if (this.selectedChild && this.selectedChild.isFullScreen) {
  205. console.warn("fullscreen sceen layout");
  206. /*
  207. fullScreenScene=true;
  208. children=[{domNode: this.selectedChild.domNode,region: "center"}];
  209. dojo.query("> [region]",this.domNode).forEach(function(c){
  210. if(this.selectedChild.domNode!==c.domNode){
  211. dojo.style(c.domNode,"display","none");
  212. }
  213. })
  214. */
  215. }else{
  216. children = query("> [region]", this.domNode).map(function(node){
  217. var w = dijit.getEnclosingWidget(node);
  218. if (w){return w;}
  219. return {
  220. domNode: node,
  221. region: dattr.get(node,"region")
  222. }
  223. });
  224. if (this.selectedChild){
  225. children = array.filter(children, function(c){
  226. if (c.region=="center" && this.selectedChild && this.selectedChild.domNode!==c.domNode){
  227. dstyle.set(c.domNode,"zIndex",25);
  228. dstyle.set(c.domNode,'display','none');
  229. return false;
  230. }else if (c.region!="center"){
  231. dstyle.set(c.domNode,"display","");
  232. dstyle.set(c.domNode,"zIndex",100);
  233. }
  234. return c.domNode && c.region;
  235. },this);
  236. // this.selectedChild.region="center";
  237. // dojo.attr(this.selectedChild.domNode,"region","center");
  238. // dojo.style(this.selectedChild.domNode, "display","");
  239. // dojo.style(this.selectedChild.domNode,"zIndex",50);
  240. // children.push({domNode: this.selectedChild.domNode, region: "center"});
  241. // children.push(this.selectedChild);
  242. // console.log("children: ", children);
  243. }else{
  244. array.forEach(children, function(c){
  245. if (c && c.domNode && c.region=="center"){
  246. dstyle.set(c.domNode,"zIndex",25);
  247. dstyle.set(c.domNode,'display','none');
  248. }
  249. });
  250. }
  251. }
  252. // We don't need to layout children if this._contentBox is null for the operation will do nothing.
  253. if (this._contentBox) {
  254. this.layoutChildren(this.domNode, this._contentBox, children);
  255. }
  256. array.forEach(this.getChildren(), function(child){
  257. if (!child._started && child.startup){
  258. child.startup();
  259. }
  260. });
  261. },
  262. layoutChildren: function(/*DomNode*/ container, /*Object*/ dim, /*Widget[]*/ children,
  263. /*String?*/ changedRegionId, /*Number?*/ changedRegionSize){
  264. // summary
  265. // Layout a bunch of child dom nodes within a parent dom node
  266. // container:
  267. // parent node
  268. // dim:
  269. // {l, t, w, h} object specifying dimensions of container into which to place children
  270. // children:
  271. // an array of Widgets or at least objects containing:
  272. // * domNode: pointer to DOM node to position
  273. // * region or layoutAlign: position to place DOM node
  274. // * resize(): (optional) method to set size of node
  275. // * id: (optional) Id of widgets, referenced from resize object, below.
  276. // changedRegionId:
  277. // If specified, the slider for the region with the specified id has been dragged, and thus
  278. // the region's height or width should be adjusted according to changedRegionSize
  279. // changedRegionSize:
  280. // See changedRegionId.
  281. // copy dim because we are going to modify it
  282. dim = dojo.mixin({}, dim);
  283. cls.add(container, "dijitLayoutContainer");
  284. // Move "client" elements to the end of the array for layout. a11y dictates that the author
  285. // needs to be able to put them in the document in tab-order, but this algorithm requires that
  286. // client be last. TODO: move these lines to LayoutContainer? Unneeded other places I think.
  287. children = array.filter(children, function(item){ return item.region != "center" && item.layoutAlign != "client"; })
  288. .concat(array.filter(children, function(item){ return item.region == "center" || item.layoutAlign == "client"; }));
  289. // set positions/sizes
  290. array.forEach(children, function(child){
  291. var elm = child.domNode,
  292. pos = (child.region || child.layoutAlign);
  293. // set elem to upper left corner of unused space; may move it later
  294. var elmStyle = elm.style;
  295. elmStyle.left = dim.l+"px";
  296. elmStyle.top = dim.t+"px";
  297. elmStyle.position = "absolute";
  298. cls.add(elm, "dijitAlign" + capitalize(pos));
  299. // Size adjustments to make to this child widget
  300. var sizeSetting = {};
  301. // Check for optional size adjustment due to splitter drag (height adjustment for top/bottom align
  302. // panes and width adjustment for left/right align panes.
  303. if(changedRegionId && changedRegionId == child.id){
  304. sizeSetting[child.region == "top" || child.region == "bottom" ? "h" : "w"] = changedRegionSize;
  305. }
  306. // set size && adjust record of remaining space.
  307. // note that setting the width of a <div> may affect its height.
  308. if(pos == "top" || pos == "bottom"){
  309. sizeSetting.w = dim.w;
  310. size(child, sizeSetting);
  311. dim.h -= child.h;
  312. if(pos == "top"){
  313. dim.t += child.h;
  314. }else{
  315. elmStyle.top = dim.t + dim.h + "px";
  316. }
  317. }else if(pos == "left" || pos == "right"){
  318. sizeSetting.h = dim.h;
  319. size(child, sizeSetting);
  320. dim.w -= child.w;
  321. if(pos == "left"){
  322. dim.l += child.w;
  323. }else{
  324. elmStyle.left = dim.l + dim.w + "px";
  325. }
  326. }else if(pos == "client" || pos == "center"){
  327. size(child, dim);
  328. }
  329. });
  330. },
  331. getChildren: function(){
  332. return this._supportingWidgets;
  333. },
  334. startup: function(){
  335. if(this._started){ return; }
  336. this._started=true;
  337. var parts = this.defaultView?this.defaultView.split(","):"default";
  338. var toId, subIds;
  339. toId= parts.shift();
  340. subIds = parts.join(',');
  341. if(this.views[this.defaultView] && this.views[this.defaultView]["defaultView"]){
  342. subIds = this.views[this.defaultView]["defaultView"];
  343. }
  344. if(this.models && !this.loadedModels){
  345. //if there is this.models config data and the models has not been loaded yet,
  346. //load models at here using the configuration data and load model logic in model.js
  347. this.loadedModels = model(this.models);
  348. bind(this.getChildren(), this.loadedModels);
  349. }
  350. //startup assumes all children are loaded into DOM before startup is called
  351. //startup will only start the current available children.
  352. var cid = this.id + "_" + toId;
  353. if (this.children[cid]) {
  354. var next = this.children[cid];
  355. this.set("selectedChild", next);
  356. // If I am a not being controlled by a parent layout widget...
  357. var parent = this.getParent && this.getParent();
  358. if (!(parent && parent.isLayoutContainer)) {
  359. // Do recursive sizing and layout of all my descendants
  360. // (passing in no argument to resize means that it has to glean the size itself)
  361. this.resize();
  362. // Since my parent isn't a layout container, and my style *may be* width=height=100%
  363. // or something similar (either set directly or via a CSS class),
  364. // monitor when my size changes so that I can re-layout.
  365. // For browsers where I can't directly monitor when my size changes,
  366. // monitor when the viewport changes size, which *may* indicate a size change for me.
  367. this.connect(has("ie") ? this.domNode : dojo.global, 'onresize', function(){
  368. // Using function(){} closure to ensure no arguments to resize.
  369. this.resize();
  370. });
  371. }
  372. array.forEach(this.getChildren(), function(child){
  373. child.startup();
  374. });
  375. //transition to _startView
  376. if (this._startView && (this._startView != this.defaultView)) {
  377. this.transition(this._startView, {});
  378. }
  379. }
  380. },
  381. addChild: function(widget){
  382. cls.add(widget.domNode, this.baseClass + "_child");
  383. widget.region = "center";;
  384. dattr.set(widget.domNode,"region","center");
  385. this._supportingWidgets.push(widget);
  386. dconstruct.place(widget.domNode,this.domNode);
  387. this.children[widget.id] = widget;
  388. return widget;
  389. },
  390. removeChild: function(widget){
  391. // summary:
  392. // Removes the passed widget instance from this widget but does
  393. // not destroy it. You can also pass in an integer indicating
  394. // the index within the container to remove
  395. if(widget){
  396. var node = widget.domNode;
  397. if(node && node.parentNode){
  398. node.parentNode.removeChild(node); // detach but don't destroy
  399. }
  400. return widget;
  401. }
  402. },
  403. _setSelectedChildAttr: function(child,opts){
  404. if (child !== this.selectedChild) {
  405. return deferred.when(child, dlang.hitch(this, function(child){
  406. if (this.selectedChild){
  407. if (this.selectedChild.deactivate){
  408. this.selectedChild.deactivate();
  409. }
  410. dstyle.set(this.selectedChild.domNode,"zIndex",25);
  411. }
  412. //dojo.style(child.domNode, {
  413. // "display": "",
  414. // "zIndex": 50,
  415. // "overflow": "auto"
  416. //});
  417. this.selectedChild = child;
  418. dstyle.set(child.domNode, "display", "");
  419. dstyle.set(child.domNode,"zIndex",50);
  420. this.selectedChild=child;
  421. if (this._started) {
  422. if (child.startup && !child._started){
  423. child.startup();
  424. }else if (child.activate){
  425. child.activate();
  426. }
  427. }
  428. this.layout();
  429. }));
  430. }
  431. },
  432. transition: function(transitionTo,opts){
  433. //summary:
  434. // transitions from the currently visible scene to the defined scene.
  435. // it should determine what would be the best transition unless
  436. // an override in opts tells it to use a specific transitioning methodology
  437. // the transitionTo is a string in the form of [view]@[scene]. If
  438. // view is left of, the current scene will be transitioned to the default
  439. // view of the specified scene (eg @scene2), if the scene is left off
  440. // the app controller will instruct the active scene to the view (eg view1). If both
  441. // are supplied (view1@scene2), then the application should transition to the scene,
  442. // and instruct the scene to navigate to the view.
  443. var toId,subIds,next, current = this.selectedChild;
  444. console.log("scene", this.id, transitionTo);
  445. if (transitionTo){
  446. var parts = transitionTo.split(",");
  447. toId= parts.shift();
  448. subIds = parts.join(',');
  449. }else{
  450. toId = this.defaultView;
  451. if(this.views[this.defaultView] && this.views[this.defaultView]["defaultView"]){
  452. subIds = this.views[this.defaultView]["defaultView"];
  453. }
  454. }
  455. next = this.loadChild(toId,subIds);
  456. if (!current){
  457. //assume this.set(...) will return a promise object if child is first loaded
  458. //return nothing if child is already in array of this.children
  459. return this.set("selectedChild",next);
  460. }
  461. var transitionDeferred = new deferred();
  462. deferred.when(next, dlang.hitch(this, function(next){
  463. var promise;
  464. if (next!==current){
  465. //TODO need to refactor here, when clicking fast, current will not be the
  466. //view we want to start transition. For example, during transition 1 -> 2
  467. //if user click button to transition to 3 and then transition to 1. It will
  468. //perform transition 2 -> 3 and 2 -> 1 because current is always point to
  469. //2 during 1 -> 2 transition.
  470. var waitingList = anim.getWaitingList([next.domNode, current.domNode]);
  471. //update registry with deferred objects in animations of args.
  472. var transitionDefs = {};
  473. transitionDefs[current.domNode.id] = anim.playing[current.domNode.id] = new deferred();
  474. transitionDefs[next.domNode.id] = anim.playing[current.domNode.id] = new deferred();
  475. deferred.when(waitingList, dojo.hitch(this, function(){
  476. //assume next is already loaded so that this.set(...) will not return
  477. //a promise object. this.set(...) will handles the this.selectedChild,
  478. //activate or deactivate views and refresh layout.
  479. this.set("selectedChild", next);
  480. //publish /app/transition event
  481. //application can subscript this event to do user define operation like select TabBarButton, etc.
  482. connect.publish("/app/transition", [next, toId]);
  483. transit(current.domNode,next.domNode,dojo.mixin({},opts,{transition: this.defaultTransition || "none", transitionDefs: transitionDefs})).then(dlang.hitch(this, function(){
  484. //dojo.style(current.domNode, "display", "none");
  485. if (subIds && next.transition){
  486. promise = next.transition(subIds,opts);
  487. }
  488. deferred.when(promise, function(){
  489. transitionDeferred.resolve();
  490. });
  491. }));
  492. }));
  493. return;
  494. }
  495. //we didn't need to transition, but continue to propogate.
  496. if (subIds && next.transition){
  497. promise = next.transition(subIds,opts);
  498. }
  499. deferred.when(promise, function(){
  500. transitionDeferred.resolve();
  501. });
  502. }));
  503. return transitionDeferred;
  504. },
  505. toString: function(){return this.id},
  506. activate: function(){},
  507. deactive: function(){}
  508. });
  509. });