Be sure to check out the follow-up to this post: Word-wrap for Mozilla (take 2). It features a much simplified way of accomplishing the same thing, based on the comments from RichB.
IE has one nice CSS feature that a lot of other browsers lack, which is the ability to use CSS to wrap long words. It's easy to use. Something like this will do the trick:
{
word-wrap: break-word;
}
Firefox lacks this feature, but it does offer some snazzy features of its own. One in particular is XBL.
In this blog entry, I'm going to demonstrate how to use Mozilla Bindings to (sort of) emulate CSS word-wrap. Since the ability to wrap a long series of unbroken characters is part of the CSS3 specification, I do expect to see this in future versions of Firefox. Until then, this is the best I've got.
An outside-the-box solution
For a long time, I didn't think there was really anything you could do about Firefox's lack of support for word-wrap. I figured the best you could do was set your containing element to hide overflowing data (which might be okay, but most likely not).
Just recently though, a co-worker of mine found a web page that explained how you could insert an invisible unicode character (​) at various points in a string to solve the Firefox word-wrap problem. Since Firefox views this unicode symbol as a valid character upon which to "break" a string (if necessary) it servers as a convenient to way get Firefox to wrap long strings of characters (the symbol remains completely invisible to the end user).
My co-worker first showed me a function he wrote which took a string parameter, inserted this unicode symbol every 5 characters, and then returned the modified string.
This is a good first start, but I wanted something more. I wanted a solution that didn't require me to execute a Javascript function, and I wanted a solution that could handle HTML intersperced throughout the string.
It turns out, that creating a solution that doesn't require executing a Javascript function isn't the hard part at all! In Firefox, this can be accomplished quite easily with XBL.
How does one utilize XBL? Glad you asked!
From the Mozilla Developer page:
Extensible Binding Language is a XML-based markup language to implement reusable components (bindings) that can be bound to elements in other documents. The element with a binding specified, called the bound element, acquires the new behavior specified by the binding. Bindings can be bound to elements using Cascading Style Sheets (CSS) or DOM. One element can be be bound to several bindings at once.
Setting up the binding is pretty easy, so let's tackle that part first:
{
-moz-binding: url('./wordwrap.xml#wordwrap');
}
With the CSS in place, "binding" to the DOM element is trivial: simply set the element's class property to "wordwrap".
SomeParagraphWithAVeryLongStringOfUnbrokenTextThatWillEventuallyWrap.
</p>
With the CSS and HTML taken care of, all that's left to do is write the XBL file. An XBL file is really nothing more than an XML with a specific structure so that Mozilla knows how to process it as a binding. In fact, if you prefer, you can name the file with an .xml extension (like I did), instead of an .xbl extension.
At its most basic, an XBL file takes the following form:
<bindings xmlns = "http://www.mozilla.org/xbl" xmlns:html = "http://www.w3.org/1999/xhtml">
<binding id = "wordwrap" applyauthorstyles = "false">
<implementation>
<constructor>
//
/* ECMAScript code here */
//
</constructor>
</implementation>
</binding>
</bindings>
The binding id attribute:
The root element of the file is <bindings>. Within the root element, you'll have one or more <binding> elements, each with its own unique id. Since an XBL file can have more than one binding definition, the id instructs the browser as to which binding we're interested in. Remember, back when we specified the url for the -moz-binding property, we specified the binding id by appending to it, a "#" symbol followed by the binding id (in this case "wordwrap"). So the full url ended up being: "./wordwrap.xml#wordwrap".
The applyauthorstyles attribute:
The "applyauthorstyles" attribute determines whether or not to allow styles from the document on the bound element (other than the -moz-binding). The default value is false, but you're free to change this if you want (or need) to.
The <implementation> and <constructor>
Every binding has an <implementation> element and, within it, a <constructor> element. This is where most of your ECMAScript code will go (in our case, this is where all of the ECMAScript code will go).
With a very basic explanation of an XBL file's structure out of the way, we can now go ahead and write the implementation of our binding.
It's important to note that all of the ECMAScript you write needs to go inside a CDATA block (this an XML file after all). Other than that though, you don't have too many restrictions. In fact, one nice thing about writing a binding, is that you have a great deal of flexibility. Pretty much all of the ECMAScript you're used to writing is going to work inside the XBL file. And to sweeten the deal even more, you've got some nice extras that aren't available in your normal, day-to-day ECMAScript (we'll look at one of them shortly).
Referencing the bound element:
Perhaps the most important thing you need to know (before you can do anything productive in your binding) is how access the bound element. In other words, how do you access the element to which the class (in our case "wordwrap") was applied? Fortunately, it turns out to be brain-dead easy. You simply using the this keyword; that's it!
Once you have a reference to the bound element, you can do most (if not all) of the things you'd normally do with a DOM element. You could, for instance, wire up logic such that a particular function execute whenever the bound element is moused over, as in:
elem.addEventListener('mouseover', function(e)
{
alert('The text of this element will eventually be wrapped!');
}, false);
If you put the preceding code inside the <constructor> element of the binding definition, then any time a user moused over the <p> (#myParagraph), he would see an alert saying "The text of this element will eventually be wrapped!"
Making the text wrap:
At this point, it should be fairly clear what the plan of action is:
- Access the bound element's text.
- Insert the special unicode symbol between the characters of the string.
- Update the bound element's text with the newly modified text.
From the instructions above, I've made it sounds pretty simple. Unfortunately, it's a bit more complicated than I let on. I (conveniently) failed to mention that if the bound element's text contains HTML, we need to take extra care not to insert the special unicode symbol into any of the HTML tags (or else we'll end up disrupting the author's markup).
The word wrap code
The code to make this happen isn't long, so rather than try to explain it up front, I'll first show the full code, and then provide an explanation afterwards:
<bindings xmlns = "http://www.mozilla.org/xbl" xmlns:html = "http://www.w3.org/1999/xhtml">
<binding id = "wordwrap" applyauthorstyles = "false">
<implementation>
<constructor>
//
var elem = this; // maintain a reference to the bound element
elem.addEventListener('overflow',
function()
{
// matches any "mangled" HTML tag
var exp = /<​\/*[​_\s="'\w]+>/g;
var txt = elem.innerHTML; // the bound element's innerHTML
var chars = txt.split('');
var newTxt = chars.join('​');
newTxt = newTxt.replace(exp, reconstructTag);
elem.innerHTML = newTxt;
},false);
function reconstructTag(_tag)
{
return _tag.replace(/​/g, '');
}
//
</constructor>
</implementation>
</binding>
</bindings>
Explanation:
The first thing we do is stuff the bound element (this) in a variable, elem. This is of course, totally optional, but it helps me (my brain is small and remembering what this represents eludes me quickly and frequently).
Next, we add an event listener to the bound element that "listens" for the 'overflow' event (whenever text overflows its containing element). You're probably aware the in typical Javascript, there is no such 'overflow' event. This, along with its counterpart, 'underflow' are just two the many "extras" that you get when writing Javascript in XBL.
The remainder of the code will all take place within the context of the anonymous function that executes on 'overflow'.
Next, we create a regular expression that matches any "mangled" HTML tag (more on this in a bit).
The next three steps are all quite straight-forward. We store the bound element's innerHTML in a variable called, txt. We then call .split('') on that text and store it in the variable, chars. Passing an empty string into the .split(..) function will yield an array of characters representing the string that was split. In other words, each character in the string is now an element in the array chars. Finally, we put all of those characters back together by calling chars.join('​'); and stuffing the result in newTxt. This inserts that special unicode symbol between each character in the array as it joins them back together.
The tricky part:
If you were paying attention, then at this point, you'd realize that any HTML that existed inside of the bound element, just got totally mangled with that last .join(..). statement. Think about it: if the bound element's innerHTML were something like this:
Then after executing the .join(..) statement, the variable, newTxt would now be equal to:
Besides perhaps giving you a head-ache, the above HTML snippet should make you realize that the insertion of that special unicode symbol between every character in the string has a really detrimental effect on the <span> tag.
The solution:
It turns out, that "unmangling" the HTML tags isn't too too hard (just takes some fancy regular expression work). The regular expression, exp will match any HTML tag that has had the special unicode symbol inserted into it. We can utilize this regular expression to match all occurrences of "mangled" HTML tags and (with some help from the .replace(..) function) replace them with "unmangled" versions of the tag.
In order to do this though, we need to make of use a callback function when we invoke the .replace(..) function. When you pass a function reference as the second parameter into a .replace(..) call (as in: myString.replace(regex, myFn)) Javascript will automatically pass the matched string into the callback function for you!
This is an extremely powerful concept. We can now take all "mangled" HTML tag matches and send them through the reconstructTag(..) function. The reconstructTag(..) function simply returns a copy of the string sent into it, but with all occurrences of that special unicode symbol stripped out (thus returning our "mangled" HTML tags to proper form).
With all of these concepts working together, we end up with a string that looks perfectly unaltered to the end user, but which wraps nicely when it would otherwise overflow its container.
See for yourself on my demo page: Word Wrap Binding Demo.
And, to see a slightly more complex example, take a look at my implementation of an ellipsis for Mozilla using XBL: Mozilla Ellipsis.
Final thoughts:
Mozilla Bindings offer tremendous power to the developer. You can do virtually anything with them; the sky is the limit really. In this blog post, we just looked at one example: how to make long, unbroken strings wrap when they would otherwise overflow their container. But we also saw how you can take this further (see the ellipsis demo link above).
Important Warning / Disclaimer!
It should be noted that I have absolutely no idea what kind of effect inserting the special unicode symbol (needed to make this technique work) might have on accessibility. It's an important question. Is that character going to be read aloud by screen readers (what in the world would it say)? Will search engines see it and have no idea what your text represents, thereby hurting your search engine ranking? My fear is that the effect of inserting this character into your string might not be good. But of course, without any real testing, I can't say for sure.
My recommendation would be to use this concept with caution and only when absolutely necessary. A little common sense will go along way towards ensuring you're not bloating your page will a lot of "fancy-just-for-the-heck-of-it" code.
So there ya go! Word wrap in Mozilla. Happy Binding!
21 Responses
Another cross-platform way I use from time-to-time is:
$("element").innerHTML=$("element").innerHTML.split("").join("");
It should be possible to do this correctly the first time using a treewalker. eg:
var walker=doc.createTreeWalker(elem, NodeFilter.SHOW_TEXT, null, false);
while(walker.nextNode()) {
walker.currentNode.nodeValue=walker.currentNode.nodeValue.split("").join("\u8203");
}
My first comment had the HTML stripped from it….
It should read:
.split("").join("[wbr]");
(where the [ and ] are replaced with angle brackets to create the wbr tag.)
RichB,
Thanks! These are excellent suggestions. I had completely forgotten about the <wbr> tag. That's a good idea; I'll verify it works and make the change if so.
And how exactly is this better than actually implementing word-wrap: break-word;? Why don't the FF developers just bite the bullet and make it work like the CSS Level 3 standards compliant way that IE does it.
Nico,
It's not better. IE's implementation is better, and I imagine that Firefox most likely will support it in the future.
To be clear though, this is not a case where IE "implemented the standard." This is a case where IE implemented a proprietary feature (and a good one) which eventually became a standard. So it's natural that it would take a little bit of time for other browsers to catch up and implement that standard.
You must be crazy!! All that to get FF to word-wrap …
Must be an easier method, then mangling the HTML , etc etc
Can we just wrap the text??
Sheesh
..darleen
Darleen:
I admit, it is a lot, but what else can we do? If you read the follow-up post, it has a simplified method that you might prefer: http://ecmascript.stchur.com/2007/03/01/word-wrap-for-mozilla-take-2/
Cant believe i have to do so much just to wrap a word…Mozilla guys cant get any more foolish…First of all they have ignored most important feature,on top of it some "firfox advocate" is giving some stupid solutions…I am a novice software engineer who has been assigned a bug related to wrap…there is no way i can implement this solution…where do i place this XML??do i need to parse this…how do i get my CSS access this??
I agree. It's just foolish to have to go through all this just to wordwrap a continuous line. I love Firefox, and will continue to use it as my primary browser, and will continue to write web sites for this browser, but I am disappointed that Mozilla can't see to it to implement useful, and sometimes necessary implementations such as this wordwrap issue and another that's been plaguing me; fully featured scroll bars for iframes. IE has made both of these issues very available and very easy to implement with CSS. But we can't ignore that Firefox is increasingly popular, and the developers need to understand that the robust nature of these features makes them worthy for immediate implementation. I'm disappointed that Mozilla is not keeping up.
David
PS - Oh, and by the way, Mr.sstchur, this very textarea form field doesn't seem to do what your formula suggests. There truly must be a better way.
ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
You people don't seem to understand that I'm trying to /help/ you here. I'm not saying this simulation is as good as native word-wrap support. Firefox is an extraordinarily powerful browser, and the binding mechanism discussed in this post demonstrates that.
My particular implementation of word-wrap has its flaws and I point that out. But my inability to write the "perfect" word-wrap implementation for Firefox doesn't make it any less great of a browser.
Furthermore, I suspect that future versions of Firfox will indeed support word-wrap. Until then, what's we've got here is at least /something/.
And finally, David, I'm not using the word-wrap solution on this blog. Truth be told, I run into very few cases where word-wrap was really worth it for me. It doesn't bother me that your "dddddd" example is spilling out over the boundaries. I decided to accept that when I created this blog's design. You're more than welcome to dislike it.
All I can say is thanks so much for all your efforts to sort this out. To everyone else: no, he is not crazy! There really is no simple answer to the Word-Wrap problem in Firefox. stchur's solution, and the two follow-up solutions, really are the closest I've seen to a true resolution of this problem.
And even if it does seem complex, stchur's done all the hard work anyway
And solution 3 (http://ecmascript.stchur.com/blogcode/emulating_word_wrap_take3/) is truly awesome…
about css parameters
http://css-lessons.ucoz.com/css-parameters.htm
HI i need your help i really want to create my own website/web page but i dont know how to go about doing it so can you please help me out
I was able to get this to work, but the screen is blank even though the html is there. I pop the source code into Dreamweaver and it is the same as if the wordwrap css was not there. Same exact code/html, but one shows up and the other not. Is it possible that I am missing something? I used your exact example code above.
Thanks.
dblurred:
You can try emailing me a zip of your sample if you want. I'm not sure what the issue is, but I could try to take a look.
-Steve
Hey thanks. Your post just saved me a great deal.
can i use this in table cell ….??
@mLi:
You know, I'm not sure. I use tables so rarely these days, that I've never tried. Give it a shot and let me know how it works.
its not working , when i use mozilla still it has same problem event , when i copy the code ie:xml file it show so many error.
venkat:
Can you be a bit more specific? What, exactly, isn't working?
Does the live demo work for you? http://blog.stchur.com/blogcode/emulating_word_wrap_take2/