UNPKG

avvo-styleguide

Version:
296 lines (215 loc) 7.36 kB
/* ======================================================================== * Avvo UI - truncate.js * ======================================================================== */ import 'dotdotdot/src/js/jquery.dotdotdot.js' // Advanced cross-browser ellipsis for multi-line content. // http://dotdotdot.frebsite.nl/ const $ = global.jQuery const $window = $(window) const $document = $(document) let windowResizeTimeout let windowWidth = $window.width() // TRUNCATE PUBLIC CLASS DEFINITION // ================================ const Truncate = function (element, options) { this.$element = $(element) this.options = $.extend({}, Truncate.DEFAULTS, options) this.$toggler = $(Truncate.TOGGLE_SELECTOR).filter(`[data-target="#${element.id}"]`) this.$toggler.attr('aria-controls', element.id) this.options.expandLabel = this.options.expandLabel || this.$toggler.html() this.transitioning = null this.render() } Truncate.VERSION = '1.1.0' Truncate.TRUNCATE_SELECTOR = '[data-truncate-lines]' Truncate.TOGGLE_SELECTOR = '[data-toggle="truncate"]' Truncate.TRANSITION_DURATION = 350 Truncate.WINDOW_RESIZE_DELAY = 100 Truncate.DEFAULTS = { toleranceLines: 2, truncateLines: 3, truncateLabel: 'Less', expandLabel: undefined, } Truncate.prototype.render = function () { this.calculateHeights() if (this.needsTruncation) { this.$toggler.show() if (this.$element.hasClass('is-truncated')) { this.truncate({ animate: false }) } else { this.expand({ animate: false }) } } else { this.$toggler.hide() this.$element.css('height', '') // Remove inline styles } } Truncate.prototype.calculateHeights = function () { const lineHeight = calculateLineHeight(this.$element) // Reset height… if (this.$element.hasClass('is-truncated')) { this.$element.trigger('destroy.dot') } this.expandedHeight = this.$element[0].scrollHeight this.truncatedHeight = this.options.truncateLines * lineHeight // educated guess this.needsTruncation = this.truncatedHeight < (this.expandedHeight - (this.options.toleranceLines * lineHeight)) } Truncate.prototype.truncate = function (options) { if (this.transitioning) { return } options = $.extend({ animate: true }, options) const startEvent = $.Event('truncate.ui.truncate') this.$element.trigger(startEvent) if (startEvent.isDefaultPrevented()) { return } if (options.animate) { // Hardcoding the element's height ensures there's a value to animate this.$element.height(this.$element.height()) forceRedraw(this.$element[0]) this.$element.addClass('truncating') } this.$element.attr('aria-expanded', false) this.$toggler .attr('aria-expanded', false) .trigger('blur') this.transitioning = true const complete = function () { this.$element .addClass('is-truncated') .dotdotdot() .height('') .removeClass('truncating') this.$toggler .html(this.options.expandLabel) this.transitioning = false this.$element.trigger('truncated.ui.truncate') } this.$element.height(this.truncatedHeight) if (!$.support.transition) { return complete.call(this) } this.$element .one('uiTransitionEnd', $.proxy(complete, this)) .emulateTransitionEnd(options.animate ? Truncate.TRANSITION_DURATION : 0) } Truncate.prototype.expand = function (options) { if (this.transitioning) { return } options = $.extend({ animate: true }, options) const startEvent = $.Event('expand.ui.truncate') this.$element.trigger(startEvent) if (startEvent.isDefaultPrevented()) { return } if (options.animate) { // Update truncated height with the exact height we have currently // overflow:auto and outerHeight(true) ensures we factor in margins this.truncatedHeight = this.$element.css('overflow', 'auto').outerHeight(true) this.$element.height(this.truncatedHeight) forceRedraw(this.$element[0]) this.$element.addClass('truncating') } this.$element .removeClass('is-truncated') .trigger('destroy.dot') this.$toggler .trigger('blur') this.transitioning = true const complete = function () { this.$element .height('') .removeClass('truncating') .attr('aria-expanded', true) this.$toggler .html(this.options.truncateLabel) .attr('aria-expanded', true) this.transitioning = false this.$element.trigger('expanded.ui.truncate') } this.$element.height(this.expandedHeight) if (!$.support.transition) { return complete.call(this) } this.$element .one('uiTransitionEnd', $.proxy(complete, this)) .emulateTransitionEnd(options.animate ? Truncate.TRANSITION_DURATION : 0) } Truncate.prototype.toggle = function () { const action = this.$element.hasClass('is-truncated') ? 'expand' : 'truncate' this[action]() } // Normalizes browser differences in reporting line-height. Some always report it in pixels, // others report back what was set in the stylesheet (e.g. a ratio like 1.5) // // @return int - The pixel line-height function calculateLineHeight($el) { const rawLineHeight = $el.css('line-height') let lineHeight = parseFloat(rawLineHeight) const isRatio = /^\d*\.?\d*$/.test(rawLineHeight) if (isRatio) { lineHeight *= parseFloat($el.css('font-size')) } return Math.ceil(lineHeight) } function forceRedraw(el) { // Reading "offsetHeight" forces the browser to redraw the element. return el.offsetHeight } // TRUNCATE PLUGIN DEFINITION // ========================== function Plugin(option) { return this.each(function () { const $this = $(this) let data = $this.data('ui.truncate') const options = $.extend({}, Truncate.DEFAULTS, $this.data(), typeof option === 'object' && option) // Handle special case where [data-truncate-lines] was not explicitly set if (options.truncateLines === '') { options.truncateLines = Truncate.DEFAULTS.truncateLines } // instantiate only once if (!data) { $this.data('ui.truncate', (data = new Truncate(this, options))) } // calling functions, e.g. $('.foo').truncate('toggle'); if (typeof option === 'string') { data[option]() } }) } export function init() { const old = $.fn.truncate $.fn.truncate = Plugin $.fn.truncate.Constructor = Truncate // TRUNCATE NO CONFLICT // ==================== $.fn.truncate.noConflict = function () { $.fn.truncate = old return this } // TRUNCATE DATA-API // ================= // Truncate elements on load and on explicit init $window.on('load.ui.truncate.data-api, init.ui.truncate.data-api', () => { Plugin.call($(Truncate.TRUNCATE_SELECTOR)) }) // Truncate togglers $document.on('click.ui.truncate.data-api', Truncate.TOGGLE_SELECTOR, function (event) { event.preventDefault() const $toggler = $(this) const $target = $($toggler.data('target')) Plugin.call($target, 'toggle') }) $window.on('resize.ui.truncate.data-api', () => { clearTimeout(windowResizeTimeout) windowResizeTimeout = setTimeout(() => { if ($window.width() !== windowWidth) { windowWidth = $window.width() Plugin.call($(Truncate.TRUNCATE_SELECTOR), 'render') } }, Truncate.WINDOW_RESIZE_DELAY) }) }