JavaScript to manage wide lines

JQuery logo

Update: Now you (single-)click the [-] or [+] icons to toggle folding/expanding in the core docs. See the issue Sam filed. (The core docs also currently use <div>s rather than <pre>, except on Firefox, where the JavaScript throws a <pre> around the <div> to workaround a longstanding Firefox issue.)

<script type="text/javascript">
/* On double-click, reformat <div class="screen"> for easy copying. */
var minus = "<img alt=\"minus\" class=\"toggle\" width=\"18\" height=\"10\" src=\"data:image/png;base64,iVBORw0K...\">";
var plus = "<img alt=\"plus\" class=\"toggle\" width=\"18\" height=\"10\" src=\"data:image/png;base64,iVBORw0K...\">";

$(document).ready(function() {
  /* Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=116083 */
  if (navigator.userAgent.match(/Firefox/i)) {
    $(".screen").wrap("<pre />");
    $(".programlisting").wrap("<pre />");
  }

  /*
    Include both a wrapped (.screen) and unwrapped (.flat) version of
    each screen element, with [-] image to collapse and [+] image
    to expand content.
   */
  $(".screen").each(function () {
    $(this).replaceWith(
      "<div class=\"screen\" title=\"Click [-] to flatten lines.\">"
        + minus + $(this).html() + "</div>" +
      "<div class=\"flat nodisplay\" title=\"Click [+] to wrap long lines.\">"
        + plus + $(this).html().replace(/(\n|\r\n) /g," ") + "</div>"
    )});
});

/*
  Add class="nodisplay" to parent div; remove from parent's sibling.
  If the current parent is .screen, then next sibling is .flat.
*/
$(".toggle").live("click", function() {
  $(this).parent().toggleClass("nodisplay");
  if ($(this).parent().hasClass("screen")) {
    $(this).parent().next().toggleClass("nodisplay");
  } else {
    $(this).parent().prev().toggleClass("nodisplay");
  }
});
</script>

The images are base64-encoded inside the JavaScript, I’ve snipped them above because the lines are so long.


Some commands to manage servers tend to lend themselves to examples that do not fit in 80 columns. Command with subcommands that take a string of options with arguments typically fall into this category.

One alternative to make the examples readable is to insert line breaks.

$ dsconfig
 -p 4444
 -h `hostname`
 -D "cn=Directory Manager"
 -w password
 create-plugin
 --plugin-name "Samba Password Synchronisation"
 --type samba-password
 --set enabled:true
 --set pwd-sync-policy:sync-nt-password
 --set
 samba-administrator-dn:"uid=samba-admin,ou=Special Users,dc=example,dc=com"
 -X -n

Trouble comes, then, when you want to copy the example into a terminal window. What do you do? Copy line by line? Copy into a text editor and remove the newlines?

This morning I wished my browser would just do it for me. Then after a coffee, I realized my browser probably could. I learned just enough JQuery to do join the continuation lines in <screen> (terminal window) content when clicked:

$(document).ready(function(){
  $(".screen").click(function(event){
    $(this).replaceWith(
      "<pre>" + $(this).text().replace(/\n /g," ") + "</pre>");
  });
});

Going to give it a whirl in OpenDJ HTML documentation. Unfortunately, once you click, this code cannot take you back. You have to reload the page again to see the command unfolded. It would be cool to have click be a toggle.

On click, the above folds to:

$ dsconfig -p 4444 -h `hostname` -D "cn=Directory Manager" -w password create-plugin --plugin-name "Samba Password Synchronisation" --type samba-password --set enabled:true --set pwd-sync-policy:sync-nt-password --set samba-administrator-dn:"uid=samba-admin,ou=Special Users,dc=example,dc=com" -X -n

(I cannot figure out how else to show you in this hosted WordPress blog.)

Even cooler would be the equivalent in DocBook to allow authors to drop in exactly what they see in the terminal, reformatting the output for presentation, but leaving a JavaScript toggle in the HTML version to go back to the original presentation for copying.

8 thoughts on “JavaScript to manage wide lines

  1. Jeff

    I’ve always just used line-continuation via the ‘\’ character.

    somecommand \
    someoption \
    someother \

    Displays fine, pastes fine.

  2. A couple of things that don’t work for me using \ to continue lines:

    1. The typical Emacs-style key binding Ctrl+a to go to the beginning of the line only takes me to the beginning of the continued line. There must be a way around this, but I’d have to look it up.

    2. The line doesn’t get collapsed into one until I’ve run the command, which I probably don’t want to do with an example that hasn’t been edited yet.

  3. I found a way to toggle with .live() :

    // On click, reformat <pre class="screen"> for easy copying.
    $(".screen").live("click", function() {
      $(this).replaceWith(
        "<pre class=\"flat\">" +
        $(this).text().replace(/\n /g," ") + "\n<!--" + $(this).text() + "-->" +
        "</pre>");
    });
    $(".flat").live("click", function() {
      $(this).replaceWith(
        "<pre class=\"screen\">" +
        $(this).html().replace(/(.|\n)+<!\-\-/m,"").replace(/\-\-\>/,"") +
        "</pre>");
    });
  4. Jeff

    Weird, I didn’t get notified of your other replies here, only the latest one.

    “The line doesn’t get collapsed into one until I’ve run the command, which I probably don’t want to do with an example that hasn’t been edited yet.”

    I just select carefully up to the last character and make sure not to include any following newline or blank line. The paste leaves my cursor right at the end of the line.

    I’ve also been known to type something like ‘1111’, copy/paste after that without care for the newlines, then recall that broken/failed command and edit the 111 out 😉

    But yes, I too have been bit in the last few weeks by double-clicking in the OpenDJ docs, expecting a normal full selection to take place.

  5. Should work better now.

    I’ve not tried to think about the right regular expression for deciding to
    insert \ at the end only of manually folded lines in the expanded versions.

Leave a reply to Mark Craig Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.