Determining whether an attribute is valid in stand

2019-08-19 16:00发布

I want to programmatically add attributes to DOM elements (e.g. HTML or SVG elements) dynamically at run time, i.e. based on user input. If the attribute is a standard one, I will use the name as is, but if it is not, i.e. if it requires transforming into a custom attribute, then I want to add the "data-" prefix to it.

So, is there a way of determining whether or not a string represents a "standard/normal" DOM attribute?

For example, say I have an SVG rectangle as follows:

<rect x="10" y="10" width="80" height="40" stroke="black"/>

Note that the fill color is not (yet) specified.

The user requests adding two attributes:

  • an attribute named "fill" with the value "red", which should yield fill="red" (note the absence of a prefix)
  • an attribute named "price" with the value "expensive", which should yield data-price="expensive" (note the data- prefix)

How do I dynamically distinguish between these two? The fill attribute isn't present in my current DOM element so I can't check if there is a pre-existing value for an attribute of that name. I also don't believe I can even check whether an error is thrown if I create the attribute without the prefix because, as far as I can tell, at least my current browser (Chrome v61.0) will allow me to add an attribute of price="expensive" (without the data- prefix) even though that is not best practice.

I want something like the following:

const elementNodeName = "rect";
const attributeName = "height"; // or, e.g. "price"
const nameIsValid = testAttributeNameValidity(elementNodeName, attributeName);
if (!nameIsValid) attributeName = 'data-' + attributeName;

Is there any pre-existing testAttributeNameValidity-type functionality out there?

I've tagged this with javascript as I'm looking for a Javascript solution.

** UPDATE: Solution from other SO answer works in HTML but not SVG **

Based on the suggested link in the comments, I tried using attributeName in document.createElement(elementNodeName) which is supposed to return true for a valid attribute name and false for a non-valid one. When I use the valid attribute name "id" for an HTML element (<p>), this approach works.

In contrast, I still haven't gotten this approach to work in SVG. I tried converting createElement to createElementNS, and that still does not seem to fix the problem. See my test code below. The critical functions in the test code are testHtmlAttributeNameValidity and testSvgAttributeNameValidity. If you run the code and check the output in the console, it shows that it works properly for HTML, i.e. it produces id="someId" data-price="expensive". However, for SVG, it only produces the undesired output: data-fill="yellow" data-price="expensive".

If it makes a difference, I'm working in Chrome v61.

Test code:

const testHtmlAttributeNameValidity = (elementNodeName, attributeName) => {
  return (attributeName in document.createElement(elementNodeName));
};

const testHtmlAttributeName = (attributeName, attributeValue) => {
  const elementNodeName = "p";
  const nameIsValid = testHtmlAttributeNameValidity(elementNodeName, attributeName);
  if (!nameIsValid) attributeName = 'data-' + attributeName;
  const element = document.querySelector(elementNodeName);
  element.setAttribute(attributeName, attributeValue);
};

testHtmlAttributeName("id", "someId");
testHtmlAttributeName("price", "expensive");

console.log(document.querySelector('div').innerHTML);


const svgNS = "http://www.w3.org/2000/svg";

const testSvgAttributeNameValidity = (elementNodeName, attributeName) => {
  return (attributeName in document.createElementNS(svgNS, elementNodeName));
};

const testSvgAttributeName = (attributeName, attributeValue) => {
  const elementNodeName = "rect";
  const nameIsValid = testSvgAttributeNameValidity(elementNodeName, attributeName);
  if (!nameIsValid) attributeName = 'data-' + attributeName;
  const element = document.querySelector(elementNodeName);
  element.setAttribute(attributeName, attributeValue);
};

testSvgAttributeName("fill", "yellow");
testSvgAttributeName("price", "expensive");

console.log(document.querySelector('svg').innerHTML);
#someId {
  color: green;
}
<svg height="55">
  <rect x="10" y="10" width="80" height="40" stroke="red"/>
</svg>

<div>
<p>Hello world</p>
</div>

Correctly working code should change the rectangle fill from black to yellow, which does not happen.

1条回答
一夜七次
2楼-- · 2019-08-19 16:30

The comment from @ccprog helped me solve this problem. In summary, for SVG DOM elements, I needed the original check plus a boolean-OR'd check that is identical but with the addition of the .style property.

As shown in the question, the validity of an HTML DOM element attribute name can be determined by checking as follows:

// correct for HTML:
return (
  attributeName in document.createElement(elementNodeName)
);

However, this approach will not work for SVG DOM elements. Specifically, the following will determine the validity of true attribute names like "id", but will not determine the validity of style-like attribute names like "fill":

// insufficient for SVG, i.e. only partially correct:
return (
  attributeName in document.createElementNS(svgNS, elementNodeName)
);

However, simply duplicating that test and adding the .style property to one of the two checks will allow both categories of attribute names to be checked, as follows:

// correct for SVG:
return (
  attributeName in document.createElementNS(svgNS, elementNodeName).style ||
  attributeName in document.createElementNS(svgNS, elementNodeName)
);

This is demonstrated in the following code which is essentially identical to the code in the question except for the above-noted change. The ability to determine the validity of both categories of attribute name is reflected in the appearance of both yellow fill and thick stroke width in the visible rectangle while at the same time adding the custom "data-price" attribute to the element. Note in particular how HTML DOM elements and SVG DOM elements need to be checked differently.

const testHtmlAttributeNameValidity = (elementNodeName, attributeName) => {
  return (attributeName in document.createElement(elementNodeName));
  return (
    attributeName in document.createElement(elementNodeName)
  );
};

const testHtmlAttributeName = (attributeName, attributeValue) => {
  const elementNodeName = "p";
  const nameIsValid = testHtmlAttributeNameValidity(elementNodeName, attributeName);
  if (!nameIsValid) attributeName = 'data-' + attributeName;
  const element = document.querySelector(elementNodeName);
  element.setAttribute(attributeName, attributeValue);
};

testHtmlAttributeName("id", "someHtmlId");
  // This should result in "id" attribute being changed, changing the
  // font color to green.

testHtmlAttributeName("color", "orange");
  // While "color" IS a CSS property name, it is NOT an HTML DOM element
  // attribute name. Therefore, this test should result in "color" NOT being
  // recognized as a valid attribute name, resulting in the
  // custom attribute name "data-color", with no effect on the
  // actual styling of the element, i.e. this test should NOT
  // turn the text orange. This test is included here
  // to provide a contrast to what should happen with SVG DOM
  // element style-like attributes like "fill", as shown below
  // in the section testing SVG DOM element attribute names.

testHtmlAttributeName("price", "expensive");
  // This should result in a new attribute with the name "data-price"
  // and the value "expensive".

console.log(document.querySelector('div').innerHTML);


const svgNS = "http://www.w3.org/2000/svg";

const testSvgAttributeNameValidity = (elementNodeName, attributeName) => {
  return ( // ********** THIS IS THE IMPORTANT CHANGE **********
    attributeName in document.createElementNS(svgNS, elementNodeName).style ||
    attributeName in document.createElementNS(svgNS, elementNodeName)
  );
};

const testSvgAttributeName = (attributeName, attributeValue) => {
  const elementNodeName = "rect";
  const nameIsValid = testSvgAttributeNameValidity(elementNodeName, attributeName);
  if (!nameIsValid) attributeName = 'data-' + attributeName;
  const element = document.querySelector(elementNodeName);
  element.setAttribute(attributeName, attributeValue);
};

testSvgAttributeName("id", "someSvgId");
testSvgAttributeName("fill", "yellow"); // *** Now correctly identified as valid
testSvgAttributeName("price", "expensive");

console.log(document.querySelector('svg').innerHTML);
#someHtmlId {
  color: green;
}
#someSvgId {
  stroke-width: 5px;
}
<svg height="55">
  <rect x="10" y="10" width="80" height="40" stroke="red"/>
</svg>

<div>
<p>Hello world</p>
</div>

查看更多
登录 后发表回答