Chrome Extension Tutorial – Placeholders

This is the second post in the series – Building an automatic text expander. The first post in the series is about Snippets and the third is about Date-Time Macros, Mathomania, and Auto-Inserts.

So, now, armed with the knowledge of Snippets, let’s also get to know its companion – Placeholders. To refresh your knowledge, Placeholders are pieces of text that are automatically highlighted when a snippet is used. We use [Tab] to move to next Placeholder within a snippet body.

Setting up the basics

We have already set up the formatSnippets  function to format snippets. Inside it, before the break statement, we will detect the presence of Placeholders in the snippet body. A placeholder will only be composed of alphabets, digits, and underscore, and will be enclosed within % signs. Now that we know their basic format, we can write a simple regular expression to detect them:

The function formatPlaceholders will take the node as the first argument, and the lengths where snippet body starts and ends as the second and third argument respectively. Its declaration is:

Notice the fourth argument? That’s present to prevent recursion – else we would be stuck highlighting the same placeholder again and again in case the user doesn’t type anything after highlighting the placeholder (this is an edge case, as you can see, for which we have to prepare ourselves!)

We will also have to call formatPlaceholders  when users presses the [Tab] key. The global boolean PlaceholderMode  will assist us in this work. Note that the [Tab] key has two functions: placeholder jumping and inserting four spaces, the latter being done in case PlaceholderMode  is false. Inside the handleKeyDown , we will set up an if -block for [Tab] key detection, and call formatPlaceholders there.

Learning point: event.stopPropagation() prevents the event from propagating to parent elements of the node which had the event. Do you remember when we were doing document.onkeydown , which received onkeydown s for its child elements? Using event.stopPropagation()  would prevent that from happening. More elaboration on this is available here.

What if one placeholder is already highlighted?

Our formatPlaceholder  function will also be called in case a placeholder is already highlighted, when the [Tab] key gets pressed. We will have to check for that and move on to the next placeholder.

Notice how I passed false  as an argument for formatPlaceholders’s notCheckSelection  parameter in one line. It helps prevent infinite recursion. The flow happens in this way:

  1. One placeholder is already highlighted. User presses [Tab] key, desiring to move to next placeholder, without modifying the current placeholder.
  2. formatPlaceholders  function is called, with true as the fourth argument. The condition regex.test(highlightedText) && notCheckSelection  thus becomes true and formatPlaceholders(node, node.selectionEnd, to, false)  is thus executed.
  3. The false  argument in the second call above makes the if  condition false this time; hence, the function moves forward looking for another placeholder.

Learning point: node.selectionEnd is the offset which tells us the position of the end of the highlighting of the text. So, say for example, in this text “abcdefghijklmnop”, if “bcde” is highlighted, then node.selectionEnd will be 5. It also has a counterpart node.selectionStart , which would be 1 in the above case.

Conclusion

That’s it! We are done with the placeholders. I know I have only covered simple <textarea>s and not the contenteditable <div>s. But, trust me, I can spend 10 pages writing about them and still it would never finish. There are tons of features and hacks needed to make this thing work in such rich-text editors. Believe me when I say that I spent one whole day trying to insert four spaces on [Tab] key press in GMail!

See all your hardwork live in action:

Hope you enjoyed reading! Feel free to discuss anything in the comments below.


Next Post

Leave a Reply

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