Trix: 2-way binding with <input>

Created on 7 Nov 2015  Â·  8Comments  Â·  Source: basecamp/trix

I'm trying to do 2-way <input> integration with Trix, such that I can update Trix by updating the <input>'s value† with content retrieved via AJAX. Minimal example: http://jsfiddle.net/a0cu4rsx/

However, it looks like Trix reads the <input> once when Trix loads, and never again.

† Why not just use the Trix API? My full use case is using the <input> with Ractive's 2-way binding, so I can read and write the Trix contents in the idiomatic Ractive way (updating the value). Full example looks more like this: http://jsfiddle.net/vock9ubh/

Most helpful comment

Inspired by @stevebauman, here is a slightly different approach that doesn't require jQuery, and it also populates existing content using the recommended Trix way:

<template>
    <div>
        <input :id="`trix-input-${_uid}`" type="hidden" :value="value">
        <trix-editor ref="editor" :input="`trix-input-${_uid}`"></trix-editor>
    </div>
</template>

<script>
import Trix from 'trix'

export default {
    props: ['value'],
    mounted() {
        this.$refs.editor.addEventListener('trix-change', (event) => {
            this.$emit('input', event.currentTarget.innerHTML)
        })
    },
}
</script>

All 8 comments

However, it looks like Trix reads the once when Trix loads, and never again.

That's correct.

Perhaps you could listen for changes on a second input element and update the editor using Trix's API in response.

I'm doing something like that now, but it's a bit clunky. I imagine the experience is similar for other 2-way binding frameworks.

If this feature won't be implemented, perhaps you could document that it doesn't work? Documenting "populate this <input> to populate your <trix-editor>" suggests 2-way binding would work.

In case anyone looks for an example implementation. I don't know whether it works for <input> though.

I use the following directive in Vue.js (v1.0)

/**
 * _ is Lodash.js
 * Handles displaying of the Trix widget and data flow between textarea/vue.js model and widget.
 * @example <textarea v-model="message" v-trix></textarea>
 *          Trix widget display value will be changed if "message" is changed and vice versa.
 */
Vue.directive('trix', {
    unwatch: () => {},
    bind: function () {
        this.el.id = _.randomStr();
        var $el = $(this.el);

        var path = _.result(_.findWhere(this.el._vue_directives, {name: 'model'}), 'expression');

        this.$trix = $(`<trix-editor input="${this.el.id}"></trix-editor>`);
        this.$trix.insertAfter($el);

        this.$trix.on('trix-change', (e) => {
            this._updateModel(path, e.currentTarget.innerHTML);
        });

        this.unwatch = this.vm.$watch(path, (value) => {
            value = value == undefined ? '' : value;
            if (this.$trix[0].innerHTML != value) {
                this.$trix[0].editor.loadHTML(value);
            }
        });
    },
    unbind: function () {
        this.unwatch();
    },
    _updateModel(path, value) {
        this.vm.$set(path, value);
    }
});

I ran into this issue with Vue2. This worked for me, using the above script as reference (Posted here simply because the above post helped out and pointed me in the correct direction):

let _ = require('lodash');

Vue.component('wysiwyg', {
    props: ['value'],
    template: '<div></div>',

    data() {
        return {
            trix: null,
            id: ''
        }
    },

    mounted() {
        this.id = _.sampleSize('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', 5).join('');

        this.trix = $(`<trix-editor input="${this.id}"></trix-editor>`);

        let self = this;

        this.trix.on('trix-change', (e) => {
            self.$emit('input', e.currentTarget.innerHTML)
        });

        this.$watch('value', function(value) {
            value = value === undefined ? '' : value;
            if (self.trix[0].innerHTML !== value) {
                self.trix[0].editor.loadHTML(value);
            }
        });

        this.trix.insertAfter(this.$el);
    },
});

Usage:

<wysiwyg v-model="mymodel" />

@codebykyle Got me started on the right track, thanks!!

Here's a Editor.vue component for anyone using file based components:

<template>
    <div></div>
</template>

<script>
    import Trix from 'trix';

    export default {
        props: ['value'],

        data() {
            return {
                trix: null,
                id: '',
            }
        },

        mounted() {
            this.id = _.sampleSize('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', 5).join('');

            this.trix = $(`<trix-editor input="${this.id}"></trix-editor>`);

            this.trix.on('trix-change', (e) => {
                this.$emit('input', e.currentTarget.innerHTML);
            });

            this.trix.on('trix-initialize', (e) => {
                e.currentTarget.editor.insertHTML(this.value);
            });

            this.$watch('value', function(value) {
                value = value === undefined ? '' : value;
                if (self.trix[0].innerHTML !== value) {
                    self.trix[0].editor.loadHTML(value);
                }
            });

            this.trix.insertAfter(this.$el);
        },
    }
</script>

To use:

<editor v-model="body"></editor>

Inside a component:

<template>
    <div class="form">
        <editor v-model="body"></editor>
    </div>
</template>

<script>
import Editor from './Editor';

export default {
    components: {Editor},
}
</script>

Linkable gist:

https://gist.github.com/stevebauman/ee424918ec87638768d4ce87138bed9d

Inspired by @stevebauman, here is a slightly different approach that doesn't require jQuery, and it also populates existing content using the recommended Trix way:

<template>
    <div>
        <input :id="`trix-input-${_uid}`" type="hidden" :value="value">
        <trix-editor ref="editor" :input="`trix-input-${_uid}`"></trix-editor>
    </div>
</template>

<script>
import Trix from 'trix'

export default {
    props: ['value'],
    mounted() {
        this.$refs.editor.addEventListener('trix-change', (event) => {
            this.$emit('input', event.currentTarget.innerHTML)
        })
    },
}
</script>

Thanks @reinink! Replaced my implementation in my projects with yours :smile:

worth to mention that you can use "@" sintax

... 
<trix-editor ref="trix" :input="`trix-input-${_uid}`" @trix-change="change"></trix-editor>
... 

    methods: {
        change({target}) {
            this.$emit('input', target.value)
        }
    },
Was this page helpful?
0 / 5 - 0 ratings