Skip to content

Commit 0488f83

Browse files
authored
feat: dynamic job list (#224)
1 parent 5dc6bf4 commit 0488f83

File tree

4 files changed

+135
-53
lines changed

4 files changed

+135
-53
lines changed

blocks/job-list/job-list.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@
4343

4444
.job-list__listings {
4545
display: block;
46+
padding: 0;
47+
margin: 0;
48+
list-style-type: none;
49+
50+
& > li {
51+
margin: 0;
52+
padding: 0;
53+
}
4654

4755
& .job-listing {
4856
border-top: 1px solid var(--color-border);

blocks/job-list/job-list.js

Lines changed: 111 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,127 @@
1-
export default function decorate(block) {
2-
const jobList = document.createElement('div');
3-
jobList.classList.add('job-list', 'grid-container');
1+
import { dataStore } from "../../scripts/helpers/index.js";
42

5-
const [headingRow, ...jobRows] = [...block.children];
3+
const DEFAULT_CONTENT = {
4+
noJobs: "There are no jobs currently available."
5+
};
66

7-
const heading = headingRow.children?.[0]?.children?.[0];
8-
if (heading) {
9-
heading.classList.add('job-list__heading', 'util-heading-s', 'grid-item', 'grid-item--33');
10-
heading.textContent = headingRow.textContent;
11-
jobList.append(heading);
12-
}
7+
const HEADING_LEVEL = "h3";
138

14-
const jobListings = document.createElement('div');
15-
jobListings.className = 'job-list__listings grid-item grid-item--66';
9+
/**
10+
* Display a list of jobs, under heading(s) for each discipline.
11+
*
12+
* Jobs are dynamically listed using query-index data pulled from the careers directory.
13+
* The list of disciplines is entered in the content to allow for manual ordering.
14+
*
15+
* @param {Element} block
16+
*/
17+
export default function decorate(block) {
18+
/**
19+
* Discipline headings from each row of content.
20+
* @type {string[]}
21+
*/
22+
const headings = [...block.children].map((el) => el.textContent.trim()).filter(Boolean);
1623

17-
jobRows.forEach(row => {
18-
const [title, subheading, detail, jobId] = row.children;
24+
// Create container.
25+
const blockContainer = document.createElement('div');
26+
blockContainer.classList.add('job-list-block');
27+
blockContainer.textContent = "";
1928

20-
const jobListing = document.createElement('a');
21-
jobListing.className = 'job-listing';
29+
// Replace wrapper div parent with new block.
30+
block?.parentElement?.classList?.length === 0 ? block.parentElement.replaceWith(blockContainer) : block.replaceWith(blockContainer);
2231

23-
const contentWrapper = document.createElement('span');
24-
contentWrapper.className = 'job-listing__content';
32+
// Fetch the dynamic job data async, create markup, and append to the container.
33+
(async () => {
34+
// Get jobs data from endpoint.
35+
const jobsData = await dataStore.getData(dataStore.commonEndpoints.careers);
2536

26-
if (title) {
27-
const titleEl = document.createElement('span');
28-
titleEl.className = 'job-listing__title';
29-
titleEl.textContent = title.textContent;
30-
contentWrapper.append(titleEl);
31-
}
37+
// Filter only the ones with disciplines that are displayed.
38+
const allJobsForDisplay = jobsData?.data?.length
39+
? jobsData.data.filter(job => job?.jobDiscipline && headings.includes(job.jobDiscipline))
40+
: [];
3241

33-
if (subheading) {
34-
const subheadingEl = document.createElement('span');
35-
subheadingEl.className = 'job-listing__subheading';
36-
subheadingEl.textContent = subheading.textContent;
37-
contentWrapper.append(subheadingEl);
42+
// If no jobs, display null state message.
43+
if (!allJobsForDisplay.length) {
44+
blockContainer.classList.remove('grid-container');
45+
const message = document.createElement('p');
46+
message.textContent = DEFAULT_CONTENT.noJobs;
47+
blockContainer.append(message);
48+
return;
3849
}
3950

40-
if (detail) {
41-
const detailEl = document.createElement('span');
42-
detailEl.className = 'job-listing__detail';
43-
detailEl.textContent = detail.textContent;
44-
contentWrapper.append(detailEl);
45-
}
51+
// Newly created elements that will be appended to blockContainer when finished.
52+
const allJobMarkup = document.createDocumentFragment();
4653

47-
jobListing.append(contentWrapper);
54+
// Loop through each discipline.
55+
headings.forEach(heading => {
56+
/**
57+
* Jobs with this discipline in metadata
58+
* @type {object[]}
59+
*/
60+
const jobs = jobsData?.data?.filter(job => job?.jobDiscipline == heading) ?? [];
61+
if (!jobs.length) { return; }
4862

49-
if (jobId) {
50-
jobListing.href = `https://adobe.design/jobs/job-posts/${jobId.textContent.trim()}`;
51-
}
63+
// Container with heading and job list.
64+
const jobGrouping = document.createElement('div');
65+
jobGrouping.classList.add('job-list','grid-container');
5266

53-
jobListings.append(jobListing);
54-
});
67+
// Heading element.
68+
const headingElement = document.createElement(HEADING_LEVEL);
69+
headingElement.classList.add('job-list__heading', 'util-heading-s', 'grid-item', 'grid-item--33');
70+
headingElement.textContent = heading;
5571

56-
jobList.append(jobListings);
57-
block.textContent = '';
72+
// Job list under this heading.
73+
const jobsListingsWrap = document.createElement('ul');
74+
jobsListingsWrap.classList.add('job-list__listings', 'grid-item', 'grid-item--66');
5875

59-
// Replace wrapper div parent with new block.
60-
block?.parentElement?.classList?.length === 0 ? block.parentElement.replaceWith(jobList) : block.replaceWith(jobList);;
76+
// Display each job (using "job-listing" component styles).
77+
jobs.forEach(row => {
78+
const {jobTitle, department, location, path} = row;
79+
80+
const jobListItem = document.createElement('li');
81+
const jobItem = document.createElement('a');
82+
jobItem.classList.add('job-listing');
83+
jobListItem.append(jobItem);
84+
85+
// Link to job page.
86+
if (path) {
87+
jobItem.href = path.trim();
88+
}
89+
90+
const contentWrapper = document.createElement('span');
91+
contentWrapper.classList.add('job-listing__content');
92+
jobItem.append(contentWrapper);
93+
94+
if (jobTitle) {
95+
const titleEl = document.createElement('span');
96+
titleEl.classList.add('job-listing__title');
97+
titleEl.textContent = jobTitle.trim();
98+
contentWrapper.append(titleEl);
99+
}
100+
101+
if (department) {
102+
const subheadingEl = document.createElement('span');
103+
subheadingEl.classList.add('job-listing__subheading');
104+
subheadingEl.textContent = department.trim();
105+
contentWrapper.append(subheadingEl);
106+
}
107+
108+
if (location) {
109+
const detailEl = document.createElement('span');
110+
detailEl.classList.add('job-listing__detail');
111+
detailEl.textContent = location.trim();
112+
contentWrapper.append(detailEl);
113+
}
114+
115+
// Append job.
116+
jobsListingsWrap.append(jobListItem);
117+
});
118+
119+
// Append grouping with heading and its jobs.
120+
jobGrouping.append(headingElement, jobsListingsWrap);
121+
allJobMarkup.append(jobGrouping);
122+
});
123+
124+
// Append all new elements to the main block container.
125+
blockContainer.append(allJobMarkup);
126+
})();
61127
}

blocks/job-listing/job-listing.js

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,30 @@
1+
/**
2+
* Display a single job listing item.
3+
* @param {Element} block
4+
*/
15
export default function decorate(block) {
26
const jobListing = document.createElement('a');
37
jobListing.className = 'job-listing';
48

59
const row = block.firstElementChild;
610
if (row) {
7-
const [title, subheading, detail, jobId] = row.children;
11+
// Content settings, from each column.
12+
const [title, subheading, detail] = row.children;
813

914
const contentWrapper = document.createElement('span');
1015
contentWrapper.className = 'job-listing__content';
11-
16+
1217
if (title) {
1318
const titleEl = document.createElement('span');
1419
titleEl.className = 'job-listing__title';
1520
titleEl.textContent = title.textContent;
1621
contentWrapper.append(titleEl);
22+
23+
// Job link from linked title text.
24+
const href = title.querySelector('a')?.href;
25+
if (href) {
26+
jobListing.href = href;
27+
}
1728
}
1829

1930
if (subheading) {
@@ -31,10 +42,6 @@ export default function decorate(block) {
3142
}
3243

3344
jobListing.append(contentWrapper);
34-
35-
if (jobId) {
36-
jobListing.href = `https://adobe.design/jobs/job-posts/${jobId.textContent.trim()}`;
37-
}
3845
}
3946

4047
block.textContent = '';

blocks/job-listing/job-listing.test.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ describe('Job-Listing Block', () => {
2828
expect(hasDetail).toExist();
2929
});
3030

31-
it('should link to the correct job posting URL', async () => {
32-
const jobListing = await page.$('.job-list .job-listing[href^="https://adobe.design/jobs/job-posts/"]');
31+
it('should be linked.', async () => {
32+
// Ensures the HREF attribute exists and isn't set to an empty string.
33+
const jobListing = await page.$('.job-list .job-listing[href]:not([href=""])');
3334
expect(jobListing).toExist();
3435
});
3536
});

0 commit comments

Comments
 (0)