![](http://habr.habrastorage.org/post_images/eca/149/2c0/eca1492c04bfca640129c57c03a53af3.png)
Я работаю в области 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/
Добавить комментарий