TinyMCE – iOS Elephantiasis

TinyMCE likes to spread itself out on the iPad and iPhone. Here’s one way to satisfy its appetite for space.

If you need to accept rich text input from users of your web site there’s a good chance you’re doing it with TinyMCE, one of the most popular, mature and customisable browser-side rich text editors out there.

If your layout relies on TinyMCE having a fixed height though, you might be in for a nasty surprise when someone hits your web site on an iPhone or iPad.

The problem

To illustrate what happens with TinyMCE on iOS devices, here’s a really simple example of a web page containing a TinyMCE editor with some fixed position content below it.

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>The problem...</title>
    <script type="text/javascript" src="../tiny_mce/tiny_mce.js"></script>
    <script type="text/javascript">
      tinyMCE.init({
          mode : "textareas",
          theme : "simple",
      });
    </script>
  </head>

  <body>
    <form>
      <h3>The problem...</h3>
      <div id="editor" style="position:absolute; top:50px;">
        <textarea name="myText" rows="10" cols="50"></textarea>
      </div>
      <div id="buttons" style="position:absolute; top: 220px;">
        <input type="submit" name="submit" value="OK" />
        <input type="reset" name="reset" value="Cancel" />
      </div>
    </form>
  </body>
</html>

This page looks identical when initially loaded on either a desktop browser or on an iOS device:-

Empty TinyMCE on a desktop
Empty TinyMCE on an iPhone

But enter a few lines of text and things start to look a lot different:-

TinyMCE with content added on a desktop
TinyMCE with content added on an iPhone

TinyMCE uses an iframe to contain the text the user is entering, and on iOS devices, iframes don’t get scrollbars when the content is too big to display. Instead the iframe expands to display all the content, in this example causing our fixed position button row to obscure part of the text.

The solutions

There are some workarounds out there to keep an iframe size fixed on iOS but they’re fiddly and don’t necessarily work on all iOS versions.

In the above, rather contrived example we could just remove the fixed position of our button row and let it shift down automatically as TinyMCE expands. If you have a more complex layout that contains fixed elements though, possibly in shared templates used by many other pages on your site, that solution might not be at all easy to put in place.

If you just need to shift some fixed elements around so that a page containing TinyMCE is usable and presentable on iOS devices, one option to consider is using TinyMCE’s DOM EventUtils class to add a resize event handler to the editor window to do it.

Here’s our simple example again, this time with a resize handler which will move our button row down when the editor grows:-

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>A solution...</title>
    <script type="text/javascript" src="../tiny_mce/tiny_mce.js"></script>
    <script type="text/javascript">
        tinyMCE.init({
            mode : "textareas",
            theme : "simple",
            init_instance_callback : "initTinyMCE",
        });

        var initHeight = 0;
        var initTop = 0;
        function initTinyMCE(inst) {
          initHeight = document.getElementById(
              inst.editorId + "_ifr").offsetHeight;
          initTop = document.getElementById("buttons").offsetTop;
          tinymce.dom.Event.add(inst.getWin(), "resize", function(e) {
              nudge = document.getElementById(
                inst.editorId + "_ifr").offsetHeight - 
                initHeight;
              document.getElementById("buttons").style.top = 
                  (initTop + nudge) + "px"; 
          });
        }
    </script>
  </head>

  <body>
    <form>
      <h3>A solution...</h3>
      <div id="editor" style="position:absolute; top:50px;">
        <textarea name="myText" rows="10" cols="50"></textarea>
      </div>
      <div id="buttons" style="position:absolute; top: 220px;">
        <input type="submit" name="submit" value="OK" />
        <input type="reset" name="reset" value="Cancel" />
      </div>
    </form>
  </body>
</html>

First of all we’re setting init_instance_callback in tinyMCE.init to specify a function to be called when the editor initialises. Our function saves the current position of our fixed position button row and also gets the initial height of the editor iframe. Note that this isn’t the height of the TinyMCE editor, just the text pane that expands vertically within it. Its identifier is the editorId plus an _ifr extension (in this case it will be myText_ifr).

Next up, our initialisation function uses EventUtils to add a resize handler to the editor. This function works out the difference between the initial height and the current height and then applies this “nudge” to the original position of our button row.

Here’s how this looks on an iPhone:-

Empty TinyMCE with a resize handler on an iPhone
TinyMCE with content added and a resize handler on an iPhone

Whenever TinyMCE expands our button row now obligingly moves down to accommodate it.

Using a layout that naturally copes with TinyMCE’s variable size is the tidiest solution of course, but when that’s difficult to put in place, a resize event handler that can work out the change and apply it where necessary may be your next best option.

One response to “TinyMCE – iOS Elephantiasis

  1. The above example works with TinyMCE 3.x. Here’s a version of the initTinyMCE function which uses jQuery and works with TinyMCE 4:-

    	var initHeight = 0;
    	var initTop = 0;
    	function initTinyMCE(inst) {
    		initHeight = $("textarea[name=myText]").height();
    		initTop = $("#buttons").offset().top;
    		$(inst.getWin()).bind("resize", function() {
    			nudge = $(inst.getWin()).height() - initHeight;
    			$("#buttons").css("top", initTop + nudge + "px");
    		});
    	}
    

    Note that we need to change the way we bind our resize handler as the old tinymce.dom.Event approach isn’t there anymore. Also the initial height returned for the generated TinyMCE container is the default height when this function runs, so instead we get it from our own parent element.

Leave a Reply

Your email address will not be published. Required fields are marked *