Is there a proper way to wire up Trix editor with Livewire?

Viewed 5288

When wiring together Trix editor content with Livewire, I am stumbling into problems. I believe that the issue is that when Livewire receives content from Trix, the content is swapped out and Trix becomes disabled. Is there a better way?

What I have done, that works, is as follows. At the moment, the page is the redirected to itself in order to reboot Trix (defeating the whole point of Livewire, but it's being used for other things too).

<div>
  <input
      id="newCommentTextTrixContent"
      type="hidden"
      wire:model="newCommentText"
  >

  <trix-editor
      id="newCommentTextTrixEditor"
      input="newCommentTextTrixContent"
  ></trix-editor>


  <button wire:click="addComment(document.getElementById('newCommentTextTrixEditor').innerHTML)">Add Comment</button>
</div>

I have tried

  • wire:model on the hidden input -- nothing happens
  • x-on:trix-change="$set('comment', $event.target.innerHTML) -- this works, but Trix goes grey and ceases to work after the first keypress (reboot problem?)

I'm sure something like the latter is better, but with Trix somehow being rebooted each time. It all seems a bit messy - so the question is, what's the right way to do this?

4 Answers

I got it working. With up to date Livewire and Alpine installations, the code is roughly as follows.

    <div wire:ignore>
        <trix-editor
            class="formatted-content"
            x-data
            x-on:trix-change="$dispatch('input', event.target.value)"
            x-ref="trix"
            wire:model.debounce.60s="newCommentText"
            wire:key="uniqueKey"
        ></trix-editor>
    </div>

Why does this work?

  • You need wire:ignore on the parent node, because Trix inserts the toolbar above the text area. wire:ignore stops Livewire from worrying about it and therefore not removing it or messing with it on the next cycle.
  • You need a wire:key because the DOM moves around a bit, and this helps Livewire to keep track of it.
  • I propose the long debounce, which is a hack as the .lazy modifier doesn't work well with text. Also, waiting for Ajax on each key press is painful.
  • The alpine event ensures that Trix events (like bold, italics etc) are still fired

That's it. I use this above to repetitively submit comments onto the end of a comment stream, and everything seems to work fine. Good luck!

Note, I also have CKEditor working similarly, as described here.

As an extension on @Kurucu 's answer, and the comment under it from @Rehan;

This seems to work very well. But when I apply styles like li or bold it doesnt retain in the wire:model. Ex: <div>foo<br>bar<br>foobar</div> I applied the Bullets here the tags are missing. Did you face this issue? – Rehan

To fix the issue of not having an updated value when pressing buttons bold, italic, or quote for example, add the following part to the trix editor (note the x-on:trix-change="$dispatch('input', event.target.value)"):

<div wire:ignore>
    <trix-editor
        class="formatted-content"
        x-data
        x-on:trix-change="$dispatch('input', event.target.value)"
        wire:model.debounce.1000ms="newCommentText"
        wire:key="uniqueKey"
    ></trix-editor>
</div>

The above option works but wasn't getting the data back from my field, Here's what worked for me with a little tweak using AlpineJS's @entangle. Below is my working solution:

<div class="mb-2" x-data="{ description: @entangle('description').defer }"

             x-init="$watch('description', function (value) {
                        $refs.trix.editor.loadHTML(value)
                        var length = $refs.trix.editor.getDocument().toString().length
                        $refs.trix.editor.setSelectedRange(length - 1)
                        }
                    )" wire:ignore>

            <label class="form-label">Job Description <span class="text-danger">*</span></label>
            @error('description')
            <span class="error d-inline-block"><i class="mdi mdi-alert-circle"> </i> {{$message}}</span>
            @enderror
            <input name="description" id="description" type="hidden" x-model="description">
            <div x-on:trix-change.debounce.1000ms="description = $refs.trix.value">
                <trix-editor x-ref="trix" input="description" class="overflow-y-scroll"
                             style="height: 20rem;"></trix-editor>
            </div>
        </div>

I got it to work by using Trix's built-in events.

<input id="about" name="about" type="hidden" value="{{ $about }}">
<trix-editor input="about" class="wysiwyg-content"></trix-editor>

<script>
    var about = document.getElementById("about")

    addEventListener("trix-change", function(event) {
        console.log(about.getAttribute('value'));
        @this.set('about', about.getAttribute('value'))
    })
</script>
Related