/** * An image slideshow viewer * <pre><strong>options:</strong> { * element : «Element | ID», * name : «String», * perimeter : «Integer», * sizeSnap : «Integer», * margin : «Integer», * ease : «Function», * easeEnd : «Function», * easeAuto : «Function», * easeReturn : «Function», * transition : «Integer», * transitionEnd : «Integer», * transitionReturn : «Integer», * images : «Array», * listener : «Object» * } * </pre> * @constructor */ hui.ui.ImageViewer = function(options) { this.options = hui.override({ maxWidth : 800, maxHeight : 600, perimeter : 100, sizeSnap : 100, margin : 0, ease : hui.ease.slowFastSlow, easeEnd : hui.ease.bounce, easeAuto : hui.ease.slowFastSlow, easeReturn : hui.ease.cubicInOut, transition : 400, transitionEnd : 1000, transitionReturn : 300, images : [] },options); // Collect elements ... this.element = hui.get(options.element); this.box = this.options.box; // State ... this.dirty = false; this.width = 600; this.height = 460; this.index = 0; this.position = 0; // pixels this.playing = false; this.name = options.name; this.images = options.images || []; hui.ui.extend(this); // Behavior ... this.box.listen(this); this._attach(); this._attachDrag(); if (options.listener) { this.listen(options.listener); } }; /** * Creates a new image viewer */ hui.ui.ImageViewer.create = function(options) { options = options || {}; var element = options.element = hui.build('div', {'class':'hui_imageviewer', html: '<div class="hui_imageviewer_viewer"><div class="hui_imageviewer_inner_viewer"></div></div>'+ '<div class="hui_imageviewer_text"></div>'+ '<div class="hui_imageviewer_status"></div>'+ '<div class="hui_imageviewer_controller"><div><div>'+ '<a class="hui_imageviewer_previous"></a>'+ '<a class="hui_imageviewer_play"></a>'+ '<a class="hui_imageviewer_next"></a>'+ '<a class="hui_imageviewer_close"></a>'+ '</div></div></div>'}); var box = options.box = hui.ui.Box.create({variant:'plain',absolute:true,modal:true,closable:true}); box.add(element); box.addToDocument(); return new hui.ui.ImageViewer(options); }; hui.ui.ImageViewer.prototype = { nodes : { viewer : '.hui_imageviewer_viewer', innerViewer : '.hui_imageviewer_inner_viewer', status : '.hui_imageviewer_status', text : '.hui_imageviewer_text', previous : '.hui_imageviewer_previous', controller : '.hui_imageviewer_controller', next : '.hui_imageviewer_next', play : '.hui_imageviewer_play', close : '.hui_imageviewer_close' }, _attach : function() { var self = this; this.nodes.next.onclick = function() { self.next(true); }; this.nodes.previous.onclick = function() { self.previous(true); }; this.nodes.play.onclick = function() { self.playOrPause(); }; this.nodes.close.onclick = this.hide.bind(this); this._timer = function() { self.next(false); }; this._keyListener = function(e) { e = hui.event(e); if (e.escapeKey) { self.hide(); } else if (!self.zoomed) { if (e.rightKey) { self.next(true); } else if (e.leftKey) { self.previous(true); } else if (e.returnKey) { self.playOrPause(); } } }; hui.listen(this.nodes.viewer,'mousemove',this._onMouseMove.bind(this)); hui.listen(this.nodes.controller,'mouseover',function() { self.overController = true; }); hui.listen(this.nodes.controller,'mouseout',function() { self.overController = false; }); hui.listen(this.nodes.viewer,'mouseout',function(e) { if (!hui.ui.isWithin(e,this.nodes.viewer)) { self._hideController(); } }.bind(this)); }, _draw : function(pos) { if (hui.browser.webkit) { this.nodes.innerViewer.style.webkitTransform = 'translate3d(' + this.position + 'px,0,0)'; } else { this.nodes.innerViewer.style.marginLeft = this.position + 'px'; } }, _attachDrag : function() { var initial = 0; var left = 0; var scrl = 0; var viewer = this.nodes.viewer; var inner = this.nodes.innerViewer; var max = 0; hui.drag.register({ touch : true, element : this.nodes.innerViewer, onBeforeMove : function(e) { initial = e.getLeft(); scrl = this.position; max = (this.images.length-1) * this.width * -1; }.bind(this), onMove : function(e) { left = e.getLeft(); var pos = (scrl - (initial - left)); if (pos > 0) { pos = (Math.exp(pos * -0.013) -1) * -80; } if (pos < max) { pos = (Math.exp((pos - max) * 0.013) -1) * 80 + max; } this.position = pos; this._draw(); }.bind(this), onAfterMove : function() { var func = (initial - left) < 0 ? Math.floor : Math.ceil; this.index = func(this.position * -1 / this.width); var num = this.images.length - 1; if (this.index==this.images.length) { this.index = 0; } else if (this.index < 0) { this.index = this.images.length - 1; } else { num = 1; } this._goToImage(true,num,false,true); }.bind(this), onNotMoved : this._zoom.bind(this) }); }, _onMouseMove : function() { window.clearTimeout(this.ctrlHider); if (this._shouldShowController()) { this.ctrlHider = window.setTimeout(this._hideController.bind(this),2000); if (!hui.browser.opacity) { this.nodes.controller.style.display='block'; } else { hui.effect.fadeIn({element:this.nodes.controller,duration:200}); } } }, _hideController : function() { if (!this.overController) { if (!hui.browser.opacity) { this.nodes.controller.style.display='none'; } else { hui.effect.fadeOut({element:this.nodes.controller,duration:500}); } } }, _getLargestSize : function(canvas,image) { return hui.fit(image,canvas,{upscale:false}); }, _calculateSize : function() { var snap = this.options.sizeSnap; var newWidth = hui.window.getViewWidth() - this.options.perimeter; newWidth = Math.floor(newWidth / snap) * snap; newWidth = Math.min(newWidth, this.options.maxWidth); var newHeight = hui.window.getViewHeight() - this.options.perimeter; newHeight = Math.floor(newHeight / snap) * snap; newHeight = Math.min(newHeight, this.options.maxHeight); var maxWidth = 0; var maxHeight = 0; for (var i = 0; i < this.images.length; i++) { var dims = this._getLargestSize({ width: newWidth, height: newHeight }, this.images[i]); maxWidth = Math.max(maxWidth, dims.width); maxHeight = Math.max(maxHeight, dims.height); } newHeight = Math.floor(Math.min(newHeight, maxHeight)); newWidth = Math.floor(Math.min(newWidth, maxWidth)); if (newWidth != this.width || newHeight != this.height) { this.width = newWidth; this.height = newHeight; this.dirty = true; } }, _updateUI : function() { if (this.dirty) { this.nodes.innerViewer.innerHTML=''; for (var i=0; i < this.images.length; i++) { var element = hui.build('div',{'class':'hui_imageviewer_image'}); hui.style.set(element,{width: (this.width + this.options.margin) + 'px',height : (this.height-1)+'px' }); this.nodes.innerViewer.appendChild(element); } this.nodes.controller.style.display = this._shouldShowController() ? 'block' : 'none'; this.dirty = false; this._preload(); } }, _shouldShowController : function() { return this.images.length > 1; }, _goToImage : function(animate,num,user,drag) { var initial = this.position; var target = this.position = this.index * (this.width + this.options.margin) * -1; if (animate) { var duration, ease; if (drag) { duration = 200 * num; ease = hui.ease.fastSlow; ease = hui.ease.quadOut; } else if (num > 1) { duration = Math.min(num * this.options.transitionReturn, 2000); ease = this.options.easeReturn; } else { var end = this.index === 0 || this.index == this.images.length - 1; ease = (end ? this.options.easeEnd : this.options.ease); if (!user) { ease = this.options.easeAuto; } duration = (end ? this.options.transitionEnd : this.options.transition); } hui.animate({ node : this.nodes.innerViewer, css : {marginLeft : target + 'px'}, duration : duration, ease : ease, $render : function(node,v) { this.position = initial + (target - initial) * v; this._draw(); }.bind(this) }); } else { this._draw(); } this._drawText(); }, _drawText : function() { var text = this.images[this.index].text; if (text) { this.nodes.text.innerHTML = text; this.nodes.text.style.display = 'block'; } else { this.nodes.text.innerHTML = ''; this.nodes.text.style.display = 'none'; } }, // Show / hide ... /** Show the image viewer starting at the image with a certain id. Will not show if image is not found * @param {Integer} id The id if the image to start with */ showById: function(id) { for (var i=0; i < this.images.length; i++) { if (this.images[i].id==id) { this.show(i); break; } } }, /** Show the image viewer * @param {Integer} index? Optional index to start from (zero-based) */ show: function(index) { this.index = index || 0; this._calculateSize(); this._updateUI(); var margin = this.options.margin; hui.style.set(this.element, { width: (this.width + margin) + 'px', height: (this.height + margin * 2 - 1) + 'px' }); hui.style.set(this.nodes.viewer, { width: (this.width + margin) + 'px', height: (this.height - 1) + 'px' }); hui.style.set(this.nodes.innerViewer, { width: ((this.width + margin) * this.images.length) + 'px', height: (this.height - 1) + 'px' }); hui.style.set(this.nodes.controller, { marginLeft: ((this.width - 160) / 2 + margin * 0.5) + 'px', display: 'none' }); this.box.show(); this._goToImage(false,0,false); hui.listen(document,'keydown',this._keyListener); this.visible = true; this._setHash(true); }, _setHash : function(visible) { return; // Disabled /* if (!this._listening) { this._listening = true; if (!hui.browser.msie6 && !hui.browser.msie7) { hui.listen(window,'hashchange',this._onHashChange.bind(this)); } } if (visible) { document.location='#imageviewer'; } else { hui.location.clearHash(); }*/ }, _onHashChange : function() { if (this._changing) return; this._changing = true; if (hui.location.hasHash('imageviewer') && !this.visible) { this.show(); } else if (!hui.location.hasHash('imageviewer') && this.visible) { this.hide(); } this._changing = false; }, /** Hide the image viewer */ hide: function() { this._hide(); }, _hide : function() { this.pause(); this.box.hide(); this._endZoom(); hui.unListen(document,'keydown',this._keyListener); this.visible = false; this._setHash(false); }, // Listeners ... /** @private */ $boxCurtainWasClicked : function() { this.hide(); }, /** @private */ $boxWasClosed : function() { this.hide(); }, // Data handling ... /** Clear all images in the stack */ clearImages : function() { this.images = []; this.dirty = true; }, /** * Add multiple images to the stack * @param {Array} images An array of image objects */ addImages : function(images) { for (var i=0; i < images.length; i++) { this.addImage(images[i]); } }, /** * Add an image to the stack * @param {Object} img An image object representing an image */ addImage : function(img) { this.images.push(img); this.dirty = true; }, // Playback... /** Start playing slideshow */ play : function() { if (!this.interval) { this.interval = window.setInterval(this._timer,6000); } this.next(false); this.playing=true; this.nodes.play.className='hui_imageviewer_pause'; }, /** Pauseslideshow */ pause : function() { window.clearInterval(this.interval); this.interval = null; this.nodes.play.className='hui_imageviewer_play'; this.playing = false; }, /** Start or pause slideshow */ playOrPause : function() { if (this.playing) { this.pause(); } else { this.play(); } }, _resetPlay : function() { if (this.playing) { window.clearInterval(this.interval); this.interval = window.setInterval(this._timer,6000); } }, /** Go to the previous image * @param {Boolean} user If it is initiated by the user */ previous : function(user) { var num = 1; this.index--; if (this.index < 0) { this.index = this.images.length - 1; num = this.images.length - 1; } this._goToImage(true,num,user); this._resetPlay(); }, /** Go to the next image * @param {Boolean} user If it is initiated by the user */ next : function(user) { var num = 1; this.index++; if (this.index==this.images.length) { this.index = 0; num = this.images.length - 1; } this._goToImage(true,num,user); this._resetPlay(); }, // Preloading ... _preload : function() { var guiLoader = new hui.Preloader(); guiLoader.addImages(hui.ui.getURL('gfx/imageviewer_controls.png')); var self = this; guiLoader.setDelegate({ allImagesDidLoad: function() { self._preloadImages(); } }); guiLoader.load(); }, _preloadImages : function() { var loader = new hui.Preloader(); loader.setDelegate(this); for (var i=0; i < this.images.length; i++) { var url = hui.ui.resolveImageUrl(this,this.images[i],this.width,this.height); if (url!==null) { loader.addImages(url); } } this.nodes.status.innerHTML = '0%'; this.nodes.status.style.display = ''; loader.load(this.index); }, /** @private */ allImagesDidLoad : function() { this.nodes.status.style.display = 'none'; }, /** @private */ imageDidLoad : function(loaded,total,index) { this.nodes.status.innerHTML = Math.round(loaded/total*100)+'%'; var url = hui.ui.resolveImageUrl(this,this.images[index],this.width,this.height); url = url.replace(/&/g,'&'); this.nodes.innerViewer.childNodes[index].style.backgroundImage="url('"+url+"')"; hui.cls.set(this.nodes.innerViewer.childNodes[index],'hui_imageviewer_image_abort',false); hui.cls.set(this.nodes.innerViewer.childNodes[index],'hui_imageviewer_image_error',false); }, /** @private */ imageDidGiveError : function(loaded,total,index) { hui.cls.set(this.nodes.innerViewer.childNodes[index],'hui_imageviewer_image_error',true); }, /** @private */ imageDidAbort : function(loaded,total,index) { hui.cls.set(this.nodes.innerViewer.childNodes[index],'hui_imageviewer_image_abort',true); }, // Zooming ... zoomed : false, _zoom : function(e) { var img = this.images[this.index]; if (img.width <= this.width && img.height <= this.height) { return; // Don't zoom if small } if (!this.zoomer) { this.zoomer = hui.build('div',{ 'class' : 'hui_imageviewer_zoomer', 'style' : 'width:'+this.nodes.viewer.clientWidth+'px;height:'+this.nodes.viewer.clientHeight+'px' }); this.element.insertBefore(this.zoomer,hui.dom.firstChild(this.element)); hui.listen(this.zoomer,'mousemove',this._onZoomMove.bind(this)); hui.listen(this.zoomer,'click',this._endZoom.bind(this)); } this._hideController(); this.pause(); var size = this._getLargestSize({width:2000,height:2000},img); var url = hui.ui.resolveImageUrl(this,img,size.width,size.height); var top = Math.max(0, Math.round((this.nodes.viewer.clientHeight - size.height) / 2)); this.zoomer.innerHTML = '<div style="width:'+size.width+'px;height:'+size.height+'px; margin: 0 auto;"><img src="'+url+'" style="margin-top: '+ top + 'px" /></div>'; this.zoomer.style.display = 'block'; this.zoomInfo = {width:size.width,height:size.height}; this._onZoomMove(e); this.zoomed = true; }, _onZoomMove : function(e) { if (!this.zoomInfo) { return; } var offset = hui.position.get(this.zoomer); e = new hui.Event(e); var x = (e.getLeft() - offset.left) / this.zoomer.clientWidth * (this.zoomInfo.width - this.zoomer.clientWidth); var y = (e.getTop() - offset.top) / this.zoomer.clientHeight * (this.zoomInfo.height - this.zoomer.clientHeight); this.zoomer.scrollLeft = x; this.zoomer.scrollTop = y; }, _endZoom : function() { if (this.zoomer) { this.zoomer.style.display='none'; this.zoomed = false; } } }; hui.define('hui.ui.ImageViewer',hui.ui.ImageViewer);