Published
8/7/2017
Categories
Software

Ace Code Editor - Setting Up for Inline JS Code Editing

Ace

I recently performed some maintenance on a site where users could enter HTML+JS code via a CMS. Examples of such code would be leads/conversion tracking, page views, retargeting, livechat, etc. In the case of this site, the CMS inputs provided for entering the code were TEXT inputs! As one might think, this design decision proved to be terrible UI/UX.

Issues that resulted from this design:

  • No formatting is supported. You cannot have line breaks, or for that matter tabs to indent lines of code for readability.

  • Pasting code into the text input would strip all line breaks from the code. This can break the code in certain instances, such as when a comment is present. Any code appearing after the comment token is ignored!

  • Most end-users do not know much about HTML or JS. They have no idea if the code they’re pasting is valid. Sometimes as code passes through various mediums like email, issue tracking systems (e.g. Jira), or copying directly from a webpage, certain characters can be substituted. Sometimes placeholders or other notational text is present as well. The users have no indication this is bad code and paste it as-is. The error I saw most often was single quotes replaced by the HTML entity '

The situation was bad. My first thought was to change the input to a textarea. This is a step in the right direction because you at least get line breaks back. But what about if you want to indent/tab some lines to keep the code pretty? It can’t be done with the default textarea control because tab takes you to the next form control.

I decided the best thing to do would be to convert these fields into embedded code editors. With an embedded code editor, you get all the benefits of an IDE; tabbing, bracket pairing/highlighting, syntax flags, etc. This would be the solution that would most beneficially impact the client and their users.

I started by googling around and after a few minutes of investigation found that the defacto embedded code editor seemed to be Ace.

The documentation on the site is pretty good, but it only showed the most basic setup which was a JS editor. With tracking code, it is almost always a combo of HTML + JS, because they usually have a pixel they fall back on when JS is disabled. e.g.:

<noscript><img src="//remote-site.com/1x1.gif" /></noscript>

Therefore, I could not just use the JS mode alone, I had to use JS + HTML. One other requirement was having multiple editors active on the same page, one for each field (think header, footer, conversion code, etc.). After A LOT of research, I was able to compile together a solution.

First, I created the form fields on the main page, setting an attribute to flag them for conversion as embedded editors:

<textarea id="frm-header-script" name="frm[header_script]" data-editor="1"></textarea><textarea id="frm-footer-script" name="frm[footer_script]" data-editor="1"></textarea>

Then I created a separate ace-engine.js file that would contain the code for configuring, initializing, and running the editors. Read the comments in the code for an explanation of what’s going on.

[ace-engine.js]

(function(){ // the 'mode' determines the base rules/syntax checks run against the code. // the 'rule_sets' below allow specifying additional rules to run beyond the base mode. // this is how you can set the editor to check HTML and JS var rule_sets = { 'tagname-lowercase': true,        'attr-lowercase': true        'attr-value-double-quotes': true,        'tag-pair': true, 'spec-char-escape': true, 'id-unique': true, 'src-not-empty': true, 'attr-no-duplication': true, 'jshint': {} // jshint is used to check javascript }; // this loop finds and converts every textarea on the page with an attribute of data-editor="1" $('textarea[data-editor]').each(function() { var textarea = $(this); // allows applying css to style it like the rest of the form ctls textarea.wrap('<div class="code-edit-wrap"></div>'); var change_timer; var edit_div = $('<div>', { position: 'absolute', width: '100%', 'class': textarea.attr('class') }).insertBefore(textarea); textarea.css('display', 'none'); var editor = ace.edit(edit_div[0]); editor.setOptions({maxLines: 30}); editor.setShowPrintMargin(false); editor.setTheme("ace/theme/chrome"); // set the editor theme editor.getSession().setMode("ace/mode/html"); // set the base syntax mode editor.$blockScrolling = Infinity; editor.getSession().setValue(textarea.val()); // loads editor with initial textarea value       // every time the editor content changes, run the rule_sets on the code editor.on('change', function (e) { clearTimeout(change_timer); change_timer = setTimeout(function(){ updateAnnotations(editor); }, 500); });      // sets the textarea value with the editor content when the form is submitted textarea.closest('form').submit(function() {     textarea.val(editor.getSession().getValue()); }); updateAnnotations(editor); });    // 'annotations' are the flags that appear in the left vertical gutter. // hovering the mouse over a flag provides the full annotation text function updateAnnotations(editor){ var code = editor.getValue(); var messages = HTMLHint.verify(code, rule_sets); var errors = [], message; for(var i=0, l=messages.length;i<l;i++){ message = messages[i]; errors.push({ row: message.line-1, column: message.col-1, text: message.message, type: message.type, raw: message.raw }); } editor.getSession().setAnnotations(errors); } })();

Once you have that, include all ace library files, along with the ‘engine.js’ above that ties them all together:

[my_page.html]

<script type="text/javascript" src="/js/acehint/htmlhint.js"></script><script type="text/javascript" src="/js/acehint/ace/ace.js" charset="utf-8"></script><script type="text/javascript" src="/js/acehint/csslint.js"></script><script type="text/javascript" src="/js/acehint/jshint.js"></script><script type="text/javascript" src="/js/acehint/engine.js"></script><!-- load last -->

Note that I had to download jshint.js separately. I also had to use an earlier version (v0.9.1), to resolve a jsnode event emitter error.

Ace it very cool and virtually plug ‘n play. But advanced configurations can be a bit cumbersome. Hopefully this article provided a quick primer in setting up an advanced configuration!