Шаблон jQuery-плагина

16.10.2011 23:03 / Артём Волк / 960 просмотров / ...

Обновление от 09.11.2011: Благодаря комментариям оптимизирован код шаблона Обновление от 17.03.2012: Применены новые соглашения по именованию переменных

Решил собрать полный шаблон jQuery-плагина, который используем чаще всего. Решение имеет следующие особенности:

  • Есть настройки по умолчанию
  • Хранилище настроек и переменных в виде специального объекта
  • Поддержка method chaining
  • Поддержка public-методов

Шаблон легко дополняется:

  • callback'ами
  • custom event'ами

Плагин:

/*
	Некоторые соглашения: 
		- Формат имени файла: jquery.<название проекта>.<название плагина>[.min].js (всё в нижнем регистре)
		- Формат имён методов и переменных -- camelCase	(исключения описаны ниже)
		- Переменные, которые ссылаются на jQuery-объекты называются с префиксом "$", например "$someVar"

	В следующем блоке комментария идёт стандартный комментарий-заголовок для плагина	
*/

/*
	Demo plugin from project 'Demo Project'

	Dependencies: // Список зависимостей
		- jQuery 1.6.4+ - http://jquery.com/
*/
;(function($) { // Стандартная анонимная функция-замыкание, на случай комбинирования с другими скриптами, которые не заканчиваются точкой с запятой перед кодом ставим ";"

	/*
		Название плагина, см. комментарий ниже, где создаётся jQuery-плагин с таким методом
	*/
	var jqPluginName = 'demoProject_demoPlugin';

	/*
		"Класс" плагина
	*/
	var demoPlugin = function(element, options) {
	/*
		Инициализация
		--------------------------------------------------------------------------------------------------
	*/
		// В переменной config храним максимум данных, организовываем иерархически как удобно, локальные переменные -- только очень служебные и временные		
		// Эта переменная у каждого экземпляра плагина своя
		var config = $.extend(true, {}, $.fn.demoProject_demoPlugin.defaults, options);

		// Эту переменную удобно использовать как контекст для поиска DOM-элементов
		config.$context = element;

		// Если это необходимо из соображений производительности -- сохраняем ссылки на выбранные DOM-элементы в переменные, используем контекст для поиска элементов
		config.ui.$clicker = $(config.ui.clicker, config.$context);

		// Кстати:
		// if (config.ui.$clicker == null) {...}; // проверка, что была попытка выбрать элемент
		// if (config.ui.$clicker.length) {...}; // проверка, что что-то выбралось

	/*
		Обработчики UI
		--------------------------------------------------------------------------------------------------
	*/		
		config.ui.$clicker.click(handleClick);		

	/*
		Private-методы
	*/
		function handleClick() {
			var _this = $(this); // Пример локальной переменной
			_this.css({color: 'red'}).fadeOut('slow');
			return false;
		}

	/*
		Public-методы
		--------------------------------------------------------------------------------------------------
	*/
		// Для имён public-методов используется UpperCamelCase. this в данном случае -- "класс" плагина
		this.Toggle = function() {
			config.ui.$clicker.slideToggle();
		}

		// В data() элемента, к которому был применён плагин, сохраняем ссылку на экземпляр объекта
		config.$context.data(jqPluginName, this);
	}

	/*
		Собственно функция-плагин, т.к. добавляется к $.fn должна вызываться на jQuery-объекте ($('#selector').demoProject_demoPlugin(...)), 
		можно добавлять напрямую к $., тогда можно будет вызывать $.demoProject_demoPlugin(...); но и поддержки chaining'а тогда не будет

		Для имён плагинов не получится использовать "неймспейсы, поэтому используется префикс "project_" 
		http://stackoverflow.com/questions/1219635/jquery-plugin-namespacing-functions
	*/
	$.fn[jqPluginName] = function(options) {		
		return this.each(function () {
			var _this = $(this);			
			if (!_this.data(jqPluginName))
			{
				new demoPlugin(_this, options);
			}
		});	
	}

	/*
		Дефолтовые настройки плагина
	*/
	$.fn[jqPluginName].defaults = {
		$context: null, // Оборачивающий блок, внутри которого работает плагин, удобно использовать в качестве контекста для селекторов
		ui: { // Эти секции группируются по смыслу и могут быть произвольной вложенности
			$clicker: null, // Ссылка на jQuery-объект
			clicker: '.default-clicker', // Селектор для выбора объекта
		}		
	}
})(jQuery); // Будет работать и при jQuery.noConflict();

Тестовый документ:

<!DOCTYPE html> <!-- HTML5, а почему бы и нет? :) -->
<html lang="ru">
	<head>
		<meta charset="utf-8">
		<title>jQuery plugin pattern demo</title>

		<script src="js/jquery-1.6.4.min.js"></script>
		<script src="js/jquery.demoproject.demoplugin.js"></script>			
		<script>
			$(function(){
				// Пример с настройками по умолчанию
				$('.context-1').demoProject_demoPlugin();

				// Пример с переопределёнными настройками
				$('.context-2').demoProject_demoPlugin({
					ui: { 
						clicker: '.custom-clicker'
					}
				});

				// Поддерживается chaining-объектов
				$('.context-3').demoProject_demoPlugin().slideUp('slow'); 

				// Пример вызова public-метода для одного экземпляра
				$('.api-demo-cliker-single:first').click(function(){				
					var demoPluginApi = $('.context-1:first').data('demoProject_demoPlugin');	
					demoPluginApi.Toggle();										
					return false;
				});				

				// Пример вызова public-метода для нескольких экземпляров
				$('.api-demo-clicker:first').click(function(){				
					$('.context-1').each(function() {
						var demoPluginApi = $(this).data('demoProject_demoPlugin');	
						demoPluginApi.Toggle();						
					});					
					return false;
				});				
			});
		</script>				
	</head>

	<body>
		<h1>Cсылки ниже используют один селектор, но разные экземпляры плагина</h1>

		<div class="context-1">
			<a href="#" class="default-clicker">Первая ссылка</a>
		</div>
		<div class="context-1">
			<a href="#" class="default-clicker">Первая ссылка</a>
		</div>
		<div class="context-1">
			<a href="#" class="default-clicker">Третья ссылка</a>
		</div>			

		<p><a href="#" class="api-demo-clicker">Клик по этой ссылке вызывает public-метод у нескольких экземпляров</a>, а по этой&nbsp;&mdash; <a class="api-demo-cliker-single" href="#">у одного</a></p>

		<h1>Ссылка ниже использует другой экземпляр плагина</h1>			
		<div class="context-2">
			<a href="#" class="custom-clicker">Первая ссылка</a>			
		</div>

		<h1>Эта ссылка будет скрыта при загрузке страницы</h1>
		<div class="context-3">
			<a href="#" class="default-clicker">Первая ссылка</a>
		</div>		
	</body>
</html>

Можно скачать архив примера.