SlideShow.js 20 KB

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