Нокаут, CKEditor И Одностраничное Приложение

У меня есть ситуация с KnockoutJS & CKEditor.

в принципе, у нас часть нашего сайта, то есть одну страницу приложения в стиле, в настоящее время она включает только 2 страницы, но, вероятно, увеличится со временем, сейчас это просто 'предложения' и 'управление' страница для элементов в списке.

сама страница управления требует какого-то текстового редактора, мы пошли с CKEditor для решения компании.

потому что эти 2 страницы не одну страницу стиль, очевидно, CKEditor не может зарегистрироваться против элементов управления, потому что их нет при загрузке страницы - достаточно простая проблема для исправления. Так что в качестве примера я прикрепил CKEditor на событие click, которое отлично сработало. Следующая проблема заключалась в том, что тогда наблюдаемые нокаутом, которые были настроены, не обновлялись, потому что CKEditor фактически не изменяет textarea, который он прикрепил, он создает все эти элементы div/html, которые вы фактически редактируете.

после немного googleing я нашел пример того, как кто-то делает это с TinyMCE -http://jsfiddle.net/rniemeyer/GwkRQ/ поэтому я подумал, что могу адаптировать что-то подобное для CKEditor.

В настоящее время я довольно близок к рабочему решению, у меня есть инициализация и обновление правильных наблюдаемых с помощью этой техники (я отправлю код внизу) и даже отправлю обратно на сервер правильно - фантастика.

проблема, которую я сейчас испытываю, связана с "одной страницей" приложение часть и reinitialisation редактора CKEditor.

в основном, что происходит, вы можете нажать из списка, чтобы управлять, а затем сохранить (который возвращается на страницу списка), затем, когда вы переходите к другому "управлению", CKEditor инициализируется, но в нем нет никаких значений, я проверил код обновления (ниже), и "значение" определенно имеет правильное значение, но оно не проталкивается к самому CKEditor.

возможно, это отсутствие понимания о потоке / инициализации процесс для CKEditor или отсутствие понимания о привязках нокаута или, возможно, это проблема с фреймворком, который был настроен для нашего одностраничного приложения - я не уверен.

вот код:

//Test one for ckeditor
ko.bindingHandlers.ckeditor = {
    init: function (element, valueAccessor, allBindingsAccessor, context) {
        var options = allBindingsAccessor().ckeditorOptions || {};
        var modelValue = valueAccessor();

        $(element).ckeditor();

        var editor = $(element).ckeditorGet();

        //handle edits made in the editor
        editor.on('blur', function (e) {
            var self = this;
            if (ko.isWriteableObservable(self)) {
                self($(e.listenerData).val());
            }
        }, modelValue, element);


        //handle destroying an editor (based on what jQuery plugin does)
        ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
            var existingEditor = CKEDITOR.instances[element.name];
            existingEditor.destroy(true);
        });
    },
    update: function (element, valueAccessor, allBindingsAccessor, context) {
        //handle programmatic updates to the observable
        var value = ko.utils.unwrapObservable(valueAccessor());
        $(element).html(value);
    }
};

таким образом, в HTML это довольно стандартный нокаут "data-bind: ckeditor", который применяет привязки для него при инициализации ViewModel.

Я поставил отладчик; в коде, чтобы увидеть поток, похоже, когда я загружаю первый раз, когда он вызывает init, затем обновление, когда я иду во второй раз, он попадает в ko.а utils.domNodeDisposal распоряжаться элементов.

Я попытался не уничтожить его, который CKEditor затем жалуется, что что-то уже существует с этим именем. Я старался не уничтожать его и не проверять, существует ли он, и инициализировать, если нет - это работает в первый раз, но во второй раз у нас нет CKEditor.

Я думаю, что есть только одна вещь, которую я упускаю, которая заставит его работать, но я исчерпал все опции.

есть ли у кого каких-либо знаний об интеграции этих 3 вещей, которые могут мне помочь?

есть ли какие-либо эксперты нокаута, которые могли бы помочь мне?

любая помощь была бы очень признательна.

MD

7 ответов


для всех, кто заинтересован, я отсортировал его:

все это был базовый порядок выполнения, мне просто нужно было установить значение textarea html, прежде чем он будет инициализирован.

Примечание Для этого используется расширение адаптера jquery .ckeditor () по элементу.

вероятно, есть и лучший способ сделать часть "размытия".

Это расширение также не работает с нужным на данный момент, но это должно быть довольно простой в сравнение.

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

        $(element).html(value);
        $(element).ckeditor();

        var editor = $(element).ckeditorGet();

        //handle edits made in the editor

        editor.on('blur', function (e) {
            var self = this;
            if (ko.isWriteableObservable(self)) {
                self($(e.listenerData).val());
            }
        }, modelValue, element);
    }
};

Я работал с этим некоторое время и снова запустил несколько проблем с этим .на подходе ("размытие"). А именно, когда люди нажали на богатый текст и ввели текст, а затем прокрутили непосредственно к кнопке Сохранить в моей форме, наблюдаемый не обновлялся достаточно быстро. Есть масса способов справиться с задержками, но я хотел что-то более официальное. Я покопался в документации CKEditor и нашел этот перл: focusManager

это встроенный функциональность, которая обрабатывает все экземпляры фокуса и размытия и позволяет подключить истинное событие размытия к элементу управления.

вот мой bindingHandler для богатого текста тогда

ko.bindingHandlers.richText = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel) {

       var txtBoxID = $(element).attr("id");
       var instance = CKEDITOR.instances[txtBoxID];

       var options = allBindingsAccessor().richTextOptions || {};
       options.toolbar_Full = [
            ['Source', '-', 'Format', 'Font', 'FontSize', 'TextColor', 'BGColor', '-', 'Bold', 'Italic', 'Underline', 'SpellChecker'],
            ['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', 'CreateDiv', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock', '-', 'BidiLtr', 'BidiRtl'],
            ['Link', 'Unlink', 'Image', 'Table']
       ];

       //handle disposal (if KO removes by the template binding)
       ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
          if (CKEDITOR.instances[txtBoxID]) { CKEDITOR.remove(CKEDITOR.instances[txtBoxID]); };
       });

       $(element).ckeditor(options);

       //wire up the blur event to ensure our observable is properly updated
       CKEDITOR.instances[txtBoxID].focusManager.blur = function () {
          var observable = valueAccessor();
          observable($(element).val());
       };
    },
    update: function (element, valueAccessor, allBindingsAccessor, viewModel) {

       var val = ko.utils.unwrapObservable(valueAccessor());
       $(element).val(val);

    }
}

создание на работе, проделанной в других ответах вот мое решение:

  • обрабатывает изменения, используя собственный ckeditor change событие (обновления при нажатии клавиши, но не только это)
  • использует Так что вы не получите нежелательный HTML, как "magic line" и подобные вещи
  • обрабатывает управление памятью (непроверенный)

код:

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

        $(element).html(value);
        $(element).ckeditor();

        var editor = $(element).ckeditorGet();

        //handle edits made in the editor
        editor.on('change', function(e) {
            var self = this;
            if (ko.isWriteableObservable(self)) {
                self($(e.listenerData).val());
            }
        }, modelValue, element);

        //handle disposal (if KO removes by the template binding)
        ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
            if (editor) {
                CKEDITOR.remove(editor);
            };
        });
    },
    update: function(element, valueAccessor, allBindingsAccessor, context) {
        // handle programmatic updates to the observable
        var newValue = ko.utils.unwrapObservable(valueAccessor());
        if ($(element).ckeditorGet().getData() != newValue)
            $(element).ckeditorGet().setData(newValue)
    }
};

разметки я использую (Примечание afterkeydown):

<textarea 
    id="editor1" 
    data-bind="ckeditor: text, valueUpdate: 'afterkeydown'"
></textarea>

обновление: как просили в комментариях, вот минимальный рабочая скрипку.


первый пост, поэтому дайте мне знать, если я сделал что-то не так

в моем проекте я дал визуальную обратную связь о том, были ли несохраненные изменения и поэтому нуждались в наблюдаемом обновлении keyup. И на click при нажатии кнопки панели инструментов. это также соответствовало мне, используя valueUpdate:['afterkeydown','propertychange','input'] в своем data-bind атрибуты.

кроме того, на производительности, я использовал параметр метода обратного вызова .ckeditor(callback,options) а не .on(eventName,handler).

это пользовательская привязка, которую я придумал:

ko.bindingHandlers.ckeditor = {
    init: function (element, valueAccessor, allBindingsAccessor, context) {
        // get observable
        var modelValue = valueAccessor();;

        $(element).ckeditor(function(textarea) {
            // <span> element that contains the CKEditor markup
            var $ckeContainer = $(this.container.$);
            // <body> element within the iframe (<html> is contentEditable)
            var $editorBody =
                    $ckeContainer.find('iframe').contents().find('body');
            // sets the initial value
            $editorBody.html( modelValue() );
            // handle edits made in the editor - by typing
            $editorBody.keyup(function() {
                modelValue( $(this).html() );
            });
            // handle edits made in the editor - by clicking in the toolbar
            $ckeContainer.find('table.cke_editor').click(function() {
                modelValue( $editorBody.html() );
            });
        });


        // when ko disposes of <textarea>, destory the ckeditor instance
        ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
            $(element).ckeditorGet().destroy(true);
        });
    },
    update: function (element, valueAccessor, allBindingsAccessor, context) {
        // handle programmatic updates to the observable
        var newValue = ko.utils.unwrapObservable(valueAccessor());
        var $ckeContainer = $(element).ckeditorGet().container;
        if( $ckeContainer ) {
            // <span> element that contains the CKEditor markup
            $ckeContainer = $($ckeContainer.$);
            // <body> element within the iframe (<html> is contentEditable)
            var $editorBody =
                    $ckeContainer.find('iframe').contents().find('body');
            // if new value != existing value, replace it in the editor
            if( $editorBody.html() != newValue )
                $editorBody.html( newValue );
        }
    }
};

основание:

я знаю, что я должен использовать .getData() и .setData(html) вместо этого довольно избитый способ поиска <body> и <table class="cke_editor"> внутри iframe содержание.

причина, по update: состояние внутри:

if( $(element).ckeditorGet().getData() != newValue )
    $(element).ckeditorGet().setData( newValue )

изначально было верно из-за форматирования HTML, которое делает CKEditor. И так, уведомил пользователя о грязной пластинке, хотя это и не так. очень специфично для меня поэтому я подумал, что вы должны знать, если вам интересно, почему.


Я просто использовал этот метод с CKEditor 4, чтобы перезаписать существующую (1-полосную) привязку "html" с 2-полосной привязкой. Я использую встроенный CKEditor, который может вести себя иначе (не уверен), чем полный/статический редактор. Я начал с привязки "value" и настроил ее для работы с innerHTML вместо:

ko.bindingHandlers.html = {
    'init': function (element, valueAccessor, allBindingsAccessor) {
        var eventsToCatch = ["blur"];
        var requestedEventsToCatch = allBindingsAccessor()["valueUpdate"];
        var valueUpdateHandler = null;

        if (requestedEventsToCatch) {
            if (typeof requestedEventsToCatch == "string")
                requestedEventsToCatch = [requestedEventsToCatch];

            ko.utils.arrayPushAll(eventsToCatch, requestedEventsToCatch);
            eventsToCatch = ko.utils.arrayGetDistinctValues(eventsToCatch);
        }

        valueUpdateHandler = function () {
            var modelValue = valueAccessor();
            var oldValue = ko.utils.unwrapObservable(modelValue);
            var elementValue = element.innerHTML;
            var valueHasChanged = (oldValue !== elementValue);

            if (valueHasChanged)
                modelValue(elementValue);
        }

        ko.utils.arrayForEach(eventsToCatch, function (eventName) {
            var handler = valueUpdateHandler;

            if (eventName.indexOf("after") == 0) {
                handler = function () {
                    setTimeout(valueUpdateHandler, 0)
                };

                eventName = eventName.substring("after".length);
            }

            ko.utils.registerEventHandler(element, eventName, handler);
        });
    },
    'update': function (element, valueAccessor) {
        var newValue = ko.utils.unwrapObservable(valueAccessor());
        var elementValue = element.innerHTML;
        var valueHasChanged = (newValue !== elementValue);

        if (valueHasChanged)
            element.innerHTML = newValue;
    }
};

предостережение: это, вероятно, следует обновить, чтобы использовать собственный CKEditor change событие.


я переписал это, чтобы обновить наблюдаемое на каждом keyup, а не на размытие. Это намного больше обновлений для observable, но пока вы сохраняете с помощью кнопки, это, кажется, отлично работает до сих пор!

//handle edits made in the editor
CKEDITOR.instances.thread_message.on('contentDom', function() {
  CKEDITOR.instances.thread_message.document.on('keyup', function(e) {

    var self = this;
    if (ko.isWriteableObservable(self)) {
      var ckValue = CKEDITOR.instances.element_id.getData();
      self(ckValue);
      //console.log("value: " + ckValue);
    }
  }, modelValue, element);
});

для части "размытия" я попробовал код ниже, и он, похоже, работает

              editor.on('blur', function (e) {
                var self = this;
                if (ko.isWriteableObservable(self)) {
                    var ckValue = e.editor.getData();
                    self(ckValue);
                }
            }, modelValue, element);

Я думаю, что часть "обновление" по-прежнему необходима, если вы "обновляете" наблюдаемое откуда-то еще (не через редактирование, поскольку об этом заботится "размытие")