TinyMce disappears in SPA - Knockout binding evaluated twice, causing the publisher to fail

advertisements

I’m trying to implement TinyMCE into John Papa’s HotSpa template.

I’m currently using Knockout 3.1.0, and Durandal 2.0.

For some unknown reason, my custom knockout binding init method is triggered only once on first load, but twice when view is refreshed.

By refresh, I mean using durandal router, I navigated to the same view again (but with different parameters). It still went through the whole durandal life cycle, hooks of deattached and attached are called again.

I had some debug code in there where the second call returned .editors.length = 1 which means the editor was already initialized in the first call.

Note: Inside the init method I have a method ko.utils.domNodeDisposal.addDisposeCallback and for some reason it is called after the second init call. I commented it out and tinyMCE still disappears. I looped through the editors and noticed it is deemed hidden. isHdidden = true.

<textarea id="mytinymce" class="tinymce"
          data-bind="attr: {id: FieldId}, tinymce: FieldValue”></textarea>

The page initially loads properly with the TinyMCE control, however it fails after a screen refresh.

I navigate away from the view, and back.

ko.bindingHandlers.tinymce = {
        init: function (element, valueAccessor, allBindingsAccessor,
                        context, arg1, arg2) {
            var options = allBindingsAccessor().tinymceOptions || {};
            var modelValue = valueAccessor();
            var value = ko.utils.unwrapObservable(valueAccessor());

            var el = $(element);
            var id = el.attr('id');

            //options.theme = "advanced";
            options.theme = "modern";

            options.menubar = false;
            options.plugins = [
                "advlist autolink autosave link image lists charmap print preview hr anchor pagebreak spellchecker",
                "searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking",
                "table contextmenu directionality template textcolor paste fullpage textcolor"
            ];
            //tinymce buttons
            //http://www.tinymce.com/tryit/classic.php
            options.toolbar_items_size = 'small';
            options.toolbar1 =
            "bold italic underline strikethrough | alignleft aligncenter alignright alignjustify | forecolor backcolor | hr removeformat | subscript superscript  ";

            ////handle edits made in the editor. Updates after an undo point is reached.
            options.setup = function (editor) {
                editor.on('change', function (e) {
                    if (ko.isWriteableObservable(modelValue)) {
                        var content = editor.getContent({ format: 'raw' });
                        modelValue(content);
                    }
                });

            };

            el.html(value);

            $(element).tinymce(options);

            //handle disposal (if KO removes by the template binding)
            ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
                var tinymceInstance = tinyMCE.get(id);
                if (tinymceInstance != undefined) {
                    // if instance already exist, then get rid of it... we don't want it
                    tinymceInstance.remove();
                }
            });
        },
        update: function (element, valueAccessor, allBindingsAccessor,
                          context) {

            var el = $(element);
            var value = ko.utils.unwrapObservable(valueAccessor());
            var id = el.attr('id');

            //handle programmatic updates to the observable
            // also makes sure it doesn't update it if it's the same.
            // otherwise, it will reload the instance, causing the cursor to jump.
            if (id !== undefined) {
                //$(el).tinymce();

                var tinymceInstance = tinyMCE.get(id);
                if (!tinymceInstance)
                    return;
                var content = tinymceInstance.getContent({ format: 'raw' });
                if (content !== value) {
                    //tinymceInstance.setContent(value);
                    valueAccessor(content);
                    //$(el).html(value);
                }
            }
        }
    };

Unfortunately I don't see any errors in the console but the editor (including the text area) are hidden and not visible.


I have changed the html to:

<textarea data-bind="tinymce: FieldValue"></textarea>

i.e. removed the id attribute binding.

And now it works.

I noticed it still calls init twice but it works.

You must let TinyMCE control the ID.