...

среда, 25 декабря 2013 г.

[Из песочницы] Регистрация на сайте: c начала и до обеда

Привет хабр!

Я работаю в области web-разработки и на днях у меня появилась интересная задача – необходимо было создать сложную форму регистрации, на которой будет расположено двадцать два (22!) текстовых поля и один большой список с чекбоксами.

Я всегда руководствовался принципом, что большое количество полей на форме регистрации очень неприятно для пользователей, даже если они будут получать материальные бонусы по завершению. Поэтому я всегда старался сделать форму максимально простой, максимум в 4 поля, если это было возможно со стороны бизнеса (заказчика). И даже пренебрегал полем с капчей, использовав вместо нее скрытую js-капчу, или вовсе отказывался от нее. Но все попытки донести это заказчику были безуспешны.

Под хабракатом я попытаюсь создать максимально универсальное решение подобных задач




Первая мысль была о том, что пропали все полимеры, но еще немного подумав стало очевидно, что многие поля можно разбить по группам, а некоторые так вообще удалить. В итоге у меня получилось 4 формы:



  • «регистрационные данные» (логин, пароль, емейл, телефон)

  • «персональные данные» (дата рождения, пол, инициалы)

  • «место работы» (сфера, должность, оклад :))

  • «интересные ленты» (список чекбоксов)

  • «завершение регистрации», вывести все введенные данные




Теперь это надо как-то оформить для пользователей, исключив возникновение маниакального импульса и немедленного закрытия окна от вида всех этих полей и форм. Лучше всего сделать заполнение полей поэтапно, форма за формой. И еще желательно сделать roadmap (карту), которая наглядно покажет, что предстоит заполнить, на каком этапе я сейчас и сколько мне еще осталось.

Погуглив я не нашел ничего подходящего и начал подумывать как приспособить готовый слайдер изображений (вместо изображений использовать div с полями). Но мне очень хотелось рисовать roadmap с ползунком, который по мере заполнения форм будет двигаться вперед, пока не дойдет до конца. И еще неплохо бы предусмотреть нелинейное движение ползунка — чтобы можно было перепрыгивать пункты, если совсем не хочется заполнять какую-то форму.

После экспериментов с разными слайдерами я полностью отказался от этой идеи – решил создать свой собственный, новенький и чудесный велосипедик. Пусть он будет с квадратными колесами, т.к. опыт разработки плагинов нулевой, зато будет на 100% удовлетворять задаче, не содержать ничего лишнего и, самое главное, возможно понадобиться еще кому-то.

Итак, обертка для плагина, пусть он будет называться Roadmap.

(function ($) {
$.fn.Roadmap = function (){

};
})(jQuery);


Отлично. Теперь добавим возможность настройки некоторых опций плагина (перед его инициализацией), предусмотрим несколько публичных методов и добавим замыкание, чтобы мы могли использовать функции jQuery для нашего объекта.



(function ($) {
$.Roadmap = $.Roadmap || {};
$.extend($.Roadmap, {
extend: function (methods) {
$.extend($.fn.Roadmap, methods);
$.fn.extend(methods);
}
});

//объявляем возможные настройки плагина
$.fn.Roadmap = function (options) {
var options = $.extend({
onInit: null,
allowJump: true,
voyagerSpeed: 300,
voyagerPosition: 0,
checkpoints: [],
onloadEvent: null,
ckeckpointNext: null,
checkpointPrev: null,
width: 400,
}, options);

//добавляем публичные методы
//используем $.Roadmap.extend, в самом верху
$.Roadmap.extend({
CurrentPosition: function () {
},
MoveNext: function () {
},
MovePrev: function () {
}
});

//замыкание, дает возможность использовать методы\объекты jQuery, например $.animate()
return this.each(function () {

//код плагина

});
};
})(jQuery);


Готово. Но код плагина как-то теряется, лучше вынести его в отдельное место, так будет более читабельно и не забыть описать публичные функции. И чтобы было совсем читабельно, все в спойлер.


код плагина полностью


(function ($) {
$.Roadmap = $.Roadmap || {};
$.extend($.Roadmap, {
extend: function (methods) {
$.extend($.fn.Roadmap, methods);
$.fn.extend(methods);
}
});

$.fn.Roadmap = function (options) {
var options = $.extend({
onInit: null, //подразумевается функция, вызов при инициализации
allowJump: true, //возможность нелинейно заполнять формы, т.е перепрыгивать
voyagerSpeed: 300, //скорость анимации ползунка при переходах между checkpoint
voyagerPosition: 0, //начальная позиция ползунка
checkpoints: [], //массив чекпоинтов и их callbacks
ckeckpointNext: null, //callback при прокрутке вперед (срабатывает при MoveNext())
checkpointPrev: null, //анаогично верхнему
width: 400, //ширина плагина в пикселях
}, options);

var $roadmap = $("<div>").addClass("roadmap"),
$voyager = $("<div>").addClass("voyager"),
voyagerPosition = options.voyagerPosition,
voyagerOffset = -1;

//private функции (она всего одна, но всеже)
var methods = {
determineWidth: function ($obj) {
var r = /[px|em]{2,}/g,
w = 0;

w += parseInt($obj.css("padding-left").replace(r, ""));
w += parseInt($obj.css("padding-right").replace(r, ""));
w += parseInt($obj.css("border-left-width").replace(r, ""));
w += parseInt($obj.css("border-right-width").replace(r, ""));
w += parseInt($obj.css("margin-left").replace(r, ""));
w += parseInt($obj.css("margin-right").replace(r, ""));
return w;
}
};

//основной код, инициализация плагина
var make = function () {
$(this)
.css("width", options.width)
.css("display", "none");

var $mark = $("<div>").addClass("mark"),
$map = $("<div>").addClass("map"),
$checkpoint = {};


$(options.checkpoints).each(function (i, o) {
$checkpoint = $("<div>").addClass("checkpoint");
$checkpoint
.append($("<div>"))
.click(function (e) {
if ((options.allowJump ||
!e.originalEvent) &&
$(e.target).closest(".voyager").length == 0) {

var ts = $(this),
tsOffset = ts.offset(),
rmOffset = $roadmap.offset();

voyagerPosition = i;
if (voyagerOffset < 0) {
voyagerOffset = $voyager.offset().left;
$voyager.css("left", voyagerOffset);
}

$voyager.animate({ left: (voyagerOffset + tsOffset.left - rmOffset.left - parseInt($map.css("padding-left"))) }, 400);

$("div.mark")
.find("div.marklabel").removeClass("active").end()
.find("div.marklabel:eq(" + i + ")").addClass("active");

if (o.hndl != null &&
typeof (o.hndl) === "function") {
o.hndl(voyagerPosition);
}
}
});

$map.append($checkpoint);

if (i < options.checkpoints.length - 1) {
$map.append($("<div>").addClass("road"))
$mark
.append($("<div>").addClass("marklabel").html(o.text))
.append($("<div>").addClass("road"));
}
else {
$map.append($("<div>").addClass("clear"));
$mark.append($("<div>").addClass("marklabel").html(o.text))
}
});

$roadmap
.append($map)
.append($mark);

$(this).append($roadmap);

var roadLength = 0,
checkpointsTotalLength = 0;

checkpointsTotalLength += methods.determineWidth($checkpoint.find("div"));
checkpointsTotalLength += methods.determineWidth($checkpoint);

roadLength = Math.floor((options.width - checkpointsTotalLength * 4) / 3);
roadLength -= methods.determineWidth($map);
roadLength -= methods.determineWidth($(".road", $map));

$map
.find(".road").width(roadLength).end()
.find(".checkpoint").eq(voyagerPosition).prepend($voyager).end();

$mark
.find(".marklabel:eq(" + voyagerPosition + ")").addClass("active").end()
.find(".marklabel").width(checkpointsTotalLength).end()
.find(".road").width(roadLength).end();

if (options.onInit != null &&
typeof (options.onInit) === "function") {
options.onInit();
}

$(this).show();
$map.find(".checkpoint").eq(voyagerPosition).trigger("click");
};

//public функции, о которых я говорил выше
$.Roadmap.extend({
CurrentPosition: function () {
return voyagerPosition;
},
MoveNext: function () {
if (voyagerPosition + 1 < options.checkpoints.length) {
++voyagerPosition;

$("div.roadmap .checkpoint:eq(" + voyagerPosition + ")").trigger("click");

if (typeof (options.ckeckpointNext) === "function") {
options.ckeckpointNext(voyagerPosition);
}
}
},
MovePrev: function () {
if (voyagerPosition - 1 >= 0) {
--voyagerPosition;

$("div.roadmap .checkpoint:eq(" + voyagerPosition + ")").trigger("click");

if (typeof (options.ckeckpointPrev) === "function") {
options.ckeckpointPrev(voyagerPosition);
}
}
}
});
return this.each(make);
};
})(jQuery);//Публичные методы
$.Roadmap.extend({
CurrentPosition: function () {
return voyagerPosition;
},
MoveNext: function () {
if (voyagerPosition + 1 < options.checkpoints.length) {
++voyagerPosition;

$("div.roadmap .checkpoint:eq(" + voyagerPosition + ")").trigger("click");

if (typeof (options.ckeckpointNext) === "function") {
options.ckeckpointNext(voyagerPosition);
}
}
},
MovePrev: function () {
if (voyagerPosition - 1 >= 0) {
--voyagerPosition;

$("div.roadmap .checkpoint:eq(" + voyagerPosition + ")").trigger("click");

if (typeof (options.ckeckpointPrev) === "function") {
options.ckeckpointPrev(voyagerPosition);
}
}
}
});





Осталось вставить его на страницу, например так



$("#rmp").Roadmap({
checkpoints:
[{ text: "Учетные данные", },
{ text: "Персональные данные}]
});


Плагин доступен на github

Демо можно увидеть здесь


PS

Для передвижения $voyager по карте я использовал $.animate(), отказавшись от использования CSS3. Причиной послужило странное поведение конструкции $.css("-webkit-transform", «translateX(100)») в одной из последних версий Chromium (28.0.1482.0 (194616)), хотя в следующим релизе все работало.


PSPS

Плагин, на текущий момент, не готов к промышленной эксплуатации и нуждается в доделке. Ошибки позиционирования в разных браузерах, плавная смена форм, использование шаблонизатора в массиве checkpoints, ajax подтягивание форм и т.д… Но уже не в этом году, улетаю в отпуск )


Всех с наступающим 2014!


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers.


Комментариев нет:

Отправить комментарий