16

我遇到了涉及 KnockoutJS 和 CKEditor 的情况。

基本上,我们网站的一部分是“单页”应用程序样式,目前它只涉及 2 个页面,但可能会随着时间的推移而扩展,目前它只是一个“列表”页面和一个用于项目的“管理”页面列表。

管理页面本身需要某种富文本编辑器,我们已经使用 CKEditor 为公司范围的解决方案。

因为这 2 个页面是“单页”样式,显然 CKEditor 无法注册管理元素,因为它们在页面加载时不存在 - 足够简单的问题来解决。因此,作为一个示例,我将 CKEditor 附加到了一个效果很好的点击事件上。下一个问题是,已经设置的 Knockout observables 没有得到更新,因为 CKEditor 实际上并没有修改它附加的文本区域,它创建了所有这些你实际编辑的 div/html 元素。

经过一番谷歌搜索后,我找到了一个使用 TinyMCE 执行此操作的示例 - http://jsfiddle.net/rniemeyer/GwkRQ/所以我想我可以为 CKEditor 调整类似的内容。

目前我已经非常接近有一个可行的解决方案了,我已经使用这种技术初始化和更新了正确的 observables(我将在底部发布代码),甚至正确地发回服务器 - 太棒了。

我目前遇到的问题是“单页”应用程序部分和 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 中,它是一个相当标准的淘汰赛“数据绑定:ckeditor”,它在 ViewModel 初始化时为其应用绑定。

我放了调试器;在代码中查看流程,看起来当我第一次加载时它调用 init,然后更新,当我第二次进入时它会点击 ko.utils.domNodeDisposal 来处理元素。

我试过不破坏它,然后 CKEditor 抱怨该名称已经存在。我试过不破坏它并检查它是否存在并初始化如果它不存在 - 这是第一次有效,但第二次我们没有 CKEditor。

我认为我缺少的只有一件事可以让它发挥作用,但我已经用尽了所有选择。

有谁知道整合这三件事可以帮助我吗?

有没有可以帮助我的淘汰专家?

任何帮助将非常感激。

医学博士

4

7 回答 7

11

对于任何感兴趣的人,我对其进行了排序:

这只是一个基本的执行顺序,我只需要在初始化之前将值设置为 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);
    }
};
于 2012-06-07T04:58:15.673 回答
8

我已经处理了一段时间了,并且再次遇到了 .on("blur") 方法的几个问题。也就是说,当人们点击富文本并输入文本然后直接滚动到我表单上的“保存”按钮时,可观察对象的更新速度不够快。有很多方法可以处理延迟,但我想要更正式的方法。我深入研究了 CKEditor 文档并找到了这个 gem: focusManager

这是处理所有焦点和模糊实例的内置功能,并允许您将真正的模糊事件连接到控件。

这是我的富文本绑定处理程序

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

    }
}
于 2013-02-04T14:03:01.863 回答
7

在其他答案中完成的工作的基础上,这是我的解决方案:

  • 使用 ckeditor 自己的change事件处理更改(按键更新,但不仅如此)
  • 使用 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>

更新:根据评论中的要求,这是一个最小的工作 Fiddle

于 2013-12-25T17:03:07.817 回答
5

第一次发帖,如果我做错了什么,请告诉我

在我的项目中,我给出了关于是否有未保存的更改以及是否需要更新 observable 的视觉反馈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()and而不是这种在内容中查找和.setData(html)查找的相当老套的方式。<body><table class="cke_editor">iframe

原因是,对于update:以下条件:

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

由于 CKEditor 的 HTML 格式,最初是正确的。因此,通知用户有关脏记录,即使它不是。对我来说非常具体,所以我认为你应该知道,以防你想知道为什么。

于 2012-09-27T13:36:25.763 回答
0

我刚刚在 CKEditor 4 中使用了这种技术,用 2 路绑定覆盖了现有的(1 路)“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;
    }
};

警告:这可能应该更新为使用 CKEditor 自己的change事件。

于 2013-04-25T14:36:01.577 回答
0

我重写了这个来更新每个 keyup 上的 observable,而不是模糊。这是对 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);
});
于 2013-06-04T18:00:14.353 回答
-1

对于“模糊”部分,我尝试了下面的代码,它似乎工作

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

我认为如果您从其他地方“更新”可观察对象(不是通过编辑,因为这是由“模糊”处理的),仍然需要“更新”部分

于 2012-08-26T23:41:08.600 回答