Sebastian
Sebastian

Reputation: 149

Shorter method when using append child

I am creating some elements from an object returned via fetch request and wanted to know was there a better method to simplify this?

As it stands it works as expected but it seems ridiculously long for what seems like a simple task?

async function getJobs() {

  const response = await fetch('https://app.beapplied.com/public/all-jobs');
  const data = await response.json();
  console.log(data.jobs);
  for (const job of data.jobs) {
    const jobContainer = document.getElementById("jobData");

    const jobListing = document.createElement('li');
    jobContainer.appendChild(jobListing);
    jobListing.setAttribute('class', 'whr-item')

    const jobLink = document.createElement('a');
    jobListing.appendChild(jobLink);
    jobLink.setAttribute('href', job.applyLink)

    let jobTitle = document.createElement("h3");
    jobTitle.innerHTML = job.title;
    jobTitle.setAttribute('class', 'whr-title')
    jobLink.appendChild(jobTitle);

    const jobInfo = document.createElement('ul');
    jobListing.appendChild(jobInfo);
    jobInfo.setAttribute('class', 'whr-info')

    let jobTeam = document.createElement("li");
    jobTeam.innerHTML = job.team;
    jobTeam.setAttribute('class', 'whr-dept')
    jobInfo.appendChild(jobTeam);

    let jobLocation = document.createElement("li");
    jobLocation.innerHTML = job.location
    jobLocation.setAttribute('class', 'whr-location')
    jobInfo.appendChild(jobLocation);
  }
}

getJobs()
<ul id="jobData" class="whr-items"></ul>

Upvotes: 1

Views: 169

Answers (3)

Invizi
Invizi

Reputation: 1298

I created my own class for making simple elementsthat include the tag, class and text. You can expand and modify it to suite all your needs.

UPDATE:

I added an example of how to use the class for your question.

Because of CORS error, I copied the request data and made an object instead of doing a fetch request. It aslo means you will have to scroll down a bit, as the json data is pretty long.

I also made a helpler function called insert for attaching nodes easier.

class Elm {
  constructor(tag) {
    this.tag = tag;
    this.classList = [];
    this.attributes = [];
    this.txt = ""
    return this;
  }

  class(cls) {
    this.classList.push(cls);
    return this;
  }

  text(txt) {
    this.txt = txt;
    return this;
  }

  attr(attr, val) {
    this.attributes.push({
      attr,
      val
    });
    return this;
  }

  create() {
    const e = document.createElement(this.tag);
    if (this.classList.length)
      e.classList.add(...this.classList);
    if (this.txt.length) e.textContent = this.txt;
    if (this.attributes.length) this.attributes.forEach(atr => e.setAttribute(atr.attr, atr.val));
    return e;
  }
}

function insert(targetNode, childrenArray) {
  targetNode.append(...childrenArray);
  return targetNode;
}

async function getJobs() {
  const data = {
    jobs: [{
        id: 12878,
        title: "Facilities Coordinator",
        location: "Avon Tyrrell Outdoor Centre",
        closes: "2021-09-27T07:59:00.000Z",
        public: true,
        archived: false,
        phase: "ACTIVE_APPLYING",
        OrgId: 1415,
        createdAt: "2021-09-15T10:30:13.312Z",
        package: "£18,525",
        hash: "dbppfgddee",
        Org: {
          customLogoURL: "https://beapplied.global.ssl.fastly.net/org/1415/logo.png",
          apiUrl: "https://app.beapplied.com/B1o/public-jobs",
          retentionMonths: 48,
          id: 1415,
          name: "UK Youth",
          customLogo: true,
          domain: "ukyouth.org",
          OrgTiers: [{
            id: "1382",
            OrgId: "1415",
            TierId: "4",
            createdAt: "2021-02-19T18:34:45.692Z",
            updatedAt: "2021-02-19T18:34:45.692Z",
          }, ],
        },
        orgLogo: "https://beapplied.global.ssl.fastly.net/org/1415/logo.png",
      },
      {
        id: 12875,
        title: "Digital Recruitment Officer",
        location: "Home based with some travel",
        closes: "2021-10-15T11:00:00.000Z",
        public: true,
        archived: false,
        phase: "ACTIVE_APPLYING",
        OrgId: 1085,
        createdAt: "2021-09-15T09:30:44.770Z",
        package: "£30,000 per annum plus benefits",
        hash: "lalasq2s0f",
        Org: {
          customLogoURL: "https://beapplied.global.ssl.fastly.net/org/1085/logo.png",
          apiUrl: "https://app.beapplied.com/NDV/public-jobs",
          retentionMonths: 48,
          id: 1085,
          name: "The Trussell Trust",
          customLogo: true,
          domain: "trusselltrust.org",
          OrgTiers: [{
            id: "1052",
            OrgId: "1085",
            TierId: "4",
            createdAt: "2020-08-19T15:36:57.700Z",
            updatedAt: "2020-08-19T15:36:57.700Z",
          }, ],
        },
        orgLogo: "https://beapplied.global.ssl.fastly.net/org/1085/logo.png",
      },
      {
        id: 12873,
        title: "Editorial Manager",
        location: "Central London, Blackfriars - we offer flexibility to work remotely",
        closes: "2021-09-28T07:00:00.000Z",
        public: true,
        archived: false,
        phase: "ACTIVE_APPLYING",
        OrgId: 2,
        createdAt: "2021-09-15T07:30:43.858Z",
        package: "circa £45k plus excellent benefits",
        hash: "yefkapfn7d",
        Org: {
          customLogoURL: "https://logo.clearbit.com/nesta.org.uk",
          apiUrl: "https://app.beapplied.com/0M/public-jobs",
          retentionMonths: 48,
          id: 2,
          name: "Nesta",
          customLogo: false,
          domain: "nesta.org.uk",
          OrgTiers: [{
            id: "93",
            OrgId: "2",
            TierId: "4",
            createdAt: "2017-02-13T12:56:56.014Z",
            updatedAt: "2018-02-21T10:42:01.318Z",
          }, ],
        },
        orgLogo: "https://logo.clearbit.com/nesta.org.uk",
      },
      {
        id: 12867,
        title: "Partnerships Senior Account Executive - Motorsport & Football",
        location: "London",
        closes: "2021-09-21T22:59:00.000Z",
        public: true,
        archived: false,
        phase: "ACTIVE_APPLYING",
        OrgId: 878,
        createdAt: "2021-09-14T18:24:32.381Z",
        package: "£27,000 ",
        hash: "ozd7o9yxmy",
        Org: {
          customLogoURL: "https://beapplied.global.ssl.fastly.net/org/878/logo.png",
          apiUrl: "https://app.beapplied.com/gDo/public-jobs",
          retentionMonths: 48,
          id: 878,
          name: "OMG UK",
          customLogo: true,
          domain: "omnicommediagroup.com",
          OrgTiers: [{
            id: "845",
            OrgId: "878",
            TierId: "4",
            createdAt: "2020-02-17T15:45:22.547Z",
            updatedAt: "2020-02-17T15:45:22.547Z",
          }, ],
        },
        orgLogo: "https://beapplied.global.ssl.fastly.net/org/878/logo.png",
      },
      {
        id: 12862,
        title: "Researcher",
        location: "London or Birmingham ",
        closes: "2021-10-17T19:00:00.000Z",
        public: true,
        archived: false,
        phase: "ACTIVE_APPLYING",
        OrgId: 1490,
        createdAt: "2021-09-14T14:01:43.555Z",
        package: "£24,000-30,000 PA depending on experience",
        hash: "pjqnt94d3x",
        Org: {
          customLogoURL: "https://beapplied.global.ssl.fastly.net/org/1490/logo.png",
          apiUrl: "https://app.beapplied.com/0vJ/public-jobs",
          retentionMonths: 48,
          id: 1490,
          name: "BOP Consulting",
          customLogo: true,
          domain: "bop.co.uk",
          OrgTiers: [{
            id: "1457",
            OrgId: "1490",
            TierId: "4",
            createdAt: "2021-04-27T20:02:37.063Z",
            updatedAt: "2021-04-27T20:02:37.063Z",
          }, ],
        },
        orgLogo: "https://beapplied.global.ssl.fastly.net/org/1490/logo.png",
      },
      {
        id: 12861,
        title: "Talent Manager: FTC Nov 2021 -April 2022",
        location: "London and Remote - we are a hybrid organisation",
        closes: "2021-09-29T11:00:00.000Z",
        public: true,
        archived: false,
        phase: "ACTIVE_APPLYING",
        OrgId: 706,
        createdAt: "2021-09-14T13:57:57.425Z",
        package: "£37,600 - £41,000",
        hash: "9fcnvbhovr",
        Org: {
          customLogoURL: "https://beapplied.global.ssl.fastly.net/org/706/logo.png",
          apiUrl: "https://app.beapplied.com/X4M/public-jobs",
          retentionMonths: 48,
          id: 706,
          name: "Comic Relief",
          customLogo: true,
          domain: "comicrelief.com",
          OrgTiers: [{
            id: "673",
            OrgId: "706",
            TierId: "4",
            createdAt: "2018-05-10T07:28:08.566Z",
            updatedAt: "2019-01-08T08:07:28.542Z",
          }, ],
        },
        orgLogo: "https://beapplied.global.ssl.fastly.net/org/706/logo.png",
      },
      {
        id: 12860,
        title: "Analyst (A Sustainable Future)",
        location: "Central London, Blackfriars - we offer flexibility to work remotely",
        closes: "2021-10-05T07:00:00.000Z",
        public: true,
        archived: false,
        phase: "ACTIVE_APPLYING",
        OrgId: 2,
        createdAt: "2021-09-14T12:42:54.213Z",
        package: "£34k, plus excellent benefits",
        hash: "1yut7jhl6h",
        Org: {
          customLogoURL: "https://logo.clearbit.com/nesta.org.uk",
          apiUrl: "https://app.beapplied.com/0M/public-jobs",
          retentionMonths: 48,
          id: 2,
          name: "Nesta",
          customLogo: false,
          domain: "nesta.org.uk",
          OrgTiers: [{
            id: "93",
            OrgId: "2",
            TierId: "4",
            createdAt: "2017-02-13T12:56:56.014Z",
            updatedAt: "2018-02-21T10:42:01.318Z",
          }, ],
        },
        orgLogo: "https://logo.clearbit.com/nesta.org.uk",
      },
    ],
  };

  const container = document.querySelector("#jobData");

  data.jobs.forEach(job => {

    const jobItem = new Elm("li")
      .class("whr-item")
      .create();

    const jobTitle = new Elm("a")
      .attr("href", job.Org.apiUrl)
      .text(job.title)
      .create();

    const jobInfo = new Elm("ul")
      .class("whr-info")
      .create();

    const jobTeam = new Elm("li")
      .class("whr-dept")
      .text(`Group: ${job.Org.name}`)
      .create();

    const jobLocation = new Elm("li")
      .class("whr-location")
      .text(`Location: ${job.location}`)
      .create();

    // <JobItem class="whr-item">
    //   <JobTitle href="">Title of the Job </JobTitle>
    //   <JobInfo class="whr-info">
    //     <JobTeam class="whr-dept">Team Name</JobTeam>
    //     <JobLocation class="whr-location">Location of the Job</JobLocation>
    //   </JobInfo>
    // </JobItem>

    const final = insert(jobItem, [
      jobTitle,
      insert(jobInfo, [
        jobTeam,
        jobLocation,
      ]),
    ]);

    container.appendChild(final);
  });
}

window.onload = () => getJobs();
html,
body {
  padding: 0;
  margin: 0;
  height: 100%;
}

.whr-items {
  padding: 0;
}

.whr-item {
  display: flex;
  flex-direction: column;
  padding: 0.75rem 1rem;
  list-style-type: none;
}

.whr-item:nth-child(2n) {
  background-color: lightgrey;
}

.whr-item a {
  text-decoration: none;
  color: black;
  font-size: 1.2rem;
  margin-bottom: 0.5rem;
}

.whr-item li,
.whr-item ul {
  padding: 0;
  list-style-type: none;
}
<ul id="jobData" class="whr-items"></ul>

Upvotes: 0

navnath
navnath

Reputation: 3714

function getJobs() {

    fetch('https://app.beapplied.com/public/all-jobs')
    .then(response => response.json())
    .then(data => data.jobs.map(job => createJobHTML(job))) // create HTML for each job
    .then(htmlArr => document.getElementById("jobData").innerHTML = htmlArr.join(''));
  
}

function createJobHTML(job){
  return `<li class="whr-item">
      <a href="${job.applyLink}">
        <h3 class="whr-title">${job.title}</h3>
      </a>
      <ul class="whr-info">
        <li class="whr-dept">${job?.team ?? '-'}</li>
        <li class="whr-location">${job?.location ?? '-'}</li>
      </ul>
    </li>`;
}

getJobs();
<ul id="jobData" class="whr-items"></ul>

Upvotes: 0

CertainPerformance
CertainPerformance

Reputation: 370789

Since you're assigning to the .innerHTML of the nested children, writing out the HTML string and interpolating would be equivalent and a lot more understandable at a glance:

const jobContainer = document.getElementById("jobData");
for (const job of data.jobs) {
    jobContainer.insertAdjacentHTML(
        'beforeend',
        `<li class="whr-item">
          <a href="${job.applyLink}">
            <h3>${job.title}</h3>
          </a>
          <ul>
            <li class="whr-dept">${job.team}</li>
            <li class="whr-location">${job.location}</li>
          </ul>
        </li>
        `
    );
}

That said, interpolating external values directly HTML (or using .innerHTML with external values) is a security risk, since it allows for arbitrary code execution. I'd recommend writing out the HTML without the inserted values, then insert them safely once the structure has been created. For example, you could use createElement for the .whr-item so you have a reference to it, insert its HTML without the dynamic values, then do

item.querySelector('a').href = job.applyLink;
item.querySelector('h3').href = job.title;
item.querySelector('.whr-dept').href = job.team;
item.querySelector('.whr-location').href = job.location;

This would be a safer approach.

Upvotes: 2

Related Questions