Как стать автором
Обновить

Как защититься от неожиданной отправки комментария по Ctrl+Enter?

Время на прочтение6 мин
Количество просмотров4.7K
(Опыт успешной борьбы с ветряными мельницами.)

C завидной регулярностью в комментариях встречаются оборванные на полуслове сообщения с приписками о том, что «извините, само отправилось», "сорвалось", и продолжением мысли. Иногда говорят, что разгадали причину такого поведения сайта. Поэтому хочу сообщить, что я не одинок в своей догадке, и более того, около полугода назад я решил эту проблему с помощью юзерскрипта. С тех пор ложные отправки у меня прекратились, но я не мог быть уверен, что причина ложных отправок только в этом, поэтому опыт использования скрипта и догадки других пользователей должны были это подтвердить.

И вот, попалась на глаза заметка о том, что кто-то ещё тоже видит причину ложной отправки в этом:
habrahabr.ru/blogs/client_side_optimization/137318/#comment_4572800 (31 янв 2012)
Поиск по Гуглу показал, что причину разгадывали и раньше: habrahabr.ru/qa/9515/#answer_40702 (15 июля 2011)
habrahabr.ru/qa/8447/#answer_35939 (12 июня 2011)
habrahabr.ru/company/regru/blog/105763/#comment_3318569 (8 окт 2010)
habrahabr.ru/blogs/bsdelniki/118485/#comment_3863623 (1 мая 2011)
habrahabr.ru/blogs/google/118622/#comment_3873012 (9 мая 2011) и т.д., можно продолжать долго, эти ссылки — из первых 30 результатов поиска (поиск по Сфинксу на сайте не годится, он не ищет по точной фразе; поиск Гугла и Яндекса встроен в тот же скрипт. Надо учитывать, что поиск по поисковику — неполон, не покажет гарантированно всех случаев, а для новых сообщений вообще не будет некоторое время (часы, дни, недели) работать).

Способ решения через техподдержку сайта уже показал свою неэффективность. Например, баг скрипта показа исходного кода программ при появлении тега <BR> не исправлен более полугода. Кроме того, у создателей скрипта может быть противоположное мнение о «полезности» данной функции. Поэтому отключение автоотправки по Ctrl+Enter гораздо быстрее реализовать самостоятельно (некоторые заботы, зато сразу, надёжно (до смены скриптов на сайте) и контролируемо).

До октября 2011 успешно работал юзерскрипт:
var win = (typeof unsafeWindow !='undefined')? unsafeWindow: window;
if(win.commentForm)
		win.commentForm.sendOnEnter = function(){};
(кроме браузера Chrome)

С того времени скрипты на сайте обновились, и видно (эксперименты можно безвредно провести в режиме браузера «offline»), что за отправку отвечают уже другие участки кода, вот они:

(файл view.js — для раздела QA)
var answer_form = $('#add_answer_form');
var answer_text = $('#answer_text');
/**
* CTRL + ENTER
*/
answer_text.keydown(function (e) {
	if ( (e.ctrlKey || e.metaKey ) && e.keyCode == 13) {
		$('input.submit',answer_form).click()
	}
});

И в другом месте (файл comments.js, для остальных разделов):
$(document).ready(function(){
	var comments_form = $('#comments_form');
	var comment_text = $('#comment_text');
	comment_text.keydown(function (e) {
	if ( (e.ctrlKey || e.metaKey ) && e.keyCode == 13) {
		$('input.submit',comments_form).click()
	}
	});
...

Забавны эти скрипты. Если бы у нас был рутовый доступ к сайту или хотя бы проксомитрон (известный инструмент правки потока на прокси-сервере), дело бы было за малым. Сейчас же надо сделать в несколько раз больше дополнительных движений, чтобы исправить разрушительные последствия нескольких строчек. С другой стороны, в образовательных целях, это хорошо. Нам усложняют задачу, а мы крепчаем (в знании способов решения). И здесь покажем, как решить задачу, «раскрутив» проблему от источника.

Видим навешивание события с анонимной функцией через jQuery. Насколько известно, событие можно снять, если написать $('#comment_text').unbind('keydown', та же самая функция), а с функцией — проблема, она анонимная, а юзерскрипт срабатывает вообще до создания обрабочика keydown. (UPD 05.02, 22:00taliban показал, как с помощью «DOMContentLoaded» применить более простое решение через метод jQuery .unbind('keydown'). Код — в дополнении к статье внизу.) Повторение текста функции, например — не решает вопроса. Отключать и переделывать следствие — $('input.submit', $('#comments_form')).click()? Вопроса не решает, потому что в неё не передаётся «e» — объект события, из которого взяли бы данные о нажатой клавише. Значит, переделываем «keydown» — метод jQuery, обрабатывающий нажатие на клавишу в таком виде (текст из сжатого jQuery):
function (a, c) {
	c == null && (c = a, a = null);
	return arguments.length > 0 ? this.bind(b, a, c) : this.trigger(b);
}

Здесь b — очевидно, константа «keydown», с — (в соответствии с описанием в документации) — handler(eventObject) — тот самый пользовательский обработчик, тело которого нам надо изменить, a — необязательный хеш — данные для обработчика. И этот участок кода в библиотеке — общий для многих событий. Нужно не нарушить эту функцию для прочих применений в jQuery и внести проверку, когда this равен $('textarea'). Метод нужно переопределить, обратившись не к самой функции, а к её прототипу.

И, поскольку действия происходят в юзерскрипте, эти операции непросто будет заставить исполняться в Хроме (там разделены окружения для юзерскриптов и скриптов), а в остальных — в начале надо писать объект окружения — win, который (typeof unsafeWindow !='undefined')? unsafeWindow: window.

Частичное решение задачи (Firefox, Opera, Safari) будет выглядеть так, и на нём пока остановимся, потому что общее решение — вне пределов сути статьи.
var win = (typeof unsafeWindow !='undefined')? unsafeWindow: window;
if(win.$){ //удалить отправку по Ctrl+Enter (не Chrome)
	var comT = win.$('#comment_text, #answer_text');
	if(comT){ //если найдена Textarea с вредным действием...
		comT.constructor.prototype.keydown = function(ha, fKeyD){
			fKeyD == null && (fKeyD = ha, ha = null); //повторяем прототип функции
			var k ='keydown';
			if(this[0].tagName=='TEXTAREA' &&  /^(comment|answer)_text$/.test(this[0].id)){
				fKeyD = function(ev){
					if((ev.ctrlKey || ev.metaKey)&& ev.keyCode == 13) //для демонстрации
						win.console.log('Ctrl-enter не пройдёт');
				};
			}
			return arguments.length > 0 ? this.bind(k, ha, fKeyD) : this.trigger(k);
		}
	}
}

Что получили с точки зрения красивости решения? Функцию библиотеки jQuery пришлось нагрузить специальной проверкой, потому что нет другой возможности отменить срабатывание действия отправки, и внутри неё подменить функцию, делающую клик по кнопке, пустой функцией (у нас она не пустая исключительно для демонстрации). Исходный текст библиотеки, разумеется, не патчится, потому что к нему у нас доступа как не было, так и нет.

На базе этого кода несложно сделать маленький юзерскрипт, решающий только эту задачу (просто использовать весь). Но, так как имеется общий юзерскрипт, решающий 2-3 десятка задач, под названием HabrAjax, создадим для него эту функцию. Она начнёт действовать у скрипта с версией 2.06 и выше.

Чтобы проверить неотправку сообщения по Ctrl-Enter в указанных браузерах, нужно установить юзерскрипт (короткий, приведённый выше, или HabrAjax), загрузить страницу сайта с полем ввода комментария, перевести браузер в режим «оффлайн» и убедиться, что изнутри поля ввода по Ctrl-Enter форма не отправляется. Если отключить скрипт, такое сочетание приведёт к анимации кнопки «Отправить», сопровождающей попытку отправки по Ajax (видна в консоли Firebug, например).

Задача решена — теперь случайное нажатие Ctrl + Enter, которое иногда случается при быстром наборе, не приведёт к неожиданной отправке (неисправляемого!) сообщения на сервер.

Для общего решения, включающего Chrome, проще всего сделать обходной манёвр. Создать новую клавишу отправки. Удалить старую клавишу со всеми навешанными на неё событиями. По клику на новой — форма будет автоматически отправляться во всех браузерах. Заодно, в Firefox можно будет исправить стили кнопки отправки, которые не поддавались исправлению юзерстилями другими способами вследствие особого порядка приоритетов применения стилей.
    UPD: про таб из первого комментария. Можно перехватывать таб и не давать его использовать по умолчанию браузера. Для написания кода это даже лучше — табами создаются отступы. Но кто-то может использовать табы для переключения между полями, а кто-то этого никогда не делает. Думаю включить такую настройку в общий скрипт HabAjax. И, скажем, по умолчанию запретить переход между полями. Есть противоположные мнения?
    UPD 22:30: По результатам обсуждения в комментариях, настройки HabrAjax обогатились двумя новыми (отключаемыми): «не отправлять комментарий по Ctrl+Enter» и «не уходить из textarea по Tab, а вводить табы». Последнее, если точнее, относится только к комментариям и позволяет ещё вводить "&nbsp;" по Ctrl+пробел. Между делом, сделано выделение нововведённых настроек в списке. В Chrome обе настройки не проявляются (даже в списке), поскольку не поддержаны, но через некоторое время есть возможность переделать скрипт так, чтобы он стал универсальным (что, кстати, до сих пор было верно для всех возможностей скрипта). Выложено в версии 0.808.
(На подходе (чтобы закрыть тему ввода символов) — управление отступами по Tab — Chift+Tab.)
    UPD 5 февр., 22:00: По инициативе и с помощью taliban написан скрипт для Firefox/Opera, использующий отключение обработчика событий средствами jQuery:
// ==UserScript==
// @name noCtrlEnter
// @version 1.1, 2012-02-05
// @description блокирует отправку комментария по Ctrl+Enter из textarea
// @include http://habrahabr.ru/*
document.addEventListener( 'DOMContentLoaded', function(){
	var win = (typeof unsafeWindow !='undefined')? unsafeWindow: window;
	if(!win.$) return;
	var comT = win.$('#comment_text, #answer_text');
	comT.length && comT.unbind('keydown');
},!1);
Теги:
Хабы:
Всего голосов 28: ↑22 и ↓6+16
Комментарии30

Публикации