Tuesday, February 21, 2012

Truncating Text by Pixel Width

If you've ever tried truncating text by the number of characters then you've probably noticed that most of the fonts we use are not fixed-width, resulting in different lengths depending on which characters are used. This is particularly true of spaces which are much thinner than other characters.



I ran into this problem recently when creating a message preview module and I solved it by placing the text in a hidden div set to my desired width, then chopping off characters from the end until it fit. Of course, we don't want partial words so I wait until we reach a space before returning the truncated text.

Here is the div (placed in the page body):
<div id="hiddenTruncateHelper" style="position: absolute; visibility: hidden; display: block; height: auto;"></div>

...and the JavaScript:
var truncate = function (str, width, height) {
    var bits, bit, i;
    $("#hiddenTruncateHelper").width(width);
    $("#hiddenTruncateHelper").text(str);
    bits = str.split('');
    if ($("#hiddenTruncateHelper").getHiddenDimensions().height > height) {
        for (i = bits.length - 1; i > -1; --i) {
            $("#hiddenTruncateHelper").text(bits.join(''));
            bit = bits[i];
            bits.length = i;
            if (' ' === bit && $("#hiddenTruncateHelper").getHiddenDimensions().height <= height) {
                break;
            }
        }
    }
    return bits.join('');
};

One problem I had was that the browser kept returning zero for the div height because it is hidden. While searching for an answer I found a blog by Tim Banks where he shares an extension method to get the dimensions for a hidden element.
//Optional parameter includeMargin is used when calculating outer dimensions
(function ($) {
    $.fn.getHiddenDimensions = function (includeMargin) {
        var $item = this,
        props = { position: 'absolute', visibility: 'hidden', display: 'block' },
        dim = { width: 0, height: 0, innerWidth: 0, innerHeight: 0, outerWidth: 0, outerHeight: 0 },
$hiddenParents = $item.parents().andSelf().not(':visible'),
includeMargin = (includeMargin == null) ? false : includeMargin;

        var oldProps = [];
        $hiddenParents.each(function () {
            var old = {};

            for (var name in props) {
                old[name] = this.style[name];
                this.style[name] = props[name];
            }

            oldProps.push(old);
        });

        dim.width = $item.width();
        dim.outerWidth = $item.outerWidth(includeMargin);
        dim.innerWidth = $item.innerWidth();
        dim.height = $item.height();
        dim.innerHeight = $item.innerHeight();
        dim.outerHeight = $item.outerHeight(includeMargin);

        $hiddenParents.each(function (i) {
            var old = oldProps[i];
            for (var name in props) {
                this.style[name] = old[name];
            }
        });

        return dim;
    }
} (jQuery));

That's all there is to it. We just call our method and pass in the string, desired height and width (in pixels) and it returns the truncated text. Just add some ellipses with a tooltip (title) and we're done!
<a href="#" title="FULL TEXT">...</a>

If you have a suggestion for how this solution could be improved or a question, please post it here. Thanks for reading!

Thursday, February 9, 2012

Microsoft PEX and Moles

Microsoft Research is currently beta testing a couple new frameworks that can make unit testing your code a breeze. Pex is a testing tool that analyzes code and generates unit tests. Moles is a framework that isolates code with dependencies on other application layers or frameworks. With just a few mouse clicks, you can generate suites of tests against code that previously may have been difficult or impossible to test.

Tonight I'll be giving a presentation on these technologies at the Nashville .NET User Group. For anyone who can't make it you can watch and discuss it in my Google Hangout. I'm hoping to post a video of the presentation to this blog within the next couple weeks. I'll also be giving a similar presentation and Code PaLOUsa on March 17th. Below are links to the demo solutions and Prezi used in the presentation.

Presentation on Prezi
Using Moles to Mock DateTime (Y2KBug.zip)
Using Moles to Stub Interfaces (MolesDemo.zip)
Using PEX to Generate Parameterized Unit Tests (PEXDemo.zip, PEXDemo-Result.zip)