javascript: in a chain, how to get size of previous array?

Viewed 600

When chaining filter and reduce, how to get the size of the filtered array? I need that size for tailoring responsive design CSS. My (simplified) code, uses the 3rd and 4th parameters of the callback:

json.articles
.filter(a => keep(a,name))
.reduce((el, a,i,t) =>
    i===0? DOMtag('section',{'class':`media${t.length}`})
        .appendChild(DOMtag(`article`, content(a,0))).parentElement
    : el.appendChild(DOMtag(`article`, content(a,i))).parentElement,
    null);

or, even simpler (thanks to luc and his lazy evaluation suggestion below):

json.articles
.filter(a => keep(a,name))
.reduce((el, a,i,t) =>
    (el || DOMtag('section',{'class':`media${t.length}`}))
        .appendChild(DOMtag(`article`, content(a,i))).parentElement,
    null);

Both code work, but if someone has an idea about binding this somehow, it would be possible to use the initial value of the accumulator, such as:

json.articles
.filter(a => keep(a,name))
.reduce((el, a) =>
    el.appendChild(DOMtag(`article`, content(a,i))).parentElement,
    DOMtag('section',{'class':`media${this.length}`}));

Any idea?

1 Answers

There is no way to have access to the filtered array length at the moment of passing the default value of reduce since the expression is evaluated before reduce is called.

You can, though, simplify the contents of your function as follows:

// Sample data
const json = {articles: [{name: 'test'},{},{name: 'test2'},{},]};
// functional helper function to illustrate the example
function DOMTag(tagName, {content, className}) {
  const tag = document.createElement(tagName);

  if (content) {
    tag.appendChild(document.createTextNode(content));
  }
  if (className) {
    tag.className = className;
  }
  return tag;
}

// Start of actual answer code:
const section = json.articles
  .filter(a => a.name)
  .map(a => DOMTag(`article`, {content: a.name}))
  .reduce((p, c, i, a) =>
    (p || DOMTag('section',{className:`media${a.length}`}))
      .appendChild(c).parentElement
      , null);

section.className = `media${section.childNodes.length}`

// Validation
console.log(section.outerHTML);    

Another option that would be clean is to set the className outside of the chain:

// Sample data
const json = {articles: [{name: 'test'},{},{name: 'test2'},{},]};
// functional helper function to illustrate the example
function DOMTag(tagName, content) {
  const tag = document.createElement(tagName);
  if (content) {
    tag.appendChild(document.createTextNode(content));
  }
  return tag;
}

// Start of actual answer code:
const section = json.articles
  .filter(a => a.name)
  .map(el => DOMTag('article', el.name))
  .reduce((p,c) => (p.appendChild(c), p), DOMTag('section'))

section.className = `media${section.childNodes.length}`

// Validation
console.log(section.outerHTML);

Option2: use a function to abstract the creation of the container:

// Sample data
const json = {articles: [{name: 'test'},{},{name: 'test2'},{},]};
// functional helper function to illustrate the example
function DOMTag(tagName, {content, className}) {
  const tag = document.createElement(tagName);
  if (content) {
    tag.appendChild(document.createTextNode(content));
  }
  if (className) {
    tag.className = className;
  }
  return tag;
}

// Start of actual answer code:

function section(children) {
  const section = DOMTag('section', {
    className: `media${children.length}`
  });
  children.forEach(c => section.appendChild(c));
  return section;
}

const s = section(
  json.articles
    .filter(a => a.name)
    .map(el => DOMTag('article', {content: el.name}))
)

// Validation
console.log(s.outerHTML);

Option 3: more functions! If you absolutely must use reduce:

// Sample data
const json = {articles: [{name: 'test'},{},{name: 'test2'},{},]};
// functional helper function to illustrate the example
function DOMTag(tagName, {content, className}) {
  const tag = document.createElement(tagName);
  if (content) {
    tag.appendChild(document.createTextNode(content));
  }
  if (className) {
    tag.className = className;
  }
  return tag;
}

// Start of actual answer code:

function defaultSection() {
  let m = false;
  return (a = []) => {
    if (!m) {
      m = DOMTag('section', {className: `media${a.length}`});
    }
    return m;
  };
}

const section = json.articles
  .filter(a => a.name)
  .map(el => DOMTag('article', {content: el.name}))
  .reduce((p,c,i,a) => {
    p(a).appendChild(c);
    return p;
  }, defaultSection())();

// Validation
console.log(section.outerHTML);

Related