SlideShow.js 20 KB

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