scrollable.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808
  1. /*
  2. Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
  3. Available via Academic Free License >= 2.1 OR the modified BSD license.
  4. see: http://dojotoolkit.org/license for details
  5. */
  6. if(!dojo._hasResource["dojox.mobile.scrollable"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.mobile.scrollable"] = true;
  8. /*=====
  9. // summary:
  10. // Utility for enabling touch scrolling capability.
  11. // description:
  12. // Mobile WebKit browsers do not allow scrolling inner DIVs. (You need
  13. // the two-finger operation to scroll them.)
  14. // That means you cannot have fixed-positioned header/footer bars.
  15. // To solve this issue, this module disables the browsers default scrolling
  16. // behavior, and re-builds its own scrolling machinery by handling touch
  17. // events. In this module, this.domNode has height "100%" and is fixed to
  18. // the window, and this.containerNode scrolls. If you place a bar outside
  19. // of this.containerNode, then it will be fixed-positioned while
  20. // this.containerNode is scrollable.
  21. //
  22. // This module has the following features:
  23. // - Scrolls inner DIVs vertically, horizontally, or both.
  24. // - Vertical and horizontal scroll bars.
  25. // - Flashes the scroll bars when a view is shown.
  26. // - Simulates the flick operation using animation.
  27. // - Respects header/footer bars if any.
  28. //
  29. // dojox.mobile.scrollable is a simple function object, which holds
  30. // several properties and functions in it. But if you transform it to a
  31. // dojo class, it can be used as a mix-in class for any custom dojo
  32. // widgets. dojox.mobile._ScrollableMixin is such a class.
  33. //
  34. // Also, it can be used even for non-dojo applications. In such cases,
  35. // several dojo APIs used in this module, such as dojo.connect,
  36. // dojo.create, etc., are re-defined so that the code works without dojo.
  37. // When in dojo, of course those re-defined functions are not necessary.
  38. // So, they are surrounded by the excludeStart and excludeEnd directives
  39. // so that they will be excluded from the build.
  40. //
  41. // If you use this module for non-dojo application, you need to explicitly
  42. // assign your outer fixed node and inner scrollable node to this.domNode
  43. // and this.containerNode respectively.
  44. //
  45. // example:
  46. // Use this module from a non-dojo applicatoin:
  47. // | function onLoad(){
  48. // | var scrollable = new dojox.mobile.scrollable();
  49. // | scrollable.init({
  50. // | domNode: "outer", // id or node
  51. // | containerNode: "inner", // id or node
  52. // | fixedHeaderHeight: document.getElementById("hd1").offsetHeight
  53. // | });
  54. // | }
  55. // | <body onload="onLoad()">
  56. // | <h1 id="hd1" style="position:absolute;width:100%;z-index:1;">
  57. // | Fixed Header
  58. // | </h1>
  59. // | <div id="outer" style="height:100%;overflow:hidden;">
  60. // | <div id="inner" style="position:absolute;width:100%;">
  61. // | ... content ...
  62. // | </div>
  63. // | </div>
  64. // | </body>
  65. //
  66. =====*/
  67. if(typeof dojo != "undefined" && dojo.provide){
  68. dojo.provide("dojox.mobile.scrollable");
  69. }else{
  70. dojo = {doc:document, global:window, isWebKit:navigator.userAgent.indexOf("WebKit") != -1};
  71. dojox = {mobile:{}};
  72. }
  73. dojox.mobile.scrollable = function(){
  74. this.fixedHeaderHeight = 0; // height of a fixed header
  75. this.fixedFooterHeight = 0; // height of a fixed footer
  76. this.isLocalFooter = false; // footer is view-local (as opposed to application-wide)
  77. this.scrollBar = true; // show scroll bar or not
  78. this.scrollDir = "v"; // v: vertical, h: horizontal, vh: both, f: flip
  79. this.weight = 0.6; // frictional drag
  80. this.fadeScrollBar = true;
  81. this.disableFlashScrollBar = false;
  82. this.threshold = 0; // drag threshold value in pixels
  83. this.init = function(/*Object?*/params){
  84. if (params){
  85. for(var p in params){
  86. if (params.hasOwnProperty(p)) {
  87. this[p] = ((p == "domNode" || p == "containerNode") && typeof params[p] == "string") ?
  88. dojo.doc.getElementById(params[p]) : params[p]; // mix-in params
  89. }
  90. }
  91. }
  92. this._v = (this.scrollDir.indexOf("v") != -1); // vertical scrolling
  93. this._h = (this.scrollDir.indexOf("h") != -1); // horizontal scrolling
  94. this._f = (this.scrollDir == "f"); // flipping views
  95. this._ch = []; // connect handlers
  96. this._ch.push(dojo.connect(this.containerNode,
  97. dojox.mobile.hasTouch ? "touchstart" : "onmousedown", this, "onTouchStart"));
  98. if(dojo.isWebKit){
  99. this._ch.push(dojo.connect(this.domNode, "webkitAnimationEnd", this, "onFlickAnimationEnd"));
  100. this._ch.push(dojo.connect(this.domNode, "webkitAnimationStart", this, "onFlickAnimationStart"));
  101. }
  102. if(dojo.global.onorientationchange !== undefined){
  103. this._ch.push(dojo.connect(dojo.global, "onorientationchange", this, "resizeView"));
  104. }else{
  105. this._ch.push(dojo.connect(dojo.global, "onresize", this, "resizeView"));
  106. }
  107. this.resizeView();
  108. var _this = this;
  109. setTimeout(function(){
  110. _this.flashScrollBar();
  111. }, 600);
  112. };
  113. this.cleanup = function(){
  114. for(var i = 0; i < this._ch.length; i++){
  115. dojo.disconnect(this._ch[i]);
  116. }
  117. this._ch = null;
  118. };
  119. this.resizeView = function(e){
  120. // moved from init() to support dynamically added fixed bars
  121. this._appFooterHeight = (this.fixedFooterHeight && !this.isLocalFooter) ?
  122. this.fixedFooterHeight : 0;
  123. this.containerNode.style.paddingTop = this.fixedHeaderHeight + "px";
  124. // has to wait a little for completion of hideAddressBar()
  125. var c = 0;
  126. var _this = this;
  127. var id = setInterval(function() {
  128. // adjust the height of this view a couple of times
  129. _this.domNode.style.height = (dojo.global.innerHeight||dojo.doc.documentElement.clientHeight) - _this._appFooterHeight + "px";
  130. _this.resetScrollBar();
  131. if(c++ >= 4) { clearInterval(id); }
  132. }, 300);
  133. };
  134. this.onFlickAnimationStart = function(e){
  135. dojo.stopEvent(e);
  136. };
  137. this.onFlickAnimationEnd = function(e){
  138. if(e && e.srcElement){
  139. dojo.stopEvent(e);
  140. }
  141. this.stopAnimation();
  142. if(this._bounce){
  143. var _this = this;
  144. var bounce = _this._bounce;
  145. setTimeout(function(){
  146. _this.slideTo(bounce, 0.3, "ease-out");
  147. }, 0);
  148. _this._bounce = undefined;
  149. }else{
  150. this.hideScrollBar();
  151. this.removeCover();
  152. }
  153. };
  154. this.onTouchStart = function(e){
  155. if(this._conn && (new Date()).getTime() - this.startTime < 500){
  156. return; // ignore successive onTouchStart calls
  157. }
  158. if(!this._conn){
  159. this._conn = [];
  160. this._conn.push(dojo.connect(dojo.doc, dojox.mobile.hasTouch ? "touchmove" : "onmousemove", this, "onTouchMove"));
  161. this._conn.push(dojo.connect(dojo.doc, dojox.mobile.hasTouch ? "touchend" : "onmouseup", this, "onTouchEnd"));
  162. }
  163. this._aborted = false;
  164. if(dojo.hasClass(this.containerNode, "mblScrollableScrollTo2")){
  165. this.abort();
  166. }
  167. this.touchStartX = e.touches ? e.touches[0].pageX : e.clientX;
  168. this.touchStartY = e.touches ? e.touches[0].pageY : e.clientY;
  169. this.startTime = (new Date()).getTime();
  170. this.startPos = this.getPos();
  171. this._dim = this.getDim();
  172. this._time = [0];
  173. this._posX = [this.touchStartX];
  174. this._posY = [this.touchStartY];
  175. if(e.target.nodeType != 1 || (e.target.tagName != "SELECT" && e.target.tagName != "INPUT" && e.target.tagName != "TEXTAREA")){
  176. dojo.stopEvent(e);
  177. }
  178. };
  179. this.onTouchMove = function(e){
  180. var x = e.touches ? e.touches[0].pageX : e.clientX;
  181. var y = e.touches ? e.touches[0].pageY : e.clientY;
  182. var dx = x - this.touchStartX;
  183. var dy = y - this.touchStartY;
  184. var to = {x:this.startPos.x + dx, y:this.startPos.y + dy};
  185. var dim = this._dim;
  186. if(this._time.length == 1){ // the first TouchMove after TouchStart
  187. if(dx < this.threshold && dy < this.threshold){ return; }
  188. this.addCover();
  189. this.showScrollBar();
  190. }
  191. var weight = this.weight;
  192. if(this._v){
  193. if(to.y > 0){ // content is below the screen area
  194. to.y = Math.round(to.y * weight);
  195. }else if(to.y < -dim.o.h){ // content is above the screen area
  196. if(dim.c.h < dim.d.h){ // content is shorter than display
  197. to.y = Math.round(to.y * weight);
  198. }else{
  199. to.y = -dim.o.h - Math.round((-dim.o.h - to.y) * weight);
  200. }
  201. }
  202. }
  203. if(this._h || this._f){
  204. if(to.x > 0){
  205. to.x = Math.round(to.x * weight);
  206. }else if(to.x < -dim.o.w){
  207. if(dim.c.w < dim.d.w){
  208. to.x = Math.round(to.x * weight);
  209. }else{
  210. to.x = -dim.o.w - Math.round((-dim.o.w - to.x) * weight);
  211. }
  212. }
  213. }
  214. this.scrollTo(to);
  215. var max = 10;
  216. var n = this._time.length; // # of samples
  217. if(n >= 2){
  218. // Check the direction of the finger move.
  219. // If the direction has been changed, discard the old data.
  220. var d0, d1;
  221. if(this._v && !this._h){
  222. d0 = this._posY[n - 1] - this._posY[n - 2];
  223. d1 = y - this._posY[n - 1];
  224. }else if(!this._v && this._h){
  225. d0 = this._posX[n - 1] - this._posX[n - 2];
  226. d1 = x - this._posX[n - 1];
  227. }
  228. if(d0 * d1 < 0){ // direction changed
  229. // leave only the latest data
  230. this._time = [this._time[n - 1]];
  231. this._posX = [this._posX[n - 1]];
  232. this._posY = [this._posY[n - 1]];
  233. n = 1;
  234. }
  235. }
  236. if(n == max){
  237. this._time.shift();
  238. this._posX.shift();
  239. this._posY.shift();
  240. }
  241. this._time.push((new Date()).getTime() - this.startTime);
  242. this._posX.push(x);
  243. this._posY.push(y);
  244. };
  245. this.onTouchEnd = function(e){
  246. if(!this._conn){ return; } // if we get onTouchEnd without onTouchStart, ignore it.
  247. for(var i = 0; i < this._conn.length; i++){
  248. dojo.disconnect(this._conn[i]);
  249. }
  250. this._conn = null;
  251. var n = this._time.length; // # of samples
  252. var clicked = false;
  253. if(!this._aborted){
  254. if(n <= 1){
  255. clicked = true;
  256. }else if(n == 2 && Math.abs(this._posY[1] - this._posY[0]) < 4){
  257. clicked = true;
  258. }
  259. }
  260. if(clicked){ // clicked, not dragged or flicked
  261. this.hideScrollBar();
  262. this.removeCover();
  263. if(dojox.mobile.hasTouch){
  264. var elem = e.target;
  265. if(elem.nodeType != 1){
  266. elem = elem.parentNode;
  267. }
  268. var ev = dojo.doc.createEvent("MouseEvents");
  269. ev.initEvent("click", true, true);
  270. elem.dispatchEvent(ev);
  271. }
  272. return;
  273. }
  274. var speed = {x:0, y:0};
  275. // if the user holds the mouse or finger more than 0.5 sec, do not move.
  276. if(n >= 2 && (new Date()).getTime() - this.startTime - this._time[n - 1] < 500){
  277. var dy = this._posY[n - (n > 3 ? 2 : 1)] - this._posY[(n - 6) >= 0 ? n - 6 : 0];
  278. var dx = this._posX[n - (n > 3 ? 2 : 1)] - this._posX[(n - 6) >= 0 ? n - 6 : 0];
  279. var dt = this._time[n - (n > 3 ? 2 : 1)] - this._time[(n - 6) >= 0 ? n - 6 : 0];
  280. speed.y = this.calcSpeed(dy, dt);
  281. speed.x = this.calcSpeed(dx, dt);
  282. }
  283. var pos = this.getPos();
  284. var to = {}; // destination
  285. var dim = this._dim;
  286. if(this._v){
  287. to.y = pos.y + speed.y;
  288. }
  289. if(this._h || this._f){
  290. to.x = pos.x + speed.x;
  291. }
  292. if(this.scrollDir == "v" && dim.c.h <= dim.d.h){ // content is shorter than display
  293. this.slideTo({y:0}, 0.3, "ease-out"); // go back to the top
  294. return;
  295. }else if(this.scrollDir == "h" && dim.c.w <= dim.d.w){ // content is narrower than display
  296. this.slideTo({x:0}, 0.3, "ease-out"); // go back to the left
  297. return;
  298. }else if(this._v && this._h && dim.c.h <= dim.d.h && dim.c.w <= dim.d.w){
  299. this.slideTo({x:0, y:0}, 0.3, "ease-out"); // go back to the top-left
  300. return;
  301. }
  302. var duration, easing = "ease-out";
  303. var bounce = {};
  304. if(this._v){
  305. if(to.y > 0){ // going down. bounce back to the top.
  306. if(pos.y > 0){ // started from below the screen area. return quickly.
  307. duration = 0.3;
  308. to.y = 0;
  309. }else{
  310. to.y = Math.min(to.y, 20);
  311. easing = "linear";
  312. bounce.y = 0;
  313. }
  314. }else if(-speed.y > dim.o.h - (-pos.y)){ // going up. bounce back to the bottom.
  315. if(pos.y < -dim.o.h){ // started from above the screen top. return quickly.
  316. duration = 0.3;
  317. to.y = dim.c.h <= dim.d.h ? 0 : -dim.o.h; // if shorter, move to 0
  318. }else{
  319. to.y = Math.max(to.y, -dim.o.h - 20);
  320. easing = "linear";
  321. bounce.y = -dim.o.h;
  322. }
  323. }
  324. }
  325. if(this._h || this._f){
  326. if(to.x > 0){ // going right. bounce back to the left.
  327. if(pos.x > 0){ // started from right of the screen area. return quickly.
  328. duration = 0.3;
  329. to.x = 0;
  330. }else{
  331. to.x = Math.min(to.x, 20);
  332. easing = "linear";
  333. bounce.x = 0;
  334. }
  335. }else if(-speed.x > dim.o.w - (-pos.x)){ // going left. bounce back to the right.
  336. if(pos.x < -dim.o.w){ // started from left of the screen top. return quickly.
  337. duration = 0.3;
  338. to.x = dim.c.w <= dim.d.w ? 0 : -dim.o.w; // if narrower, move to 0
  339. }else{
  340. to.x = Math.max(to.x, -dim.o.w - 20);
  341. easing = "linear";
  342. bounce.x = -dim.o.w;
  343. }
  344. }
  345. }
  346. this._bounce = (bounce.x !== undefined || bounce.y !== undefined) ? bounce : undefined;
  347. if(duration === undefined){
  348. var distance, velocity;
  349. if(this._v && this._h){
  350. velocity = Math.sqrt(speed.x+speed.x + speed.y*speed.y);
  351. distance = Math.sqrt(Math.pow(to.y - pos.y, 2) + Math.pow(to.x - pos.x, 2));
  352. }else if(this._v){
  353. velocity = speed.y;
  354. distance = to.y - pos.y;
  355. }else if(this._h){
  356. velocity = speed.x;
  357. distance = to.x - pos.x;
  358. }
  359. duration = velocity !== 0 ? Math.abs(distance / velocity) : 0.01; // time = distance / velocity
  360. }
  361. this.slideTo(to, duration, easing);
  362. };
  363. this.abort = function(){
  364. this.scrollTo(this.getPos());
  365. this.stopAnimation();
  366. this._aborted = true;
  367. };
  368. this.stopAnimation = function(){
  369. // stop the currently running animation
  370. dojo.removeClass(this.containerNode, "mblScrollableScrollTo2");
  371. if(this._scrollBarV){
  372. this._scrollBarV.className = "";
  373. }
  374. if(this._scrollBarH){
  375. this._scrollBarH.className = "";
  376. }
  377. };
  378. this.calcSpeed = function(/*Number*/d, /*Number*/t){
  379. return Math.round(d / t * 100) * 4;
  380. };
  381. this.scrollTo = function(/*Object*/to, /*?Boolean*/doNotMoveScrollBar){ // to: {x, y}
  382. var s = this.containerNode.style;
  383. if(dojo.isWebKit){
  384. s.webkitTransform = this.makeTranslateStr(to);
  385. }else{
  386. if(this._v){
  387. s.top = to.y + "px";
  388. }
  389. if(this._h || this._f){
  390. s.left = to.x + "px";
  391. }
  392. }
  393. if(!doNotMoveScrollBar){
  394. this.scrollScrollBarTo(this.calcScrollBarPos(to));
  395. }
  396. };
  397. this.slideTo = function(/*Object*/to, /*Number*/duration, /*String*/easing){
  398. this._runSlideAnimation(this.getPos(), to, duration, easing, this.containerNode, 2);
  399. this.slideScrollBarTo(to, duration, easing);
  400. };
  401. this.makeTranslateStr = function(to){
  402. var y = this._v && typeof to.y == "number" ? to.y+"px" : "0px";
  403. var x = (this._h||this._f) && typeof to.x == "number" ? to.x+"px" : "0px";
  404. return dojox.mobile.hasTranslate3d ?
  405. "translate3d("+x+","+y+",0px)" : "translate("+x+","+y+")";
  406. };
  407. this.getPos = function(){
  408. // summary:
  409. // Get the top position in the midst of animation
  410. if(dojo.isWebKit){
  411. var m = dojo.doc.defaultView.getComputedStyle(this.containerNode, '')["-webkit-transform"];
  412. if(m && m.indexOf("matrix") === 0){
  413. var arr = m.split(/[,\s\)]+/);
  414. return {y:arr[5] - 0, x:arr[4] - 0};
  415. }
  416. return {x:0, y:0};
  417. }else{
  418. return {y:this.containerNode.offsetTop, x:this.containerNode.offsetLeft};
  419. }
  420. };
  421. this.getDim = function(){
  422. var d = {};
  423. // content width/height
  424. d.c = {h:this.containerNode.offsetHeight - this.fixedHeaderHeight, w:this.containerNode.offsetWidth};
  425. // view width/height
  426. d.v = {h:this.domNode.offsetHeight + this._appFooterHeight, w:this.domNode.offsetWidth};
  427. // display width/height
  428. d.d = {h:d.v.h - this.fixedHeaderHeight - this.fixedFooterHeight, w:d.v.w};
  429. // overflowed width/height
  430. d.o = {h:d.c.h - d.v.h + this.fixedHeaderHeight + this.fixedFooterHeight, w:d.c.w - d.v.w};
  431. return d;
  432. };
  433. this.showScrollBar = function(){
  434. if(!this.scrollBar){ return; }
  435. var dim = this._dim;
  436. if(this.scrollDir == "v" && dim.c.h <= dim.d.h){ return; }
  437. if(this.scrollDir == "h" && dim.c.w <= dim.d.w){ return; }
  438. if(this._v && this._h && dim.c.h <= dim.d.h && dim.c.w <= dim.d.w){ return; }
  439. var createBar = function(self, dir){
  440. var bar = self["_scrollBarNode" + dir];
  441. if(!bar){
  442. var wrapper = dojo.create("div", null, self.domNode);
  443. var props = { position: "absolute", overflow: "hidden" };
  444. if(dir == "V"){
  445. props.right = "2px";
  446. props.width = "5px";
  447. }else{
  448. props.bottom = (self.isLocalFooter ? self.fixedFooterHeight : 0) + 2 + "px";
  449. props.height = "5px";
  450. }
  451. dojo.style(wrapper, props);
  452. wrapper.className = "mblScrollBarWrapper";
  453. self["_scrollBarWrapper"+dir] = wrapper;
  454. bar = dojo.create("div", null, wrapper);
  455. dojo.style(bar, {
  456. opacity: 0.6,
  457. position: "absolute",
  458. backgroundColor: "#606060",
  459. fontSize: "1px",
  460. webkitBorderRadius: "2px",
  461. MozBorderRadius: "2px",
  462. webkitTransformOrigin: "0 0",
  463. zIndex: 2147483647 // max of signed 32-bit integer
  464. });
  465. dojo.style(bar, dir == "V" ? {width: "5px"} : {height: "5px"});
  466. self["_scrollBarNode" + dir] = bar;
  467. }
  468. return bar;
  469. };
  470. if(this._v && !this._scrollBarV){
  471. this._scrollBarV = createBar(this, "V");
  472. }
  473. if(this._h && !this._scrollBarH){
  474. this._scrollBarH = createBar(this, "H");
  475. }
  476. this.resetScrollBar();
  477. };
  478. this.hideScrollBar = function(){
  479. var fadeRule;
  480. if(this.fadeScrollBar && dojo.isWebKit){
  481. if(!dojox.mobile._fadeRule){
  482. var node = dojo.create("style", null, dojo.doc.getElementsByTagName("head")[0]);
  483. node.textContent =
  484. ".mblScrollableFadeOutScrollBar{"+
  485. " -webkit-animation-duration: 1s;"+
  486. " -webkit-animation-name: scrollableViewFadeOutScrollBar;}"+
  487. "@-webkit-keyframes scrollableViewFadeOutScrollBar{"+
  488. " from { opacity: 0.6; }"+
  489. " 50% { opacity: 0.6; }"+
  490. " to { opacity: 0; }}";
  491. dojox.mobile._fadeRule = node.sheet.cssRules[1];
  492. }
  493. fadeRule = dojox.mobile._fadeRule;
  494. }
  495. if(!this.scrollBar){ return; }
  496. var f = function(bar){
  497. dojo.style(bar, {
  498. opacity: 0,
  499. webkitAnimationDuration: ""
  500. });
  501. bar.className = "mblScrollableFadeOutScrollBar";
  502. };
  503. if(this._scrollBarV){
  504. f(this._scrollBarV);
  505. this._scrollBarV = null;
  506. }
  507. if(this._scrollBarH){
  508. f(this._scrollBarH);
  509. this._scrollBarH = null;
  510. }
  511. };
  512. this.calcScrollBarPos = function(/*Object*/to){ // to: {x, y}
  513. var pos = {};
  514. var dim = this._dim;
  515. var f = function(wrapperH, barH, t, d, c){
  516. var y = Math.round((d - barH - 8) / (d - c) * t);
  517. if(y < -barH + 5){
  518. y = -barH + 5;
  519. }
  520. if(y > wrapperH - 5){
  521. y = wrapperH - 5;
  522. }
  523. return y;
  524. };
  525. if(typeof to.y == "number" && this._scrollBarV){
  526. pos.y = f(this._scrollBarWrapperV.offsetHeight, this._scrollBarV.offsetHeight, to.y, dim.d.h, dim.c.h);
  527. }
  528. if(typeof to.x == "number" && this._scrollBarH){
  529. pos.x = f(this._scrollBarWrapperH.offsetWidth, this._scrollBarH.offsetWidth, to.x, dim.d.w, dim.c.w);
  530. }
  531. return pos;
  532. };
  533. this.scrollScrollBarTo = function(/*Object*/to){ // to: {x, y}
  534. if(!this.scrollBar){ return; }
  535. if(this._v && this._scrollBarV && typeof to.y == "number"){
  536. if(dojo.isWebKit){
  537. this._scrollBarV.style.webkitTransform = this.makeTranslateStr({y:to.y});
  538. }else{
  539. this._scrollBarV.style.top = to.y + "px";
  540. }
  541. }
  542. if(this._h && this._scrollBarH && typeof to.x == "number"){
  543. if(dojo.isWebKit){
  544. this._scrollBarH.style.webkitTransform = this.makeTranslateStr({x:to.x});
  545. }else{
  546. this._scrollBarH.style.left = to.x + "px";
  547. }
  548. }
  549. };
  550. this.slideScrollBarTo = function(/*Object*/to, /*Number*/duration, /*String*/easing){
  551. if(!this.scrollBar){ return; }
  552. var fromPos = this.calcScrollBarPos(this.getPos());
  553. var toPos = this.calcScrollBarPos(to);
  554. if(this._v && this._scrollBarV){
  555. this._runSlideAnimation({y:fromPos.y}, {y:toPos.y}, duration, easing, this._scrollBarV, 0);
  556. }
  557. if(this._h && this._scrollBarH){
  558. this._runSlideAnimation({x:fromPos.x}, {x:toPos.x}, duration, easing, this._scrollBarH, 1);
  559. }
  560. };
  561. this._runSlideAnimation = function(/*Object*/from, /*Object*/to, /*Number*/duration, /*String*/easing, node, idx){
  562. // idx: 0:scrollbarV, 1:scrollbarH, 2:content
  563. if(dojo.isWebKit){
  564. this.setKeyframes(from, to, idx);
  565. dojo.style(node, {
  566. webkitAnimationDuration: duration + "s",
  567. webkitAnimationTimingFunction: easing
  568. });
  569. dojo.addClass(node, "mblScrollableScrollTo"+idx);
  570. if(idx == 2){
  571. this.scrollTo(to, true);
  572. }else{
  573. this.scrollScrollBarTo(to);
  574. }
  575. }else if(dojo.fx && dojo.fx.easing){
  576. // If you want to support non-webkit browsers,
  577. // your application needs to load necessary modules as follows:
  578. //
  579. // | dojo.require("dojo.fx");
  580. // | dojo.require("dojo.fx.easing");
  581. //
  582. // This module itself does not make dependency on them.
  583. var s = dojo.fx.slideTo({
  584. node: node,
  585. duration: duration*1000,
  586. left: to.x,
  587. top: to.y,
  588. easing: (easing == "ease-out") ? dojo.fx.easing.quadOut : dojo.fx.easing.linear
  589. }).play();
  590. if(idx == 2){
  591. dojo.connect(s, "onEnd", this, "onFlickAnimationEnd");
  592. }
  593. }else{
  594. // directly jump to the destination without animation
  595. if(idx == 2){
  596. this.scrollTo(to);
  597. this.onFlickAnimationEnd();
  598. }else{
  599. this.scrollScrollBarTo(to);
  600. }
  601. }
  602. };
  603. this.resetScrollBar = function(){
  604. // summary:
  605. // Resets the scroll bar length, position, etc.
  606. var f = function(wrapper, bar, d, c, hd, v){
  607. if(!bar){ return; }
  608. var props = {};
  609. props[v ? "top" : "left"] = hd + 4 + "px"; // +4 is for top or left margin
  610. props[v ? "height" : "width"] = d - 8 + "px";
  611. dojo.style(wrapper, props);
  612. var l = Math.round(d * d / c); // scroll bar length
  613. l = Math.min(Math.max(l - 8, 5), d - 8); // -8 is for margin for both ends
  614. bar.style[v ? "height" : "width"] = l + "px";
  615. dojo.style(bar, {"opacity": 0.6});
  616. };
  617. var dim = this.getDim();
  618. f(this._scrollBarWrapperV, this._scrollBarV, dim.d.h, dim.c.h, this.fixedHeaderHeight, true);
  619. f(this._scrollBarWrapperH, this._scrollBarH, dim.d.w, dim.c.w, 0);
  620. this.createMask();
  621. };
  622. this.createMask = function(){
  623. // summary:
  624. // Creates a mask for a scroll bar edge.
  625. // description:
  626. // This function creates a mask that hides corners of one scroll
  627. // bar edge to make it round edge. The other side of the edge is
  628. // always visible and round shaped with the border-radius style.
  629. if(!dojo.isWebKit){ return; }
  630. var ctx;
  631. if(this._scrollBarWrapperV){
  632. var h = this._scrollBarWrapperV.offsetHeight;
  633. ctx = dojo.doc.getCSSCanvasContext("2d", "scrollBarMaskV", 5, h);
  634. ctx.fillStyle = "rgba(0,0,0,0.5)";
  635. ctx.fillRect(1, 0, 3, 2);
  636. ctx.fillRect(0, 1, 5, 1);
  637. ctx.fillRect(0, h - 2, 5, 1);
  638. ctx.fillRect(1, h - 1, 3, 2);
  639. ctx.fillStyle = "rgb(0,0,0)";
  640. ctx.fillRect(0, 2, 5, h - 4);
  641. this._scrollBarWrapperV.style.webkitMaskImage = "-webkit-canvas(scrollBarMaskV)";
  642. }
  643. if(this._scrollBarWrapperH){
  644. var w = this._scrollBarWrapperH.offsetWidth;
  645. ctx = dojo.doc.getCSSCanvasContext("2d", "scrollBarMaskH", w, 5);
  646. ctx.fillStyle = "rgba(0,0,0,0.5)";
  647. ctx.fillRect(0, 1, 2, 3);
  648. ctx.fillRect(1, 0, 1, 5);
  649. ctx.fillRect(w - 2, 0, 1, 5);
  650. ctx.fillRect(w - 1, 1, 2, 3);
  651. ctx.fillStyle = "rgb(0,0,0)";
  652. ctx.fillRect(2, 0, w - 4, 5);
  653. this._scrollBarWrapperH.style.webkitMaskImage = "-webkit-canvas(scrollBarMaskH)";
  654. }
  655. };
  656. this.flashScrollBar = function(){
  657. if(this.disableFlashScrollBar){ return; }
  658. this._dim = this.getDim();
  659. if(this._dim.d.h <= 0){ return; } // dom is not ready
  660. this.showScrollBar();
  661. var _this = this;
  662. setTimeout(function(){
  663. _this.hideScrollBar();
  664. }, 300);
  665. };
  666. this.addCover = function(){
  667. if(!dojox.mobile.hasTouch && !this.noCover){
  668. if(!this._cover){
  669. this._cover = dojo.create("div", null, dojo.doc.body);
  670. dojo.style(this._cover, {
  671. backgroundColor: "#ffff00",
  672. opacity: 0,
  673. position: "absolute",
  674. top: "0px",
  675. left: "0px",
  676. width: "100%",
  677. height: "100%",
  678. zIndex: 2147483647 // max of signed 32-bit integer
  679. });
  680. this._ch.push(dojo.connect(this._cover,
  681. dojox.mobile.hasTouch ? "touchstart" : "onmousedown", this, "onTouchEnd"));
  682. }else{
  683. this._cover.style.display = "";
  684. }
  685. }
  686. this.setSelectable(this.domNode, false);
  687. var sel;
  688. if(dojo.global.getSelection){
  689. sel = dojo.global.getSelection();
  690. sel.collapse(dojo.doc.body, 0);
  691. }else{
  692. sel = dojo.doc.selection.createRange();
  693. sel.setEndPoint("EndToStart", sel);
  694. sel.select();
  695. }
  696. };
  697. this.removeCover = function(){
  698. if(!dojox.mobile.hasTouch && this._cover){
  699. this._cover.style.display = "none";
  700. }
  701. this.setSelectable(this.domNode, true);
  702. };
  703. this.setKeyframes = function(/*Object*/from, /*Object*/to, /*Number*/idx){
  704. if(!dojox.mobile._rule){
  705. dojox.mobile._rule = [];
  706. }
  707. // idx: 0:scrollbarV, 1:scrollbarH, 2:content
  708. if(!dojox.mobile._rule[idx]){
  709. var node = dojo.create("style", null, dojo.doc.getElementsByTagName("head")[0]);
  710. node.textContent =
  711. ".mblScrollableScrollTo"+idx+"{-webkit-animation-name: scrollableViewScroll"+idx+";}"+
  712. "@-webkit-keyframes scrollableViewScroll"+idx+"{}";
  713. dojox.mobile._rule[idx] = node.sheet.cssRules[1];
  714. }
  715. var rule = dojox.mobile._rule[idx];
  716. if(rule){
  717. if(from){
  718. rule.deleteRule("from");
  719. rule.insertRule("from { -webkit-transform: "+this.makeTranslateStr(from)+"; }");
  720. }
  721. if(to){
  722. if(to.x === undefined){ to.x = from.x; }
  723. if(to.y === undefined){ to.y = from.y; }
  724. rule.deleteRule("to");
  725. rule.insertRule("to { -webkit-transform: "+this.makeTranslateStr(to)+"; }");
  726. }
  727. }
  728. };
  729. this.setSelectable = function(node, selectable){
  730. // dojo.setSelectable has dependency on dojo.query. Re-define our own.
  731. node.style.KhtmlUserSelect = selectable ? "auto" : "none";
  732. node.style.MozUserSelect = selectable ? "" : "none";
  733. node.onselectstart = selectable ? null : function(){return false;};
  734. node.unselectable = selectable ? "" : "on";
  735. };
  736. };
  737. (function(){
  738. // feature detection
  739. if(dojo.isWebKit){
  740. var elem = dojo.doc.createElement("div");
  741. elem.style.webkitTransform = "translate3d(0px,1px,0px)";
  742. dojo.doc.documentElement.appendChild(elem);
  743. var v = dojo.doc.defaultView.getComputedStyle(elem, '')["-webkit-transform"];
  744. dojox.mobile.hasTranslate3d = v && v.indexOf("matrix") === 0;
  745. dojo.doc.documentElement.removeChild(elem);
  746. dojox.mobile.hasTouch = (typeof dojo.doc.documentElement.ontouchstart != "undefined" &&
  747. navigator.appVersion.indexOf("Mobile") != -1);
  748. }
  749. })();
  750. }