Можно ли создавать пользовательские селектора jQuery, которые перемещаются по предкам? например a: ближайший или: родительский селектор
Я пишу много плагинов jQuery и настраиваю jQuery-селектора, которые я использую все время, например :focusable
и :closeto
, для предоставления часто используемых фильтров.
например.: focusable выглядит следующим образом
jQuery.extend(jQuery.expr[':'], {
focusable: function (el, index, selector) {
return $(el).is('a, button, :input[type!=hidden], [tabindex]');
};
});
и используется как любой другой селектор:
$(':focusable').css('color', 'red'); // color all focusable elements red
Я заметил, что ни один из доступных селекторов jQuery не может перемещаться назад предков. Полагаю, это потому, что они были разработаны, чтобы следовать основным правилам селектора CSS, которые расширяются.
Возьмем этот пример: который находит метку для ввода с фокусом:
$('input:focus').closest('.form-group').find('.label');
Мне нужен эквивалентный тип сложных селекторов для плагинов, , поэтому было бы полезно предоставить такой селектор как одну строку (чтобы они могли быть предоставлены в качестве параметров плагина).
например. что-то вроде:
$('input:focus < .form-group .label');
или
$('input:focus:closest(.form-group) .label');
Примечание. Предположите, что более сложные операции и что требуется предварительная навигация (я понимаю, что этот конкретный пример может быть выполнен с помощью has
, но это не помогает).
например. он также должен поддерживать это:
options.selector = ':closest(".form-group") .label';
$('input').click(function(){
var label = $(this).find(options.selector);
});
Можно ли расширить селектора jQuery для расширения поведения поиска (а не просто добавить больше логических фильтров)? Как вы расширяете поведение пользовательского поиска?
Обновление:
Кажется, что полный пользовательский селектор (например, <
) не так прост, как добавление псевдоселектора в парсер JQuery Sizzle. В настоящее время я смотрю эту документацию Sizzle, но я нахожу несоответствия с версией jQuery. (например, свойство Sizzle.selectors.order
существует во время выполнения).
Для справки, jQuery сохраняет Sizzle
в свойстве jQuery.find
и Sizzle.selectors
в свойстве jQuery.expr
.
До сих пор я добавил это:
jQuery.expr.match.closest = /^:(?:closest)$/;
jQuery.expr.find.closest = function (match, context, isXML){
console.log("jQuery.expr.find.closest");
};
и назовите его простым тестом: http://jsfiddle.net/z3vwk1ko/2/
но он никогда не попадает в инструкцию console.log, и я все равно получаю "Syntax error, unrecognized expression: unsupported pseudo: closest"
. При трассировке внутри jQuery он пытается применить его как filter
вместо find
, поэтому мне не хватает некоторой ключевой части.
Обновление 2:
Обработка селекторов работает справа налево (см. извлечение из jQuery 1.11.1 ниже), поэтому, если последний аргумент не существует в контексте, он прерывается раньше. Это означает, что в текущем коде jQuery Sizzle не будет происходить переход вверх, если мы хотим найти элемент в другой ветке DOM предка:
// Fetch a seed set for right-to-left matching
i = matchExpr["needsContext"].test(selector) ? 0 : tokens.length;
while (i--) {
token = tokens[i];
// Abort if we hit a combinator
if (Expr.relative[(type = token.type)]) {
break;
}
if ((find = Expr.find[type])) {
// Search, expanding context for leading sibling combinators
if ((seed = find(
token.matches[0].replace(runescape, funescape),
rsibling.test(tokens[0].type) && testContext(context.parentNode) || context
))) {
// If seed is empty or no tokens remain, we can return early
tokens.splice(i, 1);
selector = seed.length && toSelector(tokens);
if (!selector) {
push.apply(results, seed);
return results;
}
break;
}
}
}
Я был удивлен, увидев это, но понимаю теперь, что он сделал механизм правил намного легче писать. Это означает, однако, что мы должны убедиться, что правый конец селектора является настолько конкретным, насколько это возможно, поскольку это оценивается первым. Все, что происходит после этого, - это просто прогрессивная обрезка результирующего набора (я всегда считал, что первые элементы в селекторе должны быть более конкретными для повышения эффективности).
Ответы
Ответ 1
Основываясь на многочисленных комментариях и одном подробном объяснении того, почему это невозможно, мне пришло в голову, что цель, которую я хочу, может быть встречена с помощью $(document).find()
, но с некоторой концепцией целевых элементов. То есть, какой-то способ нацеливать исходные элементы запроса внутри селектора.
С этой целью я придумал следующий селектор :this
, который работает так (без каламбура):
// Find all labels under .level3 classes that have the .starthere class beneath them
$('.starthere').findThis('.level3:has(:this) .label')
Это позволяет нам эффективно искать DOM, затем вниз в соседние ветки в одной строке селектора!, то есть он выполняет ту же работу (но в одном селекторе):
$('.starthere').parents('.level3').find('.label')
Шаги:
1 - Добавить новый метод jQuery.findThis
2 - Если селектор имеет :this
, замените поиск и поиск по идентификатору из document
вместо
3 - Если селектор не содержит процесс :this
, обычно используя оригинальный find
4 - Протестируйте с помощью селектора типа $('.target').find('.ancestor:has(:this) .label')
, чтобы выбрать метку внутри предка (-ов) целевого элемента (ов)
Это переработанная версия, основанная на комментариях, которая не заменяет существующую находку и использует сгенерированный уникальный идентификатор.
// Add findThis method to jQuery (with a custom :this check)
jQuery.fn.findThis = function (selector) {
// If we have a :this selector
if (selector.indexOf(':this') > 0) {
var ret = $();
for (var i = 0; i < this.length; i++) {
var el = this[i];
var id = el.id;
// If not id already, put in a temp (unique) id
el.id = 'id'+ new Date().getTime();
var selector2 = selector.replace(':this', '#' + el.id);
ret = ret.add(jQuery(selector2, document));
// restore any original id
el.id = id;
}
ret.selector = selector;
return ret;
}
// do a normal find instead
return this.find(selector);
}
// Test case
$(function () {
$('.starthere').findThis('.level3:has(:this) .label').css({
color: 'red'
});
});
Известные проблемы:
-
Это оставляет пустой атрибут id
для целевых элементов, у которых не было атрибута id
, чтобы начать (это не вызывает никаких проблем, но не так аккуратно, как хотелось бы)
-
Из-за того, как он должен искать из документа, он может только эмулировать parents()
, а не closest()
, но у меня есть чувство, что я могу использовать подобный подход и добавить псевдо-селектор :closest()
этот код.
Первая версия ниже:
1 - Сохранить метод jQuery find
для справки
2 - замените новый метод jQuery.find
3 - Если селектор имеет :this
, замените поиск и поиск по идентификатору из document
вместо
4 - Если селектор не содержит процесс :this
, обычно используя оригинал find
5 - Протестируйте с помощью селектора типа $('.target').find('.ancestor:has(:this)')
, чтобы выбрать предка целевых элементов
// Save the original jQuery find we are replacing
jQuery.fn.findorig = jQuery.fn.find
// Replace jQuery find with a custom :this hook
jQuery.fn.find = function (selector) {
// If we have a :this selector
if (selector.indexOf(':this') > 0) {
var self = this;
var ret = $();
for (var i = 0; i < this.length; i++) {
// Save any existing id on the targetted element
var id = self[i].id;
if (!id) {
// If not id already, put in a temp (unique) one
self[i].id = 'findme123';
}
var selector2 = selector.replace(':this', '#findme123');
ret = ret.add(jQuery(selector2, document));
// restore any original id
self[i].id = id;
}
ret.selector = selector;
return ret;
}
return this.findorig(selector);
}
// Test case
$(function () {
$('.starthere').find('.level3:has(:this)').css({
color: 'red'
});
});
Это основано на 6-часовом рабстве над исходным кодом jQuery/Sizzle, поэтому будьте осторожны. Всегда рады услышать о способах улучшения этой замены find
, поскольку я новичок в внутренних функциях jQuery:)
Теперь это означает, что я могу решить начальную проблему, как сделать пример оригинальной метки:
options.selector = ".form-group:has(:this) .label";
$('input').click(function(){
var label = $(this).find(options.selector);
});
например. http://jsfiddle.net/TrueBlueAussie/z3vwk1ko/25/
Ответ 2
Невозможно, и я расскажу, почему
Чтобы создать настраиваемый селектор, вам нужно что-то вроде этого:
jQuery.extend(jQuery.expr[':'], {
focusable: function (el, index, selector) {
return $(el).is('a, button, :input[type!=hidden], [tabindex]');
};
})
Логика выбора позволяет вам выбрать подмножество элементов из текущего выбора, например, если $( "div" ) выбрать ВСЕ разделы на странице, то следующие два примера будут выберите все divs без элементов:
$("div:empty")
$("div").filter(":empty")
В соответствии с тем, как jQuery позволяет писать пользовательские селектор, нет способа вернуть элементы вне исходного набора, вы можете выбрать только true или false для каждого элемента набора, когда его спросят. Например, селектор ": contains" реализован таким образом в версии 2.1.1 jQuery:
function ( text ) {
return function( elem ) {
return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
};
}
Но...
(Возможно, вы хотите сначала просмотреть последнее редактирование)
Возможно, вы найдете полезный селектор всех предков данного элемента, например, представьте этот сценарий:
<element1>
<div>
<span id="myelementid"></span>
</div>
<div>
<p>Lorem ipsum sir amet dolor... </p>
</div>
</element1>
<element2>
</element2>
И вам нужно что-то, связанное с родителем div myelementid, element1 и всеми предками и т.д., вы можете использовать селектор "has", таким образом:
$("*:has('#myelementid')")
Первый селектор вернет ВСЕ элементы, отфильтрованные "has", вернет все элементы, имеющие #myelementid как descendent, ergo, all предки из #myelementid.
Если вас беспокоит производительность (и, возможно, вам стоит), вам нужно заменить "*" на что-то более конкретное, возможно, вы ищете div
$("div:has('#myelementid')")
Или для данного класса
$(".agivenclass:has('#myelementid')")
Я надеюсь, что это поможет
ИЗМЕНИТЬ
Вы можете использовать селектор parent() для jQuery, поскольку он возвращает ВСЕ предков элемента, даже включая корневой элемент документа (т.е. тег html), таким образом:
$('#myelementid').parents()
Если вы хотите отфильтровать определенные элементы (например, div), вы можете использовать это:
$('#myelementid').parents(null, "div")
Или это:
$('#myelementid').parents().filter("div")