Развитие моего проекта потребовало «прикрутить» в редактор на основе tinyMCE возможность вставлять изображения, которые хранились бы непосредственно на сервере приложения, и что бы это было бесшовно для конечного пользователя. Погуглив совсем немого и найдя все полезное как всегда на stackoverflow.com и изучив все что есть на сегодняшний день по этой теме, я понял, что придется изобретать свой велосипед. Основные мои мотивы были следующие:
Практически все «готовые» открытые реализации используют файловый принцип хранения, когда пользователь загружает сначала файл на сервер, в область хранения, а затем вставляет его в редактор. В итоге получается довольно сложный процесс, и кроме этого, сервер приложения рискует превратиться в файл помойку. Второй мотив — все решения были достаточно сложны и тяжелы. Например, некоторые из них использовали компоненты silverlight, другие тащили за собой кучу внешних зависимостей. В общем, моя цель: написать простой, легкий загрузчик изображений для tinyMCE который работает на MVC. (я думаю, что это будет справедливо для любого движка MVC а не только ASP.NET). В этой статье я расскажу, как просто написать свой загрузчик и плагин для tinyMCE.
В качестве интерфейса загрузки я хотел бы получить диалог с кнопкой выбора локального файла и панели предварительного просмотра с двумя кнопками — «вставить» и «отмена». Загрузку сделаем асинхронную, с использованием jquery.form. Она будет автоматической, сразу после выбора файла.
Сначала напишем контроллер загрузчика.
Для хранения я использовал сервис. Для тестовых целей он просто все хранит в статическом хеше:
Позже можно загнать все данные в базу или на диск, как кому нравиться.
Создадим вид, Index, он у нас один.
И скрипт:
Вот в принципе и все, можно тестировать наш контроллер загрузчика.
В результате мы имеем:
Теперь перейдем к созданию плагина для tinyMCE. Исходники копируем из плагина example.
Я назвал свой плагин imagelight.
editor_plugin.js
Переносим весь код из скрипта в
js/dialog.js
Кстати, если не перенести, то работать не будет — у jquery и tinyMCEPopup конфликт инициализации.
Для файлов ресурсов напишем:
Модифицируем вид.
В итоге мы имеем довольно простой и легкий загрузчик. На все ушло пару часов. Итоговый результат:
Практически все «готовые» открытые реализации используют файловый принцип хранения, когда пользователь загружает сначала файл на сервер, в область хранения, а затем вставляет его в редактор. В итоге получается довольно сложный процесс, и кроме этого, сервер приложения рискует превратиться в файл помойку. Второй мотив — все решения были достаточно сложны и тяжелы. Например, некоторые из них использовали компоненты silverlight, другие тащили за собой кучу внешних зависимостей. В общем, моя цель: написать простой, легкий загрузчик изображений для tinyMCE который работает на MVC. (я думаю, что это будет справедливо для любого движка MVC а не только ASP.NET). В этой статье я расскажу, как просто написать свой загрузчик и плагин для tinyMCE.
1. Загрузчик
В качестве интерфейса загрузки я хотел бы получить диалог с кнопкой выбора локального файла и панели предварительного просмотра с двумя кнопками — «вставить» и «отмена». Загрузку сделаем асинхронную, с использованием jquery.form. Она будет автоматической, сразу после выбора файла.
Сначала напишем контроллер загрузчика.
public class ImageController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public JsonResult Index(HttpPostedFileBase file)
{
var service = new ImageService();
byte[] data = new byte[file.ContentLength];
file.InputStream.Read(data, 0, file.ContentLength);
var image = service.CreateImage(file.FileName, file.ContentType, data);
JsonResult res = Json(new {image.Id});
res.ContentType = "text/html"; // Для того, чтобы работал jquery.form (for iframe implementation)
return res;
}
[OutputCache(Duration = 0)]
public ActionResult ById(string id)
{
var service = new ImageService();
var image = service.GetImage(id);
return new FileStreamResult(new MemoryStream(image.Data), image.ContentType);
}
}
Для хранения я использовал сервис. Для тестовых целей он просто все хранит в статическом хеше:
class ImageService
{
static Dictionary<string, ImageData> _images = new Dictionary<string, ImageData>();
public ImageData CreateImage(string fileName, string contentType, byte[] data)
{
var image = new ImageData(fileName, contentType, data);
_images.Add(image.Id, image);
return image;
}
public ImageData GetImage(string id)
{
return _images[id];
}
internal class ImageData
{
private byte[] _data;
public string FileName { get; set; }
public string ContentType { get; set; }
public string Id { get; set; }
public byte[] Data
{
get { return _data; }
}
public ImageData(string fileName, string contentType, byte[] data)
{
FileName = fileName;
ContentType = contentType;
_data = data;
Id = Guid.NewGuid().ToString();
}
}
}
Позже можно загнать все данные в базу или на диск, как кому нравиться.
Создадим вид, Index, он у нас один.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script type="text/javascript" src="@Url.Content("~/Scripts/tiny_mce/tiny_mce_popup.js")"></script>
<script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.form.js")" type="text/javascript"></script>
<style type="text/css">
#image {height: 262px;overflow: auto;width: 440px;}
#file { opacity:0;position:relative;z-index: 2;-moz-opacity:0 ; filter:alpha(opacity: 0);width:100%;height:100% }
</style>
</head>
<body>
@using (Html.BeginForm("Index", "Image", FormMethod.Post, new { id = "upload", enctype = "multipart/form-data" }))
{
<div>
<div>
<fieldset>
<legend>Предварительный просмотр</legend>
<div id="image"><img id="preview" alt="Кликни для просмотра файлов" src="" /></div>
<input id="file" type="file" name="file" onchange="$('#upload').submit();"/>
</fieldset>
</div>
</div>
<div>
<input type="button" id="insert" name="insert" value="Вставить" />
<input type="button" id="cancel" name="cancel" value="Отмена" >
</div>
}
</body>
</html>
И скрипт:
$(document).ready(function () {
var options = {
dataType: "json",
beforeSubmit: function (data) {
$('#preview').attr('src', '/Content/images/spinning.gif');
},
success: function (data) {
$('#preview').attr('src', '/Image/ById/' + data.Id);
}
};
// Грузим асинхронно
$('#upload').ajaxForm(options);
// Выбор файла при клике на изображении
$('#preview').click(function () {
$('#file').click();
});
// Auto popup file select dialog
//$('#file').click();
});
Вот в принципе и все, можно тестировать наш контроллер загрузчика.
В результате мы имеем:
2. Плагин
Теперь перейдем к созданию плагина для tinyMCE. Исходники копируем из плагина example.
Я назвал свой плагин imagelight.
editor_plugin.js
(function() {
// Load plugin specific language pack
tinymce.PluginManager.requireLangPack('imagelight');
tinymce.create('tinymce.plugins.ImagelightPlugin', {
init : function(ed, url) {
tinyMCE.activeEditor.execCommand('mceExample');
ed.addCommand('mceImagelight', function() {
ed.windowManager.open({
file : '/Image',
width : 480,
height : 385,
inline : 1
}, {
plugin_url : url // Plugin absolute URL
});
});
ed.addButton('imagelight', {
title : 'imagelight.desc',
cmd : 'mceImagelight',
image : url + '/img/imagelight.png'
});
ed.onNodeChange.add(function(ed, cm, n) {
cm.setActive('imagelight', n.nodeName == 'IMG');
});
},
getInfo : function() {
return {
longname : 'Imagelight plugin',
author : 'AldeFalco',
authorurl : 'http://tinymce.moxiecode.com',
infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/example',
version : "1.0"
};
}
});
tinymce.PluginManager.add('imagelight', tinymce.plugins.ImagelightPlugin);
})();
Переносим весь код из скрипта в
js/dialog.js
tinyMCEPopup.requireLangPack();
var ImagelightDialog = {
init: function () {
var options = {
dataType: "json",
beforeSubmit: function (data) {
$('#preview').attr('src', '/Content/images/spinning.gif');
},
success: function (data) {
$('#preview').attr('src', '/Image/ById/' + data.Id);
}
};
$('#upload').ajaxForm(options);
$('#preview').click(function () {
$('#file').click();
});
},
insert : function() {
// Insert the contents from the input into the document
tinyMCEPopup.editor.execCommand('mceInsertContent', false, $('#image').html());
tinyMCEPopup.close();
}
};
tinyMCEPopup.onInit.add(ImagelightDialog.init, ImagelightDialog);
Кстати, если не перенести, то работать не будет — у jquery и tinyMCEPopup конфликт инициализации.
Для файлов ресурсов напишем:
tinyMCE.addI18n('en.imagelight',{
desc : 'Light Image Uploader'
});
tinyMCE.addI18n('en.imagelight_dlg',{
title : 'Upload image',
preview : 'Preview',
alt : 'Click here to upload an image file',
});
Модифицируем вид.
<title>{#imagelight_dlg.title}</title>
<script type="text/javascript" src="@Url.Content("~/Scripts/tiny_mce/plugins/imagelight/js/dialog.js")"></script>
...
<div class="panel_wrapper">
<div id="general_panel" class="panel current">
<fieldset>
<legend>{#imagelight_dlg.preview}</legend>
<div id="image"><img id="preview" alt="{#imagelight_dlg.alt}" src="" /></div>
<input id="file" type="file" name="file" onchange="$('#upload').submit();"/>
</fieldset>
</div>
</div>
<div class="mceActionPanel">
<input type="button" id="insert" name="insert" value="{#insert}" onclick="ImagelightDialog.insert();" />
<input type="button" id="cancel" name="cancel" value="{#cancel}" onclick="tinyMCEPopup.close();" />
</div>
}
В итоге мы имеем довольно простой и легкий загрузчик. На все ушло пару часов. Итоговый результат: