Morzaram
Hey guys I’ve made a guide on how to connect Quill to Phoenix.
For the sake of formatting it might be easier to view it on my Notion Doc Here
How to connect Quill with Phoenix
- Make a hook file ex
./assets/js/hooks/textEditHook.js
import Quill from 'quill';
export let TextEditor = {
mounted() {
console.log('Mounting');
let quill = new Quill(this.el, {
theme: 'snow'
});
quill.on('text-change', (delta, oldDelta, source) => {
if (source == 'api') {
console.log("An API call triggered this change.");
} else if (source == 'user') {
console.log(this);
this.pushEventTo(this.el.phxHookId, "text-editor", {"text_content": quill.getContents()})
}
});
},
updated(){
console.log('U');
}
}
- Import it to your
./assets/js/app.js
import { TextEditor } from './hooks/textEditHook' //Put this at the top
- Add hooks and assign it to Hooks.TextEditor (call this whatever but you must assign to Hooks
let Hooks = {}
// WYSIWYG
Hooks.TextEditor = TextEditor
- Assign the hooks to your LiveSocket
let liveSocket = new LiveSocket("/live", Socket, {hooks: Hooks, params: {_csrf_token: csrfToken}})
Note!! This must be at the top level of the map; Not within params #IDidThisByAccident
- Assign the phx-hook to where you want the element replaced
<div id="editor" phx-hook="TextEditor" phx-target={@myself} />
Note: I’ve set the phx-target to itself since I’m placing the handle-event function in there
- Import the CSS whatever way you want. For my lazy butt I just did an inline CSS import at the top
<link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
- Load that page and give it a shot
My code
My HEEX
<div>
<link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
<h2><%= @title %></h2>
<.form
let={f}
for={@changeset}
id="profile-form"
phx-target={@myself}
phx-change="validate"
phx-submit="save">
<%= label f, :name %>
<%= text_input f, :name %>
<%= error_tag f, :name %>
<%= label f, :description %>
<div id="editor" phx-hook="TextEditor" phx-target={@myself} />
<%= error_tag f, :description %>
<%= label f, :birthdate %>
<%= date_select f, :birthdate %>
<%= error_tag f, :birthdate %>
<%= inputs_for f, :links, fn fl -> %>
<div class="flex flex-row space-x-6">
<div id="link-title">
<p>Title</p>
<%= text_input fl, :title %>
</div>
<div id="link-url">
<p>url</p>
<%= text_input fl, :url %>
</div>
</div>
<% end %>
<a phx-click="add-link" phx-target={@myself} >Add Link</a>
<%= inputs_for f, :pics, [multipart: true], fn fl -> %>
<div class="flex flex-row space-x-6">
<div id="link-title">
<p>Caption</p>
<%= text_input fl, :alt %>
</div>
<div id="link-url">
<p>url</p>
<%= text_input fl, :url %>
</div>
</div>
<% end %>
<a phx-click="add-pic" phx-target={@myself} >Add Pic</a>
<div>
<%= submit "Save", phx_disable_with: "Saving..." %>
</div>
</.form>
</div>
My App.js
// Svelte
import 'svonix'
// We import the CSS which is extracted to its own file by esbuild.
// Remove this line if you add a your own CSS build pipeline (e.g postcss).
import "../css/app.css"
import { TextEditor } from './hooks/textEditHook'
let Hooks = {}
// WYSIWYG
Hooks.TextEditor = TextEditor
// If you want to use Phoenix channels, run `mix help phx.gen.channel`
// to get started and then uncomment the line below.
// import "./user_socket.js"
// You can include dependencies in two ways.
//
// The simplest option is to put them in assets/vendor and
// import them using relative paths:
//
// import "../vendor/some-package.js"
//
// Alternatively, you can `npm install some-package --prefix assets` and import
// them using a path starting with the package name:
//
// import "some-package"
//
// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
import "phoenix_html"
// Establish Phoenix Socket and LiveView configuration.
import {Socket} from "phoenix"
import {LiveSocket} from "phoenix_live_view"
import topbar from "../vendor/topbar"
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {hooks: Hooks, params: {_csrf_token: csrfToken}})
// Show progress bar on live navigation and form submits
topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"})
window.addEventListener("phx:page-loading-start", info => topbar.show())
window.addEventListener("phx:page-loading-stop", info => topbar.hide())
// connect if there are any LiveViews on the page
liveSocket.connect()
// expose liveSocket on window for web console debug logs and latency simulation:
// >> liveSocket.enableDebug()
// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
// >> liveSocket.disableLatencySim()
window.liveSocket = liveSocket
My Hook (with comments)
import Quill from 'quill';
export let TextEditor = {
mounted() {
console.log('Mounting');
let quill = new Quill(this.el, {
theme: 'snow'
});
// Read more on what .on('events') you can put here
// https://quilljs.com/docs/api/#events
quill.on('text-change', (delta, oldDelta, source) => {
if (source == 'api') {
console.log("An API call triggered this change.");
} else if (source == 'user') {
console.log(this);
//This sends the event of
// def handle_event("text-editor", %{"text_content" => content}, socket) do
this.pushEventTo(this.el.phxHookId, "text-editor", {"text_content": quill.getContents()})
}
});
},
updated(){
console.log('U');
}
}
My handle-event
def handle_event("text-editor", %{"text_content" => content}, socket) do
IO.inspect(content)
{:noreply, socket}
end
Things to know
How to push event to desired live view controller…
Set the phx-target in phoenix
<div id="editor" phx-hook="TextEditor" phx-target={@myself} >
<%= text_input f, :description%>
</div>
In your js file reference it like this…
this.el.phxHookId
You will see above in the code snippet how this works together
Note: I’m using arrow functions so I don’t have to rebind this #ShitJSDoes
Trending in Guides/Tuts
# ~/src/livebook/.iex.exs
System.cmd("xdg-open", [ LivebookWeb.Endpoint.access_url() ] )
Because Livebook requires a unique passcode on ...
New
Other Trending Topics
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
I am happy to introduce the very α version of the new programming language compiled to BEAM.
Welcome Cure.
It has literally three kille...
New
Hi there! We created Gust: A task orchestrator inspired by Airflow.
For those who have never heard about Aiflow, it’s a Python-based wor...
New
Hi everyone!
The first release candidate for the Expert language server project is now available!
We’ve published a press release detai...
New
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixirconf-us
- #ai
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #hex
- #security










Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
Morzaram
Also, I’m saving the information as JSON in the db since I’m using posgres so I believe
the field should look like this
field :description, :mapcvkmohan
Good guide @Morzaram . Thanks. I think, more or less similar approach should work for Prosemirror as well.
MMAcode
Hi Morzaram, I am trying to setup Quilljs for rich editing with Elixir and Phoenix. I see you did it about year ago (thank you for sharing your notes and experience!). Do you have that repo available so that I could use it for inspiration? I struggle a bit as I am relatively new to Phoenix. Eg now I trying to work out how to set default quill form values based on what quill json is stored in db (ecto-postgres).
Any answer/tips will be much apreciated
Morzaram
I switched to tip-tap and then abandoned it but you can find it in this commit here:
https://github.com/Morzaram/cc_js_hooks_archvied/commit/a910ed4c4f3330ba03abe38d49cee0785721b60e
I’ve updated the notion to include the link at the bottom of the page. Please comment in only once place instead of sending duplicate messages…
IMHO, if you’re doing some heavy js stuff really consider if you need live view for that page. It’s hard to get right and the article might not be the best way to do it now adays.
mayel
We also have an integration at GitHub - bonfire-networks/bonfire_editor_quill · GitHub
MMAcode
Anybody interested how I got it working can checkout my commit here. In contains all changes I did (may be excluding some mix commands) to implement quill and get it saving and loading data from postgres: rich text implemented and working (in this single commit) · MMAcode/our_experience@b8568aa · GitHub
CharlesIrvine
@Morzaram I am curious about your experience with Quill.
CharlesIrvine
Hello @MMAcode. I tried to build and run your project, but I ran into a problem:
Do you have any suggestions on how to work around this problem? Is there a way I can create that missing file?
Thanks!
MMAcode
Hi Charles, that file contains secrets to like username and password to connect to Auth0. There is also other file missing which also contains secrets like username and password for my postgres. Overall I don’t know how to fix this, my code should be used as a working example, not as an exact code to use.
CharlesIrvine
Got ya. Not a problem. Thanks