Back to Freecodecamp

Build an Availability Table

curriculum/challenges/english/blocks/lab-availability-table/66b36358ed4f261d64840c24.md

latest20.6 KB
Original Source

--description--

Objective: Fulfill the user stories below and get all the tests to pass to complete the lab.

User Stories:

  1. You should have a table with at least three columns and five rows, headers included.
  2. Cells in the first row should be table headers containing days of the week.
  3. Cells in the first column should be table headers with a class of time and should contain time values as their text.
  4. All data cells should have the class of available-#, where # is a number from 0 to 5 that specifies the number of available people at that time.
  5. In your :root selector, you should define six CSS variables named --color#, where # is a number from 0 to 5, and assign them each a color value. Use these variables to set the background color of the corresponding .available-# elements.
  6. In your :root selector, you should define CSS variables named --solid-border and --dashed-border. Use these variables to style the bottom borders of your data cells, alternating between solid and dashed borders depending on the row. Give the rows either the class of sharp or half to style them.
  7. You should have a div element with the id of legend. It should contain a span element with the text Availability and a div element with the id of legend-gradient.
  8. You should give the #legend-gradient element a linear gradient that transitions between all the colors from --color0 to --color5 with hard lines.

Note: Be sure to link your stylesheet in your HTML to apply your CSS.

--hints--

You should have a table element.

js
assert.isNotNull(document.querySelector('table'));

You should have only one table element.

js
assert.lengthOf(document.querySelectorAll('table'), 1);

Your table should have at least 5 rows.

js
assert.isAtLeast(document.querySelectorAll('tr')?.length, 5);

Your table should have at least 3 columns.

js
const rows = [...document.querySelectorAll('tr')];
assert.isNotEmpty(rows);
rows.forEach((row) => {
    assert.isAtLeast(row.children.length, 3);
})

The first row in your table should contain at least three th elements.

js
assert.isAtLeast(document.querySelectorAll('tr:first-child>th').length, 3)

The first row in your table should not contain td elements.

js
const firstRow = document.querySelector('tr');
assert.isNotNull(firstRow);
assert.isEmpty(firstRow.querySelectorAll('td'));

You should have at least two rows with the class of sharp.

js
assert.isAtLeast(document.querySelectorAll('tr[class~="sharp"]').length, 2);

You should have at least two rows with the class of half.

js
assert.isAtLeast(document.querySelectorAll('tr[class~="half"]').length, 2);

th elements in the first column should have the class of time and contain time values.

js
const firstColumnData = [...document.querySelectorAll('tr th:first-child')];
assert.isNotEmpty(firstColumnData);
for (let cell of firstColumnData) {
    if (cell.innerText.match(/\d/)?.length) {
        assert.isTrue(cell.classList.contains('time'));
    }
}

You should have at least four th elements with the class of time that contain time values.

js
const timeClass = document.querySelectorAll('.time');
assert.isAtLeast(timeClass.length, 4);
for (let cell of timeClass) {
    assert.match(cell.innerText, /\d/)
}

All your td elements should have the class of available-#, where # is a number from 0 to 5.

js
const cells = [...document.querySelectorAll('tr[class="sharp"]>td, tr[class="half"]>td')];
assert.isNotEmpty(cells);
for (let cell of cells) {
    const classString = [...cell.classList].join(' ');
    assert.lengthOf(classString.match(/(?<=\s|^)available-[0-5](?=\s|$)/gm), 1);
}

You should have at least eight .available-# elements, where # is a number between 0 and 5.

js
assert.isAtLeast(document.querySelectorAll('td[class*="available-"]').length, 8);

You should define a --color0 variable in your root selector.

js
const root = new __helpers.CSSHelp(document).getStyle(':root');
assert.exists(root); 
assert.isNotEmpty(root.getPropVal('--color0'))

You should define a --color1 variable in your root selector.

js
const root = new __helpers.CSSHelp(document).getStyle(':root');
assert.exists(root); 
assert.isNotEmpty(root.getPropVal('--color1'))

You should define a --color2 variable in your root selector.

js
const root = new __helpers.CSSHelp(document).getStyle(':root');
assert.exists(root); 
assert.isNotEmpty(root.getPropVal('--color2'))

You should define a --color3 variable in your root selector.

js
const root = new __helpers.CSSHelp(document).getStyle(':root');
assert.exists(root); 
assert.isNotEmpty(root.getPropVal('--color3'));

You should define a --color4 variable in your root selector.

js
const root = new __helpers.CSSHelp(document).getStyle(':root');
assert.exists(root); 
assert.isNotEmpty(root.getPropVal('--color4'));

You should define a --color5 variable in your root selector.

js
const root = new __helpers.CSSHelp(document).getStyle(':root');
assert.exists(root); 
assert.isNotEmpty(root.getPropVal('--color5'));

You should use --color0 to set the background color of .available-0 elements.

js
const a = new __helpers.CSSHelp(document).getStyle('.available-0');
assert.exists(a); 
assert.strictEqual(a.backgroundColor, 'var(--color0)');

You should use --color1 to set the background color of .available-1 elements.

js
const a = new __helpers.CSSHelp(document).getStyle('.available-1');
assert.exists(a); 
assert.strictEqual(a.backgroundColor, 'var(--color1)');

You should use --color2 to set the background color of .available-2 elements.

js
const a = new __helpers.CSSHelp(document).getStyle('.available-2');
assert.exists(a); 
assert.strictEqual(a.backgroundColor, 'var(--color2)');

You should use --color3 to set the background color of .available-3 elements.

js
const a = new __helpers.CSSHelp(document).getStyle('.available-3');
assert.exists(a); 
assert.strictEqual(a.backgroundColor, 'var(--color3)');

You should use --color4 to set the background color of .available-4 elements.

js
const a = new __helpers.CSSHelp(document).getStyle('.available-4');
assert.exists(a); 
assert.strictEqual(a.backgroundColor, 'var(--color4)');

You should use --color5 to set the background color of .available-5 elements.

js
const a = new __helpers.CSSHelp(document).getStyle('.available-5');
assert.exists(a); 
assert.strictEqual(a.backgroundColor, 'var(--color5)');

You should define a --solid-border variable in your root selector.

js
const root = new __helpers.CSSHelp(document).getStyle(':root');
assert.exists(root); 
assert.isNotEmpty(root.getPropVal('--solid-border'));

You should define a --dashed-border variable in your root selector.

js
const root = new __helpers.CSSHelp(document).getStyle(':root');
assert.exists(root); 
assert.isNotEmpty(root.getPropVal('--dashed-border'));

You should target td elements that are children of .sharp elements.

js
const tds = new __helpers.CSSHelp(document).getStyle('.sharp td') || new __helpers.CSSHelp(document).getStyle('.sharp>td');
assert.exists(tds);

You should use --solid-border to set the bottom-border of td elements that are children of .sharp elements.

js
const tds = new __helpers.CSSHelp(document).getStyle('.sharp td') || new __helpers.CSSHelp(document).getStyle('.sharp>td');
assert.exists(tds);
assert.strictEqual(tds.borderBottom, 'var(--solid-border)');

You should target td elements that are children of .half elements.

js
const tds = new __helpers.CSSHelp(document).getStyle('.half td') || new __helpers.CSSHelp(document).getStyle('.half>td');
assert.exists(tds);

You should use --dashed-border to set the bottom-border of td elements of .half elements.

js
const tds = new __helpers.CSSHelp(document).getStyle('.half td') || new __helpers.CSSHelp(document).getStyle('.half>td');
assert.exists(tds);
assert.strictEqual(tds.borderBottom, 'var(--dashed-border)');

You should have a div element with the id of legend.

js
assert.equal(document.querySelector('#legend')?.tagName, 'DIV');

You should have a span element with the text Availability inside your #legend.

js
assert.equal(document.querySelector('#legend span')?.textContent.trim(), 'Availability');

You should have a div element with the id of legend-gradient inside your #legend.

js
assert.equal(document.querySelector('#legend #legend-gradient')?.tagName, 'DIV');

You should set the background image of #legend-gradient to a linear gradient.

js
const legendGradient = new __helpers.CSSHelp(document).getStyle('#legend-gradient');
assert.exists(legendGradient); 
assert.include(legendGradient.backgroundImage, "linear-gradient(")

You should have the transitions from one color to the following color as a hard line for your #legend-gradient. Make sure your percentages are whole numbers.

js
const legendGradient = new __helpers.CSSHelp(document).getStyle('#legend-gradient');
assert.exists(legendGradient);
const gradientStr = legendGradient.backgroundImage;

const COLOR_COUNT = 6;
const EXPECTED_SHORTHAND_STOPS = 6;
const EXPECTED_EXPANDED_STOPS = 12;

for (let i = 0; i < COLOR_COUNT; i++) {
  assert.include(gradientStr, `--color${i}`, `gradient should include --color${i}`);
}

const GRADIENT_PATTERNS = {
  SHORTHAND: /var\(\s*--color[0-5]\s*\)\s+\d+%\s+\d+%/g,
  EXPANDED: /var\(\s*--color[0-5]\s*\)\s+\d+%(?!\s+\d+%)/g
};

const matchGradientFormat = (gradientStr, pattern) => gradientStr.match(pattern);

const shorthandMatches = matchGradientFormat(gradientStr, GRADIENT_PATTERNS.SHORTHAND);
const expandedMatches = matchGradientFormat(gradientStr, GRADIENT_PATTERNS.EXPANDED);

const colorPattern = /var\(\s*--color(\d)\s*\)/g;
const allColorMatches = [...gradientStr.matchAll(colorPattern)];
const colorNumbers = allColorMatches.map(match => parseInt(match[1]));

if (shorthandMatches?.length === EXPECTED_SHORTHAND_STOPS) {
  for (let i = 0; i < COLOR_COUNT; i++) {
    const count = colorNumbers.filter(num => num === i).length;
    assert.equal(count, 1, `In shorthand format, --color${i} should appear exactly once, but it appears ${count} times`);
  }
  
  for (let i = 0; i < COLOR_COUNT; i++) {
    const idx = colorNumbers.indexOf(i);
    assert.isAtLeast(idx, 0, `--color${i} should be in the gradient`);
    if (i > 0) {
      const prevIdx = colorNumbers.indexOf(i - 1);
      assert.isBelow(prevIdx, idx, `--color${i - 1} should appear before --color${i}`);
    }
  }
} else if (expandedMatches?.length === EXPECTED_EXPANDED_STOPS) {
  for (let i = 0; i < COLOR_COUNT; i++) {
    const count = colorNumbers.filter(num => num === i).length;
    assert.equal(count, 2, `In expanded format, --color${i} should appear exactly twice, but it appears ${count} times`);
  }
  
  for (let i = 0; i < COLOR_COUNT; i++) {
    const firstIdx = colorNumbers.indexOf(i);
    const lastIdx = colorNumbers.lastIndexOf(i);
    assert.equal(lastIdx - firstIdx, 1, `--color${i} should appear in consecutive positions for hard lines`);
    
    if (i > 0) {
      const prevLastIdx = colorNumbers.lastIndexOf(i - 1);
      assert.isBelow(prevLastIdx, firstIdx, `--color${i - 1} should complete before --color${i} starts`);
    }
  }
  
  const percentPattern = /var\(\s*--color\d\s*\)\s+(\d+)%/g;
  const percentMatches = [...gradientStr.matchAll(percentPattern)];
  const percentages = percentMatches.map(match => parseInt(match[1]));
  
  for (let i = 1; i < percentages.length - 1; i += 2) {
    assert.equal(percentages[i], percentages[i + 1], `For hard lines, percentage at position ${i} (${percentages[i]}%) should equal percentage at position ${i + 1} (${percentages[i + 1]}%) where colors meet`);
  }
} else {
  assert.fail('Gradient must use either shorthand format (6 stops with two percentages each) or expanded format (12 stops with one percentage each)');
}

--seed--

--seed-contents--

html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Availability Table</title>
</head>

<body>

</body>

</html>
css

--solutions--

html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Availability Table</title>
    <link rel="stylesheet" href="styles.css">
</head>

<body>
    <main>
        <table>
            <tbody>
                <tr class="sharp">
                    <th rowspan="2" class="time">9 AM</th>
                    <th>Monday</th>
                    <th>Tuesday</th>
                    <th>Wednesday</th>
                    <th>Thursday</th>
                    <th>Friday</th>
                </tr>
                <tr class="half">
                    <td class="available-0"></td>
                    <td class="available-1"></td>
                    <td class="available-2"></td>
                    <td class="available-2"></td>
                    <td class="available-5"></td>
                </tr>
                <tr class="sharp">
                    <th rowspan="2" class="time">10 AM</th>
                    <td class="available-3"></td>
                    <td class="available-4"></td>
                    <td class="available-4"></td>
                    <td class="available-5"></td>
                    <td class="available-5"></td>
                </tr>

                <tr class="half">
                    <td class="available-3"></td>
                    <td class="available-4"></td>
                    <td class="available-5"></td>
                    <td class="available-5"></td>
                    <td class="available-5"></td>
                </tr>
                <tr class="sharp">
                    <th rowspan="2" class="time">11 AM</th>
                    <td class="available-0"></td>
                    <td class="available-2"></td>
                    <td class="available-4"></td>
                    <td class="available-4"></td>
                    <td class="available-0"></td>
                </tr>
                <tr class="half">
                    <td class="available-3"></td>
                    <td class="available-4"></td>
                    <td class="available-1"></td>
                    <td class="available-5"></td>
                    <td class="available-5"></td>
                </tr>
                <tr class="sharp">
                    <th rowspan="2" class="time">12 PM</th>
                    <td class="available-5"></td>
                    <td class="available-5"></td>
                    <td class="available-3"></td>
                    <td class="available-2"></td>
                    <td class="available-0"></td>
                </tr>
                <tr class="half">
                    <td class="available-3"></td>
                    <td class="available-0"></td>
                    <td class="available-4"></td>
                    <td class="available-5"></td>
                    <td class="available-5"></td>
                </tr>
                <tr class="sharp">
                    <th rowspan="2" class="time">1 PM</th>
                    <td class="available-2"></td>
                    <td class="available-3"></td>
                    <td class="available-3"></td>
                    <td class="available-2"></td>
                    <td class="available-0"></td>
                </tr>
                <tr class="half">
                    <td class="available-3"></td>
                    <td class="available-4"></td>
                    <td class="available-4"></td>
                    <td class="available-5"></td>
                    <td class="available-5"></td>
                </tr>
                <tr class="sharp">
                    <th rowspan="2" class="time">2 PM</th>
                    <td class="available-0"></td>
                    <td class="available-3"></td>
                    <td class="available-4"></td>
                    <td class="available-4"></td>
                    <td class="available-4"></td>
                </tr>
                <tr class="half">
                    <td class="available-3"></td>
                    <td class="available-4"></td>
                    <td class="available-4"></td>
                    <td class="available-5"></td>
                    <td class="available-5"></td>
                </tr>
                <tr class="sharp">
                    <th rowspan="2" class="time">3 PM</th>
                    <td class="available-3"></td>
                    <td class="available-3"></td>
                    <td class="available-4"></td>
                    <td class="available-5"></td>
                    <td class="available-4"></td>
                </tr>
                <tr class="half">
                    <td class="available-3"></td>
                    <td class="available-4"></td>
                    <td class="available-4"></td>
                    <td class="available-5"></td>
                    <td class="available-5"></td>
                </tr>
                <tr class="sharp">
                    <th rowspan="2" class="time">4 PM</th>
                    <td class="available-5"></td>
                    <td class="available-5"></td>
                    <td class="available-5"></td>
                    <td class="available-4"></td>
                    <td class="available-4"></td>
                </tr>
                <tr class="half">
                    <td class="available-3"></td>
                    <td class="available-0"></td>
                    <td class="available-4"></td>
                    <td class="available-2"></td>
                    <td class="available-5"></td>
                </tr>
                <tr class="sharp">
                    <th rowspan="2" class="time">5 PM</th>
                    <td class="available-5"></td>
                    <td class="available-5"></td>
                    <td class="available-5"></td>
                    <td class="available-3"></td>
                    <td class="available-4"></td>
                </tr>
                <tr>
                    <td></td>
                    <td></td>
                    <td></td>
                    <td></td>
                    <td></td>
                </tr>
            </tbody>
        </table>
        <div id="legend">
            <span id="legend-title">Availability</span>
            <div id="legend-line"><span>0</span>
                <div id="legend-gradient"></div><span>5+</span>
            </div>
        </div>
    </main>
</body>

</html>
css
:root {
    font-size: 16px;
    font-family: Arial, Helvetica, sans-serif;
    --color0: hsl(0, 0%, 100%);
    --color1: hsl(120, 95%, 90%);
    --color2: hsl(120, 95%, 80%);
    --color3: hsl(120, 95%, 65%);
    --color4: hsl(120, 95%, 40%);
    --color5: hsl(120, 95%, 25%);
    --solid-border: 0.1rem solid black;
    --dashed-border: 0.09rem dashed black;
}

* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

body {
    background-color: hsl(150, 30%, 75%);
    width: 100vw;
    height: 100vh;
}

main {
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: space-evenly;
}

table {
    border-collapse: collapse;
    table-layout: fixed;
    width: 32rem;
}

tr {
    height: 1.1rem;
}

tr :first-child {
    width: 4rem;
}

td {
    border-right: var(--solid-border);
    border-left: var(--solid-border);
}

.sharp td {
    border-bottom: var(--solid-border);
}

.sharp th:not([class*="time"]) {
    border-bottom: var(--solid-border);
}

.half td {
    border-bottom: var(--dashed-border);
}

tbody :last-child td {
    border: 0;
}

.time {
    text-align: right;
    border: 0;
    padding: 0.4rem;
}

.available-0 {
    background-color: var(--color0);
}

.available-1 {
    background-color: var(--color1);
}

.available-2 {
    background-color: var(--color2);
}

.available-3 {
    background-color: var(--color3);
}

.available-4 {
    background-color: var(--color4);
}

.available-5 {
    background-color: var(--color5);
}

#legend {
    display: flex;
    flex-direction: column;
    align-items: center;
    width: 18rem;
    height: 3.5rem;
    text-align: center;
}

#legend span {
    display: inline-block;
    width: 5rem;
    height: 2rem;
}

#legend-gradient {
    width: 100%;
    height: 60%;
    border: var(--solid-border);
    background-image: linear-gradient(90deg,
            var(--color0) 0% 17%,
            var(--color1) 17% 34%,
            var(--color2) 34% 50%,
            var(--color3) 50% 67%,
            var(--color4) 67% 83%,
            var(--color5) 83% 100%);
}

#legend-line {
    width: 60%;
    height: 100%;
    display: flex;
}