jQuery custom radio and checkbox

в 13:35, , рубрики: checkbox, custom form elements, javascript, jquery, jquery plugin, radio, Песочница, стилизация радио и чекбоксов, метки: , , , , ,

Как обычно все началось из-за отсутствия или плохого поиска, или не полной реализации того что мне было необходимо.
А необходимы мне были кастомные radio и checkbox'ы которые я смог бы применять в своей повседневной работе при верстке. При этом они должны были бы работать в IE6+ и всех основных браузерах.
Также элементы должны были реагировать по клику на лейбл. И еще одно их могло быть на 1 странице сколько угодно с разными стилями(да иногда случаются такие мего дизайны).
Поэтому я решил взять все лучшее, что встречал в реализациях чекбоксов и радиобатонов на JS. И написать свой плагин jQuery который удовлетворял бы мои потребности.

Вид элементов реализовывался с помощью спрайта с 4 состояниями:
— неактивный вид;
— неактивный мышь нажата;
— активный;
— активный мышь нажата.
image

Т.к. придется работать со спрайтом то необходимо знать смешения по вертикали картинок в нем. Для этого необходимо передавать в плагин высоту элемента, а в спрайте расположить их так чтоб они занимали одинаковое по высоте пространство.
Также необходимо чтоб при вызове плагина передавался класс, через который я смогу стилизовать элемент.
После первоначальной реализации я понял, что необходимы еще пару условий, без которых плагин будет работать некорректно. Это возможность смены состояний элементов(актинвный/неактивный) и стилизация динамически созданных элементов.

Приведу для начала весь код плагина:

(function ($) {
    $.CustomData = {
        elements:$()
    };

    $.fn.extend({

        Custom:function (options) {

            var elements = this;
            $.CustomData.elements = $.CustomData.elements.add(elements);

            /*Дефолтные значения параметров*/
            var defaults = {
                customStyleClass:"checkbox",
                customHeight:"16"
            };

            /*Заменяем дефолтные опции на переданные если таковые есть*/
            options = $.extend(defaults, options);

            /*Вид при нажатии на активный и неактивный элементы*/
            var pushed = function () {
                var element = $(this).children('input');
                if (element.is(':checked')) {
                    /*смещения в спрайте*/
                    $(this).css('backgroundPosition', "0px -" + options.customHeight * 3 + "px");
                } else {
                    $(this).css('backgroundPosition', "0px -" + options.customHeight + "px");
                }
            };

            /*Отмечаем нажатый елемент все остальные сбрасываем, если они в групе(radio)*/
            var check = function () {
                var element = $(this).children('input');
                if (element.is(':checked') && element.attr('type') === 'checkbox') {/*Отмеченный чекбокс*/
                    $(this).css('backgroundPosition', '0px 0px');
                    $(this).children('input').attr('checked', false).change();
                    /*Меняем атрибут на неотмеченный и вызываем событие смены состояния элемента*/
                } else {
                    if (element.attr('type') === 'checkbox') {/*Неотмеченный чекбокс*/
                        $(this).css('backgroundPosition', "0px -" + options.customHeight * 2 + "px");
                    } else {
                        /*Радиобатоны*/
                        $(this).css('backgroundPosition', "0px -" + options.customHeight * 2 + "px");
                        $('input[name=' + element.attr('name') + ']').not(element).parent().css('backgroundPosition', '0px 0px');
                    }

                    $(this).children('input').attr('checked', 'checked').change();
                }

            };

            /*Обновление картинки при клике по лейблу и загрузке документа*/
            var update = function () {
                $.CustomData.elements.each(function () { /*Проходим по всем елементам и проверяем их состояние*/
                    if ($(this).is(':checked')) {
                        $(this).parent().css('backgroundPosition', "0px -" + $(this).attr('data-height') * 2 + "px");
                    } else {
                        $(this).parent().css('backgroundPosition', "0px 0px");
                    }
                });
            };

            /*Обновление при изменении состояния disabled/enabled */
            var refresh = function () {
                if (!$(this).prop('disabled')) {
                    $(this).parent().mousedown(pushed);
                    $(this).parent().mouseup(check);
                    $(this).parent().removeClass('disabled');
                } else {
                    $(this).parent().addClass('disabled');
                    $(this).parent().unbind('mousedown', pushed);
                    $(this).parent().unbind('mouseup', check);
                }
            };

            return this.each(function () {
                if ($(this).attr('data-init') != '1') {
                    $(this).attr('data-init', '1');
                    $(this).attr('data-height', options.customHeight);
                    /*Оборачиваем в <span></span>*/
                    $(this).wrap('<span/>');
                    /*Приписываем класс оформления переданный в параметрах*/
                    var span = $(this).parent().addClass(options.customStyleClass);
                
                    if ($(this).is(':checked') === true) { /*Задаем картинку еси элемент отмечен*/
                        span.css('backgroundPosition', "0px -" + (options.customHeight * 2) + "px");
                    }

                    /*Бинд на изменение состояния элемента и кастомное событие для обновления после программного изменения состояния кнопки*/
                    $(this).bind('change', update);
                    $(this).bind('custom.refresh', refresh);

                    if (!$(this).prop('disabled')) {
                        /*Бинд функций на span*/
                        span.mousedown(pushed);
                        span.mouseup(check);
                    } else {
                        /*Добавление класса если элемент неактивен*/
                        span.addClass('disabled');
                    }
                }
            });
        }
    });

})(jQuery);

Начал я с реализации структуры плагина, а вид она имеет такой:

(function ($) {
    $.fn.extend({

        Custom:function (options) {

		/*Дефолтные значения параметров*/
		var defaults = {
			customStyleClass:"checkbox",
			customHeight:"16"
		};

		/*Заменяем дефолтные опции на переданные если таковые есть*/
		options = $.extend(defaults, options);

            };

            return this.each(function () {
                
            });
        }
    });

Можно встретить такое описание выше напечатанного — «используется для создания типичного дополнения jQuery".

Начнём с конца:

return this.each(function () {
	if ($(this).attr('data-init') != '1') {
		$(this).attr('data-init', '1');
		$(this).attr('data-height', options.customHeight);
		/*Оборачиваем в <span></span>*/
		$(this).wrap('<span/>');
		/*Приписываем класс оформления переданный в параметрах*/
		var span = $(this).parent().addClass(options.customStyleClass);
	
		if ($(this).is(':checked') === true) { /*Задаем картинку еси элемент отмечен*/
			span.css('backgroundPosition', "0px -" + (options.customHeight * 2) + "px");
		}

		/*Бинд на изменение состояния элемента и кастомное событие для обновления после программного изменения состояния кнопки*/
		$(this).bind('change', update);
		$(this).bind('custom.refresh', refresh);

		if (!$(this).prop('disabled')) {
			/*Бинд функций на span*/
			span.mousedown(pushed);
			span.mouseup(check);
		} else {
			/*Добавление класса если элемент неактивен*/
			span.addClass('disabled');
		}
	}
});

В принципе в комментариях к коду все описано, могу добавить лишь

$(this).attr('data-init', '1');

использую для того чтоб знать стилизован уже элемент или нет(конечно можно и через родителя узнать но я решил протестировать такой вот метод).

$(this).attr('data-height', options.customHeight);

Здесь сохраняю высоту элемента т.к. элементов у меня с различными спрайтами может быть сколько угодно.

$(this).bind('custom.refresh', refresh);

Тоже интересный метод, привязывается на кастомное событие «custom.refresh» вызов функции refresh.
Например изменилось состояние элемента на неактивное, тогда необходимо изменить вид элементы и снять все события с него.
Например:

$('#radio3').removeAttr('disabled').trigger('custom.refresh');

элемент стал неактивен и произошло события «custom.refresh» благодаря которому выполнилась функция refresh.

Далее идут функции которые описаны в этой части:

Custom:function (options) {...};

Функция pushed — ставит смещение в спрайте для активного и неактивного вида, она довольно простая:

var pushed = function () {
	var element = $(this).children('input');
	if (element.is(':checked')) {
		/*смещения в спрайте*/
		$(this).css('backgroundPosition', "0px -" + options.customHeight * 3 + "px");
	} else {
		$(this).css('backgroundPosition', "0px -" + options.customHeight + "px");
	}
};

Фукция check:

/*Отмечаем нажатый элемент все остальные сбрасываем, если они в группе (radio)*/
var check = function () {
	var element = $(this).children('input');
	if (element.is(':checked') && element.attr('type') === 'checkbox') {/*Отмеченный чекбокс*/
		$(this).css('backgroundPosition', '0px 0px');
		$(this).children('input').attr('checked', false).change();
		/*Меняем атрибут на неотмеченный и вызываем событие смены состояния элемента*/
	} else {
		if (element.attr('type') === 'checkbox') {/*Неотмеченный чекбокс*/
			$(this).css('backgroundPosition', "0px -" + options.customHeight * 2 + "px");
		} else {
			/*Радиобатоны*/
			$(this).css('backgroundPosition', "0px -" + options.customHeight * 2 + "px");
			$('input[name=' + element.attr('name') + ']').not(element).parent().css('backgroundPosition', '0px 0px');
		}

		$(this).children('input').attr('checked', 'checked').change();
	}
};

Также все довольно ясно из комментариев в коде добавлю лишь об этом

$(this).children('input').attr('checked', false).change();

.attr('checked', false).change() — необходимо чтоб событие change() файрилось и атрибут checked был изменен. Пришлось поискать это на stackoverflow.

Функция Update:

/*Обновление картинки при клике по лейблу*/
var update = function () {
	$.CustomData.elements.each(function () { /*Проходим по всем елементам и проверяем их состояние*/
		if ($(this).is(':checked')) {
			$(this).parent().css('backgroundPosition', "0px -" + $(this).attr('data-height') * 2 + "px");
		} else {
			$(this).parent().css('backgroundPosition', "0px 0px");
		}
	});
};

Тут не вышло обойтись без переменной, куда можно было б сохранить все элементы, на которых вызывается плагин($.CustomData.elements). Описывается она выше $.fn.extend({...})

$.CustomData = {
    elements:$()
}; 

и при вызове плагина в нее помещается набор элементов

var elements = this;
$.CustomData.elements = $.CustomData.elements.add(elements);

И последняя функция, которая необходима при изменении состояния элемента refresh:

/*Обновление при изменении состояния disabled/enabled */
var refresh = function () {
	if (!$(this).prop('disabled')) {
		$(this).parent().mousedown(pushed);
		$(this).parent().mouseup(check);
		$(this).parent().removeClass('disabled');
	} else {
		$(this).parent().addClass('disabled');
		$(this).parent().unbind('mousedown', pushed);
		$(this).parent().unbind('mouseup', check);
	}
};

Довольно простая в понимании, просто снимаем обработчики или добавляем вновь и дописываем/убираем класс «disabled», через который можно задать вид элемента в неактивном состоянии(обычно прозрачность меняют).

Пример класса с описанием вида радиобатона:

.radio {
        display:block;
	height: 25px;
	width: 19px;
	background: url("radio.png") no-repeat 0 0 transparent;
	position:relative;
}

.radio.disabled{
	opacity:0.5;
	filter:Alpha(opacity="50");
}

.radio input{
	position:absolute;
	right:-400px;
	top:0px;
}

Вызов плагина:

$("input[type='radio']").Custom({
	customStyleClass:'radio',
	customHeight:'25'
});

Надеюсь это поможет таким как я в написании своих первых плагинов на jQuery.

Это все о чем я хотел поведать в связи со своими плохими поисками нужного мне плагина. Благодарю хорошего человека Андрея за помощь в познании jQuery и написании плагина а также разработчика вот этого плагина http://ryanfait.com/resources/custom-checkboxes-and-radio-buttons/ за то что он был на js и не до конца допилен, дав мне возможность для написания своего с необходимыми мне фишками. Буду рад коментам и критике для улучшения работы плангина.

Плагин тут https://github.com/n0r8/Custom-radio-checkbox

Автор: n0r8

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


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