Pull to refresh

Простой загрузчик изображений в tinyMCE для веб приложении на MVC

Reading time6 min
Views20K
Развитие моего проекта потребовало «прикрутить» в редактор на основе tinyMCE возможность вставлять изображения, которые хранились бы непосредственно на сервере приложения, и что бы это было бесшовно для конечного пользователя. Погуглив совсем немого и найдя все полезное как всегда на stackoverflow.com и изучив все что есть на сегодняшний день по этой теме, я понял, что придется изобретать свой велосипед. Основные мои мотивы были следующие:
Практически все «готовые» открытые реализации используют файловый принцип хранения, когда пользователь загружает сначала файл на сервер, в область хранения, а затем вставляет его в редактор. В итоге получается довольно сложный процесс, и кроме этого, сервер приложения рискует превратиться в файл помойку. Второй мотив — все решения были достаточно сложны и тяжелы. Например, некоторые из них использовали компоненты 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>
}


В итоге мы имеем довольно простой и легкий загрузчик. На все ушло пару часов. Итоговый результат:





Tags:
Hubs:
Total votes 14: ↑10 and ↓4+6
Comments2

Articles