Регистрация на сайте: c начала и до обеда

от автора

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

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


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

  • «регистрационные данные» (логин, пароль, емейл, телефон)
  • «персональные данные» (дата рождения, пол, инициалы)
  • «место работы» (сфера, должность, оклад :))
  • «интересные ленты» (список чекбоксов)
  • «завершение регистрации», вывести все введенные данные

Теперь это надо как-то оформить для пользователей, исключив возникновение маниакального импульса и немедленного закрытия окна от вида всех этих полей и форм. Лучше всего сделать заполнение полей поэтапно, форма за формой. И еще желательно сделать roadmap (карту), которая наглядно покажет, что предстоит заполнить, на каком этапе я сейчас и сколько мне еще осталось.
Погуглив я не нашел ничего подходящего и начал подумывать как приспособить готовый слайдер изображений (вместо изображений использовать с полями). Но мне очень хотелось рисовать 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!

ссылка на оригинал статьи http://habrahabr.ru/post/207304/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *