Joe Consterdine Joe Consterdine - 3 months ago 11
Javascript Question

Returning a variable from a function returning 'Undefined'

I'm making a to do list. On my 'editTaskEnter' I'm simply trying to call the 'editTask' above it.

Now I can copy all the code from the 'editTask' and copy it in to 'EditTaskEnter' and it works fine, but that's a lot of code repeated. Instead I'd just like to call that editTask function when the user hits enter.

The issue I'm getting is with the scope of the variables.

I'm getting this error: 'Uncaught TypeError: Cannot read property 'querySelector' of undefined'.

I thought if I returned each variable in the 'editTask' they'd be accessible in my 'editTaskEnter' function. Obviously not :)

Any ideas why?

JS

// To do list

// Cache DOM
var addToDo = document.getElementById('add-to-do');
var taskHolder = document.getElementById('task-holder');
var uncompleteTasks = document.getElementById('uncompleted-tasks');
var completedTasks = document.getElementById('completed-tasks');

// Bind events
var bindEvents = function(listItem, checkboxEventHandler) {
// Delete
var deleteToDo = listItem.querySelector('.delete-to-do');
deleteToDo.addEventListener('click', deleteTask);
// Edit
listItem.querySelector('.edit-to-do').addEventListener('click', editTask);
listItem.querySelector('.edit-holder').addEventListener('keyup', editTaskEnter);
// Checkbox
var checkbox = listItem.querySelector('input.edit-to-do');
checkbox.onchange = checkboxEventHandler;
}

// Create list item
var createListItem = function() {
var listItem = document.createElement('li');
var deleteToDo = document.createElement('button');
deleteToDo.innerHTML = 'delete';
deleteToDo.classList.add('delete-to-do');
var editToDo = document.createElement('button');
editToDo.innerHTML = 'edit';
editToDo.classList.add('edit-to-do');
var toDoStatus = document.createElement('input');
toDoStatus.type = 'checkbox';
toDoStatus.classList.add('edit-to-do');
var editHolder = document.createElement('input');
editHolder.type = 'text';
editHolder.classList.add('edit-holder');

listItem.appendChild(deleteToDo);
listItem.appendChild(editToDo);
listItem.appendChild(toDoStatus);
listItem.appendChild(editHolder);

return listItem;
}

// Add task
var addTask = function(e) {
var taskHolderValue = taskHolder.value;
if(taskHolderValue) {
var taskHolderElement = document.createElement('label');
taskHolderElement.classList.add('to-do-item');
taskHolderElement.innerHTML = taskHolderValue;
var listItem = createListItem();
listItem.insertBefore(taskHolderElement, listItem.childNodes[0]);
uncompleteTasks.appendChild(listItem);
bindEvents(listItem, taskCompleted);

taskHolder.value = '';
} else {
alert("You didn't add a to a to do!");
}
}

var addTaskEnter = function(e) {
var key = 'which' in e ? e.which : e.keyCode;
if(key === 13) {
addTask();
}
}

// Delete task
var deleteTask = function() {
var listItem = this.parentNode;
console.log(listItem);
var parentItem = listItem.parentNode;
parentItem.removeChild(listItem);
}

// Edit task
var editTask = function() {
var defaultValue = this.parentNode.querySelector('label').innerHTML;
var listItem = this.parentNode;
var listParent = this.parentNode;
var editedValue = listParent.querySelector('input.edit-holder').value;
if(listItem.classList.contains('editing') && editedValue) {
listParent.querySelector('label').innerHTML = editedValue;
} else {
defaultValue;
}
listItem.classList.toggle('editing');

return defaultValue;
}

// Edit task enter
var editTaskEnter = function(e) {
var key = 'which' in e ? e.which : e.keyCode;
if(key === 13) {
editTask();
}
}

// Task completed
var taskCompleted = function() {
var listItem = this.parentNode;
completedTasks.appendChild(listItem);
this.parentNode.classList.add('completed');
bindEvents(listItem, taskUncompleted);
}

// Task uncompleted
var taskUncompleted = function() {
var listItem = this.parentNode;
uncompleteTasks.appendChild(listItem);
this.parentNode.classList.remove('completed');
bindEvents(listItem, taskCompleted);
}

// Add task
addToDo.addEventListener("click", addTask);
taskHolder.addEventListener("keyup", addTaskEnter);

// Loop over uncomplete tasks
for(i=0; i<completedTasks.length; i++) {
var listItem = completedTasks[i];
uncompleteTasks.appendChild(listItem);
bindEvents(listItem, completedTasks);
}

Answer Source

When you call a JavaScript function like this: myFunction(), then the this object in the function will be a reference to the global object.

That is exactly what happens when you call editTask in editTaskEnter. this will point to the global object (window), which doesn't have a parentNode property. this.parentNode will therefore be undefined, and undefined doesn't have a method called querySelector.

Modifying your editTaskEnter function to the following should make it work:

// Edit task enter
var editTaskEnter = function (e) {
  if(e.key === 'Enter') {
    editTask.call(this);
  }
}

The editTask.call(this) has the effect that the this object in editTask is the same as in editTaskEnter.