Search code examples
javascriptbackbone.jstinymcebackbone-model

Bind a Backbone Model attribute to a TinyMCE text


I'm making a form with rich text in it. I have TinyMCE for that, Backbone controls the logic and underscore is used for templating.

I can't find if there's a way to bind a Backbone model to the TinyMCE value?

Something like:

var backboneModel = new BackBobeModel();
tinymce.init({ 
    selector:'#rich-text',
    'data-object': backboneModel.richText
});

Solution

  • As far as I know, TinyMCE doesn't bind automatically with Backbone. So I made a simple reusable TextArea component.

    It's made from a Backbone View which init its own root <textarea> element as a TinyMCE instance and binds itself into its Change event.

    TinyMCE Backbone component

    var TextArea = Backbone.View.extend({
        tagName: 'textarea',
        initialize: function(options) {
            // set the default options
            options = this.options = _.extend({
                attribute: 'value',
                format: 'raw'
            }, options);
    
            // initialize a default model if not provided
            if (!this.model) this.model = new Backbone.Model();
            if (!this.getValue()) this.setValue('');
    
            this.listenTo(this.model, 'change:' + options.attribute, this.onModelChange);
        },
        render: function() {
            this.$el.html(this.getValue());
            tinymce.init({
                target: this.el,
                init_instance_callback: this.onEditorInit.bind(this)
            });
            return this;
        },
    
        // simple accessors
        getValue: function() {
            return this.model.get(this.options.attribute) || '';
        },
        setValue: function(value, options) {
            return this.model.set(this.options.attribute, value, options);
        },
        // event handlers
        onEditorInit: function(editor) {
            editor.on('Change', this.onTextChange.bind(this));
            this.editor = editor;
        },
        onTextChange: function(e) {
            this.setValue(this.editor.getContent());
        },
        onModelChange: function(model, value, options) {
            if (!this.editor) return;
            this.editor.setContent(value, { format: this.options.format });
        }
    });
    

    Use it as-is

    You can use it without a model, and it will create its own model to keep track of the data.

    var component = new TextArea({ content: "initial content" });
    

    It's possible to retrieve the data or even listen to the component's model.

    component.getValue();
    // or use events:
    Backbone.listenTo(component.model, 'change:value', function(model, value, options) {
        // use the new value
    });
    

    Use it with a custom model

    Say you have a custom model with arbitrary attributes.

    var car = new CarModel({ 
        make: "mazda", 
        description: "This is a car"
        // ...
    });
    

    Just pass it to the component, defining which attribute it should use to update the model.

    var component = new TextArea({ 
        model: car, 
        // 
        attribute: 'description'
    });
    

    The same initial model instance description attribute will now be auto-updated anytime a user types in the TinyMCE box.

    Backbone.listenTo(car , 'change:description', function(model, value, options) {
        console.log("Car description changed:", value);
    });