Skip to content

Instantly share code, notes, and snippets.

@tanthammar
Last active February 8, 2023 22:18
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save tanthammar/20a70865415f9f84ec4cca054f3b8396 to your computer and use it in GitHub Desktop.
Save tanthammar/20a70865415f9f84ec4cca054f3b8396 to your computer and use it in GitHub Desktop.
Livewire trix input
@push('body-styles')
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/trix/1.2.0/trix.css">
@endpush
<form x-data="form()">
<input x-ref="description" id="description" name="description" value='{{ $description }}' type="hidden" />
<div wire:ignore>
<trix-editor input="description"></trix-editor>
</div>
<input x-ref="info" id="info" name="info" value='{{ $info }}' type="hidden" />
<div wire:ignore>
<trix-editor input="info"></trix-editor>
</div>
<button x-on:click.prevent="save">Save</button>
</form>
@push('scripts')
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/trix/1.2.0/trix.js"></script>
<script>
function form() {
return {
save() {
window.livewire.emit('alpineSave', {
info: this.$refs.info.value,
description: this.$refs.description.value,
});
}
}
}
</script>
@endpush
//livewire component
protected $listeners = ['alpineSave'];
public function alpineSave($array) {
$this->fill([
'info' => data_get($array, 'info'),
'description' => data_get($array, 'description'),
]);
// save the data ...
}
@ProjektGopher
Copy link

I love this solution.

@tanthammar
Copy link
Author

This is no longer needed. It is resolved with the v2 @entangle('propertyName').defer directive.

@ryangjchandler
Copy link

@tanthammar Would you be able to update this Gist with the @entangle solution?

@ryatkins
Copy link

ryatkins commented Sep 16, 2020

Here is a pretty good @entangle method I have come up with working with @ryangjchandler and @calebporzio

                    <div
                        x-data="{ content: @entangle('form.html_text').defer }"

                        x-init="$watch('content', function (value) {
                            $refs.trix.editor.loadHTML(value)
                            var length = $refs.trix.editor.getDocument().toString().length
                            $refs.trix.editor.setSelectedRange(length - 1)
                            }
                        )"
                        class="mt-5 block"
                    >
                        <label class="form-label">Content*</label>
                        <input id="html_text" type="hidden" x-model="content">
                        <div x-on:trix-change.debounce.1000ms="content = $refs.trix.value" wire:ignore>
                            <trix-editor x-ref="trix" input="html_text" class="overflow-y-scroll" style="height: 20rem;"></trix-editor>
                        </div>
                        @error('form.html_text')
                            <span class="form-error">
                                {{ $message }}
                            </span>
                        @enderror
                    </div>

In Livewire, make sure you are converting your line breaks to <br>s

$myText = nl2br($this->form['html_text']);

@tanthammar
Copy link
Author

@ryatkins
Thank you very much for sharing!!

@tanthammar
Copy link
Author

@ryangjchandler and @ryatkins

This is my new take on the trix field for Livewire v2

<div x-data="{ trix: @entangle($trixfield).defer }">
    <input value="{{ $trixfield }}" id="{{ $trixfield }}" name="{{ $trixfield }}" type="hidden" />
    <div wire:ignore
         x-on:trix-change.debounce.500ms="trix = $refs.trixInput.value"
         x-on:trix-file-accept="return event.preventDefault()">
        <trix-editor x-ref="trixInput" input="{{ $trixfield }}" class="form-input"></trix-editor>
    </div>
</div>

@deanzod
Copy link

deanzod commented Oct 21, 2020

@tanthammar

Just a little update on this. Your solution works great. The only thing is, if you want to clear the text area after submission you can't due to the wire:ignore. You might do this for example if the text area is a comment box. e.g. post a comment then comment appears below and textarea is clear for a new comment.

Not sure if I introduce any new problems by fixing this one but I have tested the following with validation on 'content' being required|min:25 and it works as expected for:

trying to submit an empty field
not enough characters
successful submission -> field clears ($this->content = null)

<div x-data="{ trix: @entangle('content').defer }">
    <input id="content" name="content" type="hidden" />
     <div wire:ignore>
         <trix-editor x-model.debounce.300ms="trix" input="content">
         </trix-editor>
     </div>
    @error('content') <span class="error">{{ $message }}</span> @enderror
</div>

@tanthammar
Copy link
Author

@deanzod
Not tested yet, but that looks like a nice improvement.
Thank you for sharing!

@tanthammar
Copy link
Author

tanthammar commented Oct 24, 2020

@deanzod
I've made som tests.

Your version is working fine except on one point.

  • It's not possible to set the value of the trix input, to anything else but null, from Livewire.
    • $this->content = "Hello" or $this->content = "<div>Hello</div>" doesn't work.
  • Also found that we can't use x-model if enabling trix attachments. Had to use my version with x-ref. Didn't investigate any further.

I tried with and without @entangle.defer.

(I'm sure there's some way to solve it by emitting browser events)

@deanzod
Copy link

deanzod commented Oct 24, 2020

I will have a look later but to prepopulate it you can use x-init

Maybe not :(

@deanzod
Copy link

deanzod commented Oct 24, 2020

ok, you can use value on the hidden input.

<div x-data="{ trix: @entangle('content').defer }">
    <input id="content" name="content" type="hidden" value="{{$content}}" />
     <div wire:ignore>
         <trix-editor x-model.debounce.300ms="trix" input="content">
         </trix-editor>
     </div>
    @error('content') <span class="error">{{ $message }}</span> @enderror
</div>

I've not dabbled with the attachments bit yet but hopefully it is also fixable

@tanthammar
Copy link
Author

tanthammar commented Oct 24, 2020

@deanzod
Sorry, I was unclear. There is no issue with setting the initial value. It is when you alter the value in backend that Alpine, entangle doesn’t update trix. The only value you can update, from backend is null.
I already have a working version for attachments, but that had to use $refs, like in my example.

@deanzod
Copy link

deanzod commented Oct 25, 2020

Hmmm. Not sure I can recreate that issue. This is a barebones version I am testing with:

class Test extends Component
{
    public $content = 'hello';

    public function submit()
    {
        $this->validate(['content' => 'required']);
        $this->content = "goodbye";
    }
    public function render()
    {
        return view('livewire.test');
    }
}
<div>
    <form wire:submit.prevent="submit()">
        <div x-data="{ trix: @entangle('content').defer }">
            <input id="content" name="content" type="hidden" value="{{$content}}" />
            <div wire:ignore>
                <trix-editor x-model.debounce.300ms="trix">
                </trix-editor>
            </div>
            @error('content') <span class="error">{{ $message }}</span> @enderror
        </div>
        <button type="submit">Submit</button>
    </form>
</div>

@dsazup
Copy link

dsazup commented Oct 26, 2020

Anyone got trix working with attachments? I'm using something like this.

<div x-data="{ content: @entangle('content').defer }">
    <input type="hidden" id="x" value="{{ $initialValue }}"/>
    <div wire:ignore
         @trix-change="content = $event.target.value"
         @trix-attachment-add="attachmentAdd"
        <trix-editor input="x" class="form-input"></trix-editor>
    </div>
</div>

attachmentAdd just uploads the file and sets attributes on attachement from trix. It all works fine, however there is a problem. If I start with blank content and I drag and drop an image to trix and click save, it will not save. Even if I do

         @trix-change="content = $event.target.value; console.log(content)"

I can see that content is correct. However, for some reason @entangle does not update the server correctly.
🤔 anyone run into this?

@tanthammar
Copy link
Author

@dsazup try @ryatkins version, think you have to use alpine $watch. Just tested to drop an attachment, with an empty Trix field, without issues.

@ighmouraceneb
Copy link

ighmouraceneb commented Dec 11, 2020

Have you tested trix with v2.3.5. from livewire It gives me an error

alpine.js: 1873 Uncaught SyntaxError: Unexpected token u in JSON at position 0
    at JSON.parse (<anonymous>)

On the other hand when I return to v2.2.8 the error disappears

@tanthammar
Copy link
Author

@ighmouraceneb
I'm on AlpineJS 2.7.3, Livewire 2.3.5 and Trix 1.2.0, and I have no errors.

@ighmouraceneb
Copy link

ighmouraceneb commented Dec 17, 2020

@tanthammar

@ighmouraceneb
I'm on AlpineJS 2.7.3, Livewire 2.3.5 and Trix 1.2.0, and I have no errors.

@tanthammar
in my case, I generate the inputs automatically. so we can have 0,1,2 ... trix-editor
so in the view I have this

@foreach($fields as $index => $field)
   <x-back.input.label for="'{{ $field->field->value }}-{{ $index }}'">{{__($field->config['label'])}}</x-back.input.label>
     <x-back.input.trix-text
                 id="'{{ $field->field->value }}-{{ $index }}'"
                wire:model.defer="textFormattedLongSummary.{{ $index }}"
                :error="$errors->first('textFormattedLongSummary.'.$index)">
     </x-back.input.trix-text>
@endforeach

component trix-text :

@php
    $id = md5($attributes->wire('model'));

@endphp
<div
    class="rounded-md shadow-sm"
    x-data="{
        value:@entangle($attributes->wire('model')),
        isFocused(){ return document.activeElement !== this.$refs.trix },
        setValue(){ this.$refs.trix.editor.loadHTML(this.value) },

    }"
    x-init="setValue(); $watch('value', () => isFocused() && setValue())"
    x-on:trix-change="value = $event.target.value"
    {{ $attributes->whereDoesntStartWith('wire:model') }}
    wire:ignore>

    <input id="{{$id}}"  type="hidden">
    <trix-editor input="{{$id}}" x-ref="trix" class="form-textarea block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5"></trix-editor>
</div>

and in the controller I defined as follows:

   public $TextFormattedLongSummary = [];

in the console I have this error

Uncaught SyntaxError: Unexpected token u in JSON at position 0
    at JSON.parse (<anonymous>)
    at SupportAlpine.js:70
    at Array.forEach (<anonymous>)
    at SupportAlpine.js:55
    at alpine.js:1465
    at Array.forEach (<anonymous>)
    at new Component (alpine.js:1465)
    at Object.initializeComponent (alpine.js:1870)
    at alpine.js:1857
    at alpine.js:1835

Apparently @entangle does not support nested arrays

@ighmouraceneb
Copy link

ighmouraceneb commented Dec 19, 2020

I init the array in the mount function and it worked :

   public function mount()
    {
        foreach ($this->fields as $index => $field) {
 
            if($field->field->value === "TextFormattedLongSummary") {
                $this->TextFormattedLongSummary[$index] = "";
            }
        }
    }

the problem is solved with version v2.3.6 of livewire

@mediabeastnz
Copy link

Anyone had issues with not being able to pasting using this setup?

@oladip
Copy link

oladip commented Feb 8, 2023

the upload is not adding up. Initial wire:model does not hold the uploaded image. Any assistant to handle the uploaded images and text together in the same livewire component? Thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment