var snippets = [{ name: "name", body: "My name is %name%", timestamp: "March 03, 2015" }, { name: "reply", body: "Dear %customer%, bug number %bug_number% has been fixed.", timestamp: "March 03, 2015" }], PlaceholderMode = false, beginLength = 0, endLength = 0; /*assumes node can only be textarea element*/ function formatPlaceholders(node, from, to, notCheckSelection) { var value = node.value, highlightedText = value.substring(node.selectionStart, node.selectionEnd); // matches a placeholder var regex = /%[A-Za-z0-9_]+%/; // if the highlighted text is a placeholder; move to next placeholder if (regex.test(highlightedText) && notCheckSelection) { formatPlaceholders(node, node.selectionEnd, to, false); return; } // index of placeholder var index = value.substring(from).search(regex); // make sure a placeholder is found if (index > -1) { // start highlighting this placeholder node.selectionStart = index + from; // first get whole placeholder length for (var i = index + from + 1; value[i] !== "%"; i++); // increase i by 2 (because condition of loop ignores both `%`) i += 2; // subtract one because loop has already started // at first `%` i -= 1; // now `i` is at end of placeholder node.selectionEnd = i; return true; // placeholder is found } else { return false; // no placeholder found } } function formatSnippets(node) { /*assumes node is a textarea element*/ var offset = node.selectionStart, // the caret position in the node snip, // holds current snippet length, // holds current snippet name’s length valueND = node.value; // holds value of node // loop through Snippets arrray for (var i = 0, len = snippets.length; i < len; i++) { snip = snippets[i]; length = snip.name.length; // the key part // gets the text immediately behind the user’s cursor (caret) // checks if it corresponds with the current snippet name var textBehind = valueND.substring(offset - length, offset); if (textBehind === snip.name) { // performs snippet replacement var snipBody = snip.body, // take value before snip name beginValue = valueND.substring(0, offset - length), // take value after snip name end endValue = valueND.substring(offset); // these are globals // length where snip body starts beginLength = beginValue.length; // length where snip body ends endLength = (beginValue + snipBody).length; //snipBody = formatMacros(snipBody); // covered in 3rd blog post // concatenate everything together! node.value = beginValue + snipBody + endValue; // set caret position (at end of snippet body) node.selectionEnd = node.selectionStart = endLength; if (/%[a-zA-Z0-9_]+%/.test(snip.body)) { // this is a global boolean used later // it has been declared in the top level context PlaceholderMode = true; formatPlaceholders(node, beginLength, endLength, true); } // snippet replacement done; move out of loop break; } } } // formatSnippets function ends function handleKeyDown(event) { var keyCode = event.keyCode; // [Tab] key press if (keyCode === 9 && !event.shiftKey && !event.ctrlKey) { if (PlaceholderMode) { event.preventDefault(); // prevents the default event event.stopPropagation(); // prevents its propagation to parent elements formatPlaceholders(this, beginLength, endLength, true); } // insert four spaces if the user wishes /* else if(Data.tabKey){ // this is beyond the scope of this post }*/ } // [Shift] + [Space] = formatting snippets else if (event.shiftKey && keyCode === 32 && !event.ctrlKey) { event.preventDefault(); // do not insert space (default event) formatSnippets(this); // format snippets in `this` (node) } } document.onkeydown = function (event) { var node = event.target; // run only if the node is a <textarea> if (node.tagName === "TEXTAREA") handleKeyDown.call(node, event); };
Try typing "name or "reply", pressing Shift+Space, and you'll see the magical placeholders. Use [Tab] key to switch between multiple placeholders. <br> <br> <textarea></textarea>