2
\$\begingroup\$

I have an array ofusers containinguser name with theirrules on my practice project
I have generated a table inHTML from that array inJS, So I can

change thestatus of anyuser rule by toggling on cell, or toggling all rules by clicking onname of user, ordeny orgrant rules for all users by clicking on column vertically!

BUT I want to update the original arrayusers rules, if the rules of any user change;
In other words How can I make the array inJS synchronize with the table inHTML.

const users = [  {    name: "Admin",    rules: { Create: true, Read: true, Update: true, Delete: true },  },  {    name: "Developer",    rules: { Create: true, Read: true, Update: false, Delete: true },  },  {    name: "Customer",    rules: { Create: false, Read: true, Update: false, Delete: false },  },];let rules = ["Create", "Read", "Update", "Delete"];let status = {  "": "red",  green: "red",  red: "green",};let icons = {  "": "🟥",  green: "🟥",  red: "🟩",};const table = document.createElement("table");const thead = table.createTHead();const tr = thead.insertRow();const th = tr.appendChild(document.createElement("th"));th.appendChild(document.createTextNode("Users"));rules.forEach((rule, i) => {  const th = tr.appendChild(document.createElement("th"));  th.appendChild(document.createTextNode(rule));  th.setAttribute("status", "");  th.onclick = (e) => {    const cols = [      ...document.querySelectorAll(`tbody tr td:nth-child(${i + 2})`),    ];    cols.forEach((col) => {      col.textContent = icons[e.target.getAttribute("status") ?? ""];    });    e.target.setAttribute(      "status",      status[e.target.getAttribute("status") ?? ""]    );  };});const tbody = table.createTBody();users.forEach((user) => {  const tr = tbody.insertRow();  const td = tr.appendChild(document.createElement("td"));  td.appendChild(document.createTextNode(user.name));  td.onclick = (e) => {    rulesUser = [...td.parentElement.children];    rulesUser.slice(1).forEach((rule) => {      rule.textContent = icons[e.target.getAttribute("status") ?? ""];    });    e.target.setAttribute(      "status",      status[e.target.getAttribute("status") ?? ""]    );  };  rules.forEach((rule) => {    const td = tr.insertCell();    td.appendChild(      document.createTextNode(        Object.entries(user.rules)          .filter((entry) => entry[1] == true)          .map((entry) => entry[0])          .includes(rule)          ? "🟩"          : "🟥"      )    );    td.setAttribute(      "status",      Object.entries(user.rules)        .filter((entry) => entry[1] == true)        .map((entry) => entry[0])        .includes(rule)        ? "green"        : "red"    );    td.onclick = (e) => {      e.target.textContent = icons[e.target.getAttribute("status") ?? ""];      e.target.setAttribute(        "status",        status[e.target.getAttribute("status") ?? ""]      );    };  });});document.body.appendChild(table);
* {  font-family: sans-serif;}table {  border-collapse: collapse;  border: 1px solid;}td,th {  padding: 5px 10px;  text-align: center;  border: 1px solid;  cursor: pointer;}thead th {  background-color: gray;}tbody th {  background-color: lightgray;}thead th:first-child {  background-color: lightblue;}

Any feedback or suggestion to make the code better, I'd be really grateful if somebody could help me with this problem so I can continue building my practice project. Thanks ❤️

askedJun 19, 2022 at 8:59
TAHER El Mehdi's user avatar
\$\endgroup\$
1
  • \$\begingroup\$Do you have to build the table using JS createElement etc. or can you just write some html on the page?\$\endgroup\$CommentedJul 9, 2022 at 12:47

1 Answer1

2
\$\begingroup\$

Personally I would break the code down into discrete functions that do specific peices of work.

I would opt to use theusers object graph as the 'source of truth' as to what state each element is in, and use that to update the DOM. This can then be leveraged for both the initial setup and the click handlers.

I think generally speaking it not advised to use custom HTML attributes to convey state, or at least would should prefix them with thex- convention and possibly something to prevent attribute clashes with browser plugins that might be decorating DOM elements once the page is loaded. Another more accepted approach here is to use CSS classes and add/remove those classes to alter the visual state (show/hide elements for example).

The below code is an example, and its a bit quick and dirty to illustrate the point. Ideally I suspect I'd want to be encapsulating the whole thing in a class, passing it a DOM element to act as the container for all generated code. Rather than using globals forusers,rules,state etc you'd want this in the class, or at least passed into the functions so that its clear what state each function is operating on.

const users = [  {    name: "Admin",    rules: { Create: true, Read: true, Update: true, Delete: true },  },  {    name: "Developer",    rules: { Create: true, Read: true, Update: false, Delete: true },  },  {    name: "Customer",    rules: { Create: false, Read: true, Update: false, Delete: false },  },];let rules = ["Create", "Read", "Update", "Delete"];const state = ["🟥", "🟩"]const ruleToggle = (index, enabled) => {  users.forEach(user => user.rules[rules[index]] = enabled)}const userToggle = (index, enabled) => {  rules.forEach(rule => {    users[index].rules[rule] = enabled  })}const userRuleToggle = (userIdx, ruleIdx) => {  users[userIdx].rules[rules[ruleIdx]] = !users[userIdx].rules[rules[ruleIdx]]}const updateRules = (userIdx) => {  for (let ruleIdx=0 ; ruleIdx < rules.length ; ruleIdx++) {    const sel = `tbody tr:nth-child(${userIdx + 1}) td:nth-child(${ruleIdx + 2})`    updateUserRule(userIdx, ruleIdx, document.querySelector(sel))  }}const updateUsers = (ruleIdx) => {  for (let userIdx = 0; userIdx < users.length ; userIdx++) {    const sel = `tbody tr:nth-child(${userIdx + 1}) td:nth-child(${ruleIdx + 2})`    updateUserRule(userIdx, ruleIdx, document.querySelector(sel))  }}const updateUserRule = (userIdx, ruleIdx, el) => {  el.textContent = state[users[userIdx].rules[rules[ruleIdx]] ? 1 : 0]  }const toggleStatus = (el) => {  const enabled = !parseInt(el.getAttribute("status"))  el.setAttribute("status", enabled ? 1 : 0)  return enabled}const insertColumnHeadings = (tr) => {  const th = tr.appendChild(document.createElement("th"));  th.textContent = "Users"  rules.forEach((rule, i) => {    const th = tr.appendChild(document.createElement("th"));    th.textContent = rule    toggleStatus(th);    th.onclick = (ev) => {      const enabled = toggleStatus(ev.target)      ruleToggle(i, enabled)      updateUsers(i)    };  });}const insertRows = (tbody) => {  users.forEach((user, userIdx) => {    const tr = tbody.insertRow();    const td = tr.appendChild(document.createElement("td"));    td.textContent = user.name    toggleStatus(td);      td.onclick = (ev) => {      const enabled = toggleStatus(ev.target)      userToggle(userIdx, enabled)      updateRules(userIdx)    };    rules.forEach((rule, ruleIdx) => {      const td = tr.insertCell();      updateUserRule(userIdx, ruleIdx, td)      td.onclick = (el) => {        userRuleToggle(userIdx, ruleIdx)        updateUserRule(userIdx, ruleIdx, el.target)      };    });  });}const table = document.createElement("table");const thead = table.createTHead();const tr = thead.insertRow();insertColumnHeadings(tr)const tbody = table.createTBody();insertRows(tbody)document.body.appendChild(table);
* {  font-family: sans-serif;}table {  border-collapse: collapse;  border: 1px solid;}td,th {  padding: 5px 10px;  text-align: center;  border: 1px solid;  cursor: pointer;}thead th {  background-color: gray;}tbody th {  background-color: lightgray;}thead th:first-child {  background-color: lightblue;}

answeredJun 19, 2022 at 17:40
Dave Meehan's user avatar
\$\endgroup\$
3
  • \$\begingroup\$Thanks, Sir Meehan, at first when I coded it, I didn't realize I must update the array when DOM changed, good technique to update after each toggle!\$\endgroup\$CommentedJun 19, 2022 at 21:43
  • \$\begingroup\$+thanks for the advice to not use custom HTML attributes to convey state, next time I will prefix it with something special to not clashes with browser plugins!\$\endgroup\$CommentedJun 19, 2022 at 21:45
  • \$\begingroup\$As you said If I used OOP it would be cleaner as a class user with methods...\$\endgroup\$CommentedJun 19, 2022 at 21:47

You mustlog in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.