[JavaScript] Text splitted in different elements


JavaScript jQuery

Have you ever wondered to fit undetermined length texts inside different HTML blocks that don't have to be contiguous?

I don't remember exactly where I read about this feature, possibly from an Adobe CSS request or idea. In any case, I wanted to do something like this time ago, but at that time I didn't have the proper skills to think about a solution.

In this article I'll show you my solution, which includes the basic idea to keep it simple to explain and to understand. The solution is a jQuery method. Yeah I know, jQuery may not be the lightest framework around, but it's for sure one of those used in the majority of the cases. You can always convert it in plain JS if you want :)

Limitations: the very limitation of this method, which can and should be expanded, is that it will handle only plain text. You can easily imagine how tricky would be to handle heigher-than container's height images, while taking care of opening inline-blocks should be easier, still would increase the number of lines, while I want to keep things as clear as possible, at least in this article.


Attention: this work is under the Creative Commons Attribution 3.0 Unported licence

Attention: you need jQuery to make it work (the demo already includes it)


Live demo

jquery.splitinelements.zip (contains the $.splitInElements method).


Method definition and argument:

:::JavaScript
(function( $ ) {
    $.fn.splitInElements = function(container_element) {
        // the method will be implemented here
    };
}( jQuery ));

The method will be called something like this: $("my selector").splitInElements(). The optional argument container_element provides the script with the wrapping text tag. Unfortunately I found no reliable way to handle text-only data without any wrapping tag. The default value, as we'll see in a moment, is a paragraph. If it's not present in the element, or it's not the only child of it, all the content will be wrapped inside an instance of it.

:::JavaScript
        if(!container_element)
            container_element = "p";
        // missing data-next attribute
        if($(this).data("next") == undefined)
            return this;

As already written, the default wrapping element, if not specified, is a paragraph. The method works with an HTML5 proto-attribute, data-* (in this case data-next). It specifies which is the next element the system needs to put the overflowing text into, and it's the next element's ID. This is a required attribute, otherwise the method will simply return.

Example:

:::html
<div id="first" data-next="second">my text here</div>
<div id="second" data-next="..."></div>

The last element in chain doesn't need to have this attribute. Note though that if it still has a fixed height and the text is longer than it should, it will overflow. It's a goot habit then to let the last element be height-free. If you want, you can even set its display to none, but the text will result truncated.

:::JavaScript
    if($(this).children().size() != 1 || $(this).find("> " + container_element).size() == 0) {
        $(this).wrapInner("<" + container_element + " />");
    }
    var $toSplit = $($(this).find("> " + container_element)[0]);
    var $next = $("#" + $(this).data("next"));

    var innerHeight = $(this).innerHeight();
    var extra = [];

These lines will simply wrap the text, if the previously explained element is not presen inside the processed one. Eventually it will setup a couple of variables we're going to use in the next few lines: the text to split, the next element to put the overflowing text into and the maximum height the div handles. The extra variable is an array, as we're going to split it into words: it will be used to fill the next element.

:::JavaScript
    while(innerHeight < $toSplit.outerHeight(true)) {
        // need wiser search (avoid splitting tags)
        var words = $toSplit.html().split(" ");
        extra.push(words.pop());
        $toSplit.html(words.join(" "));
    }

This cycle will strip out one word per time, starting from the end of the string, until the inner element will match or will be less than the container's height. innerHeight() returns the space available for the inner elements, while outerHeight() specifies the element's height, including padding. If the optional argument is present and it's true, it will count its margins, too.

:::JavaScript
    $next.html(extra.reverse().join(" ")).wrapInner("<" + container_element + " />");
    $next.splitInElements(container_element);

    return this;

The first line simply joins the overflowing words (in the right order), wrap them in the wrapping element (paragraph or as specified by the user) and puts it in the next container. The second line will call the method recursively, but pointing to the next element in chain. As you can imagine, this will end once the last element won't have any data-next attribute set. The last line simply returns the element itself, so that you can concatenate other methods, like $("#mydiv").splitInElements().whateverOtherMethod();

That's it! Simply enought to understand it, isn't it?

- 25th August 2013

> back