ASP.NET MVC: Редактируем Html в CKEditor через Knockout

ru-RU | создано: 04.02.2013 | опубликовано: 04.02.2013 | обновлено: 02.01.2018 | просмотров за всё время: 10185

В этой статье при помощи Knockout будем редактировать Html-код в WYSIWYG редакторе CKeditor.

Введение

Редактирование html-кода достаточно тривиальная задача. Если учесть, что количество WYSIWYG-редакторов в сети огромное количество, то единственным объяснением, почему именно взят именно CKeditor это то, что я с ним работаю уже более 6 лет, а еще этот редактор может в загружать файлы (например, картинок или флэш) на сервер (правда для этого используется модуль CKFinder).

Тестовый проект

Сразу к делу. Создаю новый проект. Mvc будет 4-ый, .Net пусть будет 4.5, потому как это не принципиально для Knockout. После того как проект создала Visual Studio запускаю команду обновления всех nuget-пакетов. Теперь когда проект почти готов, надо бы загрузить и установить сам редактор CKEditor. Иду по ссылки и качаю последнюю версию (на момент написания статьи версия 4.0.1). Распаковываю архив и (для экономии времени) закидываю всю папку ckeditor в корень проекта. По идеи можно было бы "почистить" немного папку, удалив файлы примера и ненужные плагины, а еще файлы справки (всё равно их никто не читает).

107-1

Классы и формы

Создаю новый класс, который будет использоваться… Не будет у меня никаких классов, ибо цель данной статьи показать другое. Ставим новый nuget-пакет, который называется JsSite:

PM> install-package jssite 
Successfully installed 'JsSite 0.3.0'. 
Successfully added 'JsSite 0.3.0' to CKEditor_Knockout.

PM>

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

Для начала удаляю содержание представления (view) в Home/Index.cshtml, потому что буду писать новую разметку под Knockout. А теперь немного кода на JavaScript.

/// <reference path="../knockout-2.2.1.debug.js" />
/// <reference path="site.services.js" />
/// <reference path="site.controls.js" />
/// <reference path="site.core.js" />

$(function () {

    "use strict";


    site.vm.homeIndex = function () {
        var meta = new site.fw.Metadata("Демонстрация редактирования HTML",
            "Данный CKEditor используется в связке с Knockout. Дополнительные инструкции в статье.",
            "http://www.calabonga.net/blog/post/107"),
            title = ko.observable("Этот текст редактируется!"),
            html = title,
            save = function () {
                alert("Сохранилось! " + html());
            };

        return {
            title: title,
            save: save,
            meta: meta,
            html: html
        };
    }();

    ko.applyBindings(site.vm.homeIndex);

});

В строке 6 создается viewModel для страницы, а в строке 24 осуществляется привязка.

В строке 10 создаем поле, которое я и собираюсь редактировать. А 11-ой я создаю временную переменную, дальше будет понятно.

Теперь немного много разметки. Вся форма (представление Index) целиком:

@{
    ViewBag.Title = "Home Page";
}

<h1 data-bind="text: meta.title"></h1>
<p>
    <span data-bind="text: meta.description"></span>
    <a data-bind="attr: {href: meta.helplink}">
        <img style="border: 0" src="~/Images/help.png" alt="" /></a>
</p>
<p><span data-bind="html: title"></span></p>
<p>
    <input id="html" name="html" data-bind="ckeditor: html" type="text" value=" " />
</p>
<p>
    <button data-bind="click: save">сохранить</button>
</p>

@section scripts
{
    <script src="~/ckeditor/ckeditor.js"></script>
    <script src="~/Scripts/knockout-2.2.1.js"></script>
    <script src="~/Scripts/app/site.bindingHandlers.js"></script>
    <script src="~/Scripts/app/site.core.js"></script>
    <script src="~/Scripts/app/site.controls.js"></script>
    <script src="~/Scripts/app/site.services.js"></script>
    <script src="~/Scripts/app/site.vm.homeIndex.js"></script>
}

Ключевыми строчками кода является 13 (прекрасный номер для главной строки, не правда ли?) и строчка номер 23. В 13-ой посмотрите на атрибут data-bind и, соответственно, на ckeditor (код может скачать и посмотреть). В 24 строке как раз, тот самый custom binding.

Запускаем

107-2

Отобразился редактор – уже хорошо! Попробуем чего-нибудь написать в редакторе:

107-3

Работает!

Заключение

В качестве заключения хочется сказать следующее. У меня задача была редактировать только свойства типа ko.observable(), поэтому я сделал свойство html в моделе и делаю привязку к нему.

Редактировать простой текст простого свойства из Literal Object не получится.

Комментарии к статье (11)

12.03.2013 11:08:33 камаз

Салабонга, подскажи пожайлуста как увязать CKEidtitor c  моделью. 

EF для вьюхи генерирует код эдитора 

  @Html.EditorFor(model => model.Field)

как сделать, чтоб  из 

<input id="html" name="html" data-bind="ckeditor: html" type="text" value=" " />

 текст отправлялся в model.Field

12.03.2013 13:37:54 Calabonga

Всё очень просто, камаз:

добавляешь ссылки на скрипты:

<script src="@Url.Content("~/ckeditor/ckeditor.js")"></script> 

в разметке ставишь так:

<div class="editor-field">
     @Html.TextAreaFor(model => model.Content, new { @class = "editor", id = "editorSmall" })<br />
     @Html.ValidationMessageFor(model => model.Content)
</div>

и скрипты для подключения к разметке:

var editorel = $('#editorSmall').val();
if (editorel != undefined) {
  var editor = CKEDITOR.replace('editorSmall', { toolbar: [['Bold', 'Italic', 'Underline', 'Strike']] });

  $('form').submit(function (e) {
    for (instance in CKEDITOR.instances) {
      CKEDITOR.instances[instance].updateElement();
    }
    var content = unescape(CKEDITOR.instances.editorSmall.getData());
    if ($('form').valid() && content.length > 0) {
      return true;
    } else {
        alert('Кажется, есть ошибки при заполнении формы!');
    return false;
    }
  });
}
12.03.2013 21:52:31 камаз

почему-то у меня не получилось, сделал всё как вы написали только остались непонятки со скриптом для подключения к разметке.. засовывал его и ваши .js файлы в папке app и подключал отдельным файлом, и даже вешал  на страницу внутри тега <script>

12.03.2013 22:49:06 Calabonga
Стоп! Если речь идет об использовании knockout, то причем тут модель (model.field)? Если knockout не используется, то предыдущий мой комментарий. Если используется, то статья и, в частности, демо-проект.
12.03.2013 23:58:32 камаз

я всё понял, спасибо огромнейшее))

кстати нашёл совсем тривилаьное решение 

<script type="text/javascript">
    CKEDITOR.replace('Content');
    CKEDITOR.editorConfig = function (config) {
        ignoreEmptyParagraph = true;
    };
</script>

13.03.2013 1:39:10 Calabonga

CKEDITOR.replace('Content');

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

29.03.2013 11:48:23 rhot

Добрый день!

Ваш код не работает, если на странице много ckeditor'ов. Подскажите пожалуйста как исправить код ko.bindingHandlers.ckeditor ?
Я вообще не очень понимаю, что означает эта строчка:
 options.editor = CKEDITOR.replace(element, options);
точнее зачем она нужна, зачем так сохранять эдитор?

29.03.2013 13:59:47 Calabonga

rhot, Честно говоря, я не ставил перед собой задачу отображать на странице более одного редактора. Я запустил, проверил, действительно, не работает! На данный момент, к сожалению не располагаю достаточным количеством времени, чтобы заняться проблемой. Но как только, так сразу.

29.03.2013 14:37:08 камаз

rhot, CKEDITOR.replace(element, options); - он заменяет textarea с id = element  на ckeditor, то есть елы ты хошеь много ckeditorов, тебе нужно написать заменитель на каждый id контролла.

30.03.2013 1:58:18 Calabonga

rhot

Вы всё правильно сделали поправив код. Дело в том что я хранил настройки в одном месте для всех инстансов, у меня не стояло задачи "иметь много на странице". Убрав этот функционал, вы "разрешили" использовать несколько инстансов на одной странице. Всё правильно.

03.09.2013 12:41:01 Nick

Спасибо за пост.

Хочу добавить, чтобы бинд был "двустороний" и сразу подтягивался текст в редактор, то в bindingHandlers в update нужно дописать:


CKEDITOR.instances[element.id].setData(value);