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

в 8:57, , рубрики: javascript, jquery plugins, web, Веб-разработка, метки: , ,

Регистрация на сайте: 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!

Автор: pushthebutton

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js