[Изменить] 7 месяцев спустя
Цитата из проекта github
jQuery не годится, а плагины jQuery - не то, что делает модульный код.
Серьезно, «плагины jQuery» не являются стратегией разумной архитектуры. Написание кода с жесткой зависимостью от jQuery также глупо.
[оригинал]
Поскольку я дал критическую оценку этому шаблону, я предложу альтернативу.
Чтобы упростить жизнь, используются jQuery
1.6+ и ES5 (используйте ES5 Shim ).
Я потратил некоторое время на переработку шаблона плагина, который вы дали, и выкатил свой собственный.
Ссылки:
Сравнение:
Я реорганизовал шаблон так, чтобы он был разбит на шаблон (85%) и код лесов (15%). Предполагается, что вам нужно всего лишь отредактировать код скаффолдинга, и вы можете оставить стандартный код без изменений. Для этого я использовал
- наследование
var self = Object.create(Base)
Вместо того, чтобы редактировать класс Internal
, который у вас есть, вы должны редактировать подкласс. Все ваши функции шаблона / по умолчанию должны быть в базовом классе (в моем коде он называется Base
).
- Convention
self[PLUGIN_NAME] = main;
По соглашению плагин, определенный в jQuery, будет вызывать метод define по self[PLUGIN_NAME]
по умолчанию. Это рассматривается как метод плагина main
и имеет отдельный внешний метод для ясности.
- исправление обезьян
$.fn.bind = function _bind ...
Использование исправлений обезьян означает, что пространство имен событий выполняется автоматически для вас. Эта функциональность бесплатна и не достигается за счет читабельности (звоните getEventNS
все время).
OO Techniques
Лучше придерживаться правильного JavaScript OO, чем классической OO-эмуляции. Для этого вы должны использовать Object.create
. (какие ES5 просто используют прокладку для обновления старых браузеров).
var Base = (function _Base() {
var self = Object.create({});
/* ... */
return self;
})();
var Wrap = (function _Wrap() {
var self = Object.create(Base);
/* ... */
return self;
})();
var w = Object.create(Wrap);
Это отличается от стандартных OO, к которым привыкли люди на основе new
и .prototype
. Этот подход предпочтителен, потому что он подтверждает концепцию, что в JavaScript есть только объекты, и это прототип ОО-подхода.
[getEventNs
]
Как уже упоминалось, этот метод был подвергнут рефакторингу путем переопределения .bind
и .unbind
для автоматического внедрения пространств имен. Эти методы перезаписываются в приватной версии jQuery $.sub()
. Перезаписанные методы ведут себя так же, как ваше пространство имен. Он именует события уникальным образом на основе плагина и экземпляра оболочки плагина вокруг HTMLElement (с использованием .ns
.
[getData
]
Этот метод был заменен методом .data
, который имеет тот же API, что и jQuery.fn.data
. Тот факт, что это тот же API, облегчает его использование, в основном это тонкая оболочка jQuery.fn.data
с пространством имен. Это позволяет вам установить данные пары ключ / значение, которые сразу же сохраняются только для этого плагина. Несколько плагинов могут использовать этот метод параллельно без каких-либо конфликтов.
[publicMethods
]
Объект publicMethods был заменен любым методом, определяемым в Wrap
, который автоматически становится открытым. Вы можете напрямую вызывать любой метод для объекта Wrapped, но у вас нет доступа к этому объекту.
[$.fn[PLUGIN_NAME]
]
Это был рефакторинг, поэтому он предоставляет более стандартизированный API. Это API
$(selector).PLUGIN_NAME("methodName", {/* object hash */}); // OR
$(selector).PLUGIN_NAME({/* object hash */}); // methodName defaults to PLUGIN_NAME
элементы в селекторе автоматически переносятся в объект Wrap
, вызывается метод или каждый выбранный элемент из селектора, а возвращаемое значение всегда является элементом $.Deferred
.
Это стандартизирует API и тип возвращаемого значения. Затем вы можете позвонить по номеру .then
для возвращенной отсрочки, чтобы получить фактические данные, которые вам нужны. Использование отсроченного здесь очень эффективно для абстракции вне зависимости от того, является ли плагин синхронным или асинхронным.
_create
Добавлена функция создания кэширования. Это вызывается для превращения HTMLElement
в элемент Wrapped, и каждый элемент HTMLE будет упакован только один раз. Это кэширование дает вам существенное сокращение памяти.
$.PLUGIN_NAME
Добавлен еще один публичный метод для плагина (всего два!).
$.PLUGIN_NAME(elem, "methodName", {/* options */});
$.PLUGIN_NAME([elem, elem2, ...], "methodName", {/* options */});
$.PLUGIN_NAME("methodName", {
elem: elem, /* [elem, elem2, ...] */
cb: function() { /* success callback */ }
/* further options */
});
Все параметры являются необязательными. elem
по умолчанию <body>
, "methodName"
по умолчанию "PLUGIN_NAME"
и {/* options */}
по умолчанию {}
.
Этот API является очень гибким (с 14 перегрузками методов!) И достаточно стандартным, чтобы привыкнуть к syntnax для каждого метода, который будет выставлен вашим плагином.
Общественное облучение
Объекты Wrap
, create
и $
выставляются глобально. Это позволит продвинутым пользователям плагинов максимально гибко работать с вашим плагином. Они могут использовать create
и модифицированное подразделение $
в своей разработке, и они также могут использовать обезьяну патч Wrap
. Это позволяет подключать методы вашего плагина. Все три из них отмечены _
перед их именем, поэтому они являются внутренними, и их использование нарушает условия гарантии того, что ваш плагин работает.
Внутренний объект defaults
также отображается как $.PLUGIN_NAME.global
. Это позволяет пользователям переопределить ваши значения по умолчанию и установить глобальный плагин defaults
. В этой настройке плагина все хэши переходят в методы, поскольку объекты объединяются со значениями по умолчанию, поэтому это позволяет пользователям устанавливать глобальные значения по умолчанию для всех ваших методов.
Фактический код
(function($, jQuery, window, document, undefined) {
var PLUGIN_NAME = "Identity";
// default options hash.
var defaults = {
// TODO: Add defaults
};
// -------------------------------
// -------- BOILERPLATE ----------
// -------------------------------
var toString = Object.prototype.toString,
// uid for elements
uuid = 0,
Wrap, Base, create, main;
(function _boilerplate() {
// over-ride bind so it uses a namespace by default
// namespace is PLUGIN_NAME_<uid>
$.fn.bind = function _bind(type, data, fn, nsKey) {
if (typeof type === "object") {
for (var key in type) {
nsKey = key + this.data(PLUGIN_NAME)._ns;
this.bind(nsKey, data, type[key], fn);
}
return this;
}
nsKey = type + this.data(PLUGIN_NAME)._ns;
return jQuery.fn.bind.call(this, nsKey, data, fn);
};
// override unbind so it uses a namespace by default.
// add new override. .unbind() with 0 arguments unbinds all methods
// for that element for this plugin. i.e. calls .unbind(_ns)
$.fn.unbind = function _unbind(type, fn, nsKey) {
// Handle object literals
if ( typeof type === "object" && !type.preventDefault ) {
for ( var key in type ) {
nsKey = key + this.data(PLUGIN_NAME)._ns;
this.unbind(nsKey, type[key]);
}
} else if (arguments.length === 0) {
return jQuery.fn.unbind.call(this, this.data(PLUGIN_NAME)._ns);
} else {
nsKey = type + this.data(PLUGIN_NAME)._ns;
return jQuery.fn.unbind.call(this, nsKey, fn);
}
return this;
};
// Creates a new Wrapped element. This is cached. One wrapped element
// per HTMLElement. Uses data-PLUGIN_NAME-cache as key and
// creates one if not exists.
create = (function _cache_create() {
function _factory(elem) {
return Object.create(Wrap, {
"elem": {value: elem},
"$elem": {value: $(elem)},
"uid": {value: ++uuid}
});
}
var uid = 0;
var cache = {};
return function _cache(elem) {
var key = "";
for (var k in cache) {
if (cache[k].elem == elem) {
key = k;
break;
}
}
if (key === "") {
cache[PLUGIN_NAME + "_" + ++uid] = _factory(elem);
key = PLUGIN_NAME + "_" + uid;
}
return cache[key]._init();
};
}());
// Base object which every Wrap inherits from
Base = (function _Base() {
var self = Object.create({});
// destroy method. unbinds, removes data
self.destroy = function _destroy() {
if (this._alive) {
this.$elem.unbind();
this.$elem.removeData(PLUGIN_NAME);
this._alive = false;
}
};
// initializes the namespace and stores it on the elem.
self._init = function _init() {
if (!this._alive) {
this._ns = "." + PLUGIN_NAME + "_" + this.uid;
this.data("_ns", this._ns);
this._alive = true;
}
return this;
};
// returns data thats stored on the elem under the plugin.
self.data = function _data(name, value) {
var $elem = this.$elem, data;
if (name === undefined) {
return $elem.data(PLUGIN_NAME);
} else if (typeof name === "object") {
data = $elem.data(PLUGIN_NAME) || {};
for (var k in name) {
data[k] = name[k];
}
$elem.data(PLUGIN_NAME, data);
} else if (arguments.length === 1) {
return ($elem.data(PLUGIN_NAME) || {})[name];
} else {
data = $elem.data(PLUGIN_NAME) || {};
data[name] = value;
$elem.data(PLUGIN_NAME, data);
}
};
return self;
})();
// Call methods directly. $.PLUGIN_NAME(elem, "method", option_hash)
var methods = jQuery[PLUGIN_NAME] = function _methods(elem, op, hash) {
if (typeof elem === "string") {
hash = op || {};
op = elem;
elem = hash.elem;
} else if ((elem && elem.nodeType) || Array.isArray(elem)) {
if (typeof op !== "string") {
hash = op;
op = null;
}
} else {
hash = elem || {};
elem = hash.elem;
}
hash = hash || {}
op = op || PLUGIN_NAME;
elem = elem || document.body;
if (Array.isArray(elem)) {
var defs = elem.map(function(val) {
return create(val)[op](hash);
});
} else {
var defs = [create(elem)[op](hash)];
}
return $.when.apply($, defs).then(hash.cb);
};
// expose publicly.
Object.defineProperties(methods, {
"_Wrap": {
"get": function() { return Wrap; },
"set": function(v) { Wrap = v; }
},
"_create":{
value: create
},
"_$": {
value: $
},
"global": {
"get": function() { return defaults; },
"set": function(v) { defaults = v; }
}
});
// main plugin. $(selector).PLUGIN_NAME("method", option_hash)
jQuery.fn[PLUGIN_NAME] = function _main(op, hash) {
if (typeof op === "object" || !op) {
hash = op;
op = null;
}
op = op || PLUGIN_NAME;
hash = hash || {};
// map the elements to deferreds.
var defs = this.map(function _map() {
return create(this)[op](hash);
}).toArray();
// call the cb when were done and return the deffered.
return $.when.apply($, defs).then(hash.cb);
};
}());
// -------------------------------
// --------- YOUR CODE -----------
// -------------------------------
main = function _main(options) {
this.options = options = $.extend(true, defaults, options);
var def = $.Deferred();
// Identity returns this & the $elem.
// TODO: Replace with custom logic
def.resolve([this, this.elem]);
return def;
}
Wrap = (function() {
var self = Object.create(Base);
var $destroy = self.destroy;
self.destroy = function _destroy() {
delete this.options;
// custom destruction logic
// remove elements and other events / data not stored on .$elem
$destroy.apply(this, arguments);
};
// set the main PLUGIN_NAME method to be main.
self[PLUGIN_NAME] = main;
// TODO: Add custom logic for public methods
return self;
}());
})(jQuery.sub(), jQuery, this, document);
Как видно, код, который вы должны редактировать, находится ниже строки YOUR CODE
. Wrap
объект действует аналогично вашему Internal
объекту.
Функция main
является основной функцией, вызываемой с помощью $.PLUGIN_NAME()
или $(selector).PLUGIN_NAME()
, и должна содержать вашу основную логику.