2

It is possible to expands an object literal as html5 data- attributes?

Having the following object:

const requirements = {
    'data-description': 'some text...',
    'data-pointer': true,
    'data-speaker': true
}

I would like to expand it in a anchor tag in order to get something like this:

<a href="#" class="show-modal" data-description="some-text" data-pointer="true" data-speaker="true">Show modal</a>

I tried to use the spread syntax in this way <a href="#" class="show-modal" `${...requirements}`>Show modal</a> But nothing get printed

I am now depending on this function that builds an anchor and passes the data dynamically.

function buildAnchor(requirements) {
    const anchor = document.createElement('a');

    anchor.setAttribute('class', 'show-modal');
    anchor.setAttribute('href', '#');
    anchor.textContent = 'More info';

    Object.keys(requirements).forEach(data => {
        anchor.setAttribute(data, requirements[data]);
    });

    return anchor.outerHTML;
}

This function do the job, but i would like to know if it's possible to use spread syntax

Thanks in advance

Mario
  • 4,784
  • 3
  • 34
  • 50
  • Spread is for arrays. You have an object. – Scott Marcus Oct 08 '17 at 15:29
  • 2
    It is also possible in objects as i can read in mdn https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator – Mario Oct 08 '17 at 15:36
  • Yes, but for your usage (a function that builds the element), you need an array. See my answer below. – Scott Marcus Oct 08 '17 at 15:52
  • @user615274: Please clarify, are you saying that the only thing that matters about the question is the ability to use *spread syntax*, or are you saying that you simply want a nice, short, clean solution? – llama Oct 08 '17 at 16:40
  • ...the title ***Expand object literal as html5 data- attributes*** sure seems to give a clear summary that you actually care about a real-world solution to a real-world problem, but I guess this is still a burning question for some... – llama Oct 08 '17 at 16:42

4 Answers4

4

How about straightforwardly using an HTMLElement's dataset property and then assigning a simplified configuration object to it via Object.assign ... like ...

var requirements = {
  'description': 'some text...',
  'pointer': true,
  'speaker': true
};
var elmLink = document.createElement('a');

elmLink.href = '';
Object.assign(elmLink.dataset, requirements);

console.log('elmLink : ', elmLink);
.as-console-wrapper { max-height: 100%!important; top: 0; }
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
  • This answer works, but avoids the entire point of the question, which was to use the spread syntax. – Scott Marcus Oct 08 '17 at 15:49
  • 1
    @ScottMarcus ... the OP's question and also the title that was chosen do state something else ... is it "... possible to expands an object literal as html5 data- attributes?" – Peter Seliger Oct 08 '17 at 15:53
  • And yet, the last line of the question is: *This function do the job, but i would like to know if it's possible to **use spread syntax***. – Scott Marcus Oct 08 '17 at 15:55
1

You can define the data-* attribute as a single JSON string, then use JSON.parse() to create a JavaScript object representation of the .dataset property.

Note the single quote surrounding attribute value within template literal that surrounds valid JSON at HTML string.

const requirements = {
    'description': 'some text...',
    'pointer': true,
    'speaker': true
}

const a = `<a data-requirements='${JSON.stringify(requirements)}'>click</a>`;

document.body.insertAdjacentHTML("beforeend", a);

let data = JSON.parse(document.querySelector("a").dataset.requirements);
           
console.log(data, data.description, data.pointer, data.speaker);
guest271314
  • 1
  • 15
  • 104
  • 177
0

The spread syntax won't do what you want. That's really for including key/value pairs from one object into another, or for destructuring assignment.

You can use Object.entries() with .map() and .join() inside a template literal.

const requirements = {
  'data-description': 'some text...',
  'data-pointer': true,
  'data-speaker': true
};
const s = `<a href="#" class="show-modal" ${Object.entries(requirements).map(([k, v]) => k + "='" + v + "'").join(" ")}>Show modal</a>`;

console.log(s);

I think it would be cleaner to move the attribute creation into its own function.

const requirements = {
  'data-description': 'some text...',
  'data-pointer': true,
  'data-speaker': true
};
const s = `<a href="#" class="show-modal" ${oToA(requirements)}>Show modal</a>`;

console.log(s);

function oToA(obj) {
  return Object.entries(obj)
               .map(([k, v]) => k + "='" + v + "'")
               .join(" ")
}

You could enhance the function by replacing embedded quotes with HTML entities.

llama
  • 2,535
  • 12
  • 11
  • @ScottMarcus: He's asking about using it with his existing data structure. Sure *you* can change his requirements, but that doesn't mean *he* can change his requirements. – llama Oct 08 '17 at 15:52
0

From MDN:

Spread syntax allows an iterable such as an array expression to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected, or an object expression to be expanded in places where zero or more key-value pairs (for object literals) are expected.

Since you are trying to build a function that takes in the requirements as arguments (not key/value pairs), an array structure would make better sense than an object structure.

const requirements = [
    {'data-description': 'some text...'},
    {'data-pointer': true},
    {'data-speaker': true}
];

(function populate(){
  var anchor = document.createElement("a");
  // Convert the arguments object to an array and enumerate it
  // so that each object key can become an attribute and the 
  // corresponding value can become the value:
  Array.prototype.slice.call(arguments).forEach(function(arg){
    for(var prop in arg){
      anchor.setAttribute(prop, arg[prop]);
    }
  });
  console.log(anchor);
})(...requirements);
Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
  • 1
    It is wrong to say that spread is just for arrays. It's for object literals to as described in your own docs link. Also, it avoids the point of the question, which is to work with the existing data structure. – llama Oct 08 '17 at 15:53
  • @guest271314: At that point it is actually spread, because it's taking an existing collection, and "spreading" them into another collection. Rest is where you're receiving the "rest" (remainder) of the arguments. So "spread" sends a list of items, and "rest" receives them. Though he *could* use "rest" syntax in the function parameter. `function populate(...args) {`, thereby avoiding the need for `Array.prototype.slice.call(arguments).forEach(...`, since `args` would be an actual Array. – llama Oct 08 '17 at 17:08
  • @llama Appears to be rest element syntax at Answer, where `...` is used at last (only) parameter to funtion – guest271314 Oct 08 '17 at 17:13
  • @guest271314: But it's in the function call. Conceptually, it's the same as using `...` to include items into a new array. Like: `var newArr = [1, 2, ...oldArray];` Directionally, the content of an existing collection is being spread out into a new collection. – llama Oct 08 '17 at 17:15
  • @llama Are you suggesting that `(function fn() {})(...arr)` is different from `function fn(...arr){}`? – guest271314 Oct 08 '17 at 17:17
  • @guest271314: Yes. ;-) The first example you give is taking an array and spreading them into the call of the function (into its parameters or arguments object). The second example is defining a parameter that takes the rest of the arguments passed (which in that case is all the arguments) and puts them into the `arr` array. – llama Oct 08 '17 at 17:18
  • @llama Either pattern provides same results. `...` used at function parameter is rest parameter. Described the syntax as rest element at previous comment https://stackoverflow.com/questions/46632795/expand-object-literal-as-html5-data-attributes/46632997?noredirect=1#comment80216913_46632993, which is not accurate. – guest271314 Oct 08 '17 at 17:21
  • @guest271314: The results are certainly related, but the difference is in where the `...` is used. Notice in the docs for the spread syntax, that all the `...` examples are taking an existing collection, and inserting its members into a new collection. That's equivalent to having the `...` at the call of a function. `foo(...arr)`. Whereas having the `...` at the *definition* of the function parameter states that any remaining arguments should be added to a *new* Array. It's subtle distinction, but still a distinction. – llama Oct 08 '17 at 17:24
  • @llama When `...` is used before the last parameter passed to a function call the appropriate description is rest parameter, see [What is SpreadElement in ECMAScript documentation? Is it the same as Spread operator at MDN?](https://stackoverflow.com/q/37151966/) – guest271314 Oct 08 '17 at 17:27
  • @guest271314: Yes, that's what I'm saying. But in this answer, the `...` is not used to define a parameter; it's used to *call* the IIFE function. The `(...requrements)` at the end is the same as doing `foo(...requirements)` if the function had been named `foo`. – llama Oct 08 '17 at 17:28
  • @llama _"the `...` is not used to define a parameter"_ ? _"it's used to call the IIFE function"_ with the parameter `...requirements` – guest271314 Oct 08 '17 at 17:30
  • 1
    @guest271314: The `...requirements` isn't a parameter. It's an Array that was created above the IIFE, so they're being passed as arguments. Remember, "parameters" are in a function *definition*, whereas "arguments" are in a function *call*. – llama Oct 08 '17 at 17:31
  • 1
    ...Here's a related Q&A: https://stackoverflow.com/questions/156767/whats-the-difference-between-an-argument-and-a-parameter – llama Oct 08 '17 at 17:32
  • @llama Why is `...requirements` not a declaration? `...requirements` is declared as the value to be passed to the function call, yes? – guest271314 Oct 08 '17 at 17:39
  • 1
    @guest271314: Because it's not defining any new identifiers. It's just passing existing data to the function. Using the spread syntax, it's basically equivalent to doing `foo(requirements[0], requirements[1], requirements[2])`, except in an IIFE instead of a function named `foo`. – llama Oct 08 '17 at 17:41