Team:Amsterdam/maarten/wall.js
From 2012.igem.org
/* --- description: The Wall is a plugin for Mootools javascript framework designed to create walls of infinite dimensions. Its flexibility allows different applications, from infinite wall mode to Coda slider mode. The Wall creates compatible interfaces with the newer browsers and iPhone and iPad mobile devices.
license: MIT-style
authors: - Marco Dell'Anna
requires: - core/1.3: '*'
provides: - The Wall ...
- /
/*
* Mootools The Wall * Version 1.0 * Copyright (c) 2011 Marco Dell'Anna - http://www.plasm.it * * Inspiration: * - Class implementation inspired by [Infinite Drag] (http://ianli.com/infinitedrag/) by Ian Li, Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license. * * Requires: * MooTools http://mootools.net * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * Log: * 1.1 - Inserito onResize Windows * 1.0 - Inizio implementazione release stabile */
var Wall = new Class({
__target: undefined, init : false, Implements : Options, id : 0, // ID Elemento Attivo coordinates :[], wall : undefined, viewport : undefined, grid : [], minx : 0, maxx : 0, wallFX : undefined, slideshowInterval:undefined, options : { printCoordinates : false, // Inserisce le coordinate nel tile speed : 1000, // Velocità spostamento transition : Fx.Transitions.Quad.easeOut, autoposition : false, // Autoposizionamento wall draggable : true, // Abilita drag inertia : false, // Abilita inertia invert : false, // Inverte direzione drag width : 0, // W tile height : 0, // H tile startx : 0, // Tile iniziale starty : 0, // Tile iniziale rangex : [-500, 500], // Definisce il numero di colonne (non pixel) rangey : [-500, 500], // Definisce il numero di righe (non pixel) handle : undefined, // Definisce un differente handle slideshow : false, // Abilita Slideshow Wall showDuration : 3000, // Durata visualizzazione Slideshow preload : false, // Precarica contenuto callOnUpdate : Function, // Azione on drag/complete callOnChange : Function, // Azione scatenata quando viene impostato id elemento attivo detectMobile : true // Detect mobile device },
initialize : function(id, options) { // Set opzioni this.setOptions(options); this.__target = id; // Imposta wall e Viewport this.wall = document.id(this.__target); this.viewport = document.id(this.__target).getParent(); }, /** * Initialize The Wall */ initWall : function() { // Calcola tutte le coordinate this.coordinates = this.calculateCoordinates(); // Prepopolate if( this.options.preload == true ) this.preloadContent(); // Calcola Spostamento Min e Max per Assi X,Y var bb = this.setBoundingBox(); // Imposta Coordiname BB this.maxx = bb.maxx; this.maxy = bb.maxy; this.minx = bb.minx; this.miny = bb.miny; // Verifica Init Class if( this.init == false ){ // Definisce Effetto di spostamento this.wallFX = new Fx.Morph(this.wall, { duration: this.options.speed, transition: this.options.transition, onStart: function(){ /*periodicalID = (function(){ this.options.callOnUpdate(this.updateWall()); }).periodical(Math.floor(this.options.speed/4), this);*/ }.bind( this ), onComplete: function(){ this.options.callOnUpdate(this.updateWall()); // clearTimeout(periodicalID); }.bind( this ) }); // Inizializza Resize Windows window.addEvent('resize', function(){ this.options.callOnUpdate(this.updateWall()); }.bind( this ));
// Inizializza Class this.init = true; }else{ // Sgancia elemento solo se draggabile if( this.options.draggable == true ) this.wallDrag.detach(); }
// Definisce Handler var handler = this.options.handle != undefined ? document.id(this.options.handle) : document.id(this.__target); // Click sul Wall document.id(this.__target).addEvent("click", function(e){ e.stopPropagation(); // Reset Movement this.moved = 0; }.bind( this )) // Definisce oggetto draggabile if( this.options.draggable == true ){ this.wallDrag = document.id(this.__target).makeDraggable({ handle:handler, limit: { x: [this.minx, this.maxx], y: [this.miny, this.maxy] }, invert:this.options.invert, onStart: function(el, e){ clearTimeout(this.periodicalID); // Reset Movement this.moved = 0; // Posizione Inizio Drag this.xPos = e.page.x; this.yPos = e.page.y; }.bind( this ), onDrag: function(el, e){ this.xspeed = e.page.x - this.xPos; // x mouse speed this.yspeed = e.page.y - this.yPos; // y mouse speed this.xPos = e.page.x; this.yPos = e.page.y; // e.stopPropagation(); // Interrompe Slideshow this.clearSlideShow(); // Tronca transizione se riparte il drag if( this.wallFX ) this.wallFX.cancel(); this.options.callOnUpdate(this.updateWall()); // Considera movimento this.moved++; }.bind( this ), onComplete: function(el, e){ e.preventDefault(); // Verifica inertia if( this.options.inertia == true ){ // START Inertia this.periodicalID = (function(){ if( this.options.invert == true ){ var finX = this.wall.getStyle("left").toInt() - this.xspeed; var finY = this.wall.getStyle("top").toInt() - this.yspeed; }else{ var finX = this.wall.getStyle("left").toInt() + this.xspeed; var finY = this.wall.getStyle("top").toInt() + this.yspeed; } if( finX < 0) this.wall.setStyle("left", Math.max(this.minx, finX)); if( finY < 0) this.wall.setStyle("top", Math.max(this.miny, finY)); if( finX > 0) this.wall.setStyle("left", Math.min(this.maxx, finX)); if( finY > 0) this.wall.setStyle("top", Math.min(this.maxy, finY)); // Decrementa velocità di spostamento this.xspeed *= 0.9; this.yspeed *= 0.9; // Aggiorna Wall this.options.callOnUpdate(this.updateWall()); // Interrompe spostamento se prossimo a 0.6 if (Math.abs(this.xspeed) < 2 && Math.abs(this.yspeed) < 2) { // Attiva elemento del coda, se presente var p = this.calculateProximity(); // Calcola l'id in base alle coordinate this.id = this.getIdFromCoordinates(p.c,p.r); // Attiva elemento del coda this.codaActiveItem(this.id); this.options.callOnUpdate(this.updateWall()); // Ricalcola posizione if( this.options.autoposition == true ) this.normalizePosition(); // Clear Periodical clearTimeout(this.periodicalID); } }).periodical(20, this); // END Inertia } // Riposizionamento automatico if( this.options.autoposition == true && this.options.inertia == false){ // Riposiziona, se richiesto e se lo slideshow è terminato if( this.slideshowInterval == undefined || this.options.slideshow == false ) this.normalizePosition(); }else{ // Attiva elemento del coda, se presente var p = this.calculateProximity(); // Calcola l'id in base alle coordinate this.id = this.getIdFromCoordinates(p.c,p.r); // Attiva elemento del coda this.codaActiveItem(this.id); } // Callback wall this.options.callOnUpdate(this.updateWall()); }.bind( this ) }); // Imposta Cursore this.wall.setStyle("cursor", "move"); // Scarica Prediodical this.wallDrag.addEvent("mousedown", function(e){ e.stop(); clearTimeout(this.periodicalID); e.stopPropagation(); }.bind(this)); }else{ // Imposta Cursore default this.wall.setStyles({ "cursor":"default", "position":"absolute" }); }
// Imposta posizione iniziale this.wall.setStyles({ "left": this.options.startx*this.options.width, "top": this.options.starty*this.options.height }) // Aggiorna Wall ed esegue CallBack di creazione this.options.callOnUpdate(this.updateWall());
// Inizializza Slideshow if( this.options.slideshow == true ) this.initSlideshow(); // Inizializza Device Mobile if( this.options.detectMobile && this.detectMobile() && this.options.draggable == true ) this.initMobile();
// return this; }, /** * Verifica se il Wall si è spostato * @return boolean */ getMovement: function(){ var m = this.moved > 0 ? true : false; // Resetta variabile movimento this.moved = 0; return m; }, /** * @PRIVATE * Calcola lo spazio di contenimento del wall e il relativo spostamento * @return oggetto {minx, miny, maxx, maxy} */ setBoundingBox: function(){ // Estrae Coordinate Viewport var vp_coordinate = this.viewport.getCoordinates(); // Tile Size var tile_w = this.options.width; var tile_h = this.options.height; // Viewport Size var vp_w = vp_coordinate.width; var vp_h = vp_coordinate.height; var vp_cols = Math.ceil(vp_w / tile_w); var vp_rows = Math.ceil(vp_h / tile_h); // Calcola X min e X max var maxx = Math.abs(this.options.rangex[0]) * tile_w; var maxy = Math.abs(this.options.rangey[0]) * tile_h; var minx = -( (Math.abs(this.options.rangex[1])) * tile_w ) + vp_w; var miny = -( (Math.abs(this.options.rangey[1])) * tile_h ) + vp_h; return {"minx":minx,"miny":miny,"maxx":maxx,"maxy":maxy} },
/** * @PRIVATE * Calcola tutte le coordinate possibili del Wall * @return array di oggetti {colonna, riga} */ calculateCoordinates: function(){ var indice = 0; var coordinates = []; for(var r=this.options.rangey[0]; r<this.options.rangey[1]; r++){ for(var c=this.options.rangex[0]; c<this.options.rangex[1]; c++){ coordinates[indice] = {"c":c, "r":r}; if(c==0&&r==0){ this.id = indice; } indice++; } } return coordinates; }, /** * Estrae id da Coordinate spaziali * @return numeric id */ getIdFromCoordinates: function(gc,gr){ var indice = 0; for(var r=this.options.rangey[0]; r<this.options.rangey[1]; r++){ for(var c=this.options.rangex[0]; c<this.options.rangex[1]; c++){ if(c==gc&&r==gr){ return indice; } indice++; } } return indice; }, /** * Restituisce le coordinate del tassello richiesto * @return object o.c, o.r */ getCoordinatesFromId: function(id){ return this.coordinates[id]; }, /** * Restituisce id elemento attivo * @return numeric */ getActiveItem: function(){ return this.id; }, /** * @PRIVATE * Calcola la posizione più prossima al punto raggiunto * @return Object - Coordinate del punto */ calculateProximity: function(){ var wallx = this.wall.getStyle("left").toInt()*-1; var wally = this.wall.getStyle("top").toInt()*-1; var w = this.options.width; var h = this.options.height; // Calcola posizione var npx = Math.round(wallx/w); var npy = Math.round(wally/h); return {"c":npx, "r":npy}; },
/** * @PRIVATE * Normalizza la posizione del Wall se è impostato il settaggio "autoposition" * @return */ normalizePosition: function(){ var p = this.calculateProximity(); // Sposta al punto this.moveTo(p.c, p.r); return; }, /** * @PRIVATE * Aggiorna gli elementi del wall. Calcola gli elementi visibili non ancora generati * @return array new nodes */ updateWall: function(){ // Array Nodes var newItems = []; // Estrae Coordinate Wall e Viewport var vp_coordinate = this.viewport.getCoordinates(); var wall_coordinate = this.wall.getCoordinates();
// Tile Size var tile_w = this.options.width; var tile_h = this.options.height; // Viewport Size var vp_w = vp_coordinate.width; var vp_h = vp_coordinate.height; var vp_cols = Math.ceil(vp_w / tile_w); var vp_rows = Math.ceil(vp_h / tile_h);
// Ricalcola bounding // Calcola Spostamento Min e Max per Assi X,Y var bb = this.setBoundingBox(); // Imposta Coordiname BB this.maxx = bb.maxx; this.maxy = bb.maxy; this.minx = bb.minx; this.miny = bb.miny; // Aggiorna dim viewport if(this.options.draggable == true) this.wallDrag.options.limit.x = [this.minx, this.maxx]
// Posizioni var pos = { left: wall_coordinate.left - vp_coordinate.left, top: wall_coordinate.top - vp_coordinate.top } // Calcola visibilità elemento var visible_left_col = Math.ceil(-pos.left / tile_w) - 1; var visible_top_row = Math.ceil(-pos.top / tile_h) - 1;
for (var i = visible_left_col; i <= visible_left_col + vp_cols; i++) { for (var j = visible_top_row; j <= visible_top_row + vp_rows; j++) { if (this.grid[i] === undefined) { this.grid[i] = {}; } if (this.grid[i][j] === undefined) { var item = this.appendTile(i, j); if( item.node !== undefined ) newItems.push(item); } } } // Update viewport info. wall_width = wall_coordinate.width; wall_height = wall_coordinate.height; wall_cols = Math.ceil(wall_width / tile_w); wall_rows = Math.ceil(wall_height / tile_h); return newItems; }, /** * @PRIVATE * Aggiunge un elemento al Wall * @return object {nodo_Dom, x, y} */ appendTile: function(i,j){ this.grid[i][j] = true; // Tile Size var tile_w = this.options.width; var tile_h = this.options.height; // Valori Min/Max var range_col = this.options.rangex; var range_row = this.options.rangey; if (i < range_col[0] || (range_col[1]) < i) return {}; if (j < range_row[0] || (range_row[1]) < j) return {}; var x = i * tile_w; var y = j * tile_h; var e = new Element("div").inject(this.wall); e.setProperties({ "class": "tile", "col": i, "row": j, "rel": i+"x"+j }).setStyles({ "position": "absolute", "left": x, "top": y, "width": tile_w, "height": tile_h }) if( this.options.printCoordinates ) e.set("text", i+"x"+j); return {"node":e, "x":j, "y":i}; }, /** * Esegue operazione di alimentazione massificata eseguendo la generazione di tutti i tasselli * Azione applicabile al coda, sconsigliato su wall di grandi dimensioni */ preloadContent: function(){ // Array Nodes var newItems = []; Object.each(this.coordinates, function(e){ if (this.grid[e.c] === undefined) this.grid[e.c] = {}; var item = this.appendTile(e.c, e.r); newItems.push(item); }.bind(this)) // Popola tutto il wall this.options.callOnUpdate(newItems); return newItems; }, /** * Imposta CallBack di di inizializzazione tile del Wall */ setCallOnUpdate: function(f){ this.options.callOnUpdate = f; return f; }, /** * Imposta CallBack di aggiornamento focus elemento */ setCallOnChange: function(f){ this.options.callOnChange = f; return f; },
/** * @PRIVATE * Inizializza Slideshow * Lo slideshow viene interrotto al Drag o Touch */ initSlideshow: function(){ // Controllo Speed if( this.options.slideshow == true ){ if( this.options.showDuration < this.options.speed ) this.options.showDuration = this.options.speed; this.slideshowInterval = this.getAutomaticNext.periodical(this.options.showDuration, this ); } }, /** * @PRIVATE * Richiede elemento successivo nel coda Slideshow * return */ getAutomaticNext: function(){ this.clearSlideShow(); if( this.options.slideshow == true ){ this.slideshowInterval = this.getAutomaticNext.periodical(this.options.showDuration, this ); } // Verifica elemento 1+this.id > this.coordinates.length-1 ? this.id = 0 : this.id++; this.moveTo(this.coordinates[this.id].c, this.coordinates[this.id].r); // Richiede prossima slide },
/** * @PRIVATE * Interrompe Slideshow * return */ clearSlideShow: function(){ clearTimeout(this.slideshowInterval); this.slideshowInterval = undefined; }, /** * Esegue spostamento del Wall alle coordinate indicate * return false || nodo Dom attivo */ moveTo: function(c,r){
// Verifica validità valori possibile e valore indicato if( c < 0 ) c = Math.max(c, this.options.rangex[0]); if( c > 0 ) c = Math.min(c, this.options.rangex[1]); if( r < 0 ) r = Math.max(r, this.options.rangey[0]); if( r > 0 ) r = Math.min(r, this.options.rangey[1]);
// Esegue Morph this.wallFX.cancel().start({ 'left': Math.max(-(c*this.options.width), this.minx), 'top': Math.max(-(r*this.options.height), this.miny) }); // Calcola l'id in base alle coordinate this.id = this.getIdFromCoordinates(c,r);
// Attiva elemento del coda this.codaActiveItem(this.id); // var name = this.coordinates[this.id].c+"x"+this.coordinates[this.id].r; var item = $$("#"+this.__target+" div[rel="+name+"]"); if( item.length > 0) return $$("#"+this.__target+" div[rel="+name+"]")[0]; return false; }, /** * Posiziona il Wall su elemento attivo * return Object node Dom elemento con focus di posizionamento */ moveToActive: function(){ // Muove il Wall alle coordinate del tile con id attivo return this.moveTo(this.coordinates[this.id].c, this.coordinates[this.id].r) }, /** * Posiziona il Wall su elemento successivo * return Object node Dom elemento con focus di posizionamento */ moveToNext: function(){ this.clearSlideShow(); if( 1+this.id < this.coordinates.length ){ this.id++; } return this.moveTo(this.coordinates[this.id].c, this.coordinates[this.id].r) },
/** * Posiziona il Wall su elemento precedente * return Object node Dom elemento con focus di posizionamento */ moveToPrev: function(){ this.clearSlideShow(); if( (this.id-1) >= 0 ){ this.id--; } return this.moveTo(this.coordinates[this.id].c, this.coordinates[this.id].r) }, /** * Richiede la lista dei punti sotto forma di Link * @target: ID DOM element dove inserire i links * @return array list element */ getListLinksPoints: function( id_target ){ var items = []; // Crea Hyperlink per ogni elemento del Wall Array.each(this.coordinates, function(e,i){ var a = new Element("a.wall-item-coda[html="+(1+i)+"][href=#"+(1+i)+"]"); a.addEvent("click", function(evt){ // Disabilita slideshow this.clearSlideShow(); this.id = i; this.codaActiveItem(i); evt.stop(); this.moveTo(e.c, e.r); }.bind( this )) a.addEvent("touchend", function(evt){ this.fireEvent("click", evt) }) // Inserisce nel target a.inject(document.id(id_target)); // Aggiunge ad array elementi items.push(a); }.bind( this )) // Imposta id coda target this.coda_target = id_target; // Imposta lista elementi del coda this.coda_items = items; // Imposta attivo il primo elemento del coda this.codaActiveItem(0); return items; },
/** * @PRIVATE * Attiva Elemento del Coda console * @i indice dell'elemento cliccato 1,2,3,4,5 * @return node Dom element */ codaActiveItem: function(i){ // Esegue CallBack this.options.callOnChange(i); // Attivazione if( this.coda_target ){ // Rimuove link attivi Array.each(this.coda_items, function(e,i){ e.removeClass("wall-item-current"); }) // Attiva corrente this.coda_items[i].addClass("wall-item-current"); return this.coda_items[i]; } }, /** * @PRIVATE * Esegue Detect di device iPad, iPod, iPhone * @return boolean */ detectMobile: function(){ var ua = navigator.userAgent; var isiPad = /iPad/i.test(ua) || /iPhone OS 3_1_2/i.test(ua) || /iPhone OS 3_2_2/i.test(ua) || /iPhone/i.test(ua) || /iPod/i.test(ua) return isiPad; }, /** * @PRIVATE * Inizializza comportamenti per il magico ditino */ initMobile: function(){ // Touch Start Slider this.wall.__root = this this.wall.addEvent('touchstart',function(e) { if( e ) e.stop(); // Interrompe Slideshow this.__root.clearSlideShow(); // Data Start this._startXMouse = e.page.x; this._startYMouse = e.page.y; this._startLeft = this.getStyle("left").toInt(); this._startTop = this.getStyle("top").toInt(); this._width = this.getStyle("width").toInt(); this._height = this.getStyle("height").toInt(); });
// Touch Move Slider this.wall.addEvent('touchmove',function(e) { if( e ) e.stop(); // Horizontal var _deltax = this._startXMouse - e.page.x; var _x = this.getStyle("left").toInt();
if( _x > Math.min(this.__root.minx, 0) ){ endx = Math.min(this._startLeft - _deltax, this.__root.maxx) }else{ endx = Math.max( this.__root.minx, this._startLeft - _deltax) } // Imposta posizione X if( endx <= this.__root.maxx) this.setStyle("left", endx ); // Vertical var _deltay = this._startYMouse - e.page.y; var _y = this.getStyle("top").toInt();
if( _y > Math.min(this.__root.miny, 0) ){ endy = Math.min(this._startTop - _deltay, this.__root.maxy) }else{ endy = Math.max( this.__root.miny, this._startTop - _deltay) } // Imposta posizione Y if( endy <= this.__root.maxy) this.setStyle("top", endy ); // Aggiorna Wall ed esegue CallBack di creazione this.__root.options.callOnUpdate(this.__root.updateWall()); });
// Touch Move End this.wall.addEvent('touchend',function(e) { if( this.options.autoposition == true){ // Riposiziona, se richiesto e se lo slideshow è terminato if( this.slideshowInterval == undefined || this.options.slideshow == false ) this.normalizePosition(); }else{ // Attiva elemento del coda, se presente var p = this.calculateProximity(); // Calcola l'id in base alle coordinate this.id = this.getIdFromCoordinates(p.c,p.r); // Attiva elemento del coda this.codaActiveItem(this.id); } // Aggiorna Wall ed esegue CallBack di creazione this.options.callOnUpdate(this.updateWall()); }.bind(this)); }
});