Skip to content

BYU-CS-260/vue-todo

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Vue Todo List

This is a simple todo list application built using Vue.

To learn how this is built, clone this repository, then write code in index.html and script.js.

Inside your cloned repository, start up a Python server to view the site in your browser:

python3 -m http.server
# or
python2 -m SimpleHTTPServer

You can navigate to localhost:8000 to see the site.

A static list

Start with this in the body:

<body>
  <div id="app">
    <h1>A List of Things To Do</h1>
    <ul>
      <li v-for="item in todos">
        <label>{{ item.text }}</label>
      </li>
    </ul>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script src="/script.js"></script>
</body>

And this in scripts.js:

var app = new Vue({
  el: '#app',
  data: {
    todos: [{
      text: "make an app",
      completed: false,
    }, {
      text: "declare victory",
      completed: false,
    }, {
      text: "profit",
      completed: false
    }],
  },
});

You should see a list of three things to do, with a style applied to the list to remove the bullets, add a background, increase spacing, and add some width.

We have setup script.js to create a Vue instance. It runs inside the #app div, and it provides some data in the todos variable.

Inside index.html, we use v-for to loop through each of the items in the todos variable, assign the item to a variable, and then display the text of the item using the handlebars syntax.

You can open the console or the Vue DevTools and modify the todos array to see how data binding works in Vue. From the console:

app.todos.push({text:'Success'});

A dynamic list

Displaying a static list is not very useful, and modifying the list in the console is painful, so let's create a dynamic list.

In index.html, add the following right after the h1 tag:

    <form v-on:submit.prevent="addItem">
      <input type="text" v-model="message">
      <button type="submit">Add</button>
    </form>

This creates a form using standard HTML, with some Vue attributes added in.

  1. We use v-on to tell Vue that when it sees a submit event for the form, it should call the addItem method. The addition of .prevent to the submit event will prevent the standard browser behavior that submits the form to the server and causes the page to reload.

  2. We use v-model to bind the text input to a variable called message that Vue will supply.

To make this work, modify the Vue data in script.js to read:

  data: {
    todos: [{
      text: "make an app",
      completed: false,
    }, {
      text: "declare victory",
      completed: false,
    }, {
      text: "profit",
      completed: false
    }],
    message: '',
  },

This removes the static list of items and creates the new variable, message, that is bound to the form input.

Next, add the following after the data declaration in script.js:

  methods: {
    addItem() {
      this.todos.push({text: this.message, completed:false});
      this.message = '';
    },
  }

This adds set of methods that Vue will export, in addition to the data. The addItem method uses what is stored in message to create a new item object and add it to the todos array. Note that we also create a new property on the object, completed, which we will use later.

The message variable has a two-way binding, so if you type something into the form input, the variable is automatically updated with the new content. Likewise, if you modify the content of the variable in JavaScript, the change is automatically shown in the input on the screen.

You should now be able to use the form input to add items to the list.

Completing items

It's not much good having a todo list unless we can check items off the list. Modify your index.html so that a check box displays in front of each item:

    <input type="checkbox" v-model="item.completed"/>
    <label v-bind:class="{ completed: item.completed }">{{ item.text }}</label>

This creates a standard HTML check box input, with two Vue attributes. We also add an attribute to the label.

  1. We use v-model to connect the value of the input (whether the box is checked) to the completed property on the item object. This way, when we need to redraw the list, the box will only be checked if the item is actually completed.

  2. We use v-bind to tell Vue that the class attribute should incude a completed class that is bound to the value of completed on the item property. If you look in style.css, you will see that the completed class will make the item appear crossed out.

You should now be able to check items off your list and have them show as crossed out when they are completed.

Deleting items

It's great to check off items, but sometimes you want to delete items off your list. Let's add a delete button next to each item in index.html:

    <input type="checkbox" v-model="item.completed"/>
    <label v-bind:class="{ completed: item.completed }">{{ item.text }}</label>
    <button v-on:click="deleteItem(item)" class="delete">X</button>

This uses a standard button element, with the v-on attribute that tells Vue to call the deleteItem method when the click event is triggered for the button. We pass the current item to this method.

To make this work, add another method in script.js, after addItem:

    deleteItem(item) {
      var index = this.todos.indexOf(item);
      if (index > -1)
        this.todos.splice(index,1);
    },

This method simply deletes the item from the todos array.

Notice that the delete button only shows up when we hover over an item. This is done in CSS; see if you can find the relevant styles in styles.css.

You should now be able to delete items from your list.

A happy message

Everyone feels good when their todo list is empty. So let's add a message that displays when the list is empty. Put this right after the h1 tag in index.html:

    <p v-show="activeTodos.length === 0">You are done with all your tasks! Good job!</p>

To make this work, we need to add a computed property, which we will use to compute the subset of todo items that are active (not yet completed). You should put this in between the data and the methods:

  computed: {
    activeTodos() {
      return this.todos.filter(item => {
        return !item.completed;
      });
    },
  }

The computed property is called activeTodos. In this computed property, we filter the todo list to return only the items that are not completed. This property is cached and recomputed based on its dependencies, which in this case is the todo list. Every time this changes, the function is re-evaluated.

You should now be able to show a message if you have no active tasks.

More buttons

It might be helpful to display a subset of the items, such as only those that are completed, or only those that are not completed. We might also like to delete all the completed items at once.

Add some buttons to index.html that handle these actions, right between the form and the ul.

    <div class="controls">
      <button v-on:click="showAll()">Show All</button>
      <button v-on:click="showActive()">Show Active</button>
      <button v-on:click="showCompleted()">Show Completed</button>
      <button v-on:click="deleteCompleted()">Delete Completed</button>
    </div>

In addition, change the li tag so it looks like this:

	<li v-for="item in filteredTodos">

We have the following additions:

  1. A button that, when we click it, calls the showAll method.

  2. A button that, when we click it, calls the showActive method.

  3. A button that, when we click it, calls the showCompleted method.

  4. A button that, when we click it, calls the deleteCompleted method.

  5. A div that wraps these buttons and is only shown if the filteredTodos array is not empty.

We have also changed the li tag so it loops over the filteredTodos array.

We will need to make a number of changes to script.js. First, add a show property to the data, which should now read:

  data: {
    todos: [{
      text: "make an app",
      completed: false,
    }, {
      text: "declare victory",
      completed: false,
    }, {
      text: "profit",
      completed: false
    }],
    message: '',
    show: 'all',
  },

For the buttons, add the following methods:

    showAll() {
      this.show = 'all';
    },
    showActive() {
      this.show = 'active';
    },
    showCompleted() {
      this.show = 'completed';
    },
    deleteCompleted() {
      this.todos = this.todos.filter(item => {
      	return !item.completed;
      });
    },

These are all basic methods. The first three simply change the show property in the data. The last one filters the todo list so it contains only the items that have not been completed, which effectively deletes the completed items.

Finally, we need to add another computed property, which we will use to compute the subset of todo items that will be shown on the screen. You should put this after the activeTodos computed property:

    filteredTodos() {
      if (this.show === 'active')
        return this.todos.filter(item => {
         return !item.completed;
        });
      if (this.show === 'completed')
        return this.todos.filter(item => {
          return item.completed;
         });
      return this.todos;
    },

The computed property is called filteredTodos. In this computed property, we filter the todo list based on the current state of the show property.

You should now be able to show the active, completed, or all items in the list. You should also be able to delete all the completed items with one button click.

Drag and drop

The last thing we'll do is add drag-and-drop on the list items, so you can rearrange them. Vue integrates easily with HTML 5 dragging and dropping.

In index.html, modify the li element so it reads:

	<li v-for="item in filteredTodos" draggable="true" v-on:dragstart="dragItem(item)" v-on:dragover.prevent v-on:drop="dropItem(item)">

This will allow the items to be dragged and dropped. The draggable attribute is standard HTML 5 syntax to indicate the item can be dropped. The v-on:dragstart attribute calls the dragItem method when dragging starts. The v-on:dragover attribute prevents HTML5 from triggering an event every time the an item is dragged over another. The v-on:drop attribute calls the dropItem method when the item is dropped. You can see a complete list of available JavaScript methods.

Note that the item passed to dragItem is the item being dragged, and the item being dropped is the item it is being dropped onto. Remember, the li tag is generated for each separate item, so these are set to different variables.

To make this work in script.js, first add a drag property to the data so it looks like this:

  data: {
    ...
    message: '',
    show: 'all',
    drag: {},
  },

Then add these methods at the end of your method list:

    dragItem(item) {
      this.drag = item;
    },
    dropItem(item) {
      const indexItem = this.todos.indexOf(this.drag);
      const indexTarget = this.todos.indexOf(item);
      this.todos.splice(indexItem,1);
      this.todos.splice(indexTarget,0,this.drag);
    },

The dragItem method sets the drag property so we can keep track of which item is being dragged. The dropItem method places the dropped item ahead of the one it is dropped onto.

You should now be able to use drag-and-drop to reorder the items in the list. Isn't this fantastic?!

The entire index.html is 34 lines of code, and script.js is 64 lines of code.

You can examine the styles in styles.css to see how we made those work.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • CSS 72.3%
  • HTML 27.7%