Автосохранение черновика при создании записи

Уверен, из названия топика ясно чем мы будем заниматься. Для того, чтобы цель выглядела «материальнее»:

Скриншоты:
Автосохранение запрещено

Автосохранение разрешено

Сообщение об успешности или ошибке — стандартный Notice в правом верхнем углу.
Реализация.

Чтобы реализовать автосохранение, нам понадобиться поправить два файла и один добавить. Начинаем.

1. /templates/skin/ваш_скин/actions/ActionTopic/add.tpl

Здесь нам нужно добавить js скрипт и немного скорректировать форму.
Находим:
{literal}
<script language="JavaScript" type="text/javascript">
document.addEvent('domready', function() {	
	new Autocompleter.Request.HTML($('topic_tags'), DIR_WEB_ROOT+'/include/ajax/tagAutocompleter.php', {
		'indicatorClass': 'autocompleter-loading', // class added to the input during request
		'minLength': 2, // We need at least 1 character
		'selectMode': 'pick', // Instant completion
		'multiple': true // Tag support, by default comma separated
	}); 
});
</script>
{/literal}

Меняем на (комментарии в коде):

{literal}
<script language="JavaScript" type="text/javascript">
document.addEvent('domready', function() {	
	new Autocompleter.Request.HTML($('topic_tags'), DIR_WEB_ROOT+'/include/ajax/tagAutocompleter.php', {
		'indicatorClass': 'autocompleter-loading', // class added to the input during request
		'minLength': 2, // We need at least 1 character
		'selectMode': 'pick', // Instant completion
		'multiple': true // Tag support, by default comma separated
	}); 
});

// Создаем функцию для автосохранения черновика
autosave = function(){	
	// Если пользователь не запретил автосохранение, то 
	// формируем и отправляем ajax-запрос	
	if(!$('draft_autosave_disabled').get('checked')) {
		// Отправляем запрос
		JsHttpRequest.query(
	      	DIR_WEB_ROOT+'/include/ajax/autosaveTopic.php',
	       	{ params: $('form_topic').toQueryString() },
	       	function(result, errors) 
	       	{     		
	           	if (!result) {
	               	msgErrorBox.alert('Error','Please try again later');             
	       		}      
	       		if (result.bStateError) {
	               	msgErrorBox.alert(result.sMsgTitle,result.sMsg);
	       		} else {   
					msgNoticeBox.alert('Автосохранение','Черновик успешно сохранен');
					// Указываем пользователю на время сохранения
					$('autosave_time').innerHTML = "Последнее автосохранение: "+result.sTime;
					$('autosave_time').style.display="block";
					// Добавляем в форму информацию о идентификаторе черновика
					if(result.sId) {
						$('draft_id').setProperty('value', result.sId);
					}
		        }
	       	},
	       	true
	    );
	}
};
// Вызываем выполнение функции каждые 30 секунд
// Настроить по своему усмотрению
autosave.periodical(30000);
</script>
{/literal}


Теперь находим форму для создания топика и вносим следующие коррективы.

Меняем
<form action="" method="POST" enctype="multipart/form-data">

На
<form action="" method="POST" enctype="multipart/form-data" id="form_topic">

(Это сделано чисто из соображения удобства селектора. Если хотите, можно этого не делать — просто поменять в js селектор.)

Сразу после этой строки вставляем
<input type="hidden" name="draft_id" value="0" id="draft_id"/>

(Здесь мы будем хранить идентификатор текущего autosave-черновика.)

Перед
<p class="buttons">

Добавляем
<p><label for=""><input type="checkbox" id="draft_autosave_disabled" name="draft_autosave_disabled" class="checkbox" value="0"/> 
					— запретить автосохранение черновика</label><br />
					<span class="form_note" id="autosave_time">Последнее автосохранение: Не сохранялось.</span></p>	

(Это сделано для того, чтобы пользователь мог создавая запись, вручную отключить автосохранения — ему достаточно для этого поставить в этом поле галочку.)

2. Создаем файл /include/ajax/autosaveTopic.php

Это файл будет обрабатывать запрос на сохранение и возвращать ответ. Скачиваем файл здесь. Комментарии к действиям по коду расставлены достаточно обильно.

(С удовольствием бы выложил код в записи, но там 200 строк, и не эстетично, и редактор не позволяет :))

3. Файл /classes/actions/ActionTopic.class.php

Далее, есть несколько путей реализации. Я выбрал наиболее простой, с той целью, чтобы не трогать структуру базы и не переписывать все Event`ы по другому.

Для того, чтобы понять, что мы делаем в этом action`е, давайте представим сам процесс работы пользователя с новой записью.

а) Открывается страничка «создать» («редактировать», в данный момент не важно).
б) На ней создается форма, в которой указан идентификатор autosave-черновика = 0.
в) При отправке ajax запроса на автосохранение, система создаст новый черновик, вернет его идентификатор, который будет занесен в поле draft_id.
г) Каждый следующий запрос автосохранения будет обновлять этот черновик.
д) Далее, существует два варианта: первая, пользователь не смог по каким-то причинам сохранить запись в черновики или опубликовать — тогда наше автосохранение ему очень поможет (храниться со всеми черновиками), вторая — пользователь сохранил запись или опубликовал ее. В этом случае, мы просто удаляем autosave-черновик, закрепленный за записью. Останется либо новый черновик пользователя, либо публикация, либо autosave создастся заново (в случае, когда запись не прошла валидацию и возвращена системой «на доработку»).

Есть и другие схемы работы, но о них позже. Давайте сначала реализуем это.

Находим
protected function SubmitAdd() {
		/**
		 * Проверяем отправлена ли форма с данными(хотяб одна кнопка)
		 */		
		if (!isset($_REQUEST['submit_topic_publish']) and !isset($_REQUEST['submit_topic_save'])) {
			return false;
		}	

И добавляем после этих строк
/**
		 * Здесь мы проверяем, если есть прикрепленный autosave,
		 * то удаляем его. В случае, если заметка не пройдет валидацию,
		 * autosave-черновик будет создан заново 
		 */		
		if( $sDraftId = getRequest('draft_id',0) ) {
			@$this->Topic_DeleteTopic($sDraftId);
		}	


Находим
protected function SubmitEdit($oTopic) {	

И добавляем после этой строки
/**
		 * Здесь мы проверяем, если есть прикрепленный autosave,
		 * то удаляем его. В случае, если заметка не пройдет валидацию,
		 * autosave-черновик будет создан заново 
		 */
		if( $sDraftId = getRequest('draft_id',0) ) {
			@$this->Topic_DeleteTopic($sDraftId);
		}	


На этом наши приключения закончились, можете тестировать. Если о чем-то забыл, пишите в комментариях.

Теперь, о других схемах. Вообще, предполагаю, что будет недовольство из-за увеличения количество запросов на базу данных и объема хранимой информации. Но…

В WordPress действует система «ревизии» записи. Каждое автосохранение, если оно чем-то отличается от предыдущего будет записано в базу отдельной строкой, с пометкой review. Плюс в том, что ревизии живут своей отдельной жизнью, и можно вернуть свою запись к состоянию любой из них. Минус также очевиден, очень большой объем дополнительной информации. В блоге одного человека это может быть некритичным, а вот на блогоплатформе так играться, наверное, не стоит. Тем более, такое решение вынуждает нас менять структуру базы — автосейв в этом случае должен иметь особый статус и привязку к родительской заметке.

Такое решение не подходит.

Можно было бы поступить так: при передачи запроса на публикацию, при существующем autosave-черновике доставать из базы этот черновик и update`ить его, а не создавать новую запись, удаляя черновик. Но, приводит к необходимости почти полностью переписывать все Event, связанные с добавлением, редактированием и обработкой отправленных форм (submit). Вся проблема в том, что запись может и не пройти валидацию. Тогда нам нужно учитывать это, перепривязывая черновик от одной страницы до другой…

Сильно переписывать module не хочется, тем более один из центральных. Будут потом проблемы при апгрейдах.

В общем, если кто-то придумает простой выход, как оптимизировать работу с черновиками, пишите в комментариях. Будем разбираться.

22 комментария

avatar
весьма интересно!) спасибо, возьму на заметку.
  • F-5
  • +1
avatar
большое спасибо!
avatar
пользуйтесь на здоровье :)
avatar
Лучшее спасибо — плюс в карму. Жаль, не могу.
avatar
Хорошо бы ещё эту штуку в каталог запихнуть. Вещичка интересная, видел такое на одном портале как-то, и с удовольствием ею пользовался — вещь IMHO.
  • ALF
  • +1
avatar
большое спасибо))) как раз то о чем я не давно говорил)
avatar
Не единожды мечтал о такой штуке. Спасибо большое!
avatar
Хорошо бы удаление черновика делать после всех проверок. Что бы не рвать волосы на голове. Если топик не прошел валиджацию, а черновик уже удалили
avatar
С этим как раз проблем нет. Если топик не прошел валидацию, то все данные в форме останутся на месте. Ничего не пропадет, а через 30 секунд как обычно контент формы будет помещен в новосозданный авто-черновик.
avatar
Этот вариант я не продумал. Дальше уже пойдет экономия на спичках
avatar
Будет ли включено автосохранение в версию 0.4? Архиполезная вещь!
avatar
У меня БД сейчас пестрит черновичками, и у меня два вопроса:
а) черновики так и будут висеть мёртвым грузом до бесконечности?
б) Кроме как через БД никак не вернуться к сохраняемым периодически версиям (как это реализовано в WordPress, где можно перейти к той или иной версии и её восстановить)?
avatar
Принцип возврата к версиям увидел — всё ок. Но как дела с автоудалением хлама?
avatar
А как должны быть дела? Если вам нравиться система WP, то как там с этим дела?
avatar
Согласен… Тоже не удаляется автоматом. Надо просто подобрать, очевидно, какой-то такой интервал между автосохранениями, чтобы не каждые 30 сек. навая запись в таблице появлялась. Поставил 3 минуты. Но просто сколько будет хлама, если юзер, предположим, час пишет топик. С другой стороны, можно и 10 мин. поставить, но это много. В идеале, если это возможно, здорово было бы как-то крона настроить, чтоб, например, искал черновики и при наличии >=5 черновиков для одного топика за один день, сносил бы, например, в 00:00 четыре, оставляя последний. Ну как-то так…
avatar
У меня только что вылетела Опера вместе с результатом четырёхчасового мозгового штурма…

Вместо того, чтобы беситься — пошёл на лайвстрит.ру, вбил «автосохранение» в поиск и радуюсь найденному.
Непременно поставлю. Функция необходима в базовой поставке!

Авторы, СПАСИБО!
  • Daaa
  • +2
avatar
Ахтунг!
Пользователь расстроен. А расстроен вот почему:

'topic_create_text_error_unique' => 'Вы уже писали топик с таким содержанием'


Я пытаюсь разобраться, почему оное выскакивает, но такого топика (опубликованного) нет! Но в базу много записей от автосохранения черновика. Поможите, пожалуйста, что бы это могло быть?
avatar
Та же проблема. У меня проявляется при редактировании уже существующего топика.
Помогает только удаление всех черновиков и быстрая отправка топика.
avatar
Я, в итоге, отключил автосохранение, а то пока решения никакого нет, а это, похоже, баг какой-то!
avatar
Хорошо бы ещё сделать проверку на то, написал ли пользователь вообще что-то в поле ввода.

Я сейчас сделал в add.tpl:
if(!$('draft_autosave_disabled').get('checked')&&$('topic_text').get('innerHTML')!='')

, вроде бы работает, хотя могут быть неожиданности, не дружу пока с MooTools.

Ещё хорошо бы добавить проверку, были ли внесены изменения с последнего сохранения, но сейчас уже совсем нет на это времени(
  • Daaa
  • 0
avatar
проблема в том что автосохранение не знает что этот топик уже сохранен, сохраняет его снова и только тогда записывает в /> что топик сохранен под таким-то номером.

решается это так: />
avatar
проблема в том что автосохранение не знает что этот топик уже сохранен, сохраняет его снова и только тогда записывает в <input type=«hidden» name=«draft_id» value=«0» id=«draft_id»/> что топик сохранен под таким-то номером.

решается это так: <input type=«hidden» name=«draft_id» value="{$_aRequest.topic_id}" id=«draft_id»/>
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.