Нокаут, приложение CKEditor & Single Page - программирование
Подтвердить что ты не робот

Нокаут, приложение CKEditor & Single Page

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

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

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

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

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

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

Проблема, с которой я столкнулся в настоящее время, связана с частью приложения "Single Page" и повторной инициализацией CKEditor.

В основном, что происходит, вы можете щелкнуть по списку для управления, а затем сохранить (который возвращается к странице списка), а затем, когда вы переходите к другому "управлению", CKEditor инициализируется, но в нем нет никаких значений, я 'проверили код обновления (см. ниже), и' value 'определенно имеет правильное значение, но оно не попадает в сам 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.

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

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

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

Любая помощь будет высоко оценена.

MD

4b9b3361

Ответ 1

Для всех, кого это интересует, я его отсортировал:

Все это был основной порядок выполнения, мне просто нужно было установить значение в 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);
    }
};

Ответ 2

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

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

Вот мой bindHandler для богатого текста, затем

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);

    }
}

Ответ 3

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

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

код:

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>

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

Ответ 4

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

В моем проекте я дал визуальную обратную связь о том, были ли несохраненные изменения и поэтому необходимо было наблюдать наблюдаемое обновление 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. Итак, уведомил пользователя о грязной записи, даже если это не так. Очень специфичный для меня, поэтому я думал, что вам следует знать, на случай, если вам интересно, почему.

Ответ 5

Я просто использовал эту технику с CKEditor 4, чтобы переписать существующую (1-way) "html" привязку с двухсторонней привязкой. Я использую встроенный CKEditor, который может вести себя по-другому (не уверен), чем полный/статический редактор. Я начал с привязки "значение" и настроил его вместо 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;
    }
};

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

Ответ 6

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

//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);
});

Ответ 7

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

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

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