Введение
Это очень интересная (и нетривиальная ИМХО) проблема.Чтобы решить эту проблему, я сначала создал образец страницы, которая имела несколько «групп» текстовых полей и кнопок, которые их включали:
По вашему вопросу, поле ввода отключенопо умолчанию и сделал «редактируемым» нажатием кнопки.
Лучшим решением оказался простой конечный автомат.Конечный автомат помогает понять (большое количество) событий, запускаемых браузером, только просматривая события, которые относятся к текущему состоянию, и игнорируя все остальные.
Реализация
Следующееявляется диаграммой конечного автомата (текст рядом с каждой стрелкой перехода указывает источник и имя события, которое инициирует этот переход):
Решение в действии (Проект JS Fiddle)
Для справки я также включил здесь код JavaScript:
function makeEditable(inputId, btnId) {
var state = "Locked", timeout = null, $input = $("#" + inputId), $btn = $("#" + btnId);
function setStateNow(precondition, newState, e) {
if (!precondition || state === precondition) {
if (window.console) { window.console.log("State change: " + state + " => " + newState + " (from " + e.target.id + "." + e.type + ")"); }
if (newState === "Locked") { // changing from any state to Locked
$input.prop("disabled", true);
$btn.val("Edit");
} else if (state === "Locked") { // changing from Locked to any other state
$input.prop("disabled", false).focus();
$btn.val("Save");
}
if (newState === "LockPending") { // changing from any state to LockPending
timeout = setTimeout(
function () { setStateNow("LockPending", "Locked", { target: { id: e.target.id }, type: "setTimeout" }); },
20);
} else if (state === "LockPending") { // changing from LockPending to any other state
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
}
if (newState === "Editable" && state === "LockPendingMouse") {
$input.focus();
}
state = newState;
return true;
}
return false;
}
function setState(e) {
var r;
if (e.data.rules) {
for (i in e.data.rules) {
r = e.data.rules[i];
if (setStateNow(r.precondition, r.newState, e)) {
return;
}
}
} else {
setStateNow(e.data.precondition, e.data.newState, e);
}
}
$input
.focus ({ precondition: "LockPending", newState: "Editable" }, setState)
.blur({ precondition: "Editable", newState: "LockPending" }, setState);
$btn
.click({ rules: [{ precondition: "Locked", newState: "Editable" }, { precondition: "LockPendingMouse", newState: "Locked" }, { precondition: "Editable", newState: "Locked" }] }, setState)
.mousedown({ precondition: "Editable", newState: "LockPendingMouse" }, setState)
.mouseleave({ precondition: "LockPendingMouse", newState: "Editable" }, setState)
.focus ({ precondition: "LockPending", newState: "Editable" }, setState)
.blur({ precondition: "Editable", newState: "LockPending" }, setState);
}
Код определяет одну функцию, makeEditable
.Эта функция принимает идентификатор элемента управления вводом и идентификатор соответствующей кнопки, что делает его редактируемым.Затем он создает замыкание с «частным» конечным автоматом.Это означает, что у нас будет один конечный автомат для каждого вызова makeEditable
(поскольку разные группы кнопок ввода могут находиться в разных состояниях).
Фактическое «мясо» обработки изменения состояния (что делает текстовое поле редактируемымили отключено) можно найти в setStateNow
закрытой функции, когда текущее или новое состояние Locked
.
Заключение
Я успешно протестировал решение в Chrome 14, Opera 10.51, IE 9,FireFox 5.0.1, Safari 5.1, Mobile Safari (iPad2).
Одно поведение пользовательского интерфейса, на которое я хотел бы обратить внимание, - это ответ на вопрос Что произойдет, если пользователь нажимает кнопку мыши надкнопка сохранения, но, удерживая кнопку, покидает область кнопки? В этом случае я решил вернуться в редактируемое состояние и снова установить фокус на поле ввода.
Если вы нашли ошибки или подумалиспособ улучшить реализацию, пожалуйста, не стесняйтесь оставлять комментарии к этому ответу.