Source: widgets/label.js

/**
 * @fileOverview Requirejs module containing the antie.widgets.Label class.
 * @preserve Copyright (c) 2013-present British Broadcasting Corporation. All rights reserved.
 * @license See https://github.com/fmtvp/tal/blob/master/LICENSE for full licence
 */

define(
    'antie/widgets/label',
    ['antie/widgets/widget'],
    function(Widget) {
        'use strict';

        /**
         * The Label widget displays text. It supports auto-truncation (with ellipsis) of text to fit.
         * @name antie.widgets.Label
         * @class
         * @extends antie.widgets.Widget
         * @param {String} [id] The unique ID of the widget. If excluded, a temporary internal ID will be used (but not included in any output).
         * @param {String} [text] The text content of this label.
         */
        var Label = Widget.extend(/** @lends antie.widgets.Label.prototype */ {
            /**
             * @constructor
             * @ignore
             */
            init: function init (id, text, enableHTML) {
                // The current API states that if only one parameter is passed to
                // use that value as the text and auto generate an internal id
                if(arguments.length === 1) {
                    this._text = id;
                    init.base.call(this);
                } else {
                    this._text = text;
                    init.base.call(this, id);
                }
                this._truncationMode = Label.TRUNCATION_MODE_NONE;
                this._maxLines = 0;
                this._enableHTML = enableHTML || false;
                this._width = 0;
                this.addClass('label');
            },
            /**
             * Renders the widget and any child widgets to device-specific output.
             * @param {antie.devices.Device} device The device to render to.
             * @returns A device-specific object that represents the widget as displayed on the device (in a browser, a DOMElement);
             */
            render: function render (device) {
                // TODO: is there a more efficient way of doing this?
                var s = this.getTextAsRendered(device);

                if(!this.outputElement) {
                    this.outputElement = device.createLabel(this.id, this.getClasses(), s, this._enableHTML);
                } else {
                    device.setElementContent(this.outputElement, s, this._enableHTML);
                }

                return this.outputElement;
            },
            
            /**
             * Will return text as rendered on the device
             * @param {antie.devices.Device} device The device to render to.
             * @returns A string that will be displayed in the label after truncation, etc...
             */
            getTextAsRendered: function(device) {
                var s;
                if(this._width && this._maxLines && this._text && (this._truncationMode === Label.TRUNCATION_MODE_RIGHT_ELLIPSIS)) {
                    var h = device.getTextHeight('fW', this._width, this.getClasses());
                    var allowedHeight = h * this._maxLines;
                    var currentHeight = device.getTextHeight(this._text, this._width, this.getClasses());

                    var len = this._text.length;
                    while(currentHeight > allowedHeight && len > 1) {
                        len = Math.floor((len * allowedHeight) / currentHeight);
                        currentHeight = device.getTextHeight(this._text.substring(0, len) + '...', this._width, this.getClasses());
                    }
                    while(currentHeight <= allowedHeight && len <= this._text.length) {
                        len++;
                        currentHeight = device.getTextHeight(this._text.substring(0, len) + '...', this._width, this.getClasses());
                    }
                    len--;

                    if(len < this._text.length) {
                        // truncate at word boundary
                        var boundaryLen = len;
                        while(boundaryLen && !/\w\W$/.test(this._text.substring(0, boundaryLen+1))) {
                            boundaryLen--;
                        }
                        if(boundaryLen > 0) {
                            s = this._text.substring(0, boundaryLen) + '...';
                        } else {
                            s = this._text.substring(0, len) + '...';
                        }
                    } else {
                        s = this._text;
                    }
                } else {
                    s = this._text;
                }
                return s;
            },
            
            /**
             * Sets the text displayed by this label.
             * @param {String} text The new text to be displayed.
             */
            setText: function setText (text) {
                this._text = text;
                if(this.outputElement) {
                    this.render(this.getCurrentApplication().getDevice());
                }
            },
            /**
             * Gets the current text displayed by this label.
             * @returns The current text displayed by this label.
             */
            getText: function getText () {
                return this._text;
            },
            /**
             * Sets the truncation mode (currently {@link antie.widgets.Label.TRUNCATION_MODE_NONE} or {@link antie.widgets.Label.TRUNCATION_MODE_RIGHT_ELLIPSIS}).
             *
             * @deprecated TRUNCATION_MODE_RIGHT_ELLIPSIS relies on browserdevice.getTextHeight(), which can be inaccurate.
             * @param {String} mode The new truncation mode.
             */
            setTruncationMode: function setTruncationMode (mode) {
                this._truncationMode = mode;
            },
            /**
             * Sets the maximum lines displayed when a truncation mode is set.
             * @param {String} lines The maximum number of lines to display.
             */
            setMaximumLines: function setMaximumLines (lines) {
                this._maxLines = lines;
            },
            /**
             * Sets the width of this label for use with truncation only.
             * @param {Integer} width The width of this label in pixels
             */
            setWidth: function setWidth (width) {
                this._width = width;
            }
        });

        /**
         * Do not truncate the text. Let the browser wrap to as many lines required to display all the text.
         * @name TRUNCATION_MODE_NONE
         * @memberOf antie.widgets.Label
         * @constant
         * @static
         */
        Label.TRUNCATION_MODE_NONE = 0;
        /**
         * Truncate text to fit into the number of lines specified by {@link antie.widgets.Label#setMaximumLines} by removing characters at the end of the string and append an ellipsis if text is truncated.
         * @constant
         * @name TRUNCATION_MODE_RIGHT_ELLIPSIS
         * @memberOf antie.widgets.Label
         * @static
         */
        Label.TRUNCATION_MODE_RIGHT_ELLIPSIS = 1;

        return Label;
    }
);