;( function( $, window ) { 'use strict'; var _replacement; var _timer; /** * Template for special characters for replace string. * @private * @type {object} */ _replacement = { '"': '"', '&': '&', '<': '<', '>': '>', ' ': ' ', '¡': '¡', '¢': '¢', '£': '£', '¤': '¤', '¥': '¥', '¦': '¦', '§': '§', '¨': '¨', '©': '©', 'ª': 'ª', '«': '«', '¬': '¬', '­': '­', '®': '®', '¯': '¯', '°': '°', '±': '±', '²': '²', '³': '³', '´': '´', 'µ': 'µ', '¶': '¶', '·': '·', '¸': '¸', '¹': '¹', 'º': 'º', '»': '»', '¼': '¼', '½': '½', '¾': '¾', '¿': '¿', 'À': 'À', 'Á': 'Á', 'Â': 'Â', 'Ã': 'Ã', 'Ä': 'Ä', 'Å': 'Å', 'Æ': 'Æ', 'Ç': 'Ç', 'È': 'È', 'É': 'É', 'Ê': 'Ê', 'Ë': 'Ë', 'Ì': 'Ì', 'Í': 'Í', 'Î': 'Î', 'Ï': 'Ï', 'Ð': 'Ð', 'Ñ': 'Ñ', 'Ò': 'Ò', 'Ó': 'Ó', 'Ô': 'Ô', 'Õ': 'Õ', 'Ö': 'Ö', '×': '×', 'Ø': 'Ø', 'Ù': 'Ù', 'Ú': 'Ú', 'Û': 'Û', 'Ü': 'Ü', 'Ý': 'Ý', 'Þ': 'Þ', 'ß': 'ß', 'à': 'à', 'á': 'á', 'â': 'â', 'ã': 'ã', 'ä': 'ä', 'å': 'å', 'æ': 'æ', 'ç': 'ç', 'è': 'è', 'é': 'é', 'ê': 'ê', 'ë': 'ë', 'ì': 'ì', 'í': 'í', 'î': 'î', 'ï': 'ï', 'ð': 'ð', 'ñ': 'ñ', 'ò': 'ò', 'ó': 'ó', 'ô': 'ô', 'õ': 'õ', 'ö': 'ö', '÷': '÷', 'ø': 'ø', 'ù': 'ù', 'ú': 'ú', 'û': 'û', 'ü': 'ü', 'ý': 'ý', 'þ': 'þ', 'ÿ': 'ÿ', 'Œ': '?', 'œ': '?', 'Š': '?', 'š': '?', 'Ÿ': '?', 'ƒ': '?', 'ˆ': '?', '˜': '?', 'Α': 'Α', 'Β': 'Β', 'Γ': 'Γ', 'Δ': 'Δ', 'Ε': 'Ε', 'Ζ': 'Ζ', 'Η': 'Η', 'Θ': 'Θ', 'Ι': 'Ι', 'Κ': 'Κ', 'Λ': 'Λ', 'Μ': 'Μ', 'Ν': 'Ν', 'Ξ': 'Ξ', 'Ο': 'Ο', 'Π': 'Π', 'Ρ': 'Ρ', 'Σ': 'Σ', 'Τ': 'Τ', 'Υ': 'Υ', 'Φ': 'Φ', 'Χ': 'Χ', 'Ψ': 'Ψ', 'Ω': 'Ω', 'α': 'α', 'β': 'β', 'γ': 'γ', 'δ': 'δ', 'ε': 'ε', 'ζ': 'ζ', 'η': 'η', 'θ': 'θ', 'ι': 'ι', 'κ': 'κ', 'λ': 'λ', 'μ': 'μ', 'ν': 'ν', 'ξ': 'ξ', 'ο': 'ο', 'π': 'π', 'ρ': 'ρ', 'ς': '?', 'σ': 'σ', 'τ': 'τ', 'υ': 'υ', 'φ': 'φ', 'χ': 'χ', 'ψ': 'ψ', 'ω': 'ω', 'ϑ': '?', 'ϒ': '?', 'ϖ': '?', '•': '?', '…': '…', '′': '′', '″': '″', '‾': '?', '⁄': '?', '℘': '?', 'ℑ': '?', 'ℜ': '?', '™': '™', 'ℵ': '?', '←': '←', '↑': '↑', '→': '→', '↓': '↓', '↔': '?', '↵': '?', '⇐': '?', '⇑': '?', '⇒': '⇒', '⇓': '?', '⇔': '⇔', '∀': '∀', '∂': '∂', '∃': '∃', '∅': '?', '∇': '∇', '∈': '∈', '∉': '?', '∋': '∋', '∏': '?', '∑': '∑', '−': '?', '∗': '?', '√': '√', '∝': '∝', '∞': '∞', '∠': '∠', '∧': '∧', '∨': '∨', '∩': '∩', '∪': '∪', '∫': '∫', '∴': '∴', '∼': '?', '≅': '?', '≈': '?', '≠': '≠', '≡': '≡', '≤': '?', '≥': '?', '⊂': '⊂', '⊃': '⊃', '⊄': '?', '⊆': '⊆', '⊇': '⊇', '⊕': '?', '⊗': '?', '⊥': '⊥', '⋅': '?', '⌈': '?', '⌉': '?', '⌊': '?', '⌋': '?', '⟨': '?', '⟩': '?', '◊': '?', '♠': '?', '♣': '?', '♥': '?', '♦': '?', ' ': '?', ' ': '?', ' ': '?', '‌': '?', '‍': '?', '‎': '?', '‏': '?', '–': '?', '—': '?', '‘': '‘', '’': '’', '‚': '?', '“': '“', '”': '”', '„': '?', '†': '†', '‡': '‡', '‰': '‰', '‹': '?' }; /** * Template for status information about timer. * @private * @type {object} */ _timer = { refresh: null }; function ReadMore( element, options, index ) { /** * @public */ this.options = $.extend( {}, ReadMore.Defaults, options ); /** * @protected */ this.timer = $.extend( {}, _timer ); /** * Plugin element. * @public */ this.$element = element; /** * @protected */ this.$char = null; /** * @protected */ this.$btn = null; /** * @protected */ this.endPoint = 0; /** * @protected */ this.btnOffsetLeft = 0; /** * @protected */ this.isRead = false; /** * @protected */ this.isPrevent = false; /** * @protected */ this.index = index; this.setup(); this.refresh(); } ReadMore.Defaults = { namespace: 'readmore', click : function() {}, // Click start callback. line : 2 // The number of rows. }; /** * The setup of span element. * @protected * @return {void} */ ReadMore.prototype.spanizer = function() { // console.log( 'EVENT LOG: spanizer' ); this.$element.find( '[ data-readMore-txt ]' ).html( $.proxy( function( i, s ) { var spanizer; var spanHead; var replacement; var matchKeys = ''; var regexp; var filter; // Replace Special Characters in HTML. replacement = function( match ) { return _replacement[ match ]; }; for ( var key in _replacement ) { if ( _replacement.hasOwnProperty( key ) ) { matchKeys += key + '|'; } } regexp = new RegExp( matchKeys.substr( 0, matchKeys.length - 1 ), 'g' ); filter = s.replace( regexp, replacement ); spanizer = filter.split( '' ); spanHead = ''; return spanHead + spanizer.join( '' + spanHead ) + ''; }, this ) ); }; /** * Check if readable. * @protected * @return {boolean}. */ ReadMore.prototype.isReadMore = function() { // console.log( 'EVENT LOG: isReadMore' ); var count = 0; // The match count. return ( $.proxy( function() { if ( this.isRead ) { return; } var $char = this.$char; var btnOffsetLeft = this.btnOffsetLeft; var line = this.options.line; var nextItem = null; var endPoint = 0; var itemsOffsetLeft = 0; for ( var j = 0, l = $char.length; j < l; j++ ) { nextItem = $char[ j + 1 ]; if ( ( nextItem && nextItem.offsetTop !== $char[ j ].offsetTop ) && count < line ) { // The end of the line. if ( count === ( line - 1 ) ) { endPoint = j; itemsOffsetLeft = $char.eq( endPoint )[ 0 ].offsetLeft; for ( ; btnOffsetLeft < itemsOffsetLeft; endPoint-- ) { itemsOffsetLeft = $char.eq( endPoint )[ 0 ].offsetLeft; } this.endPoint = endPoint; return true; } count++; } } return false; }, this )() ); }; /** * The show/hide of the read more element. * @protected * @return {void} */ ReadMore.prototype.onReadMore = function() { // console.log( 'EVENT LOG: onReadMore' ); var isReadMore = this.isReadMore(); var styles = isReadMore ? { visibility: 'visible', zIndex : 1 } : { visibility: 'hidden', zIndex : -1 }; this.$element.css( 'visibility', 'visible' ); this.$btn.css( styles ); if ( isReadMore ) { this.$char.filter( ':gt(' + this.endPoint + ')' ).css( 'display', 'none' ); } }; /** * @protected * @return {void} */ ReadMore.prototype.onStart = function() { this.isPrevent = false; }; /** * @protected * @return {void} */ ReadMore.prototype.onMove = function() { this.isPrevent = true; }; /** * @protected * @param {HTMLElement} e The element. * @return {void} */ ReadMore.prototype.onEnd = function( e ) { if ( this.isPrevent ) { return; } e.preventDefault(); var index = $( '[data-readMore]' ).index( this.$element ); var event = [ '', index, this.options.namespace ].join( '.' ); $( e.target ).css( 'visibility', 'hidden' ); if ( typeof this.options.click === 'function' ) { this.options.click( $( e.target ) ); } this.reset(); this.$element.off( event, '[ data-readMore-btn ]' ); $( window ).off( event ); this.isRead = true; }; /** * The trigger of the read more. * @protected * @return {void} */ ReadMore.prototype.attach = function() { // console.log( 'EVENT LOG: attach' ); $.each( { 'touchstart' : this.onStart, 'touchmove' : this.onMove, 'touchend click': this.onEnd, 'resize' : this.refresh }, $.proxy( function( e, callback ) { var isResizeEvent = e === 'resize'; var namespace = [ '', this.index, this.options.namespace ].join( '.' ); var event = e.split( ' ' ).join( namespace + ' ' ) + namespace; var element = isResizeEvent ? $( window ) : this.$element; var selector = isResizeEvent ? null : '[ data-readMore-btn ]'; element.on( event, selector, $.proxy( callback, this ) ); }, this ) ); }; /** * Refreshes state of the elements data. * @protected * @return {void} */ ReadMore.prototype.refresh = function() { // console.log( 'EVENT LOG: refresh' ); if ( this.timer.refresh ) { clearTimeout( this.timer.refresh ); } this.timer.refresh = setTimeout( $.proxy( function() { this.reset(); this.onReadMore(); }, this ), 50 ); }; /** * The reset. * @protected * @return {void} */ ReadMore.prototype.reset = function() { // console.log( 'EVENT LOG: reset' ); this.btnOffsetLeft = this.$btn[ 0 ].offsetLeft; this.$char.filter( '[style]' ).css( 'display', 'inline' ); }; /** * The setup. * @protected * @return {void} */ ReadMore.prototype.setup = function() { // console.log( 'EVENT LOG: setup' ); this.spanizer(); this.$char = this.$element.find( '[ data-readMore-txt-item ]' ); this.$btn = this.$element.find( '[ data-readMore-btn ]' ); this.btnOffsetLeft = this.$btn[ 0 ].offsetLeft; this.onReadMore(); this.attach(); }; /** * The jQuery plugin for the ReadMore. * @param {object} options The plugin options. * @return {ReadMore} new ReadMore */ $.fn.readMore = function( options ) { return $( this ).each( function( i ) { return new ReadMore( $( this ), options, i ); } ); }; }( jQuery, window ) );