Wesley Brandt
Wesley Brandt

Reputation: 365

Getting an uncaught reference error in javascript on a variable that is said to be undefined. I have no idea why

My problem comes from when I try to add a new task from the tasker_matic.js file. Over in the tasker.js file, I get the following error:

tasker.js:196 Uncaught ReferenceError: task is not defined
    at Object.add (tasker.js:196)
    at addTask (tasker_matic.js:157)
    at HTMLInputElement.onclick (tasker.html?username=smaug&password=dragon:42)

Again, I have no idea why I am getting this error. I am now allowed to make any changes to tasker.js.

Here is the relevant sections of code:

tasker_matic.js:

function addTask() {
    let task = {};
    let description = document.getElementById('descField').value;
    let time = document.getElementById('dField').value;
    let colorCode = document.getElementById('cField').value;

    task.desc = description;
    task.due = time;
    task.color = colorCode;

    tasker.add(userId, task, (err, task) => {
        if (err) {
            alert('Broken');
        }
    });

}

tasker.js:

let tasker = (function () {
    let verbs = [
        "make",
        "install",
        "update",
        "generate data for",
        "talk to",
        "schedule a time for",
        "develop a plan for",
        "knit",
        "create",
        "build",
        "write",
        "get",
        "finish",
        "call",
        "arrange",
        "submit",
        "talk to",
        "do",
        "protest",
        "collect",
        "shop for"
    ];

    let nouns = [
        "a cake",
        "the boat",
        "our wedding",
        "the garage",
        "the tow truck",
        "our shed",
        "1090 tax form",
        "the IRS agent",
        "milk",
        "some LED lights",
        "monthly budget",
        "marketing plan",
        "the flowers",
        "an albatross"
    ];

    let userNames = [
        "frodo baggins",
        "gandalf gray",
        "smaug dragon"
    ];

    let Task = function (id, ownerId, desc, due, color, complete) {
        this.ownerId = ownerId;
        this.desc = desc;
        this.due = due;
        this.color = color;
        this.complete = complete;
        this.id = id || randomId();
    };

    let randomId = function () {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
            var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        });
    }

    let randomColor = function () {
        let pad = function (v) {
            return v.length == 1 ? '0' + v : v;
        }
        r = Math.floor(Math.random() * 256).toString(16);
        g = Math.floor(Math.random() * 256).toString(16);
        b = Math.floor(Math.random() * 256).toString(16);
        return '#' + pad(r) + pad(g) + pad(b);
    }

    let randomDate = function () {
        year = Math.floor(Math.random() * 14 + 2010);
        month = Math.floor(Math.random() * 12);
        day = Math.floor(Math.random() * 31);
        return new Date(year, month, day);
    };

    let choose = function (things) {
        let i = Math.floor(Math.random() * things.length);
        return things[i];
    }

    let randomDescription = function () {
        return choose(verbs) + ' ' + choose(nouns);
    };

    let makeList = function (ownerId, n) {
        result = [];
        for (i = 0; i < n; i += 1) {
            result.push(new Task(null,
                ownerId,
                randomDescription(),
                randomDate(),
                randomColor(),
                choose([true, false])));
        }
        return result;
    }

    let updateTask = function( oldTask, editedTask ) {
        let propertiesToCopy = ['desc', 'due', 'color', 'complete' ];
        propertiesToCopy.forEach( prop => {
            if( editedTask.hasOwnProperty( prop ) ) {
                oldTask[prop] = editedTask[prop];
            }
        });
    }

    let state = {
        users: userNames.reduce(function (acc, cv) {
            let parts = cv.split(' '); //
            let name = parts[0];
            let email = parts[0][0] + parts[1] + '@uwlaxer.edu';
            let id = randomId();
            let password = parts[1];
            let tasks = makeList(id, Math.random() * 50 + 20).reduce((acc, t) => { acc[t.id] = t; return acc; }, {});
            acc[id] = {
                name: name,
                email: email,
                id: id,
                password: password,
                tasks: tasks
            };
            return acc;
        }, {}),
        user: null
    };

    let getTask = function (ownerId, id) {
        try {
            return state.users[ownerId].tasks[id];
        } catch (e) {
            return null;
        }
    }

    let getUserByName = function (name) {
        for (id in state.users) {
            if (state.users[id] && state.users[id].name === name) {
                return state.users[id];
            }
        }
        return null;
    }

    let taskList = function (ownerId) {
        let result = [];
        for (let tid in state.user.tasks) {
            result.push(state.user.tasks[tid]);
        }
        return result.sort((a, b) => b.due.getTime() - a.due.getTime());
    }

    let respond = function (error, value, cb) {
        window.setTimeout(() => cb(error, value), Math.random() * 1500);
    }

    let copyTask = function( task ) {
        return new Task(task.id, task.ownerId, task.desc, task.due, task.color, task.complete);
    }

    return {
        login: function ( username, passwd, cb) {
            let user = getUserByName( username );
            if (user && user.password === passwd) {
                state.user = user;
                let cleansedUser = { name: user.name, email: user.email, id: user.id };
                respond(null, cleansedUser, cb);
            } else {
                respond('forbidden', null, cb);
            }
        },

        logout: function (cb) {
            state.user = null;
            respond(null, true, cb);
        },

        tasks: function (ownerId, cb) {
            if (ownerId === state.user.id) {
                let tasks = taskList(ownerId).map(u => new Task(u.id, u.ownerId, u.desc, u.due, u.color, u.complete));
                respond(null, tasks, cb);
            } else {
                respond('forbidden', null, cb);
            }
        },

        add: function (ownerId, task, cb) {
            if (state.user.id == ownerId) {
                if (task.desc && task.due && task.color) {
                    let due = new Date(task.due);
                    let task = new Task(task.id, ownerId, task.desc, due, task.color, Boolean(task.complete));
                    state.users[ownerId].tasks[task.id] = task;
                    respond(null, task, cb);
                } else {
                    respond('invalid task', null, cb);
                }
            } else {
                respond('forbidden', null, cb);
            }
        },

        delete: function (ownerId, taskId, cb) {
            if (state.user.id === ownerId) {
                let task = state.users[ownerId].tasks[taskId];
                delete state.users[ownerId].tasks[taskId];

                if (task) {
                    respond(null, task, cb);
                } else {
                    respond('no such task', null, cb);
                }
            } else {
                respond('forbidden', null, cb);
            }
        },

        edit: function (ownerId, taskId, task, cb) {
            if (state.user.id == ownerId) {
                if (taskId) {
                    let oldTask = getTask(ownerId, taskId);
                    if( oldTask) {
                        updateTask( oldTask, task );                        
                        respond( null, copyTask( oldTask ), cb );
                    } else {
                        respond( 'no such task', null, cb );
                    }                    
                } else {
                    respond( 'no such task', null, cb );
                }
            } else {
                respond( 'forbidden', null, cb );
            }
        }
    }

})();

tasker.html:

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>Tasker</title>
    <link rel="stylesheet" href="tasker_matic.css">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
    <script src="tasker.js"></script>
    <script src="tasker_matic.js"></script>    
</head>
<body>
  <div class="container">

    <div id="login" class="on">
      <h1>Login</h1><br>
        <form style="width:50%">
          <input id="usernameInput" type="text" name="username" placeholder="Username" class="form-control">
          <input id="passwordInput" type="password" name="password" placeholder="Password" class="form-control">
          <input type="submit" value="Login" class="btn btn-primary pull-right" onclick="login(event)">
        </form>
    </div>

    <div id="home" class="off">
      <h1>Tasker <small><span class="pull-right btn btn-primary btn-sm" id="logout" onclick="logout()"></span></small></h1>
      <div class="well well-sm">
        <form class="form-inline">
          <div class="form-group">
            <label for="descField">Description</label>
            <input class="form-control" type="text" placeholder="Description" id="descField">
          </div>

          <div class="form-group">
            <label for="cField">Color Code</label>
            <input class="form-control" type="color" id="cField"/>
          </div>

          <div class="form-group">
            <label for="dField">Due Date</label>
            <input class="form-control" type="date" id="dField"/>
          </div>

          <input class="form-control" type="button" value="+" id="addButton" onclick="addTask()"/>
        </form>
      </div>

      <div class="well well-sm">
        <form class="form-inline">
          <input class="form-control" type="text" placeholder="search" id="searchField" style="width:25%" onkeyup="searchTextChange()"/>
          <div class="checkbox">
            <label>
              <input type="checkbox" id="incompleteBox" onclick="incompleteChange()"/>Incomplete Only
            </label>

          </div>
          <div class="checkbox">
            <label>
              <input type="checkbox" id="overdueBox" onclick="overdueChange()"/>Over-due only
            </label>
          </div>
        </form>
      </div>

      <table id="tasks" class="table table-striped">
      </table>
    </div>
  </div>
</body>
</html>

Upvotes: 3

Views: 740

Answers (2)

coagmano
coagmano

Reputation: 5671

Abana's answer identifies the problem area, so I'll expand on why this doesn't work.

When you use let, similar to var the variable declaration is hoisted to the top of the scope. The difference is that it also creates a Temporal Dead Zone (TDZ) during which any attempt to access the variable throws an error.

Now looking at the code, because you start the line with let task it creates a new variable in that block's scope, but the TDZ lasts until after that line executes to completion. When it goes to create a new Task, it accesses the inner scoped task, during the TDZ period and throws an error.

The solution here, is to use a different variable name:

add: function (ownerId, task, cb) {
    if (state.user.id == ownerId) {
        if (task.desc && task.due && task.color) {
            let due = new Date(task.due);
            let innerTask = new Task(task.id, ownerId, task.desc, due, task.color, Boolean(task.complete));
            state.users[ownerId].tasks[task.id] = innerTask;
            respond(null, innerTask, cb);
        } else {
            respond('invalid task', null, cb);
        }
    } else {
        respond('forbidden', null, cb);
    }
},

Upvotes: 2

Potato Salad
Potato Salad

Reputation: 4650

There could be a naming problem with this specific method in tasker.js

add: function (ownerId, task, cb) {
        if (state.user.id == ownerId) {
            if (task.desc && task.due && task.color) {
                let due = new Date(task.due);
                let task = new Task(task.id, ownerId, task.desc, due, task.color, Boolean(task.complete));
                state.users[ownerId].tasks[task.id] = task;
                respond(null, task, cb);
            } else {
                respond('invalid task', null, cb);
            }
        } else {
            respond('forbidden', null, cb);
        }
    },

The parameter task is overriden by the newly declared variable task. Try to change this up and see what happens.

Upvotes: 2

Related Questions