View Source LiveMonacoEditor (LiveMonacoEditor v0.2.0)

LiveMonacoEditor logo

Monaco Editor component for Phoenix LiveView.

Hex Version Hex Docs MIT

Features

Installation

Add :live_monaco_editor dependency:

def deps do
  [
    {:live_monaco_editor, "~> 0.1"}
  ]
end

Once installed, change your assets/js/app.js file to load the code editor hook in the live socket:

import { CodeEditorHook } from "../../deps/live_monaco_editor/priv/static/live_monaco_editor.esm"

let Hooks = {}
Hooks.CodeEditorHook = CodeEditorHook

let liveSocket = new LiveSocket("/live", Socket, { hooks: Hooks, params: { _csrf_token: csrfToken } })

And change your assets/css/app.css file to load styling:

@import "../../deps/live_monaco_editor/priv/static/live_monaco_editor.min.css";

Usage

A new editor using the default options can be created as:

<LiveMonacoEditor.code_editor value="# My Code Editor" />

Or you can customize it as:

<LiveMonacoEditor.code_editor
  style="min-height: 250px; width: 100%;"
  value={~S{
  defmodule Math do
    def sum_list([head | tail], accumulator) do
      sum_list(tail, head + accumulator)
    end

    def sum_list([], accumulator) do
      accumulator
    end
  end

  IO.puts Math.sum_list([1, 2, 3], 0)
  }}
  opts={
    Map.merge(
      LiveMonacoEditor.default_opts(),
      %{"language" => "elixir"}
    )
  }
/>

Elixir

Interface

Set editor options

All monaco editor options are supported by passing a map to opts, for example to change the initial language and some other visual options:

<LiveMonacoEditor.code_editor
  value="<h1>My Code Editor</h1>"
  opts={
    %{
      "language" => "html",
      "fontSize" => 10,
      "minimap" => %{
        "autohide" => true,
        "showSlider" => "always"
      }
    }
  }
/>

Merge with default options

The code editor is created with default options to provide a better UX out-of-the-box, which may not suit your needs, but you can keep the defaults and overwrite some options as you wish:

<LiveMonacoEditor.code_editor
  opts={
    Map.merge(
      LiveMonacoEditor.default_opts(),
      %{"wordWrap" => "on"}
    )
  }
/>

Fetching the editor value

You can listen to events emitted by the code editor to fetch its current value and send it back to the parent LiveView where the component is used. Firstly, add a event listener:

window.addEventListener("lme:editor_mounted", (ev) => {
  const hook = ev.detail.hook

  // https://microsoft.github.io/monaco-editor/docs.html#interfaces/editor.IStandaloneCodeEditor.html
  const editor = ev.detail.editor.standalone_code_editor

  // push an event to the parent liveview containing the editor current value when the editor loses focus
  editor.onDidBlurEditorWidget(() => {
    hook.pushEvent("code-editor-lost-focus", { value: editor.getValue() })
  })
})

Then you can handle that event on the LiveView to save the editor content or perform any kind of operation you need:

def handle_event("code-editor-lost-focus", %{"value" => value}, socket) do
  {:noreply, assign(socket, :source, value)}
end

Inside forms

Do not rely on phx-change to fetch the editor content because it has known limitations due to how Monaco Editor works:

Instead use the :change option in the component:

<form>
  <LiveMonacoEditor.code_editor
    path="my_file.html"
    value="<h1>Title</h1>"
    change="set_editor_value"
  />
</form>

Which will trigger an event set_editor_value in the current LiveView process:

def handle_event("set_editor_value", %{"value" => value}, socket) do
  # do something with `value` - it contains the whole editor content
  {:noreply, socket}
end

You'll need to ignore phx-change events for the editor field:

def handle_event("validate", %{"_target" => ["live_monaco_editor", "my_file.html"]}, socket) do
  # ignore change events from the editor field
  {:noreply, socket}
end

Multiple editors

Set an unique id and path for each one:

<LiveMonacoEditor.code_editor id="html" path="my_file.html" />
<LiveMonacoEditor.code_editor id="css" path="my_file.css" />

Change language and value

<button phx-click="create-file">my_file.html</button>
def handle_event("create-file", _params, socket) do
  {:noreply,
   socket
   |> LiveMonacoEditor.change_language("html")
   |> LiveMonacoEditor.set_value("<h1>New File</h1>")}
end

More operations will be supported in new releases.

Styling

The component does not depend on any CSS framework but its parent container has to be large enough to be visible. The default style can be changed and/or classes can be applied:

<LiveMonacoEditor.code_editor
  style="height: 100%; width: 100%; min-height: 1000px; min-width: 600px;"
  class="my-2"
/>

Status

Early-stage, you can expect incomplete features and breaking changes.

Contributing

You can use the file dev.exs which is a self-contained Phoenix application running LiveMonacoEditor. Execute:

mix setup
iex -S mix dev

Visit http://localhost:4002

Looking for help with your Elixir project?

DockYard logo

At DockYard we are ready to help you build your next Elixir project. We have a unique expertise in Elixir and Phoenix development that is unmatched and we love to write about Elixir.

Have a project in mind? Get in touch!

Acknowledgements

Summary

Functions

Renders a monaco editor model.

The default Monaco Editor opts passed to <.code_editor>

Change the editor's value (content).

Functions

change_language(socket, mime_type_or_language_id, opts \\ [])

Change the editor's language.

Examples

LiveMonacoEditor.change_language(socket, "markdown", to: "my_file.md")

Options

  • :to - the editor's path name that will get the language changed. Defaults to "file".

See https://microsoft.github.io/monaco-editor/docs.html#functions/editor.setModelLanguage.html for more info.

code_editor(assigns)

Renders a monaco editor model.

Examples

Render a simple editor using default options:

<LiveMonacoEditor.code_editor value="# My Code Editor" />

Or merge with custom options:

<LiveMonacoEditor.code_editor
  opts={
    Map.merge(
      LiveMonacoEditor.default_opts(),
      %{"wordWrap" => "on"}
    )
  }
/>

Attributes

  • path (:string) - file identifier, pass unique names to render multiple editors. Defaults to "file".

  • value (:string) - initial content. Defaults to "".

  • change (:string) - event name to capture editor content changes, see https://github.com/BeaconCMS/live_monaco_editor#inside-forms for more info. Defaults to "".

  • target (:string) - target for editor events in order to use with LiveComponent, see https://github.com/BeaconCMS/live_monaco_editor#inside-forms for more info. Defaults to "".

  • opts (:map) - options for the monaco editor instance

    Example

    %{
      "language" => "markdown",
      "fontSize" => 12,
      "wordWrap" => "on"
    }

    See all available options at https://microsoft.github.io/monaco-editor/docs.html#interfaces/editor.IStandaloneEditorConstructionOptions.html

    Defaults to %{"fontFamily" => "JetBrains Mono, monospace", "fontSize" => 14, "formatOnPaste" => true, "formatOnType" => true, "guides" => %{"indentation" => false}, "language" => "markdown", "minimap" => %{"enabled" => false}, "occurrencesHighlight" => false, "parameterHints" => true, "renderLineHighlight" => "none", "scrollBeyondLastLine" => false, "scrollbar" => %{"alwaysConsumeMouseWheel" => false}, "suggestSelection" => "first", "tabCompletion" => "on", "tabIndex" => -1, "tabSize" => 2, "theme" => "default"}.

  • style (:string) - Defaults to "min-height: 100px; width: 100%;".

  • Global attributes are accepted. the arbitrary HTML attributes to add to the editor container element.

default_opts()

The default Monaco Editor opts passed to <.code_editor>

set_value(socket, value, opts \\ [])

Change the editor's value (content).

Examples

LiveMonacoEditor.set_value(socket, "Enum.all?([1, 2, 3])", to: "my_script.exs")

Options

  • :to - the editor's path name that will get the value updated. Defaults to "file".

See https://microsoft.github.io/monaco-editor/docs.html#interfaces/editor.IStandaloneCodeEditor.html#setValue for more info.