[JavaScript] Assistance Greatly Appreciated

I have given quite a lot of input, already. Work up a couple of small side projects (similar to the demo I wrote for you above) and dig up the reading materials on events. Spend a week on the subject and dig for examples.

All the buttons in the cells (rows) need is one handler. Again, attach the listener to the parent object, and delegate. If you have two buttons on the row, then your handler will have two cases and respective callbacks.

const rowButtons = querySelector('#roster');

The id would go on the table element, being it is the parent of all the table rows. Clicks in any of them will bubble up to the parent, which can then determine which button was clicked, check in or check out, and assign the handler.

The event has an eventObject with a this property which is the context of the click event. (The row the button is in can be determined by traversing upwards to the nearest TR.) Each row has sibling TD’s and the one your event callbacks will be modifying will be the fourth, or last sibling in the group. Use a query selector to target that element then swap out the text node for that cell.

I still don’t feel comfortable having a static table with hardwired id attributes on the rows. When a member is added would be the time to add a row to the table, from a template (a script that composes the HTML and inserts the dynamic data, then appends it to the TBODY as a new TR.

This is not an exact version, but suggestive of what is needed in your template.

`        <tr id="${this.memberId}">
          <td><div class="name">${this.memberName}</div></td>
          <td><div><button class="checkIn">Check In</button></div></td>
          <td><div><button class="checkOut">Check Out</button></div></td>
          <td><div class="bool">${this.isCheckedIn}</div></td>
        </tr>`

These will be added to the handler cases to call (though not in their present form)

onclick="poolRoster[0].checkIn()"
onclick="poolRoster[0].checkOut()"

I’ll leave you to it to do some more research and experimentation that will help you get more familiar with events and dynamic construction of your page.

3 Likes

Shouldn’t the roster label be given to tbody instead of table? That way it ignores the th elements?

1 Like

It mattters more that you are able to add interaction anywhere in the table, and have all of it bubble up to the table parent. The entire table is a hot zone, not just the buttons. One parent delegating all actions makes the most sense to me.

3 Likes

Gotcha’. So I should give table an id of roster and specify within the handler that I only want to deal with the td elements.

1 Like

The TD elements will not be the targets. The buttons will. However, if you wish to make the TD’s interactive, then a case would be needed for those event targets. The target should be specific so the correct callback is fired by the handler.

For an exercise, use the template snippet above and write the code that will traverse from the event target (a button) up to the element with the id attribute, and read off the value. Use traversal methods.

3 Likes

Are you talking about this?

Also, I am currently receiving the following error…

On jsbin…

"error"
"TypeError: Cannot set property 'innerHTML' of null
    at Member.memberRow (moheciw.js:89:52)
    at addMember (moheciw.js:133:12)
    at runInstance (moheciw.js:199:9)
    at HTMLDivElement.buttonHandler (moheciw.js:230:13)"

On repl…

TypeError: Cannot set property 'innerHTML' of null
    at Member.memberRow (index.js:89:52)
    at addMember (index.js:133:12)
    at runInstance (index.js:189:9)
    at HTMLDivElement.buttonHandler (index.js:220:13)

Here is my most recent code… JS Bin - Collaborative JavaScript Debugging

1 Like

This needs to be verified. I only suggested it, not meant it for actual use.

`
        <tr id="${this.memberId}">
          <td><div class="name">${this.memberName}</div></td>
          <td><div class="checkIn"><button>Check In</button></div></td>
          <td><div class="checkOut"><button>Check Out</button></div></td>
          <td><div class="bool">${this.isCheckedIn}</div></td>
        </tr>
        `

In theory, the template can be a constant since the interpolated data fields are object attributes, so should be editable. Needs confirmation.

Otherwise, use let and rest easy.

let memberRow = ...;

document.getElementById("thead").innerHTML = headerRow;

When we think about it, all we need in the source HTML is,

<table id="roster"></table>

Below we take a step by step approach to generating and appending the <thead> element to this table.

const roster = document.querySelector('#roster');
let el = document.createElement('thead');
let tr = document.createElement('tr');
let td = document.createElement('td');
td.appendChild(document.createTextNode("Member"));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode("Check In"));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode("Check Out"));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode("Checked In?"));
tr.appendChild(td);
el.appendChild(tr);
roster.appendChild(el);

https://repl.it/LjNS

It needs to be explored more fully on your end. Work with limited models, not a full blown program, when ironing out details and approaches. This is where we really get under the hood.

As for the problems in your newest iteration, there is a lot to iron out once you get the gist of the above demo and learn ways to optimize it. It will be time well spent.


I should add, the above is not intended for actual use. In practice, your source HTML should contain the THEAD (and TBODY) element already, rather than waiting for the script to append it. There is no dynamic content that would necessitate generation.

Howevr, the above model could well apply to the new table row, which is my intention in showing it to you. I’ll leave you to explore further.

3 Likes

I’ve been noticing that in most of your html, you put the <script> element after the parts of the body that the script deals with… should I be doing this as well? or is it okay to have it before the table?

1 Like

One thing we should set straight… Should. There is no should in programming. Advisable, recommended, preferred, … These are pretty much the guidelines. Should just doesn’t fit in. But, we use that word alot to convey some sort of preference or recommendation or guideline, just to be soft.

Trouble is we derail the main purpose of a guideline which is essentially the width of the highway upon which this idea is being driven. *should* often ignores *breadth*.

3 Likes

Well then, would you advise that I do this as well?

3 Likes

Clever…

Positioning of script is not based upon syntactical rules, but whether or not they will have any effect in the normal flow of the load sequence.

3 Likes

I guess the better question would be, which is more efficient in my situation?

1 Like

We are still working with nuts and bolts. I never concern myself with efficiency while I am hammering out the logic of my idea. My computer can handle whatever I give it, so slam dunk away I go with inefficient code. That is where you should be, right now. Toss efficiency and get informed of the concepts. Build your knowledge base, not your rule book.


Recall that the load sequence is what dictates how the page is rendered in the immediate sense. The first response is the HTML, rendered locally as the DOM.

The HTML requests resources from top down in the source listing. As many as can be handled in real time will begin to come down. Usually it will be fonts that get pulled down first, followed by CSS that gets poked into the DOM as soon as it is present. Follow this with behaviors that manipulate the DOM and listen for/respond to user interaction.

Therein lay the crux… Is the content ready for our script? Does it even exist when the script is run? It is our job to be sure of that.

3 Likes

Alright, I believe I grasped what you were getting at…

Here is my JavaScript right now…

//jshint esnext: true
var today = new Date(),
    dateFormattingOptions = {weekday: "long", year: "numeric", month: "long", day: "numeric"},
    dayOfWeekNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];

var pswd = "********";
let memberId = 0;

//Member
class Member {
    static setId() {
        return memberId++;
    }
    constructor(name, isCheckingIn = false){
        this.name = name;
        this.isCheckedIn = false;
        this.checkInDay = null;
        this.dayOfTheWeek = null;
        this.memberId = Member.setId();
        if (isCheckingIn === true) {
            this.checkIn();
        }
    }
    checkIn(){
        var now = new Date(), dayOfWeek = now.getDay();
        if (!this.isWeekday(dayOfWeek)) {
            displayError(103);
            this.isCheckedIn = false;
        } else {
            this.isCheckedIn = true;
            this.checkInDay = now;
            this.checkOutDay = false;
            this.dayOfTheWeek = this.getProperDayName(dayOfWeek);
            renderRoster();
        }
    }
    checkOut(){
        if (!this.isCheckedIn) {
            displayError(101);
        }
        var pass = "";
        pass = prompt("Password:");
        if (pass == pswd) {
            this.isCheckedIn = false;
            this.checkInDay = null;
            this.dayOfTheWeek = null;
            renderRoster();
        } else {
            displayError(102);
        }
    }
    displayStatus(){
        if (!this.isCheckedIn){
            return `${this.name} | Checked In: No`;
        }
        return `${this.name} | ${this.getFormattedDate()} | Checked In: Yes on ${this.dayOfTheWeek}`;
    }
    //Note: 0 = Sunday, 1 = Monday, 2 = Tuesday, 3 = Wednesday, 4 = Thursday, 5 = Friday, 6 = Saturday
    getFormattedDate(){
        return this.checkInDay.toLocaleDateString("en-US", dateFormattingOptions);
    }
    isWeekday(dayIndex){
        return dayIndex >= 1 && dayIndex <= 5;
    }
    getProperDayName(dayIndex){
        return dayOfWeekNames[dayIndex];
    }
    displayName(){
        return `${this.name}`;
    }
    displayCheckedIn(){
        if (this.checkedIn === false) {
            return "No";
        } else if (this.checkedIN === true) {
            return "Yes";
        } else {
            displayError(104);
        }
    }
    memberRow(){
        const roster = document.querySelector("#roster");
        let el = document.getElementById("tbody");
        let tr = document.createElement("tr");
        tr.id = `${this.memberId}`;
        tr.appendChild(newCell(`${this.memberName}`, "name", false));
        tr.appendChild(newCell("Check In", "checkIn", true));
        tr.appendChild(newCell("Check Out", "checkOut", true));
        tr.appendChild(newCell(`${this.isCheckedIn}`, "", false));
        tr.appendChild(newCell("History", "history", true));
        el.appendChild(tr);
    }
}

//Dates & Errors
function displayToday(){
    return "Today's date: " + today.toLocaleDateString("en-US", dateFormattingOptions) + ".";
}

function displayError(code){
    console.log("Error", code);
    var msg = "";
    if (code === 101) {
        msg = "You are already logged in.";
    } else if (code === 102) {
        msg = "Incorrect Password";
    } else if (code === 103) {
        msg = "Sorry, check-in is only allowed on weekdays.";
    } else if (code === 104) {
        msg = "An unspecified error has occured.";
    }
    alert(msg);
    console.log(msg);
    return msg;
}

//Roster
var poolRoster = [];

function newCell(node, domain, b) {
    const roster = document.querySelector("#roster");
    let div = document.createElement("div");
    let td = document.createElement("td");
    let bt = document.createElement("button");
    div.className = domain;
    if (b === true) {
        bt.appendChild(document.createTextNode(node));
        div.appendChild(bt);
    } else {
        div.appendChild(document.createTextNode(node));
    }
    td.appendChild(div);
}

function newColumnHeader(node) {
    const roster = document.querySelector("#roster");
    let th = document.createElement("th");
    th.scope = "col";
    th.appendChild(document.createTextNode(node));
}

if (poolRoster.length > 0) {
    const roster = document.querySelector("#roster");
    let el = document.getElementById("thead");
    let tr = document.createElement("tr");
    tr.appendChild(newColumnHeader("Member"));
    tr.appendChild(newColumnHeader("Check In"));
    tr.appendChild(newColumnHeader("Check Out"));
    tr.appendChild(newColumnHeader("Checked In?"));
    tr.appendChild(newColumnHeader("History"));
    el.appendChild(tr);
}

function addMember(name, checkIn){
    var member = new Member(name, checkIn);
    poolRoster.push(member);
    member.memberRow();
}

function displayRoster(){
    for (var n = 0; n < poolRoster.length; n++) {
        console.log(poolRoster[n].displayStatus());
    }
}

function memberName(num){
    for (var x = 0; x > poolRoster.length; x++) {
        if (poolRoster[num] === poolRoster[x]) {
            return poolRoster[x].displayName();
        } else {
            return "Member Name";
        }
    }
}

function memberBool(num){
    for (var x = 0; x > poolRoster.length; x++) {
        if (poolRoster[num] === poolRoster[x]) {
            return poolRoster[x].displayCheckedIn();
        } else {
            return "Member Status";
        }
    }
}

function renderRoster(){
    for (var i = 0; i > poolRoster.length; i++) {
        var nameId = ("memberName" + (poolRoster[i].memberId).toString(10));
        var boolId = ("memberBool" + (poolRoster[i].memberId).toString(10));
    }
}

function newRoster(){
    var pass = "";
    pass = prompt("Password:");
    if (pass == pswd) {
        for (var k = 0; k < 15; k++) {
            var name = prompt("Name:");
            addMember(name, false);
        }
        displayRoster();
        renderRoster();
    } else {
        displayError(102);
    }
}

function runInstance(bool){
    var pass = "";
    pass = prompt("Password:");
    if (pass == pswd) {
        var name = prompt("Name:");
        addMember(name, bool);
        displayRoster();
        renderRoster();
    } else {
        displayError(102);
    }
}

function clearRoster(){
    poolRoster.length = 0;
}

renderRoster();

//Event Listeners
const buttons = document.querySelector("#buttons");

const rowButtons = document.querySelector("#roster");

const getEventTarget = e => {
    e = e || window.event;
    return e.target || e.srcElement;
};

const buttonHandler = e => {
    let target = getEventTarget(e);
    switch (target) {
        case buttons.children[0]:
            newRoster();
            break;
        case buttons.children[1]:
            runInstance(false);
            break;
        case buttons.children[2]:
            runInstance(true); 
            break;
        case buttons.children[3]: 
            clearRoster();
    }
};

buttons.addEventListener("click", buttonHandler);
1 Like

Does it work? Was your intenteion to make it inaccesible to anyone without JS enabled? If so, then you have succeeded. Boilerplate HTML should never be scripted. I thought we covered that, already.

3 Likes

… then what was that about? JavaScript needs to be enabled for this webpage to work anyway…

1 Like

But it should not obfuscate what would otherwise be bare-bones. Let the user at least see something. If the butons are not functional, then they should not be visible. The page should still be able render as an empty table.

Always write as if there is no script, then fill in/or modify with script.

3 Likes

Ooohh so my javascript is fine… I just haven’t displayed my HTML properly?

1 Like

Put as much as possible in the raw document so the end user gets something. Scripting is an enhancement, not the gist of the document.

3 Likes

So, I’ve been reworking my scripts a bit (you can see the code here) and I received this error…

TypeError: Failed to set an indexed property on 'HTMLCollection': Index property setter is not supported.
    at Member.memberRow (index.js:87:24)
    at addMember (index.js:151:12)
    at newRoster (index.js:166:13)
    at HTMLDivElement.buttonHandler (index.js:202:13)

I understand what the error is… it failed to set an HTML element’s text node to an indexed property.

What I don’t understand is why it is or how to fix it…

I tried googling the error, but only seemed to get results that were about c#…

If you have any suggestions, I’m all ears.

1 Like