Now.js Framework Documentation
CodeEditor
CodeEditor
Overview
CodeEditor turns a <textarea> into a code editor with syntax colours, line numbers and the editing keys programmers
expect. It is part of the core bundle, so every project has it without loading anything else.
The textarea stays the editing surface: typing, the caret, selection, undo, input methods (Thai included) and form
submission are all the browser's own. A <pre> drawn behind it shows the same text in colour, and nothing is ever read
back from that layer, so the textarea's value is always exactly the code.
Use it when:
- A form edits code or structured text: CSS, HTML templates, JSON settings, SQL, PHP, JavaScript
- A page shows code that the reader may also edit or copy
Two ways to use it:
| When | |
|---|---|
<textarea data-element="code"> (CodeElementFactory) |
In forms: validation, required, FormError and data-attr work as for any field |
new CodeEditor(textarea, options) |
From script, outside forms (e.g. the playground) |
<form data-form="theme">
<textarea name="css" data-element="code" data-language="css" data-max-height="60vh" required></textarea>
</form>const editor = new CodeEditor(document.querySelector('#source'), {language: 'javascript', height: 400});
editor.getValue();Options
Pass options to the constructor, or as data-* attributes on the textarea (data-line-numbers, data-tab-size, ...).
Constructor options win over attributes.
| Option | Attribute | Default | Description |
|---|---|---|---|
language |
data-language |
'text' |
See Languages |
lineNumbers |
data-line-numbers |
true |
Show the line-number gutter |
tabSize |
data-tab-size |
2 |
Columns per indent level (1–8) |
indentWithTabs |
data-indent-with-tabs |
false |
Indent with a tab character |
autoCloseBrackets |
data-auto-close-brackets |
true |
Type ) ] } and quotes together with the opener |
autoIndent |
data-auto-indent |
true |
Keep the indent on Enter, one level deeper after an opener |
minLines |
data-min-lines |
3 (5 in forms) |
Minimum visible lines |
height |
data-height |
null |
Fixed height (px or any CSS length); scrolls inside |
maxHeight |
data-max-height |
null |
Grow with the content up to this height |
theme |
data-theme |
null |
'light' or 'dark'; by default it follows the page's data-theme |
onChange |
— | null |
function(value, editor) after every change |
Without height or maxHeight the editor grows with its content and the page scrolls.
Keys
| Key | Effect |
|---|---|
| Tab / Shift+Tab | Indent / outdent; with several lines selected, every line |
| Enter | New line with the same indent; after { [ ( (or an HTML opening tag) one level deeper, and the closer moves to its own line |
| `( [ { " ' `` | Typed with the closer; with a selection, wraps it |
| A closer next to the caret | Steps over it instead of typing a second one |
| Backspace | Inside an empty pair removes both; in leading spaces removes one indent level |
| Ctrl+/ (⌘/) | Comment or uncomment the selected lines |
| Ctrl+Z / Ctrl+Shift+Z | The browser's undo / redo, including the editor's own edits |
| Esc, then Tab / Shift+Tab | Leave the editor for the next / previous field with the keyboard (Tab alone indents) |
| Click a line number | Select that line |
Esc lets only the very next Tab leave. If the user presses another key, types or pastes (input-method text
included), clicks in the code or a line number, leaves the editor and comes back, or a script moves the caret in
between, Tab indents again. A modifier pressed on its own (the Shift of Shift+Tab) does not count.
Ctrl+Enter is left alone, so a page can bind it (the playground runs the code with it).
The editor does not stop Esc from reaching document, so a window that closes on Esc (Modal, a dialog) still
closes. To keep a window with unsaved code open, the page stops Esc itself, below document.
API
new CodeEditor(textarea, options?)
Attaches an editor. textarea is an element or a selector. A textarea that already has an editor returns that editor.
The textarea is moved inside the editor's element, editor.root; a textarea not yet in the page needs
editor.root inserted by the caller.
Instance methods
| Method | Description |
|---|---|
getValue() |
The code (the textarea's value) |
setValue(value, options?) |
Replace the code. By default like assigning textarea.value: not undoable, no input event. {history: true} makes it an undoable edit (focuses the editor, fires input) |
getLanguage() / setLanguage(name) |
Read or change the language and recolour |
insertText(text) |
Replace the selection as if typed (undoable) |
indent(outdent?) |
Indent or outdent the selected lines |
toggleComment() |
Comment or uncomment the selected lines |
focus() |
Focus the editor |
refresh() |
Re-measure and redraw (after changing fonts or sizes) |
destroy() |
Put the textarea back as it was |
Assigning textarea.value from any script recolours the code too, and form.reset() is followed.
Properties
editor.textarea, editor.root (the .code-editor element), editor.language, editor.options.
Static
| Member | Description |
|---|---|
CodeEditor.get(textarea) |
The editor attached to a textarea, or null |
CodeEditor.highlight(code, language) |
Escaped HTML with <span class="ce-{type}"> tokens |
CodeEditor.tokenize(code, language) |
[{type, text}]; joining text gives the code back exactly |
CodeEditor.registerLanguage(name, definition, aliases?) |
Add a language |
CodeEditor.resolveLanguage(name) |
Registered name for a name or alias ('text' when unknown) |
CodeEditor.defaults |
Default options |
Events
The textarea's own input event fires on every change made by typing or by the editor's keys (not on
setValue() without history).
Languages
| Name | Aliases | Comment toggle |
|---|---|---|
javascript |
js, mjs, cjs, jsx, ts, typescript |
// |
json |
— | — |
html |
htm, xml, svg, xhtml, vue |
<!-- --> (<script> and <style> bodies are coloured as JavaScript and CSS, <?php ?> as PHP) |
css |
scss, less |
/* */ |
php |
— | // |
sql |
mysql |
-- |
text |
plain, txt, plaintext |
— (no colours, no auto-closing) |
Adding a language
CodeEditor.registerLanguage('ini', {
lineComment: ';',
rules: [
{type: null, match: /\s+/y, skip: true},
{type: 'comment', match: /[;#][^\n]*/y, skip: true},
{type: 'selector', match: /\[[^\]\n]*\]/y},
{type: 'property', match: /[\w.-]+(?=\s*=)/y},
{type: 'string', match: /"[^"\n]*"?/y}
]
});Rules are tried in order at every position and the first match wins; every RegExp must be sticky (y). Text no rule
matches stays uncoloured. A rule may also have when(previousToken, state), onMatch(text, state), a type function,
or scan(text, pos, state) returning {tokens, end} for constructs a RegExp cannot express; a definition may give
state() for per-scan state (the CSS language uses it to tell selectors from declarations).
Token types used by the built-in languages, each with a --ce-{type} colour variable: keyword, string, comment,
number, constant, function, property, builtin, operator, punctuation, regex, variable, tag,
attr-name, attr-value, selector, entity, meta.
Styling
Styles are in Now/css/code-editor.css. Override the variables on .code-editor:
.code-editor {
--code-editor-font-size: 14px;
--code-editor-line-height: 22px; /* px: both layers must line up */
--code-editor-bg: #fff;
--ce-keyword: #8b5cf6;
}The textarea and the coloured layer must keep the same font, size, line height, padding and tab size, or the caret drifts
away from the letters: change them only through the --code-editor-* variables. On touch screens the font grows to
16px so phones do not zoom into the field.
Performance
- Files up to 300 lines are drawn whole; longer files draw only the lines in view (plus a margin), so typing stays
fast in files of thousands of lines (about 25 ms per key at 1,500 dense lines). - After an edit only the changed region is tokenized again.
- Beyond 300,000 characters the code is shown without colours.
Common Pitfalls
⚠️ 1. Use data-element="code" inside forms
A plain new CodeEditor() takes the textarea out of ElementManager (data-enhance="false"), so field validation does
not run on it. Forms should use CodeElementFactory.
⚠️ 2. setValue() is not undoable by default
Loading content from script is not something the user did. For actions the user takes, such as loading an example or
clearing the editor, pass {history: true} so Ctrl+Z brings the previous code back.
⚠️ 3. Lines do not wrap
Long lines scroll sideways. Wrapping would make line numbers and caret positions depend on the width.
Related Documentation
- CodeElementFactory - The code editor as a form field
- ElementManager - Form element types
- TextareaElementFactory - Plain textarea fields