Pete Lomax
Pete Lomax

Reputation: 105

rewrite this line of javascript

In https://codepen.io/kurt_cagle/pen/xqoMBG I find a function walkData with the following statement:

var buf = Object.keys(data).map((key)=>`<details><summary  id="${key}" ${Object.keys(data[key]).map((subkey)=>{return subkey != 'children'?`data-${subkey}="${data[key][subkey]}"`:' '}).join(' ')}><img class="icon" src="${me.imageBase}${data[key].icon?data[key].icon:data[key].children?'Folder.png':'Item.png'}"> </img>${data[key].label}</summary>
 ${data[key].children?me.walkData(data[key].children):""}</details>`); 

As an old-school-non-functional-dinosaur, I find that an unholy mess of map and interpolation which is almost impossible to follow, debug, or modify. The codepen "Format Javascript" button helps, but not enough. (I would post that here but the formating defeats me)

Can this be reworked to use longhand loops, intermediate variables, and shorter lines that ideally do just one thing each.

As a side issue, there are four ` in that one line, can anyone explain to me how the js manages to parse that?

Upvotes: 1

Views: 80

Answers (3)

Pete Lomax
Pete Lomax

Reputation: 105

This is what I have ended up with (some var names may be a bit cringeworthy).

const buf = []
for (const key in data) {
    const dk = data[key];
    const s = [];
    for (const subkey in dk) {
        if (subkey != "children") {
            s.push(`data-${subkey}="${dk[subkey]}"`);
        };
    };
    const sj = s.join(" ");
    const icn = dk.icon ? dk.icon : dk.children ? "Folder.png" : "Item.png";
    const src = me.imageBase + icn;
    const children = dk.children ? me.walkData(dk.children) : "";
    const bi = `<details>
                 <summary  id="${key}" ${sj}>
                  <img class="icon" src="${src}"></img>
                  ${dk.label}
                 </summary>
                 ${children}
                </details>`;
    buf.push(bi);
 };

Upvotes: 0

Mr. Polywhirl
Mr. Polywhirl

Reputation: 48693

I left your original code in-tact, I just added appropriate new-lines and indentation.

All I added was:

  • A target element to render to
  • Some fake data that I reverse engineered using the function
  • A fake walkData function that just returns "CHILDREN"...

Note: I could actually refine this code a bit and remove the closing tag for the <img> element, since those are unnecessary. The subkey map function is also a one-liner, so it can actually be converted to a lambda with not braces nor an explicit return; just like the key mapper outside of it.

Example

I little bit of formatting goes a long way. Template literals are great, because you do not have to deal with a mess of string concatenation.

const data = {
  'a': { label: 'Directory A', children: [] },
  'b': { label: 'File B', children: null }
}

const me = {
  imageBase: '/',
  walkData: (children) => 'CHILDREN'
}

const buf = Object.keys(data).map((key) => `
  <details>
    <summary
        id="${key}" ${Object.keys(data[key]).map((subkey) => {
          return subkey != 'children'
            ? `data-${subkey}="${data[key][subkey]}"` : ' '
        }).join(' ')}>
      <img class="icon"
          src="${me.imageBase}${data[key].icon
            ? data[key].icon : data[key].children
              ? 'Folder.png' : 'Item.png'}">
      </img>
      ${data[key].label}
    </summary>
    ${data[key].children ? me.walkData(data[key].children) : ""}
  </details>
`)

document.getElementById('target').innerHTML = buf.join('')
<div id="target"></div>

Update

Here is another version that has separate function calls and come comments. It is functionally the same as the previous code.

There are no explicit return statements, since all the lambdas are all one-liners.

const data = {
  'a': { label: 'Directory A', children: [] },
  'b': { label: 'File B', children: null }
}

const me = {
  imageBase: '/',
  walkData: children => 'CHILDREN'
}

const main = () => {
  document.getElementById('target').innerHTML = render(data)
}

const render = data =>
  Object.keys(data).map(key =>
    renderDetails(key, data)).join('')

const renderDetails = (key, data) =>
  `<details>
    <summary id="${key}" ${renderDataAttributes(data[key])}>
      <img class="icon" src="${renderImageSource(data[key])}">
      </img>
      ${data[key].label}
    </summary>
    ${data[key].children ? me.walkData(data[key].children) : ""}
  </details>`

const renderDataAttributes = detail =>
  Object.keys(detail)
    .filter(key => key !== 'children')          // Filter-out children
    .map(key => `data-${key}="${detail[key]}"`) // Map to a data attr
    .join(' ')                                  // Join the values

const renderImageSource = detail =>
  me.imageBase + (
    detail.icon          /* If the detail has an icon,   */
      ? detail.icon      /* Use the defined icon         */
      : detail.children  /* Else, does it have children? */
        ? 'Folder.png'   /* If so, it's  directory       */
        : 'Item.png'     /* Else, it's a file            */
  )

main()
<div id="target"></div>

Upvotes: 1

Yousuf Khan
Yousuf Khan

Reputation: 334

Here, us this as a starting point:

const buf = []
    
for (const key in data) {
        const t = Object.keys(data[key])
            .map(subkey => {
                return subkey != 'children'
                    ? `data-${subkey}="${data[key][subkey]}"`
                    : ' '
            })
            .join(' ')

        const u = `<details><summary id="${key}" ${t}><img class="icon" src="${
            me.imageBase
        }${
            data[key].icon
                ? data[key].icon
                : data[key].children
                ? 'Folder.png'
                : 'Item.png'
        }"> </img>${data[key].label}</summary>
 ${data[key].children ? me.walkData(data[key].children) : ''}</details>`

        buf.push(u)
}

Upvotes: 1

Related Questions