SlideShow.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. // wrapped by build app
  2. define("dojox/image/SlideShow", ["dijit","dojo","dojox","dojo/require!dojo/string,dojo/fx,dijit/_Widget,dijit/_Templated"], function(dijit,dojo,dojox){
  3. dojo.provide("dojox.image.SlideShow");
  4. //
  5. // dojox.image.SlideShow courtesy Shane O Sullivan, licensed under a Dojo CLA
  6. // For a sample usage, see http://www.skynet.ie/~sos/photos.php
  7. //
  8. //
  9. // TODO: more cleanups
  10. //
  11. dojo.require("dojo.string");
  12. dojo.require("dojo.fx");
  13. dojo.require("dijit._Widget");
  14. dojo.require("dijit._Templated");
  15. dojo.declare("dojox.image.SlideShow",
  16. [dijit._Widget, dijit._Templated],
  17. {
  18. // summary:
  19. // A Slideshow Widget
  20. // imageHeight: Number
  21. // The maximum height of an image
  22. imageHeight: 375,
  23. // imageWidth: Number
  24. // The maximum width of an image.
  25. imageWidth: 500,
  26. // title: String
  27. // The initial title of the SlideShow
  28. title: "",
  29. // titleTemplate: String
  30. // a way to customize the wording in the title. supported parameters to be populated are:
  31. // ${title} = the passed title of the image
  32. // ${current} = the current index of the image
  33. // ${total} = the total number of images in the SlideShow
  34. //
  35. // should add more?
  36. titleTemplate: '${title} <span class="slideShowCounterText">(${current} of ${total})</span>',
  37. // noLink: Boolean
  38. // Prevents the slideshow from putting an anchor link around the displayed image
  39. // enables if true, though still will not link in absence of a url to link to
  40. noLink: false,
  41. // loop: Boolean
  42. // true/false - make the slideshow loop
  43. loop: true,
  44. // hasNav: Boolean
  45. // toggle to enable/disable the visual navigation controls
  46. hasNav: true,
  47. // images: Array
  48. // Contains the DOM nodes that individual images are stored in when loaded or loading.
  49. images: [],
  50. // pageSize: Number
  51. // The number of images to request each time.
  52. pageSize: 20,
  53. // autoLoad: Boolean
  54. // If true, then images are preloaded, before the user navigates to view them.
  55. // If false, an image is not loaded until the user views it.
  56. autoLoad: true,
  57. // autoStart: Boolean
  58. // If true, the SlideShow begins playing immediately
  59. autoStart: false,
  60. // fixedHeight: Boolean
  61. // If true, the widget does not resize itself to fix the displayed image.
  62. fixedHeight: false,
  63. // imageStore: Object
  64. // Implementation of the dojo.data.api.Read API, which provides data on the images
  65. // to be displayed.
  66. imageStore: null,
  67. // linkAttr: String
  68. // Defines the name of the attribute to request from the store to retrieve the
  69. // URL to link to from an image, if any.
  70. linkAttr: "link",
  71. // imageLargeAttr: String
  72. // Defines the name of the attribute to request from the store to retrieve the
  73. // URL to the image.
  74. imageLargeAttr: "imageUrl",
  75. // titleAttr: String
  76. // Defines the name of the attribute to request from the store to retrieve the
  77. // title of the picture, if any.
  78. titleAttr: "title",
  79. // slideshowInterval: Number
  80. // Time, in seconds, between image transitions during a slideshow.
  81. slideshowInterval: 3,
  82. templateString: dojo.cache("dojox.image", "resources/SlideShow.html", "<div dojoAttachPoint=\"outerNode\" class=\"slideShowWrapper\">\n\t<div style=\"position:relative;\" dojoAttachPoint=\"innerWrapper\">\n\t\t<div class=\"slideShowNav\" dojoAttachEvent=\"onclick: _handleClick\">\n\t\t\t<div class=\"dijitInline slideShowTitle\" dojoAttachPoint=\"titleNode\">${title}</div>\n\t\t</div>\n\t\t<div dojoAttachPoint=\"navNode\" class=\"slideShowCtrl\" dojoAttachEvent=\"onclick: _handleClick\">\n\t\t\t<span dojoAttachPoint=\"navPrev\" class=\"slideShowCtrlPrev\"></span>\n\t\t\t<span dojoAttachPoint=\"navPlay\" class=\"slideShowCtrlPlay\"></span>\n\t\t\t<span dojoAttachPoint=\"navNext\" class=\"slideShowCtrlNext\"></span>\n\t\t</div>\n\t\t<div dojoAttachPoint=\"largeNode\" class=\"slideShowImageWrapper\"></div>\t\t\n\t\t<div dojoAttachPoint=\"hiddenNode\" class=\"slideShowHidden\"></div>\n\t</div>\n</div>"),
  83. // _imageCounter: Number
  84. // A counter to keep track of which index image is to be loaded next
  85. _imageCounter: 0,
  86. // _tmpImage: DomNode
  87. // The temporary image to show when a picture is loading.
  88. _tmpImage: null,
  89. // _request: Object
  90. // Implementation of the dojo.data.api.Request API, which defines the query
  91. // parameters for accessing the store.
  92. _request: null,
  93. postCreate: function(){
  94. // summary: Initilizes the widget, sets up listeners and shows the first image
  95. this.inherited(arguments);
  96. var img = document.createElement("img");
  97. // FIXME: should API be to normalize an image to fit in the specified height/width?
  98. img.setAttribute("width", this.imageWidth);
  99. img.setAttribute("height", this.imageHeight);
  100. if(this.hasNav){
  101. dojo.connect(this.outerNode, "onmouseover", this, function(evt){
  102. try{ this._showNav();}
  103. catch(e){} //TODO: remove try/catch
  104. });
  105. dojo.connect(this.outerNode, "onmouseout", this, function(evt){
  106. try{ this._hideNav(evt);}
  107. catch(e){} //TODO: remove try/catch
  108. });
  109. }
  110. this.outerNode.style.width = this.imageWidth + "px";
  111. img.setAttribute("src", this._blankGif);
  112. var _this = this;
  113. this.largeNode.appendChild(img);
  114. this._tmpImage = this._currentImage = img;
  115. this._fitSize(true);
  116. this._loadImage(0, dojo.hitch(this, "showImage", 0));
  117. this._calcNavDimensions();
  118. dojo.style(this.navNode, "opacity", 0);
  119. },
  120. setDataStore: function(dataStore, request, /*optional*/paramNames){
  121. // summary:
  122. // Sets the data store and request objects to read data from.
  123. // dataStore:
  124. // An implementation of the dojo.data.api.Read API. This accesses the image
  125. // data.
  126. // request:
  127. // An implementation of the dojo.data.api.Request API. This specifies the
  128. // query and paging information to be used by the data store
  129. // paramNames:
  130. // An object defining the names of the item attributes to fetch from the
  131. // data store. The three attributes allowed are 'linkAttr', 'imageLargeAttr' and 'titleAttr'
  132. this.reset();
  133. var _this = this;
  134. this._request = {
  135. query: {},
  136. start: request.start || 0,
  137. count: request.count || this.pageSize,
  138. onBegin: function(count, request){
  139. // FIXME: fires too often?!?
  140. _this.maxPhotos = count;
  141. }
  142. };
  143. if(request.query){
  144. dojo.mixin(this._request.query, request.query);
  145. }
  146. if(paramNames){
  147. dojo.forEach(["imageLargeAttr", "linkAttr", "titleAttr"], function(attrName){
  148. if(paramNames[attrName]){
  149. this[attrName] = paramNames[attrName];
  150. }
  151. }, this);
  152. }
  153. var _complete = function(items){
  154. // FIXME: onBegin above used to work for maxPhotos:
  155. _this.maxPhotos = items.length;
  156. _this._request.onComplete = null;
  157. if(_this.autoStart){
  158. _this.imageIndex = -1;
  159. _this.toggleSlideShow();
  160. } else {
  161. _this.showImage(0);
  162. }
  163. };
  164. this.imageStore = dataStore;
  165. this._request.onComplete = _complete;
  166. this._request.start = 0;
  167. this.imageStore.fetch(this._request);
  168. },
  169. reset: function(){
  170. // summary:
  171. // Resets the widget to its initial state
  172. // description:
  173. // Removes all previously loaded images, and clears all caches.
  174. dojo.query("> *", this.largeNode).orphan();
  175. this.largeNode.appendChild(this._tmpImage);
  176. dojo.query("> *", this.hiddenNode).orphan();
  177. dojo.forEach(this.images, function(img){
  178. if(img && img.parentNode){ img.parentNode.removeChild(img); }
  179. });
  180. this.images = [];
  181. this.isInitialized = false;
  182. this._imageCounter = 0;
  183. },
  184. isImageLoaded: function(index){
  185. // summary:
  186. // Returns true if image at the specified index is loaded, false otherwise.
  187. // index:
  188. // The number index in the data store to check if it is loaded.
  189. return this.images && this.images.length > index && this.images[index];
  190. },
  191. moveImageLoadingPointer: function(index){
  192. // summary:
  193. // If 'autoload' is true, this tells the widget to start loading
  194. // images from the specified pointer.
  195. // index:
  196. // The number index in the data store to start loading images from.
  197. this._imageCounter = index;
  198. },
  199. destroy: function(){
  200. // summary:
  201. // Cleans up the widget when it is being destroyed
  202. if(this._slideId) { this._stop(); }
  203. this.inherited(arguments);
  204. },
  205. showNextImage: function(inTimer, forceLoop){
  206. // summary:
  207. // Changes the image being displayed to the next image in the data store
  208. // inTimer: Boolean
  209. // If true, a slideshow is active, otherwise the slideshow is inactive.
  210. if(inTimer && this._timerCancelled){ return false; }
  211. if(this.imageIndex + 1 >= this.maxPhotos){
  212. if(inTimer && (this.loop || forceLoop)){
  213. this.imageIndex = -1;
  214. }else{
  215. if(this._slideId){ this._stop(); }
  216. return false;
  217. }
  218. }
  219. this.showImage(this.imageIndex + 1, dojo.hitch(this,function(){
  220. if(inTimer){ this._startTimer(); }
  221. }));
  222. return true;
  223. },
  224. toggleSlideShow: function(){
  225. // summary:
  226. // Switches the slideshow mode on and off.
  227. // If the slideshow is already running, stop it.
  228. if(this._slideId){
  229. this._stop();
  230. }else{
  231. dojo.toggleClass(this.domNode,"slideShowPaused");
  232. this._timerCancelled = false;
  233. var idx = this.imageIndex;
  234. if(idx < 0 || (this.images[idx] && this.images[idx]._img.complete)){
  235. var success = this.showNextImage(true, true);
  236. if(!success){
  237. this._stop();
  238. }
  239. }else{
  240. var handle = dojo.subscribe(this.getShowTopicName(), dojo.hitch(this, function(info){
  241. setTimeout(dojo.hitch(this, function(){
  242. if(info.index == idx){
  243. var success = this.showNextImage(true, true);
  244. if(!success){
  245. this._stop();
  246. }
  247. dojo.unsubscribe(handle);
  248. }}),
  249. this.slideshowInterval * 1000);
  250. }));
  251. dojo.publish(this.getShowTopicName(),
  252. [{index: idx, title: "", url: ""}]);
  253. }
  254. }
  255. },
  256. getShowTopicName: function(){
  257. // summary:
  258. // Returns the topic id published to when an image is shown
  259. // description:
  260. // The information published is: index, title and url
  261. return (this.widgetId || this.id) + "/imageShow";
  262. },
  263. getLoadTopicName: function(){
  264. // summary:
  265. // Returns the topic id published to when an image finishes loading.
  266. // description:
  267. // The information published is the index position of the image loaded.
  268. return (this.widgetId ? this.widgetId : this.id) + "/imageLoad";
  269. },
  270. showImage: function(index, /* Function? */callback){
  271. // summary:
  272. // Shows the image at index 'index'.
  273. // index: Number
  274. // The position of the image in the data store to display
  275. // callback: Function
  276. // Optional callback function to call when the image has finished displaying.
  277. if(!callback && this._slideId){
  278. this.toggleSlideShow();
  279. }
  280. var _this = this;
  281. var current = this.largeNode.getElementsByTagName("div");
  282. this.imageIndex = index;
  283. var showOrLoadIt = function() {
  284. //If the image is already loaded, then show it.
  285. if(_this.images[index]){
  286. while(_this.largeNode.firstChild){
  287. _this.largeNode.removeChild(_this.largeNode.firstChild);
  288. }
  289. dojo.style(_this.images[index],"opacity", 0);
  290. _this.largeNode.appendChild(_this.images[index]);
  291. _this._currentImage = _this.images[index]._img;
  292. _this._fitSize();
  293. var onEnd = function(a,b,c){
  294. var img = _this.images[index].firstChild;
  295. if(img.tagName.toLowerCase() != "img"){ img = img.firstChild; }
  296. var title = img.getAttribute("title") || "";
  297. if(_this._navShowing){
  298. _this._showNav(true);
  299. }
  300. dojo.publish(_this.getShowTopicName(), [{
  301. index: index,
  302. title: title,
  303. url: img.getAttribute("src")
  304. }]);
  305. if(callback) {
  306. callback(a,b,c);
  307. }
  308. _this._setTitle(title);
  309. };
  310. dojo.fadeIn({
  311. node: _this.images[index],
  312. duration: 300,
  313. onEnd: onEnd
  314. }).play();
  315. }else{
  316. //If the image is not loaded yet, load it first, then show it.
  317. _this._loadImage(index, function(){
  318. _this.showImage(index, callback);
  319. });
  320. }
  321. };
  322. //If an image is currently showing, fade it out, then show
  323. //the new image. Otherwise, just show the new image.
  324. if(current && current.length > 0){
  325. dojo.fadeOut({
  326. node: current[0],
  327. duration: 300,
  328. onEnd: function(){
  329. _this.hiddenNode.appendChild(current[0]);
  330. showOrLoadIt();
  331. }
  332. }).play();
  333. }else{
  334. showOrLoadIt();
  335. }
  336. },
  337. _fitSize: function(force){
  338. // summary:
  339. // Fits the widget size to the size of the image being shown,
  340. // or centers the image, depending on the value of 'fixedHeight'
  341. // force: Boolean
  342. // If true, the widget is always resized, regardless of the value of 'fixedHeight'
  343. if(!this.fixedHeight || force){
  344. var height = (this._currentImage.height + (this.hasNav ? 20:0));
  345. dojo.style(this.innerWrapper, "height", height + "px");
  346. return;
  347. }
  348. dojo.style(this.largeNode, "paddingTop", this._getTopPadding() + "px");
  349. },
  350. _getTopPadding: function(){
  351. // summary:
  352. // Returns the padding to place at the top of the image to center it vertically.
  353. if(!this.fixedHeight){ return 0; }
  354. return (this.imageHeight - this._currentImage.height) / 2;
  355. },
  356. _loadNextImage: function(){
  357. // summary:
  358. // Load the next unloaded image.
  359. if(!this.autoLoad){
  360. return;
  361. }
  362. while(this.images.length >= this._imageCounter && this.images[this._imageCounter]){
  363. this._imageCounter++;
  364. }
  365. this._loadImage(this._imageCounter);
  366. },
  367. _loadImage: function(index, callbackFn){
  368. // summary:
  369. // Load image at specified index
  370. // description:
  371. // This function loads the image at position 'index' into the
  372. // internal cache of images. This does not cause the image to be displayed.
  373. // index:
  374. // The position in the data store to load an image from.
  375. // callbackFn:
  376. // An optional function to execute when the image has finished loading.
  377. if(this.images[index] || !this._request) {
  378. return;
  379. }
  380. var pageStart = index - (index % (this._request.count || this.pageSize));
  381. this._request.start = pageStart;
  382. this._request.onComplete = function(items){
  383. var diff = index - pageStart;
  384. if(items && items.length > diff){
  385. loadIt(items[diff]);
  386. }else{ /* Squelch - console.log("Got an empty set of items"); */ }
  387. }
  388. var _this = this;
  389. var store = this.imageStore;
  390. var loadIt = function(item){
  391. var url = _this.imageStore.getValue(item, _this.imageLargeAttr);
  392. var img = new Image(); // when creating img with "createElement" IE doesnt has width and height, so use the Image object
  393. var div = dojo.create("div", {
  394. id: _this.id + "_imageDiv" + index
  395. });
  396. div._img = img;
  397. var link = _this.imageStore.getValue(item,_this.linkAttr);
  398. if(!link || _this.noLink){
  399. div.appendChild(img);
  400. }else{
  401. var a = dojo.create("a", {
  402. "href": link,
  403. "target": "_blank"
  404. }, div);
  405. a.appendChild(img);
  406. }
  407. dojo.connect(img, "onload", function(){
  408. if(store != _this.imageStore){
  409. // If the store has changed, ignore this load event.
  410. return;
  411. }
  412. _this._fitImage(img);
  413. dojo.attr(div, {"width": _this.imageWidth, "height": _this.imageHeight});
  414. // make a short timeout to prevent IE6/7 stack overflow at line 0 ~ still occuring though for first image
  415. dojo.publish(_this.getLoadTopicName(), [index]);
  416. setTimeout(function(){_this._loadNextImage();}, 1);
  417. if(callbackFn){ callbackFn(); }
  418. });
  419. _this.hiddenNode.appendChild(div);
  420. var titleDiv = dojo.create("div", {
  421. className: "slideShowTitle"
  422. }, div);
  423. _this.images[index] = div;
  424. dojo.attr(img, "src", url);
  425. var title = _this.imageStore.getValue(item, _this.titleAttr);
  426. if(title){ dojo.attr(img, "title", title); }
  427. }
  428. this.imageStore.fetch(this._request);
  429. },
  430. _stop: function(){
  431. // summary:
  432. // Stops a running slide show.
  433. if(this._slideId){ clearTimeout(this._slideId); }
  434. this._slideId = null;
  435. this._timerCancelled = true;
  436. dojo.removeClass(this.domNode,"slideShowPaused");
  437. },
  438. _prev: function(){
  439. // summary:
  440. // Show the previous image.
  441. // FIXME: either pull code from showNext/prev, or call it here
  442. if(this.imageIndex < 1){ return; }
  443. this.showImage(this.imageIndex - 1);
  444. },
  445. _next: function(){
  446. // summary:
  447. // Show the next image
  448. this.showNextImage();
  449. },
  450. _startTimer: function(){
  451. // summary:
  452. // Starts a timeout to show the next image when a slide show is active
  453. var id = this.id;
  454. this._slideId = setTimeout(function(){
  455. dijit.byId(id).showNextImage(true);
  456. }, this.slideshowInterval * 1000);
  457. },
  458. _calcNavDimensions: function() {
  459. // summary:
  460. // Calculates the dimensions of the navigation controls
  461. dojo.style(this.navNode, "position", "absolute");
  462. //Place the navigation controls far off screen
  463. dojo.style(this.navNode, "top", "-10000px");
  464. dojo.style(this.navPlay, 'marginLeft', 0);
  465. this.navPlay._size = dojo.marginBox(this.navPlay);
  466. this.navPrev._size = dojo.marginBox(this.navPrev);
  467. this.navNext._size = dojo.marginBox(this.navNext);
  468. dojo.style(this.navNode, {"position": "", top: ""});
  469. },
  470. _setTitle: function(title){
  471. // summary:
  472. // Sets the title to the image being displayed
  473. // title: String
  474. // The String title of the image
  475. this.titleNode.innerHTML = dojo.string.substitute(this.titleTemplate,{
  476. title: title,
  477. current: 1 + this.imageIndex,
  478. total: this.maxPhotos || ""
  479. });
  480. },
  481. _fitImage: function(img) {
  482. // summary:
  483. // Ensures that the image width and height do not exceed the maximum.
  484. // img: Node
  485. // The image DOM node to optionally resize
  486. var width = img.width;
  487. var height = img.height;
  488. if(width > this.imageWidth){
  489. height = Math.floor(height * (this.imageWidth / width));
  490. img.height = height;
  491. img.width = width = this.imageWidth;
  492. }
  493. if(height > this.imageHeight){
  494. width = Math.floor(width * (this.imageHeight / height));
  495. img.height = this.imageHeight;
  496. img.width = width;
  497. }
  498. },
  499. _handleClick: function(/* Event */e){
  500. // summary:
  501. // Performs navigation on the images based on users mouse clicks
  502. // e:
  503. // An Event object
  504. switch(e.target){
  505. case this.navNext: this._next(); break;
  506. case this.navPrev: this._prev(); break;
  507. case this.navPlay: this.toggleSlideShow(); break;
  508. }
  509. },
  510. _showNav: function(force){
  511. // summary:
  512. // Shows the navigation controls
  513. // force: Boolean
  514. // If true, the navigation controls are repositioned even if they are
  515. // currently visible.
  516. if(this._navShowing && !force){return;}
  517. this._calcNavDimensions();
  518. dojo.style(this.navNode, "marginTop", "0px");
  519. var navPlayPos = dojo.style(this.navNode, "width")/2 - this.navPlay._size.w/2 - this.navPrev._size.w;
  520. console.log('navPlayPos = ' + dojo.style(this.navNode, "width")/2 + ' - ' + this.navPlay._size.w + '/2 - '
  521. + this.navPrev._size.w);
  522. dojo.style(this.navPlay, "marginLeft", navPlayPos + "px");
  523. var wrapperSize = dojo.marginBox(this.outerNode);
  524. var margin = this._currentImage.height - this.navPlay._size.h - 10 + this._getTopPadding();
  525. if(margin > this._currentImage.height){margin += 10;}
  526. dojo[this.imageIndex < 1 ? "addClass":"removeClass"](this.navPrev, "slideShowCtrlHide");
  527. dojo[this.imageIndex + 1 >= this.maxPhotos ? "addClass":"removeClass"](this.navNext, "slideShowCtrlHide");
  528. var _this = this;
  529. if(this._navAnim) {
  530. this._navAnim.stop();
  531. }
  532. if(this._navShowing){ return; }
  533. this._navAnim = dojo.fadeIn({
  534. node: this.navNode,
  535. duration: 300,
  536. onEnd: function(){ _this._navAnim = null; }
  537. });
  538. this._navAnim.play();
  539. this._navShowing = true;
  540. },
  541. _hideNav: function(/* Event */e){
  542. // summary:
  543. // Hides the navigation controls
  544. // e: Event
  545. // The DOM Event that triggered this function
  546. if(!e || !this._overElement(this.outerNode, e)){
  547. var _this = this;
  548. if(this._navAnim){
  549. this._navAnim.stop();
  550. }
  551. this._navAnim = dojo.fadeOut({
  552. node: this.navNode,
  553. duration:300,
  554. onEnd: function(){ _this._navAnim = null; }
  555. });
  556. this._navAnim.play();
  557. this._navShowing = false;
  558. }
  559. },
  560. _overElement: function(/*DomNode*/element, /*Event*/e){
  561. // summary:
  562. // Returns whether the mouse is over the passed element.
  563. // Element must be display:block (ie, not a <span>)
  564. //When the page is unloading, if this method runs it will throw an
  565. //exception.
  566. if(typeof(dojo) == "undefined"){ return false; }
  567. element = dojo.byId(element);
  568. var m = { x: e.pageX, y: e.pageY };
  569. var bb = dojo.position(element, true);
  570. return (m.x >= bb.x
  571. && m.x <= (bb.x + bb.w)
  572. && m.y >= bb.y
  573. && m.y <= (top + bb.h)
  574. ); // boolean
  575. }
  576. });
  577. });