16

I have a field for users to input a CSS selector and I want to check if it's valid (according to css3 specification). I tried to use expressions from css3 specification as suggested in another stackoverflow topic, but it didn't work - the regexp I built just didn't match valid selectors. What I have for now is simply:

try {
     document.querySelector(selector);
} catch (e) {
     // handle bad input
}

But it doesn't seem like a good solution - querySelector function is designed for getting elements, and checking of selector is just a side effect. Furthermore it doesn't provide any information about what is wrong with the selector.

What I'm looking for is something like document.validateSelector or library for parsing CSS selectors.

17
  • 2
    What's wrong with the way you're doing it? This is the preferred approach as far as I know. Commented Jan 18, 2016 at 7:13
  • How are users inserting the selector? For example, for a class are the inputing .className? Basically, are you prefixing the selector with the appropriate identifier for class or id? Commented Jan 18, 2016 at 7:15
  • @Brett DeWoody, yes, they're supposed to type selectors just like in .css files, I don't change it in any way. Commented Jan 18, 2016 at 7:19
  • 1
    @torazaburo, well, it does work, but it just seams wrong) document.querySelector is not for that. In fact I'm just curious is there a more elegant solution. Commented Jan 18, 2016 at 7:31
  • It's fine. You're using try to pick up an error which is reported by a throw, which is how querySelector reports errors. The only reason to not use this and prefer some other solution is if you wanted details such as the type of error or its character position. Commented Jan 18, 2016 at 7:37

5 Answers 5

38

The problem with original idea is that it will search the entire document. Slow 🤨 !

However, searching an empty light-weight element that is not even attached to the DOM is fast ✌️!

const queryCheck = (s) => document.createDocumentFragment().querySelector(s)

const isSelectorValid = (selector) => {
  try { queryCheck(selector) } catch { return false }
  return true
}

console.assert(isSelectorValid('p > > > a') === false)
console.assert(isSelectorValid('p > a') === true)
console.log('Test passed')

The following version is a bit more advanced with dummy fragment enclosure:

const isSelectorValid = ((dummyElement) =>
  (selector) => {
    try { dummyElement.querySelector(selector) } catch { return false }
    return true
  })(document.createDocumentFragment())
  
console.assert(isSelectorValid('p > > > a') === false)
console.assert(isSelectorValid('p > a') === true)
console.log('Test passed')

Sign up to request clarification or add additional context in comments.

1 Comment

this should be the best answer
1

You can use a library to verify if the selector is valid, and probably get more details from parsing. Check the css selector parser.

2 Comments

There's already a library, it's the CSS selector parser built into the browser. That "library" reports errors by throwing. I don't see any reason to bring in another parser.
Why generate runtime errors, if it is possible to avoid them. A library can give more details on an invalid selector. document.querySelector(selector) is designed to query the DOM, but not to check the selector validity.
1

Thanks to @kornieff hint, I've reached out to an answer for nodejs using jsdom, if it can help anyone :

const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const { document } = (new JSDOM('')).window;

const queryCheck = s => document.createDocumentFragment().querySelector(s)

const isSelectorValid = selector => {
  try { queryCheck(selector) } catch { return false }
  return true
}
console.log(isSelectorValid("a#x#y"), isSelectorValid("a?+"));

Comments

1

Since querySelector and CSS.supports('selector('+s+')') accept unclosed input like a[href="foo,
let's use the built-in CSSStyleSheet API to check the selector can be used in a CSS rule:

let isv;
function isSelectorValid(sel) {
  if (!isv) {
    try {
      // Chrome 73 and newer
      isv = new CSSStyleSheet();
    } catch (e) {
      // This will fail on sites with an unusually strict CSP that forbids inline styles,
      // so you'll need to set `nonce` or reuse an existing `link` element.
      isv = document.head.appendChild(document.createElement('style')).sheet;
      isv.disabled = true;
    }
  }
  let res = false;
  try {
    // the leading space skips selector's trailing escape char
    const body = ` { --foo: "${Math.random()}"; }`;
    isv.insertRule(sel + body);
    res = isv.cssRules[0].cssText.endsWith(body);
    isv.deleteRule(0);
  } catch (e) {}
  return res;
}

Comments

0

Not sure it would cover all edge cases but using CSS.escape on an invalid selector returns an empty NodeList instead of throwing a syntax error.

document.querySelectorAll(CSS.escape('p > > > a')) => NodeList []
document.querySelectorAll(CSS.escape('a?+')) => NodeList []

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.