Несколько экземпляров jQuery с vars, событиями и DOM-манипуляциями
Я работаю над проектом, который начинался как просто создание функции, которая принимает аргумент (XML файл) и преобразует его в структуру HTML/CSS (более ранняя версия, которую можно найти здесь). Это отлично поработало. Тем не менее, я бы хотел реализовать больше возможностей и большую гибкость. Я прочитал эту тему (например, 1, 2, 3), но я не могу обвести вокруг него голову.
Мой плагин имеет множество специфичных для экземпляра запросов:
- Переменные
- Параметры
- обработчики событий
и есть несколько важных вариантов:
- отобразить полноэкранную версию
- отображает обычную версию (поставляется с кнопкой "open fs version" ).
- укажите контейнер для нормальной версии
- установить шрифт нормальной версии
- установить возможные шрифты для версии fs (для увеличения и уменьшения дерева)
- установите класс, который, когда пользователь нажимает на элемент с этим классом, открывает версию fs
Я нарисую базовую структуру текущего состояния плагина.
Два первых варианта - самые важные. Но по умолчанию используется true
, и если у пользователя есть оба из них на false
, плагин не будет выполняться.
Затем плагин назначает глобальные переменные и создает новые элементы DOM на основе этой информации. На практике это выглядит примерно так (обратите внимание, что глобальные переменные объявляются в верхней части моего script).
function initVars(args) {
fontsizes = fsFontSizes;
errorContainer = $(".tv-error");
var trees = [],
tooltips = [];
if (args.normalView) {
normalView = true;
$(args.container).append('<div id="tree-visualizer" style="display: none"></div>');
SS = $("#tree-visualizer");
var SSHTML = '<div class="tv-error" style="display: none"><p></p></div>' +
'<div class="tree" style="font-size: ' + args.fontSize + 'px;"></div>' +
'<aside class="tooltip" style="display: none"><ul></ul>' +
'<button>✕</button></aside>';
if (args.fsView) {
SSHTML += '<button class="tv-show-fs">Fullscreen</button>';
}
SS.append(SSHTML);
treeSS = SS.find(".tree");
tooltipSS = SS.find(".tooltip");
trees.push("#tree-visualizer .tree");
tooltips.push("#tree-visualizer .tooltip");
}
if (args.fsView) {
fsView = true;
$("body").append('<div id="fs-tree-visualizer-" class=""fs-tree-visualizer" style="display: none"></div>');
FS = $("#fs-tree-visualizer");
var FSHTML = '<div class="tv-error" style="display: none"><p></p></div>' +
'<div class="tree"></div><aside class="tooltip" style="display: none"><ul></ul>' +
'<button>✕</button></aside><div class="zoom-opts"><button class="zoom-out">-</button>' +
'<button class="zoom-default">Default</button><button class="zoom-in">+</button>' +
'<button class="close">✕</button></div>';
FS.hide().append(FSHTML);
treeFS = FS.find(".tree");
tooltipFS = FS.find(".tooltip");
zoomOpts = FS.find(".zoom-opts");
zoomCounter = Math.round(fontSizes.length / 2);
trees.push("#fs-tree-visualizer .tree");
tooltips.push("#fs-tree-visualizer .tooltip");
}
if (args.fsBtn != "") {
$(args.fsBtn).addClass("tv-show-fs");
}
anyTree = $(trees.join());
anyTooltip = $(tooltips.join());
}
Вы увидите, что я работаю с идентификаторами, что затрудняет работу с несколькими экземплярами. Один из способов решения этого вопроса, я думал, - добавить класс для стилизации и добавить идентификатор к каждому экземпляру с помощью глобального счетчика, который отслеживает экземпляры (counter++
для каждого экземпляра). Обратите внимание, что anyTree
используется, когда я хочу настроить таргетинг на дерево FS, а также на дерево нормальных представлений. Это НЕ означает, что я хочу настроить таргетинг на все деревья всех экземпляров! Это должно быть ограничено и для каждого экземпляра.
Итак, мой вопрос: как мне разрешить несколько экземпляров и особенно: как я могу перейти от глобальных переменных к локальным переменным, не теряя при этом силы, которые у меня есть сейчас? В этот момент я могу работать с глобальными переменными и получать доступ к каждой переменной везде, где захочу. Но как я могу ограничить глобальную переменную на один экземпляр? Работайте с этим счетчиком, как я предложил?
Также, где я могу назначить события? В настоящее время это так, как мой плагин инициализирует (я оставил глобальные переменные и функции):
$.treeVisualizer = function(xml, options) {
var args = $.extend({}, $.treeVisualizer.defaults, options);
/* At least one of the arguments (default true) have to be true */
if (args.normalView || args.fsView) {
initVars(args);
loadXML(xml);
} else {
console.error("Cannot initialize Tree Visualizer: either the container " +
"does not exist, or you have set both normal and fullscreen view to " +
"false, which does not make sense.");
}
/* Event handlers -- only after we've initiated the variables to globals */
$(document).ready(function() {
// Show fs-tree-visualizer tree
$(".tv-show-fs").click(function(e) {
// Show corresponding fullscreen version
FS.show();
// Execute some functions
sizeTreeFS();
e.preventDefault();
});
// Zooming
zoomOpts.find("button").click(function() {
var $this = $(this);
// Do something
});
anyTree.on("click", "a", function(e) {
// Do something, and execute function: has to
// target the right tooltip
tooltipPosition();
e.preventDefault();
});
});
}
Это подходящее место для размещения обработчиков событий?
Ответы
Ответ 1
Этот визуализатор XML выглядит хорошо.
У меня есть несколько указателей для вас.
Во-первых, вы создаете jquery-component/widget/plugin, используя $.fn.componentName вместо $.componentName.
$.fn.treeVisualizer
Во-вторых, я бы рекомендовал переместить все глобальные значения в параметры или локальные переменные в ваш компонент. Этот фрагмент кода, который вы предоставили, должен работать. Каждое глобальное значение, которое может быть различным для каждого экземпляра, должно быть вариантом.
$.fn.myComponent(options){
var defaults = {
option1: true,
option2: 5
};
options = $.extend({}, defaults, options);//defaults and options are being merged into {}
var constantValue = 15; //values that won't change, you can just move locally from global
}
В-третьих, для ваших глобальных функций я бы предложил включить их в функцию самозапускания вместе с компонентом, чтобы компонент мог получить доступ к этой функции, и они не являются ненужными глобальными.
(function initComponent(){
$.fn.myComponent(){
someFunction();
}
function someFunction(){
//do something
}
})();
//immediately invoke function/ self-invoking function
Или вы можете поместить функции внутри области компонентов.
$.fn.myComponent(){
someFunction();
function someFunction(){
//do something
}
}
В-четвертых, я бы рекомендовал использовать классы вместо id. Вы действительно можете решить эту проблему, убедившись, что идентификатор уникален, отслеживая количество экземпляров, но просто используйте классы (fe, xml-id-1). Или вы также можете использовать атрибут data-id/data-xml-id. Не волнуйтесь о том, что запрос элементов с одним и тем же классом в разных компонентах, я подойду к этому в следующем указателе.
$jqueryElement.addClass('xml-id-'+id);
$('.xml-id-'+id);//you can query it by class-name like this
Или, если вы хотите воспользоваться атрибутами данных (которые я рекомендую по классам, потому что html легче читать и имеет больше значения imo)
$jqueryElement.attr('data-xml-id', id); //fe <div data-xml-id="1"></div>
$('[data-xml-id="'+id+'"]'); //you can query it like this
Наконец, каждый запрос и событие должны быть относительно вашего компонента, чтобы вы только искали элементы и добавляли событие в экземпляр вашего компонента. Это гарантирует, что вы случайно не выполните действия над другим экземпляром вашего компонента.
$.fn.myComponent=function(){
$('[data-xml-id="1"]'); //this queries the whole DOM, don't do this!
var $el = this.find('[data-xml-id="1"]'); //this only looks for matching elements within the current instance of the component
$el.click(function(){// also add events to elements you searched within your components
//do something
);
}
Затем, наконец, инициализируйте свой компонент таким образом.
$('#tree1').treeVisualizer(xml,
{
fullscreen: true
}
);
$('#tree2').treeVisualizer(xml);
Дополнительный вопрос из комментариев:
Найти метод
Метод find действует точно так же, как и глобальный метод запроса, с той разницей, что он ищет только в DOM объекта jQueryObject.
Получает потомки каждого элемента в текущем наборе согласованных элементов, фильтруется селектором, объектом jQuery или элементом. Источник: Документация jQuery
Пример:
var footer = $('footer');
footer.find('div');// searches for div inside the footer
это использование
Внутри плагина this
относится к jQueryObject, в который вы инициализировали плагин.
$.fn.myComponent = function(){
console.log(this);// refers to $el
}
var $el = $('#id');
$el.myComponent();
Внутри обратного вызова события это относится к собственному элементу, в который вы также привязывали обработчик событий. Вы должны обернуть его в объект jQuery для выполнения jQuery-действия на нем.
$('button').click(function(){
console.log(this); //prints out the button that was clicked
$(this).css('background','#000'); //wrap native button element in jQuery object and perform jQuery methods on it
});
Всякий раз, когда вы не знаете, где находится this
, просто зарегистрируйте его на консоли.
С помощью этой информации вы можете видеть, что при использовании this.find('.tree-structure')
внутри вашего компонента вы действительно будете искать только элементы с классом древовидной структуры внутри объекта jQuery, с которым вы инициализировали компонент.
Ответ 2
Код ниже - это базовый формат, который я использую, когда создаю плагин. Я надеюсь, что это поможет вам найти решение некоторых из ваших проблем.
(function($) {
$.fn.FUNCTIONNAME = function(options) {
var obj = $(this);
var opts = $.extend({
OPTION1 : 'OPTION1 VALUE',
OPTION2 : 'OPTION2 VALUE',
}, options);
}
function FUNCTIONNAME(PARAMETER) {
FUNCTION CONTENT
IF YOU WANT TO USE OPTION VALUE,
YOU CAN CALL IT BY A FORM AS opts.OPTION1
}
obj.find($('ID OR CLASS DOESNT MATTER')).FUNCTIONNAME(PARAMETER);
// this will make the plugin only work for this element
return this; // or you can each() over every functions and return it
})(jQuery);
+ И я рекомендую вам не использовать document.ready внутри плагина. Обработчики событий можно поместить куда угодно.
EDIT: this → obj
Ответ 3
Вы можете ссылаться на элемент DOM, который вы вставляете без повторного запроса, с appendTo
, возвращающим ссылку на вставленные элементы
//before:
$(args.container).append('<div id="tree-visualizer" style="display: none"></div>');
$SS = $("#tree-visualizer"); //prefixing variables with $ when referencing jquery objects
//after:
$SS = $('<div style="display: none"></div>').appendTo( $(args.container) );
Если вы применяете привязки кликов как часть кода инициализации
$SS.append(SSHTML).on('click', '.tv-show-fs', function() {
$FS.show();
});
вы заметите, что SS
и FS
теперь могут быть локально локальными.
В качестве альтернативы вы можете отключить функциональность "показать в полноэкранном режиме" от основного плагина и избежать дублирования функций. Наивный подход может заключаться в переключении некоторых стилей, но даже если вы хотите манипулировать DOM, вы получаете эту идею. Это не похоже на то, что вы можете иметь более одного полноценного полноэкранного экземпляра одновременно.
$SS.append(SSHTML).on('click', '.tv-show-fs', function() {
$SS.toggleClass('tree-visualiser__fullscreen');
});