﻿/**
 * Eve.ReadMore
 *
 * WAS STELLT DIESE DATEI ZUR VERFUEGUNG?
 *
 * 		- Erstellt aus langen Texten einen Abstract, welche auf- und zugeklappt werden koennen.
 *
 * WAS BENOETIGT DIESE DATEI?
 *
 * 		- jquery.js (jQuery-Framework)
 *		- Eve.js
 *
 * LETZTE AENDERUNGEN:
 *		2010-01-08		Die Klasse fuer zusaetzliche "mehr"/"weniger"-Buttons angepasst (initialize(), showHideAdditionalButtons() ...),
 *						es ist nun moeglich beliebig viele Elemente als "mehr"/"weniger"-Button zu definieren. (hans-peter.beck@eventim.de)
 * 		2009-01-23		Erstellt (hans-peter.beck@eventim.de)
 *
 * Copyright 2009 by CTS Eventim AG.
 */
 

/**
 * Klasse zum Erstellen von Kurztexten, mit einem "mehr lessen"-Link, welche
 * aufklappen koennen.
 *
 * ---------------------------------------------- *
 *      BEISPIEL FUER ZUSAETZLICHE BUTTONS        *
 * ---------------------------------------------- *
 *
 * Ein Beispiel fuer zusaetzliche "mehr"/"weniger"-Buttons saehe so aus:
 *		
 *		<!-- Zusaetzlicher "mehr"-Button ... //-->
 *		<a href='javascript:void(0)' class='more eventInfoAdditionalMoreBtn eveJsReadMoreLinkWithAnimation' style='display:none;'>#message('showInfo')</a>
 *		
 *		<!-- Zusaetzlicher "weniger"-Button ... //-->
 *		<a href='javascript:void(0)' class='back eventInfoAdditionalLessBtn' style='display:none;'>#message('closeInfo')</a>
 *		
 * Wichtig bei den zusaetzlichen Buttons ist, dass sie per style="display:none;" initial versteckt werden, wenn man nicht moechte,
 * dass der Nutzer sie initial sieht, bis die ReadMore.js ausgefuehrt wurde. Ueber die Angabe einer Klasse oder ID, in diesem Fall
 * "eventInfoAdditionalMoreBtn" > options.openButtonsCls und "eventInfoAdditionalLessBtn" > options.closeButtonsCls kann die 
 * ReadMore-Klasse auf diese Buttons zugreifen.
 *
 * Ist die CSS-Klasse "eveJsReadMoreLinkWithAnimation" angegeben, dann wird der Button passend zum jeweiligen "mehr"/"weniger"
 * Button ein- und ausgefadet.
 *
 * @class	Eve.ReadMore
 * @base	Eve
 * @constructor
 * @param	{object}	options					Optionshash:
 * @param	{element}	options.containerEl		DOM-Element in dem ein zu kuerzender Quellcode enthalten ist.
 * @param	{number}	options.maxLength		Optional: Max. Anzahl der Zeichen vor dem Abschneiden des Textes (Default 150 Zeichen).
 * @param	{string}	options.cutType			Optional: 'letter', es kann nach jedem Buchstaben; 'word' nach jedem Wort; 'sentence' nach jedem Satz getrennt werden (Default 'word').
 * @param	{string}	options.showBtn			Optional: Wird eine Betitelung (kann auch HTML-Code enthalten) fuer einen "Aufklapp"-Button angegeben, dann wird ein "Aufklapp"-Button angezeigt (Default false). Der Aufklappbutton hat immer die CSS-Klasse "readMore".
 * @param	{string}	options.hideBtn			Optional: Wird eine Betitelung (kann auch HTML-Code enthalten) fuer einen "Zuklapp"-Button angegeben, dann wird ein "Zuklapp"-Button angezeigt (Default false). Der Zuklappbutton hat immer die CSS-Klasse "readLess".
 * @param	{boolean}	options.stripTags		Optional: Wenn true, werden alle HTML-Tags entfernt aus dem gesamten Text entfernt (Default false).
 * @param	{string}	options.openButtonsCls	Optional: jQuery-Selektor fuer weitere Buttons, die einen Text oeffnen ...
 * @param	{string}	options.closeButtonsCls	Optional: jQuery-Selektor fuer weitere Buttons, die einen Text schliessen ...
 * @param	{string}	options.scrollToEl		Optional: jQuery-Selektor fuer das Element zu dem gescrollt werden soll, wenn der Text aufgeklappt wird (Default false).
 * @param	{boolean}	options.iniOpen			Optional: Wenn 'true' wird die Box initial geoeffnet dargestellt (Default false).
 */
Eve.ReadMore = function(options) {

	/** Wenn es das Element nicht gibt, dann abbrechen. */
	if(!(this.el = options.containerEl || false)) return;

	/** Private Werte vorbereiten und Defaults setzen ... */
	this.maxLength       = options.maxLength || 150;
	this.cutType         = options.cutType || 'word';
	this.showBtn         = options.showBtn || false;
	this.hideBtn         = options.hideBtn || false;
	this.stripTags       = options.stripTags || false;
	this.openButtonsCls  = options.openButtonsCls || false;
	this.closeButtonsCls = options.closeButtonsCls || false;
	this.scrollToEl      = options.scrollToEl || false;
	this.iniOpen		 = options.iniOpen || false;
	
	/** Start! */
	this.initialize();
};

Eve.ReadMore.prototype = {

	origHtml : '',
	
	moreHeight : 0,
	
	lessHeight : 0,
	
	abstract : '',
	
	rest : '',
	
	cutted : null,
	
	moreSpan : '',
	
	lessSpan : false,
	
	animScene : 1,
	
	additionalOpenBtns : false,
	
	additionalCloseBtns : false,
	
	/**
	 * Schaut im Element nach, ob Text/Code vorhanden ist und ob sich das Kuerzen lohnt, wenn ja
	 * wird ein Abstract und ein "mehr"-Link erstellt, um den Text aufzuklappen.
	 *
	 * @member	Eve.ReadMore
	 */
	initialize : function() {
		
		/** Quelltext / Text ermitteln, wenn es nix gibt, Abbruch ... */
		if(!(this.origHtml = (this.el.innerHTML || false))) return;
		
		/** Abstract und Restcode/Text ermitteln ... */
		this.cutted   = this.cutHtmlText({ htmlText : this.origHtml, cutType : this.cutType, maxLength : this.maxLength , elType : 'span'});
		this.abstract = this.cutted.abstract;
		this.rest     = this.cutted.rest;
		
		/** Wenn es keinen Rest gibt, weil alles im Abstract ist, dann kann abgebrochen werden ... */
		if (this.rest.innerHTML == '') return;
		
		/** Nur den Abstract inkl. einem "Leerzeichen" in den Container einfuegen. */
		jQuery(this.el).html(this.abstract);
		jQuery(this.el).append(' ');
		
		/** "Mehr" Link in einem Span-Container (so wie Abstract und Rest) erstellen */
		if (this.showBtn) {
			
			this.moreSpan = this.createSpanWithLink({ prefix : '... ', linkCls : 'more', spanCls : 'readMore', text : this.showBtn, callback : this.showRest.scope(this, [true]) });
			
			// Gibt es zusaetzliche "Mehr"-Buttons, dann belege diese mit dem Klick-Event.
			if (this.openButtonsCls) {
			
				// Events fuer die aktuelle Klasse setzen, welche auch extern verfuegbar sind.
				this.bindEvent('fadeOutMoreBtn', this.showHideAdditionalButtons.scope(this, [this.openButtonsCls, 'hide']));
				this.bindEvent('fadeInMoreBtn', this.showHideAdditionalButtons.scope(this, [this.openButtonsCls, 'show']));
		
				jQuery(this.openButtonsCls).click(function() {
		
					jQuery(this).click();
					
					return false;
					
				}.scope(this.moreSpan.linkEl));
			}
		}
		
		/** Jetzt den Resttext noch hinzufuegen, allerdings sollte dieser unsichtbar sein (aber erst nach dem Messen der Hoehe) ... */
		jQuery(this.el).append(this.rest);
		jQuery(this.el).append(' ');
		
		/** "Weniger" Link in einen Span Container erstellen und hinzufuegen ... */
		if (this.hideBtn) {
			
			this.lessSpan = this.createSpanWithLink({ linkCls : 'back', spanCls : 'readLess', text : this.hideBtn, callback : this.hideRest.scope(this, [true]) });
		
			if (this.closeButtonsCls) {
			
				// Events fuer die aktuelle Klasse setzen, welche auch extern verfuegbar sind.
				this.bindEvent('fadeOutLessBtn', this.showHideAdditionalButtons.scope(this, [this.closeButtonsCls, 'hide']));
				this.bindEvent('fadeInLessBtn', this.showHideAdditionalButtons.scope(this, [this.closeButtonsCls, 'show']));
		
				jQuery(this.closeButtonsCls).click(function() {
		
					jQuery(this).click();
					
					return false;
					
				}.scope(this.lessSpan.linkEl));
			}	
		}
		
		/** Hoehe des umgebenden Elements im Rohzustand ermitteln (quasi ausgeklappt), dabie den Show-Button wahrend der Messung verstecken! */
		if((this.moreSpan || false)) jQuery(this.moreSpan).hide();
		
		this.moreHeight = jQuery(this.el).height();
		
		/** Fade In behebt im IE6 Fehler */
		if((this.moreSpan || false)) jQuery(this.moreSpan)[(((jQuery.browser.msie || false) && jQuery.browser.version < 8) ? 'fadeIn' : 'show' )]();
		
		// Rest unsichtbar machen, aber nur, wenn die Box initial nicht geoeffnet bleiben soll
		this.rest.style.display = 'none';
		if(this.lessSpan) this.lessSpan.style.display = 'none';
		
		/** Hoehe des umgebenden Elements im "gekuerzten" Zustand ermitten (quasi zugeklappt) */
		this.lessHeight = jQuery(this.el).height();
		
		/** Abschliessend den Element-Style auf "overflow:hidden" setzen ... */
		this.el.style.overflow = "hidden";
		
		// Wenn initial geoeffnet sein soll, dann entsprechend offen lassen
		if(this.iniOpen) {
			
			if((this.moreSpan || false)) jQuery(this.moreSpan).hide();
			
			this.rest.style.display = '';
			if(this.lessSpan) jQuery(this.lessSpan)[(((jQuery.browser.msie || false)) ? 'fadeIn' : 'show' )]();
		
			jQuery(this.el).height(this.moreHeight);
			
			this.triggerEvent('fadeInLessBtn');
			
			this.animScene = 5;
			
		} else {
			
			this.triggerEvent('fadeInMoreBtn');
			
			jQuery(this.el).height(this.lessHeight);
		};
	},
	
	/**
	 * Faded die im cssSelector angegeben Elemente ein oder aus (showOrHide). Wird fuer zusaetzliche "mehr"/"weniger" Buttons
	 * benutzt bzw. durch den EventHandler aufgerufen, siehe initialize() ...
	 *
	 * @member	Eve.ReadMore
	 * @param	{string}	cssSelector		jQuery-Selektor fuer die Elemente die betroffen sein sollen.
	 * @param	{string}	showOrHide		Wenn "show", wird eingefadet, andernfalls ausgefadet.
	 */
	showHideAdditionalButtons : function(cssSelector, showOrHide) {
		
		var cssSelector = cssSelector || '';
		var showOrHide  = showOrHide || 'show';
		
		jQuery(cssSelector).each(function() {
		
			// Es duerfen nur Buttons animiert werden, die 'eveJsReadMoreLinkWithAnimation' als CSS-Klasse haben.
			if(jQuery(this).hasClass('eveJsReadMoreLinkWithAnimation')) jQuery(this)[((showOrHide == 'show') ? 'fadeIn' : 'fadeOut' )]('fast');
		});
	},
	
	/**
	 * Erstellt ein Span-Element mit Link, auf dem ggf. ein onClick-Event hinterlegt wurde, um den Text
	 * zu expandieren oder zu minimieren.
	 *
	 * @member	Eve.ReadMore
	 * @param	{object}	options				Optionshash:
	 * @param	{string}	options.text		Beschriftung des Links.
	 * @param	{string}	options.callback	Callback-Funktion, wenn auf den Link geklickt wird.
	 * @param	{string}	options.linkCls		Optional: CSS-Klasse fuer das Link-Element.
	 * @param	{string}	options.spanCls		Optional: CSS-Klasse fuer das Span-Element.
	 * @return	Liefert ein Span-Element mit Link und darauf gelegten Event zurueck.
	 */
	createSpanWithLink : function(options) {
	
		var tmpSpan = document.createElement('span');
		if((options.spanCls || false)) jQuery(tmpSpan).addClass(options.spanCls);
		if((options.prefix || false)) jQuery(tmpSpan).append(options.prefix);
		
		var tmpLink = document.createElement('a');
		if((options.linkCls || false)) jQuery(tmpLink).addClass(options.linkCls);
		
		tmpLink.innerHTML = options.text;
		tmpLink.href = 'javascript:void(0);';
		
		if ((options.callback || false)) jQuery(tmpLink).click(options.callback);
		
		tmpSpan.linkEl = tmpLink;
		
		jQuery(tmpSpan).append(tmpLink);
		jQuery(this.el).append(tmpSpan);
		
		return tmpSpan;
	},
	
	/**
	 * Blendet den "mehr lesen"-Link aus, den Rest-Text ein und expandiert das Textfeld, blendet
	 * paralell dazu den "weniger"-Link ein.
	 *
	 * @member	Eve.ReadMore
	 */
	showRest : function(initialize) {
		
		if ((initialize || false) && this.animScene != 1) {
			
			// Sicherstellen, dass zum Element gescrolled wird, egal ob es offen oder geschlossen ist.
			var tmpElAndPos = this.getScrollElementAndOffset();
			this.scrollToElement(tmpElAndPos.el, 1000, false, tmpElAndPos.onOffset);
			
			return;
		}
		
		this.el.style.overflow = "hidden";
		this.rest.style.zoom = '';
		
		switch(this.animScene) {
		
			/** "mehr"-Link ausblenden ... */
			case 1 :
			
				this.animScene = 2;
				
				/** Um Probleme mit dem Browser beim Zusammenklappen zu verhindern, wieder zum Element-Anfang scrollen */
				var tmpElAndPos = this.getScrollElementAndOffset();
				this.scrollToElement(tmpElAndPos.el, 1000, false, tmpElAndPos.onOffset);
				
				jQuery(this.moreSpan).fadeOut('fast', this.showRest.scope(this));
				
				// Event feuern, dass der "mehr"-Button jetzt ausgefaded wird.
				this.triggerEvent('fadeOutMoreBtn');
			
			break;
			
			/** "Rest"-Text einblenden ... */
			case 2 :
				
				this.animScene = 3;
				
				jQuery(this.rest).fadeIn('fast', this.showRest.scope(this));
			
			break;
			
			/** Box expandieren ... */
			case 3 :
			
				this.animScene = 4;
				
				jQuery(this.el).animate( { height: this.moreHeight + 'px' }, 1000, this.showRest.scope(this));
								
			break;
			
			/** "weniger"-Link einblenden ... */
			case 4 :
				
				this.animScene = 5;
				
				jQuery(this.lessSpan).fadeIn('fast');
				
				// Event feuern, dass der "weniger"-Button jetzt eingefadet wird.
				this.triggerEvent('fadeInLessBtn');
								
			break;
		};
	},
	
	/**
	 * Blendet den "weniger"-Link aus und minimiert die Box paralell dazu, blendet dann den Rest-Text
	 * aus und danach den "mehr lesen"-Link wieder ein.
	 *
	 * @member	Eve.ReadMore
	 */
	hideRest : function(initialize) {
		
		if ((initialize || false) && this.animScene != 5) return;
		
		this.el.style.overflow = "hidden";
		this.rest.style.zoom = '';
		
		switch(this.animScene) {
		
			/** "mehr"-Link einblenden ... */
			case 2 :
			
				jQuery(this.moreSpan).fadeIn('fast');
				
				// Event feuern, dass der "mehr"-Button jetzt eingefaded wird.
				this.triggerEvent('fadeInMoreBtn');
				
				this.animScene = 1;
				
			break;
			
			/** "Rest"-Text ausblenden ... */
			case 3 :
				
				jQuery(this.rest).fadeOut('fast', this.hideRest.scope(this));
				
				this.animScene = 2;
			
			break;
			
			/** Box minimieren ... */
			case 4 :
			
				jQuery(this.el).animate( { height: this.lessHeight + 'px' }, 1000, this.hideRest.scope(this));
	
				this.animScene = 3;
								
			break;
			
			/** "weniger"-Link ausblenden ... */
			case 5 :
			
				/** Um Probleme mit dem Browser beim Zusammenklappen zu verhindern, wieder zum Element-Anfang scrollen */
				var tmpElAndPos = this.getScrollElementAndOffset();
				this.scrollToElement(tmpElAndPos.el, 1000, false, tmpElAndPos.offOffset);
				
				this.animScene = 4;
				
				// Workaround fuer den IE8 der ohne Kompatibilitätsmodus Mist baut!
				if(jQuery.browser.msie && jQuery.browser.version >= 8) {
					
					jQuery(this.lessSpan).hide();
					
					(this.hideRest.defer(10, this))();
					
				} else {
					
					jQuery(this.lessSpan).fadeOut(500, this.hideRest.scope(this));
				}
				
				// Event feuern, dass der "weniger"-Button jetzt ausgefaded wird.
				this.triggerEvent('fadeOutLessBtn');
				
			break;
		};
	},
	
	getScrollElementAndOffset : function() {
		
		if(!(this.scrollToElAndPos || false)) {
			
			this.scrollToElAndPos = {};
			
			if ((this.scrollToEl || false)) {
				
				this.scrollToElAndPos.el = jQuery(this.scrollToEl).get(0) || this.el;
				this.scrollToElAndPos.onOffset = 0;
				this.scrollToElAndPos.offOffset = 0;
				
			} else {
				this.scrollToElAndPos.el = this.el;
				this.scrollToElAndPos.onOffset = -40;
				this.scrollToElAndPos.offOffset = -50;
			}
		}
		
		return this.scrollToElAndPos;
	}
};

/** Ableiten */
Eve.ReadMore.extendClassBy(Eve);
