Стикеры с ASP.NET MVC и jQuery

Intro

Вообще, оригинал статьи называется "AJAX-enabled Sticky Notes With PHP & jQuery", то есть используется PHP. Но поскольку мне более интересны .NET технологии, я немного переписал статью для использования в ASP.NET MVC.

В этой статье рассматривается способ создания системы заметок в стиле стикеров с использованием библиотеки jQuery 1.4. Кроме того, нам понадобится плагин "Fancybox" и jQuery UI.

Шаг 1: База данных.

В проекте используется технология LinqToSql. Создаётся таблица с полями: Id, Name, Text, Date, xyz. Создаётся стандартный dbml-файл, в котором находится всего один класс, Stickre. Затем, мы создаём вспомогательный класс Repository, в котором осуществляется основная логика работы с БД.

public class Repository
{
	private DemoDataClassesDataContext db = new DemoDataClassesDataContext();

	public IQueryable<Stickre> Stickre
	{
		get
		{
			// удаляем старые стикеры
			var old = from s in db.Stickres
					  where (DateTime.Now - s.Date) > TimeSpan.FromHours(1.0) && s.Id > 3
					  select s;
			old.ToList().ForEach(s => db.Stickres.DeleteOnSubmit(s));
			Save();
			return db.Stickres;
		}
	}

	public void AddStickre(Stickre s)
	{
		// добавляем стикер
		db.Stickres.InsertOnSubmit(s);
	}

	public void Save()
	{
		db.SubmitChanges();
	}
}

Шаг 2: Разметка

Создаём представление (View) для нашего контроллера.

<div id="main">
	<a id="addButton" class="green-button" href="AddStickre">Добавить</a>
	<% foreach (var item in Model)
		{
			string[] xyz = item.xyz.Split('x');%>
		<div class="note <%=item.Color%>" style="left: <%=xyz[0]%>px; top: <%=xyz[1]%>px; z-index: <%=xyz[2]%>;">
			<%= Html.Encode(item.Text) %>
			<div class="author"><%= Html.Encode(item.Name) %></div>
			<span class="data"><%= item.Id %></span>
		</div>
	<% } %>
</div>

Как видите, здесь есть контейнер div, который содержит все стикеры и ограничивает их перемещение. Сами стикеры передаются из контроллера с помощью свойства Model. О контроллере будет написано чуть позже.

При нажатии на ссылку "Добавить", на странице появится форма с превью. Эта функциональность предоставлена плагином fancybox, который "отлавливает" результат запроса к AddStickre (в котором находится форма) и показывает его в виде всплывающего окна.

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<Demo.Models.Stickre>" %>

<h3 class="popupTitle">Добавить заметку</h3>
<!-- Превью: -->
<div id="previewNote" class="note yellow" style="left: 0; top: 65px; z-index: 1">
    <div class="body"></div>
    <div class="author"></div>
    <span class="data"></span>
</div>
<div id="noteData">
    <!-- Форма -->
    <form action="" method="post" class="note-form">
		<label for="Text">Текст заметки</label>
		<%= Html.TextArea("Text", Model.Text, new { id="note-body", @class="pr-body", cols="30", rows="6" })%>
		<label for="Name">Ваше имя</label>
		<%= Html.TextBox("Name", Model.Name, new { id="note-name", @class="pr-author" })%>
		<label>Цвет</label>
		<!-- Изменяем цвет -->
		<div class="color yellow"></div>
		<div class="color blue"></div>
		<div class="color green"></div>
		<!-- Отправляем данные: -->
		<a id="note-submit" href="" class="green-button">Отправить</a>
    </form>
</div>

В форме заполняются поля "Текст заметки", "Имя", а также выбирается цвет. Возможность показать стикер в реальном времени очень удобна, по нажатию на кнопку "Отправить", форма закрывается, а элемент превью копируется в основной div, освобождая нас от лишнего кода.

Шаг 3: Контроллер

Контроллер также не представляет собой ничего интересного. Для начала рассмотрим метод вывода всех стикеров.

public class HomeController : Controller
{
	Repository repository = new Repository();

	public ActionResult Index()
	{
		return View(repository.Stickre);
	}
	...
}

Этот метод использует свойство класса Repository, которое возвращает все заметки. Результат передаётся в представление в качестве параметра, в представлении эти значения находятся в свойстве Model.

Затем нам необходимы методы добавления заметки и сохранения позиции. При сохранении заметки, клиенту мы возвращаем идентификатор новой записи. Это необходимо для того, чтобы была возможность сохранить позицию стикера. Сам стикер не возвращаем, потому что на клиенте он уже есть, он был введён пользователем при добавлении.

public class HomeController : Controller
{
	...
	public ActionResult AddStickre()
	{
		// Возвращаем форму создания заметки
		var stickre = new Stickre();
		return View(stickre);
	}

	[AcceptVerbs(HttpVerbs.Post)]
	public string AddStickre(Stickre s)
	{
		// сохраняем результат ввода
		repository.AddStickre(s);
		s.Date = DateTime.Now;
		repository.Save();
		return s.Id.ToString();
	}

	public void UpdatePosition()
	{
		// обновляем данные в БД
		int id = int.Parse(Request.QueryString["id"]);
		Stickre s = repository.Stickre.First(stickre => stickre.Id == id);
		s.xyz = string.Format("{0}x{1}x{2}", Request.QueryString["x"], Request.QueryString["y"], Request.QueryString["z"]);
		repository.Save();
	}
}

Шаг 4: CSS

Вся разметка у нас находится в одном месте. Стили определены в файле style.css. Я разбил файл на три части.

styles.css – первая часть

.note{
	height		: 150px;
	padding		: 10px;
	width		: 150px;
	position	: absolute;
	overflow	: hidden;
	cursor		: move;
	font-family	: Trebuchet MS,Tahoma,Myriad Pro,Arial,Verdana,sans-serif;
	font-size	: 22px;
	/* Добавим красоты с помощью CSS3: */
	-moz-box-shadow		: 2px 2px 0 #DDDDDD;
	-webkit-box-shadow	: 2px 2px 0 #DDDDDD;
	box-shadow			: 2px 2px 0 #DDDDDD;
}
#fancy_ajax .note{ cursor : default; }
/* Три стиля для заметок: */
.yellow{
	background-color	: #FDFB8C;
	border				: 1px solid #DEDC65;	
}
.blue{
	background-color	: #A6E3FC;
	border				: 1px solid #75C5E7;	
}
.green{
	background-color	: #A5F88B;
	border				: 1px solid #98E775;	
}
/* В каждой заметке есть span с id. */
span.data	{ display : none; }
/* Кнопка "Добавить": */
#addButton{
	position			: absolute;
	top					: -70px;
	left				: 0;
}

В первой части мы определили внешний вид заметок и определили три цветовые схемы: жёлтую, синюю и зелёную. Эти цвета тоже используются для превью в форме создания заметки.

В каждой заметке есть невидимый span, в котором сохранён идентификатор заметки. Этот идентификатор отправляется на сервер при сохранении позиции стикера.

styles.css – вторая часть

/* Зелёная кнопка: */
a.green-button,a.green-button:visited{
	color		: black;
	display		: block;
	font-size	: 10px;
	font-weight	: bold;
	height		: 15px;
	padding		: 6px 5px 4px;
	text-align	: center;
	width 		: 60px;
	text-shadow	: 1px 1px 1px #DDDDDD;
	background	: url(/content/button_green.png) no-repeat left top;
}
a.green-button:hover{
	text-decoration		: none;
	background-position	: left bottom;
}
.author{
	/* Автор заметки: */
	bottom		: 10px;
	color		: #666666;
	font-family	: Arial,Verdana,sans-serif;
	font-size	: 12px;
	position	: absolute;
	right		: 10px;
}
#main{
	/* Контейнер, содержащий все стикеры и ограничивающий их перемещение: */
	margin		: 0 auto;
	position	: relative;
	width		: 980px;
	height		: 500px;
	z-index		: 10;
	background	: url(/content/add_a_note_help.gif) no-repeat left top;
}

Во второй части файла определён класс для кнопки, а также несколько мелких декоративных деталей.

styles.css – третья часть

h3.popupTitle{
	border-bottom	: 1px solid #DDDDDD;
	color			: #666666;
	font-size		: 24px;
	font-weight		: normal;
	padding			: 0 0 5px;
}
#noteData{
	/* Форма в всплывающем окне: */
	height			: 200px;
	margin			: 30px 0 0 200px;
	width			: 350px;
}
.note-form label{
	display			: block;
	font-size		: 10px;
	font-weight		: bold;
	letter-spacing	: 1px;
	text-transform	: uppercase;
	padding-bottom	: 3px;
}
.note-form textarea, .note-form input[type=text]{
	background-color: #FCFCFC;
	border			: 1px solid #AAAAAA;
	font-family		: Arial,Verdana,sans-serif;
	font-size		: 16px;
	height			: 60px;
	padding			: 5px;
	width			: 300px;
	margin-bottom	: 10px;
}
.note-form input[type=text] { height : auto; }
.color{
	/* Переключатель цвета на форме: */
	cursor			: pointer;
	float			: left;
	height			: 10px;
	margin			: 0 5px 0 0;
	width			: 10px;
}
#note-submit { margin : 20px auto; }

Шаг 5: jQuery

Для начала подключим необходимые библиотеки в head основного документа.

<link rel="stylesheet" type="text/css" href="<%= Url.Content("~/Content/Css/stickre.css") %>" />
<link rel="stylesheet" type="text/css" href="<%= Url.Content("~/Content/plugins/jquery.fancybox-1.2.6.css") %>" media="screen" />

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.0/jquery.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js"></script>
<script type="text/javascript" src="<%= Url.Content("~/Content/plugins/jquery.fancybox-1.2.6.pack.js") %>"></script>

<script type="text/javascript" src="<%= Url.Content("~/Content/Demos/stickre/script.js") %>"></script>

jQuery и jQuery UI мы подключили из библиотек Google. Теперь давайте подробно рассмотрим скрипты, используемые непосредственно для обеспечения функциональности сайта.

script.js - первая часть

$(document).ready(function(){
	var tmp;
	$('.note').each(function(){
		/* Ищем заметку с максимальным z-index */
		tmp = $(this).css('z-index');
		if(tmp>zIndex) zIndex = tmp;
	})
	/* Функция, делающая заметки таскаемыми: */
	make_draggable($('.note'));
	/* Настройки для fancybox: */
	$("#addButton").fancybox({
		'zoomSpeedIn'		: 600,
		'zoomSpeedOut'		: 500,
		'easingIn'			: 'easeOutBack',
		'easingOut'			: 'easeInBack',
		'hideOnContentClick': false,
		'padding'			: 15
	});
	/* подписываемся на событие keyup для кнопки "Добавить": */
	$('.pr-body,.pr-author').live('keyup',function(e){
		if(!this.preview)
			this.preview=$('#previewNote');
		/* Устанавливаем текст для превью, предварительно удалив html-теги: */
		this.preview.find($(this).attr('class').replace('pr-','.')).html($(this).val().replace(/<[^>]+>/ig,''));
	});
	/* Устанавливаем цвет заметки: */
	$('.color').live('click',function(){
		$('#previewNote').removeClass('yellow green blue').addClass($(this).attr('class').replace('color',''));
	});

Для начала, мы ищем заметку с максимальным значением z-index. Это значение будет увеличиваться на единицу, при перетаскивании какой-либо заметки. Таким образом, активный стикер всегда будет находится выше всех остальных.

Следующий момент, на который стоит обратить внимание - это использование метода live, вместо привычного bind. Это сделано для того, чтобы обрабатывались события вновь созданных элементов.

script.js - вторая часть

	/* Кнопка отправки заметки: */
	$('#note-submit').live('click', function(e) {
	    if ($('.pr-body').val().length < 4) {
	        alert("Текст заметки несущественен!")
	        return false;
	    }
	    if ($('.pr-author').val().length < 1) {
	        alert("Вы не ввели имя!")
	        return false;
	    }
	    $(this).replaceWith('<img src="/content/ajax_load.gif" style="margin:30px auto;display:block" />');
	    var data = {
	        'xyz': '0x0x' + (++zIndex).toString(),
	        'Text': $('.pr-body').val(),
	        'Name': $('.pr-author').val(),
	        'Color': $.trim($('#previewNote').attr('class').replace('note', ''))
	    };
	    /* Отправляем AJAX запрос: */
	    $.post('AddStickre', data, function(msg) {
	        if (parseInt(msg)) {
	            /* в ответ приходит идентификатор новой заметки: */
	            var tmp = $('#previewNote').clone();
	            tmp.find('span.data').text(msg).end().css({ 'z-index': zIndex, top: 0, left: 0 });
	            tmp.appendTo($('#main'));
	            make_draggable(tmp)
	        }
	        $("#addButton").fancybox.close();
	    });
	    e.preventDefault();
	})
});

Здесь мы обрабатываем отправку данных на сервер. Для начала проверяем данные, затем отправляем их с помощью метода post. Если передача данных осуществлена успешно, то форма ввода скрывается, а стикер добавляется в основной div.

script.js - третья часть

var zIndex = 0;
function make_draggable(elements) {
    /* Элементы - это объекты jQuery: */
    elements.draggable({
        containment: 'parent',
        start: function(e, ui) { ui.helper.css('z-index', ++zIndex); },
        stop: function(e, ui) {
            /* Отправляем новые координаты: */
            $.get('UpdatePosition', {
                x: ui.position.left,
                y: ui.position.top,
                z: zIndex,
                id: parseInt(ui.helper.find('span.data').html())
            });
        }
    });
}

В последней части, мы написали функцию make_draggable. Эта функция делает стикеры таскаемыми. Эта функция вызывается дважды. Первый раз при открытии страницы, а во второй - при создании нового стикера.

Заключение

Вы можете скачать готовый пример. Для корректной работы, необходимо запустить скрипт создания таблицы, а также прописать корректную строку подключения к базе данных в файле web.config.

Пример.
{{ title }}
  • {{ m.username }}{не представился} {{m.date}}

    {{ m.text }}

    • {{ child.username }} {{child.date}}

      {{ child.text }}

Высказаться по теме

user

2017.07.21