This document explains step-by-step how ckeditor5_phoenix was integrated into this Phoenix LiveView blog application.
Overview
ckeditor5_phoenix is an Elixir package that provides seamless integration between CKEditor 5 (a rich text editor) and Phoenix Framework. It handles the complex JavaScript initialization, LiveView synchronization, and provides Elixir components for easy use in templates.
Step 1: Add Dependency to mix.exs
File: mix.exs
defp deps do
[
# ... other dependencies
{:ckeditor5_phoenix, "~> 1.15.8"}
]
endExplanation:-
- This adds the `ckeditor5_phoenix` package to your project's dependencies
- The package version `~> 1.15.8` means it will use version 1.15.8 or any compatible newer version
- After adding this, you need to run `mix deps.get` to download the package
Why it's needed:-
- The package provides Elixir modules, JavaScript hooks, and configuration utilities needed for CKEditor 5 integration.
Step 2: Install CKEditor 5 Assets
Command:-
mix ckeditor5.installWhat it does:-
- Downloads CKEditor 5 JavaScript and CSS files to `deps/ckeditor5/`
- Installs the specific version (47.3.0) of CKEditor 5
- Makes CKEditor 5 assets available for bundling with your application
Why it's needed:-
- CKEditor 5 is a JavaScript library that needs to be available in the browser
- This command ensures the correct version is installed and accessible
Step 3: Register JavaScript Hooks
File:-assets/js/app.js
import { Hooks } from "ckeditor5_phoenix"
let liveSocket = new LiveSocket("/live", Socket, {
longPollFallbackMs: 2500,
params: {_csrf_token: csrfToken},
hooks: Hooks // CKEditor 5 hooks are registered here
})Explanation:-
- - Hooks from ckeditor5_phoenix contains JavaScript hooks that Phoenix LiveView needs
- - These hooks handle:
- - Initializing CKEditor 5 when the component mounts
- - Synchronizing content between the editor and LiveView
- - Handling editor lifecycle (mount, update, destroy)
- - Managing editor state during LiveView updates
Why it's needed:-
- Phoenix LiveView uses hooks to bridge JavaScript and Elixir
- The hooks automatically initialize CKEditor 5 when the component appears in the DOM
- They handle the two-way data binding between the editor and your LiveView
Step 4: Import CKEditor 5 Styles
File:-assets/css/app.css
#Added:
@import "../../deps/ckeditor5/dist/ckeditor5.css";
Explanation:-
- This imports CKEditor 5's default CSS styles
- The path `../../deps/ckeditor5/` points to where `mix ckeditor5.install` placed the files
- These styles provide the editor's visual appearance (toolbar, buttons, editor area)
Why it's needed:-
- Without these styles, the editor would be functional but unstyled
- The CSS includes styles for the toolbar, editor area, and all UI components
## Step 5: Import CKEditor5 Module in blogWeb
**File:**`lib/myblog_web.ex`
#Added to `live_view` macro:
def live_view do
quote do
use Phoenix.LiveView,
layout: {MyblogWeb.Layouts, :app}
use CKEditor5 # <-- Added this
unquote(html_helpers())
end
end
#Added to `html` macro:**
def html do
quote do
use Phoenix.Component
use CKEditor5 # <-- Added this
# ... rest of the macro
end
end
Explanation:-
- `use CKEditor5` is an Elixir macro that imports the `ckeditor/1` component function
- This makes the `<.ckeditor>` component available in all LiveView and HTML templates
- The macro creates a `defdelegate` that forwards calls to `CKEditor5.Components.Editor.render/1`
Why it's needed:-
- Without this, you couldn't use `<.ckeditor>` in your templates
- It provides the Elixir-side component that renders the HTML structure CKEditor 5 needs
**What `use CKEditor5` does internally:**
defmacro __using__(_opts) do
quote do
defdelegate ckeditor(assigns), to: CKEditor5.Components.Editor, as: :render
end
end
Step 6: Use CKEditor Component
File:-lib/myblog_web/live/article_live/new.ex
<.ckeditor
id="article_content"
name="article[content]"
type="classic"
value={@form[:content].value || ""}
editable_height="400px"
change_event={true}
/>
Explanation:-
- <.ckeditor> is a Phoenix component that renders the CKEditor 5 editor
- Attributes:-
- `id`: Unique identifier for the editor instance
- `name`: Form field name (used when submitting the form)
- `type="classic"`: Editor type (classic has toolbar above editor)
- `value`: Initial content to display in the editor
- `editable_height`: Height of the editable area
- `change_event={true}`: Enables automatic content sync with LiveView
Why it's needed:-
- The component handles all the complex HTML structure and data attributes
- It automatically creates the hidden input field for form submission
- It sets up the `phx-hook="CKEditor5"` attribute that triggers the JavaScript hooks
**What the component renders:**
<div
id="article_content"
phx-hook="CKEditor5"
phx-update="ignore"
cke-preset="..."
cke-editable-height="400px"
cke-initial-value="..."
cke-change-event="true"
>
<div id="article_content_editor"></div>
<input type="hidden" id="article_content_input" name="article[content]" />
</div>
Step 7: Add Event Handlers for Content Sync
File:-lib/myblog_web/live/article_live/new.ex
Added Events -
@impl true
def handle_event("ckeditor5:change", %{"data" => %{"main" => content}}, socket) do
changeset =
%Article{}
|> Blog.change_article(%{"content" => content})
|> Map.put(:action, :validate)
{:noreply, assign(socket, :form, to_form(changeset))}
end
Explanation:-
- When `change_event={true}` is set, CKEditor 5 automatically sends `ckeditor5:change` events
- The event payload contains `%{"data" => %{"main" => content}}` where `content` is the HTML
- This handler updates the form's changeset with the new content
- This enables real-time validation and preview of content as the user types
Why it's needed:-
- Keeps the LiveView state synchronized with the editor content
- Allows validation and other server-side processing as content changes
- The `"main"` key refers to the main editor instance (multi-root editors can have multiple)
Event Flow:-
1. User types in CKEditor 5
2. JavaScript hook detects change
3. Hook sends `ckeditor5:change` event to LiveView via WebSocket
4. LiveView receives event and calls `handle_event/3`
5. Changeset is updated and form is re-rendered
Step 8: Configure CKEditor 5 Presets
File:-config/config.exs
Added Config -
config :ckeditor5_phoenix,
presets: %{
default: %{
config: %{
toolbar: [/* toolbar items */],
plugins: [/* plugin names */],
codeBlock: %{
languages: [/* language options */]
}
}
}
}
Explanation:-
- Presets are pre-configured editor setups
- The default preset is used when no preset is specified
- Toolbar defines which buttons appear in the editor toolbar
- Plugins define which CKEditor 5 features are enabled
- codeBlock configures code snippet support with available languages
Why it's needed:-
- Allows customization of editor features without modifying JavaScript
- Centralizes configuration in Elixir config files
- Makes it easy to have different editor configurations for different use cases
How it works:-
1. Configuration is read at application startup
2. When `<.ckeditor>` renders, it includes the preset config as a JSON attribute
3. JavaScript hook reads the preset and initializes CKEditor 5 with those settings
Step 9: Build Assets
Command Used -
mix assets.buildWhat it does:-
- Bundles JavaScript files (including CKEditor 5 hooks)
- Compiles CSS files (including CKEditor 5 styles)
- Outputs to `priv/static/assets/` for serving to browsers
Why it's needed:-
- Phoenix serves static assets from `priv/static/`
- The build process combines all JavaScript and CSS into optimized bundles
- In development, this happens automatically, but it's needed for production
Complete Integration Flow
When a page with CKEditor loads:-
1.Server Side (Elixir)
- LiveView renders `<.ckeditor>` component
- Component generates HTML with `phx-hook="CKEditor5"` and config attributes
- HTML is sent to browser via WebSocket
2.Client Side (JavaScript):-
- Phoenix LiveView receives the HTML
- Detects `phx-hook="CKEditor5"` attribute
- Calls the `mounted()` function from the hook
- Hook reads configuration from data attributes
- Initializes CKEditor 5 instance on the target element
- Sets up event listeners for content changes
3.Content Synchronization:
- User types in editor
- CKEditor 5 fires change events
- Hook captures changes and updates hidden input
- If `change_event={true}`, sends `ckeditor5:change` to LiveView
- LiveView updates state and re-renders if needed
4.Form Submission:-
- User submits form
- Hidden input contains the HTML content
- Form data is sent to LiveView's `handle_event("save", ...)`
- Content is saved to database
## Key Files Modified
1.mix.exs- Added dependency
2.assets/js/app.js - Registered JavaScript hooks
3.assets/css/app.css - Imported CKEditor 5 styles
4.lib/blog_web.ex - Imported CKEditor5 module
5.lib/blog_web/live/article_live/new.ex - Used `<.ckeditor>` component
6.lib/blog_web/live/article_live/edit.ex - Used `<.ckeditor>` component
7.config/config.exs - Configured editor presets
Benefits of This Integration
1.**Seamless Integration:** Works naturally with Phoenix LiveView patterns
2.**Two-way Binding:** Automatic synchronization between editor and server
3.**Type Safety:** Elixir components provide compile-time checks
4.**Configuration:** Editor features configurable in Elixir config files
5.**Maintainability:** Clear separation between JavaScript and Elixir code
6.**Performance:** Only loads and initializes when needed
Additional Resources
- [CKEditor 5 Phoenix Documentation](https://hexdocs.pm/ckeditor5_phoenix)
- [CKEditor 5 Documentation](https://ckeditor.com/docs/ckeditor5/)
- [Phoenix LiveView Documentation](https://hexdocs.pm/phoenix_live_view)