/** * @Project Ellipsis plugin * @Description jquery plugin that excerpt the text at dedecated lines or * dedicated number of chars * * @Author Mohamed Hassan * @Author_url http://mohamedhassan.me * @License MIT */ ;(function($, window, document, undefined){ 'use strict'; // define the plugin name and the default options const PLUGIN_NAME = 'ellipsis'; const VERSION = '0.1.6'; /** * the default options of Ellipsis * @type {Object} */ const DEFAULTS = { type: 'lines', count: 3 }; /** * different custom events shoud * @type { Object } */ const EVENTS = { namespace: 'ellispsis', initialize: 'initialize.ellipsis', initialized: 'initialized.ellipsis', update: 'update.ellipsis', updated: 'updated.ellipsis', excerpt: 'excerpt.ellipsis', excerpted: 'excerpted.ellipsis' }; /** * Ellipsis class that excerpt the text of an attached element depending * on number of lines / characters * @param {HTMLElement} element attached element to excerpt it's text * @param {Object} options list of custom options */ class Ellipsis{ constructor(element, options = {}) { this.element = $(element); this.options = Object.assign({}, Ellipsis.DEFAULTS, options); this.text = this.element.text(); this._resizeTimeout = null; this._resizeHandler = this._updateOnResize.bind(this); this.init(); } /*========================================================================= * PUBLIC *========================================================================*/ /** * Public init() initialize the plugin and do logical stuff */ init() { this.element.trigger(EVENTS.initialize); this.element.trigger(EVENTS.excerpt); if(this.options.type === 'lines') { this._excerptLines(this.options.count); } else { this._excerptChars(this.options.count); } if(this.options.type === 'lines') { $(window).on('resize', this.element.selector, this._resizeHandler); } this.element.trigger(EVENTS.initialized); } /** * updating the ellipsed text. */ update() { this.element.trigger(EVENTS.update); if(this.options.type === 'lines') this._excerptLines(this.options.count); else this._excerptChars(this.options.count); //later this.element.trigger(EVENTS.updated); return true; } /** * reset ellipsis instance options * if any change is needed later. * * @param {Object} options text/type/count. */ reset(options) { if(options.text) this.text = options.text; if(options.type) this.options.type = options.type; if(options.count) this.options.count = options.count; if(Object.keys(options).length > 0) { this.element.text(this.text); this.update(); } } destroy() { $(window).off('resize', this._resizeHandler); this.element.text(this.text); this.element.off(EVENTS.namespace); } /*========================================================================= * private Methods *========================================================================*/ /** * excerpt the text and leave a dedicated number of chars. * * @param { Number } number the total number of chars should be in element */ _excerptChars(number) { if(number <= 0) return new Error('Number of chars to be shown is equal to or less than zero !!'); if(number >= this.text.length) return null; this.element.html(this.text.slice(0, number) + '...'); this.element.trigger(EVENTS.excerpted); return true; } /** * excerpts the text and leave a dedicated * number of lines. * * @param { Number } linesNo positive number that represent lines no. */ _excerptLines(linesNo) { let lineHeight; let originalHeight = this.element.text(this.text).height(); let targetHeight; let start = 0; let end = this.text.length - 1; let middle; lineHeight = this.element.text('w').height(); targetHeight = lineHeight * linesNo; if(originalHeight <= targetHeight) { this.element.text(this.text); return false; } while(start <= end) { middle = Math.floor((start + end) / 2); this.element.text(this.text.slice(0, middle)); if(this.element.height() <= targetHeight) { start = middle + 1; } else { end = middle - 1; } } this.element.text(this.text.slice(0, middle - 3) + '...'); this.element.trigger(EVENTS.excerpted); return true; } /** * handler that used to call update method * with a debounce period. */ _updateOnResize() { clearTimeout(this._resizeTimeout); this._resizeTimeout = setTimeout(() => { this.update(); }, 300); } /**======================================================================== * GETTERS *========================================================================*/ static get DEFAULTS () { return Object.freeze(DEFAULTS); } } /** * extend JQuery fn and initialize the plugin * @param {Object} options custom setting of Plugin * @return {Object} instance of jquery Plugin */ $.fn[PLUGIN_NAME] = function ( options ) { var args = arguments; if (options === undefined || typeof options === 'object') { return this.each(function () { if (!$.data(this, 'plugin_' + PLUGIN_NAME)) { $.data(this, 'plugin_' + PLUGIN_NAME, new Ellipsis( this, options )); } }); } else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') { var returns; this.each(function () { var instance = $.data(this, 'plugin_' + PLUGIN_NAME); if (instance instanceof Ellipsis && typeof instance[options] === 'function') { returns = instance[options].apply( instance, Array.prototype.slice.call( args, 1 ) ); } if (options === 'destroy') { $.data(this, 'plugin_' + PLUGIN_NAME, null); } }); return returns !== undefined ? returns : this; } }; // init the plugin on data attribute $(document).ready(function() { let elementsToEllipsis = $('[data-toggle="ellipsis"]'); elementsToEllipsis.each((i, element) => { let $element = $(element); let options = {}; if( $element.data('type') !== undefined ) options.type = $element.data('type'); if( $element.data('count') !== undefined ) options.count = $element.data('count'); $element.ellipsis(options); }); }); /* strip-code */ window.__ellipsis__ = Ellipsis; /* end-strip-code */ })(jQuery, window, document);