Tabindexing and inserting tabs with tinyMCE

You may have noticed that you can't press tab to jump from field to field in a form that uses tinyMCE. There is a plugin named tabfocusin the core that is here to allow just that.

But it does not seem to work correctly if you already have tabindex values defined on your inputs. So I decided to code my own.

I choose the easiest way, letting the browser do most of the job. I only copied the textarea tabindexvalue to the create iframe and it did the trick.

I created a tinyMCE for that, here the code of the init() method. By the way, I'm using the jQuery version of tinyMCE.

init : function(editor, url) {
  // We set a tabindex value to the iframe instead of the initial textarea
  editor.onInit.add(function() {
    var editorId = editor.editorId;
    var textarea = $('#'+editorId);
    $('#'+editorId+'_ifr').attr('tabindex', textarea.attr('tabindex'));
    textarea.attr('tabindex', null);
  });
}

What it does is grabbing the initial tabindexvalue of your textarea and setting it to the tinyMCE iframe. You have to wrap this in onInit.add because at the time the plugin initmethod is called, the iframe is not yet created. I also removed the tabindexvalue from the original textarea, two elements aren't supposed to have the same tabindexvalue.

This method does not work in Chrome. Chrome always add a tab character when you press the tab key in a tinyMCE editor, it does not jump to the next tabindexed element. Is that a good behavior ? I don't know, it surely is useful to be able to insert tab characters, but it also is useful to tab through the whole form.

Listening to the tab key

Anyway, I decided to hook on the keyDownevent and listen to the tab key being pressed. This way I could manually jump focus to the next field when tab is pressed, or insert a tab character when Shift+Tab is pressed (for example).

I used the tinyMCE event helper methods and wrote this (just add it right after the previous editor.onInit.add code) :

// We hook on the tab key. One press will jump to the next focusable field. Maj+tab will insert a tab
editor.onKeyDown.add(function(editor, event) {
  // We only listen for the tab key
  if (event.keyCode!=9) return;
       
  // Shift + tab will insert a tab
  if (event.shiftKey) {
    editor.execCommand('mceInsertContent', false, "\t");
    tinymce.dom.Event.cancel(event);
    return;
  }
  // Just pressing tab will jump to the next element
  var tabindex = $('#'+editor.editorId+'_ifr').attr('tabindex');
  // We get all the tabindexed elements of the page
  var inputs = [];
  $(':input[tabindex]').each(function() {
    inputs[$(this).attr('tabindex')] = this;
  });
  // We find the next after our element and focus it
  for (var position in inputs) {
    if (position<=tabindex) continue;
    inputs[position].focus();
    break;
  }

  tinymce.dom.Event.cancel(event);
  return;
});

First, we discard any key press that is not a tab key.

Then we check if the Shift key is pressed, and if so we add a tab character and stop there.

The biggest part is jumping to the next field. I can't revert to the browser default for that because every browser default behavior is different and I surely don't want to do some browser sniffing.

I first get the list of all the input fields that have a tabindexvalue (your fields should have one), then I sort them in tabindexorder and then loop through the list and stop when I've found one with a bigger tabindexthat the actual field. I focus this one and stop the loop.

One final word

I've tested under Firefox 3.6, Chrome, Safari and Opera. Haven't tested IE yet because I still have a lot of other scripts to debug in IE first.

As said earlier, maybe you could skip the whole tabindex listing if you intelligently revert to browser default for the browser that will jump to the next field but I have no idea how to test for that.

Using custom find and paginate methods in cakePHP 1.3

For the blog plugin I'm writing I needed a way to fetch only blog posts that were actually published. I didn't want to write the find conditions every time I had to make a request so I tried to define a custom find method.

I remember having read something about that a while ago, in a blog or in a changelog and so I thought it will be quite easy to implement using some cake automagic.

It was not that easy, I had to override two core methods in my AppModel, but here the result :

Using custom find methods

I wanted to be able to call my custom method writing $this->Post->find('published') so I created a __findPublished() method in my Post model.

It basically returns a find('all') with custom conditions.

I then edited my AppModel file to hook on the default find() method :

function find($type, $options = array(), $order = null, $recursive = null) {
  $methodName = '__find'.ucfirst($type);
  // Using default method if not defined
  if (!method_exists($this, $methodName)) {
    // We force default fields and order keys or we could run into trouble for undefined index
    $options = Set::merge(array('fields' => array(), 'order' => array()), $options);
    return parent::find($type, $options, $order, $recursive);
  }
  // Using custom method
  return $this->{$methodName}($options, $order, $recursive);
}

(Note that there may still be some issues with this method, especially if the $type parameter is not a string. I'm always using the find() method with a string as first argument but maybe you're not, or the core still use the old implementation.)

Anyway, what it does is testing if a __findSomething() method is defined, and if it is it returns its results, otherwise it just delegates to the default find() method.

Using custom find methods in paginate

So far, so good. But now how do you tell cake to use this custom find when paginating stuff ? The first part is easy (but required some digging into the core code). It appears that if the zero key of the $paginate var is set to a string, this will be used as the find type.

One easy way to do this is calling array_unshift($this->paginate, 'published') just before the $this->paginate('Post') call in the controller.

// Getting the paginated post list
array_unshift($this->paginate, 'published');
$itemList = $this->paginate($this->model);
$this->set('itemList', $itemList);

You'll notice that your custom find method is used for the pagination. What you may not notice at first sight is that the total count of results is not correct. Cake still uses the default find('count') without using the custom method.

We will now need to create a __paginateCountPublished() method in our Postmodel that will return the total count of posts to paginate.

Forcing Cake to do what we want is a little trickier this time. We will need to create a paginateCount() method in our AppModel. If such a method exists for a given model, Cake will use it instead of the default find('count') when paginating results. By creating it in the AppModelwe make sure that all the paginate counts use it.

This method takes a third argument called $extra which will contain our custom find name. If set, we will return the custom paginate count. If not set, we revert to the default way of calculating the total count (copy/pasted from the core).

So, here the paginateCount() method to add to your AppModel:

function paginateCount($conditions = array(), $recursive = null, $extra = array()) {
  // If no custom find specified, we return the default count
  if (empty($extra['type'])) {
      $parameters = compact('conditions');
      if ($recursive != $this->recursive) {
          $parameters['recursive'] = $recursive;
      }
      return $this->find('count', array_merge($parameters, $extra));
  }

  // We return the __paginateCountSomething
  $methodName = '__paginateCount'.ucfirst($extra['type']);
  return $this->{$methodName}($conditions, $recursive, $extra);
    }

And don't forget to create a__paginateCountPublished($conditions = array(), $recursive = null, $extra = array()) method in your Postmodel

And you're done

You can now do some $this->Post->find('published') magic in your controller. And don't forget the array_unshift() tip to use the custom find in a paginate call, it have to be the first key.

Gzipping your font files

When using @font-face to display fonts, you have to create a whole bunch of font files on your server to accomodate the quirks of the various browsers.

If you do things right (or follow the automatic kit build of FontSquirell), you should get a .eot file for IE, a .ttf/.otf file for current browsers, a .svg file for Chrome and the iPhone/iPad and a .woff file for the next browser generation.

Unfortunatly, you'll have to cope with that because there isn't much we can do about it at the moment.

But you can compress those files to make the font rendering faster. Some browsers even download all the fonts even if they will only use one, so compress them !

The easiest way is to configure your server to automatically gzip them. You should already have done that for your css and js file so it is just a matter of adding new types.

As far as I know .otf and .ttf files don't have registered mimetype, so I had to create a dummy one for them in my .htaccess :

AddType x-font/otf    .otf
AddType x-font/ttf    .ttf
AddType x-font/eot    .eot

I also added the .eot because even if an application/vnd.ms-fontobject mimetype is registered for this obscure microsoft format, when I tried to add a deflate rule on it, my Apache crashed so I took the safest way of defining a custom mimetype.

I prefixed them with an x-to make sure that it won't mess with existing mimetypes.

The second part was to add gziping to those

AddOutputFilterByType DEFLATE x-font/otf x-font/ttf x-font/eot

SVG files are in fact xml files, and you should already have them gzipped, so no need to add them here.

I haven't included .woff files because .woff files are already compressed files, so you don't need to gzip them.

Give your variables meaningful names

I just realized that the fullscreen plugin I was using with tinyMCE (v3.3.5) was throwing an error in my Firebug panel everytime I closed it.

As I wrote some tinyMCE plugins myself I thought I may have done something that was causing this. So I opened up the javascript file and checked for the error line :

var win, de = DOM.doc.documentElement;
if (ed.getParam('fullscreen_is_enabled')) {
  if (ed.getParam('fullscreen_new_window'))
    closeFullscreen(); // Call to close in new window
  else {
    DOM.win.setTimeout(function() {
      tinymce.dom.Event.remove(DOM.win, 'resize', t.resizeFunc);
                  tinyMCE.get(ed.getParam('fullscreen_editor_id')).setContent(ed.getContent({format : 'raw'}), {format : 'raw'});
                  tinyMCE.remove(ed);
                  DOM.remove('mce_fullscreen_container');
                  ed.style.overflow = ed.getParam('fullscreen_html_overflow');
                  DOM.setStyle(DOM.doc.body, 'overflow', ed.getParam('fullscreen_overflow'));
                  DOM.win.scrollTo(ed.getParam('fullscreen_scrollx'), ed.getParam('fullscreen_scrolly'));
                  tinyMCE.settings = tinyMCE.oldSettings; // Restore old settings
    }, 10);
  }
        return;
}

If you're not familiar with the complex tinyMCE syntax this may seems a little... well... complex.  I'll focus on the error line but I wanted to paste the whole code block so you can see my point.

The error line is this one :

ed.style.overflow = ed.getParam('fullscreen_html_overflow');

Of course it will throw an error, we try to access a property of an element we just removed from the DOM (two lines before : tinyMCE.remove(ed);). Why would someone want to do that ?

Well in fact, this is not ed (that we should try to access, but de (short for DOM.doc.documentElement). In fact the code was correct in 3.3.2, but someone changed it around 3.3.3.

My guess is that someone had the file opened, saw this bit of code, spotted the de, and seeing a lot of references to ed all around, thought it was a typo and 'fixed' it.

Reading the tinyMCE code (whenever you want to understand how it works, or want to study plugins before creating your own) is pretty hard. There are almost no comments, and variable names are only one or two letters long.

The tinyMCE team released a survey asking user what should be improved on the tinyMCE product. I answered it, and my main point was to improve documentation, because reading the tinyMCE core to understand its inner goings is quite a chore.

@font-face with multiple fonts and CSSTidy

The .woff font extension is the standard-to-go in terms of font embedding on the web.

You should add them first in the order of fonts you're loading, before the .ttf/.otf ones.

@font-face {
  font-family: "Unibody8SmallCaps Regular";
  src: url('fonts/unibody_8-smallcaps-webfont.woff') format('woff'), url('fonts/unibody_8-smallcaps-webfont.ttf') format('truetype');
}

The interesting thing to note is that you cannot omit the quotes around the format('woff')/format('truetype') part. Otherwise the font won't be recognized (at least by FF3.6).

CSSTidy seems to have a bug when there are multiple format()declarations in a rule, it removes quotes in each of them except the last one, thus making the whole rule unparsable by the browser.

So I started, again, to dig into the CSSTidy code and see what I could do about that.

I updated the csstidy.php file at around line 847 and changed the if statement to look like this :

if($this->sub_value != '') {
  $this->sub_value_arr[] = $this->sub_value;
  foreach($this->sub_value_arr as &$sub_value) {
    if (substr($sub_value, 0, 6) == 'format') {
      $sub_value = str_replace(array('format(', ')'), array('format("', '")'), $sub_value);
    }
  }
  $this->sub_value = '';
}

This way all sub values of the src: rule will be correctly parsed, and not only the last one.