Build Your First Vue.js App (in About 30 Minutes)

Devon Campbell - Feb 29 '20 - - Dev Community

Video Notes

If you want to work along and build a Vue.js note taking app đź“ť to learn the basics of Vue.js, start by registering a CodePen account and forking the project starter Pen.

If you like this Vue video, these might also help đź“•:

Thanks for watching the video! If you have questions, please comment here, on the video over on YouTube, or mention me on Twitter.

Real talk for a second: If you want to become a web developer, you may be focusing on learning to code. That seems like the most logical place to start, and it’s definitely the most fun. If you don’t know why you’re doing it though, you’re going to run out of gas pretty quickly. My video on finding a Big Goal explains why “why” is so important

Transcript

Hey everybody. This is Devon. We're going to build a view Vue.js app together. Let's jump right in.

This is the application we're going to end up with. This is just a simple note taking app. If I hit "Create New," it's going to drop me into a title field. And then I can tab into the note field, and I can create another note. Then we can switch back and forth. Of course, we can continue editing.

I've set you up with basically a starter app so that you don't have to build everything from scratch. What this gives you is it gives you some really basic markup. It gives you some CSS, including a CSS framework here called Milligram, and it's just really simple and straight forward. And then over in the JavaScript tab, we're starting out with the minified Vue.js just included from CloudFlare CDN.

What you want to do when you go to this starter Pen on CodePen is you want to go down here to the bottom right and click on "Fork," and if you don't have an account, you will want to sign up for CodePen and create your CodePen account.

Once you do that and you click "Sign up and save this pen," then it will give you, in your account, your own copy of this Pen, which you can change. The way you'll be able to tell if you're working on your copy is that it will say "A pen by " instead of mine.

I'm not going to be dealing with any of this CSS, so I'm just going to shrink this pain down so that I don't have to worry about it. The first thing we're going to do here is I will set up the component in Javascript, and we're just going to put some dummy data in it. I'll then go back over to the HTML and show you how you can use that data in your application, in your template.

This is how you'll instantiate a new Vue component. You're going to pass it an object that can have several different options, and the two we're going to use right now are the "el" option, which tells you which element this component is hooked into and the "data" option, which is an object that sets up some data the Vue component can use.

Let's flip over to the HTML and actually use that data. To use the data in the template, I just include it in two sets of curly braces. Whatever is in my data object under that key I use inside the curly braces is going to be inserted in that spot as you can see at the bottom.

That's just the demo of how data works and how it can be used in templates. So I'm going to get rid of this because that's not actually part of our notes app.

We're going to start by setting up our notes in the data over in the Javascript. Since we can have multiple notes, it probably makes sense for this data to be an array, and when the user starts using our app, that array is probably going to be empty so we'll just initialize notes as an empty array.

I think what I want to do with my notes is I want to make this an array of objects. I want the notes to be able to have a title and then a contents. So objects would be perfect for that. I can have two different keys in each of the notes objects: a title key and then a contents key. I can put whatever data I want to in those, and I can display them in different ways.

I'm going to start writing my template as if that was already in place even though it's not, and it won't do anything just yet… but we'll get to that. Let's flip over here to the template. I'm going to create a container that will hold all of my notes, and that's just going to be a div. If you remember from the finished notes app I showed you earlier, on the left hand side we have a list of all the titles of notes, and you can click on those to pull up the note on the right.

I'm going to start by setting up that note navigation, and since I do think of it as kind of navigation, I'm going to use a nav element and inside my nav I'm going to have an unordered list. Each note will be represented by a single list item.

Here's where things really start to get interesting with Vue. I don't want just one list item. I want as many as there are notes. The problem is, I don't know how many notes there are going to be because I don't know how many notes my user is going to create. I can use a Vue directive - it's "v-for" - that will allow me to repeat an element for each item in some array.

A directive is kind of like a magical attribute that Vue knows to pay attention to and to treat in a certain way. Let me show you how this one works. Let's start with "v-for=". You see how it looks like just any HTML attribute like class. And I'm going to say "note in notes".

This is kind of like a for loop, hence v"-for" but what it's doing is it's iterating through notes inside my data, which is currently empty, but we'll fix that later. And for each piece of data inside that notes array, it is naming them "note" each one in turn. What I'll do is, I'll do whatever I'd want to do for one note here and when I have multiple notes, Vue will repeat it once for each note in the "notes" array. What I want to do here is I want to display the title of the note inside the list item.
CodePen is telling me I've made a lot of changes without savings, so I'm going to go ahead and do that.

We don't have any actual notes yet, so we can't really see any changes because our four loop is repeating for every note in notes, and there aren't any, so those list items are not being created. What we can do is we can put some dummy data into that array just to see what it would look like and make sure that the directive is working the way it's supposed to. I've added a few dummy notes here. I'm just providing the title attribute because that's the only one I'm actually using in the template so far.
You can see in the browser pane on the bottom that our titles are being output into the browser, and I want to make sure these are inside list item elements. So I'm going to inspect one, and it looks like they are. Each one, the title is in an "li" element. That's what we would've expected, but we don't want our users to actually enter notes this way, so I'm going to just get rid of all these.

It may be a few minutes before we have added the user interface so that our users can add their own notes, so let's try to come up with an empty state for this application so that it's not just blank when there aren't any notes. It would be nice if you could see, "Oh, there are no notes right now. That's why it's blank." Let me show you how we can do that.

First of all, we have an unordered list here that is intended to contain all the note titles, and eventually those will be clickable so that we can pull up the contents of the note ones, right? If we don't have any notes, there is no need really for this unordered list. To make sure that it only gets rendered if we have notes, I'm going to use another Vue directive. It is "v-if." We're going to put a some sort of condition in our quotes, and if that condition is true, then this "ul" will be rendered. If it is not, then it will not. The condition we care about here is whether "notes" has anything in it. The way you do that in Javascript for an array is the "length" property.

This is not a Javascript intro, but just a quick explanation of how this technique works. Length is going to be 0 if there's nothing in the array. It's going to be some value greater than zero if there is something in the array. In JavaScript zero is a falsey value, so if you evaluate it as a condition, it evaluates to false, and anything higher than zero is a truthy value.

This "v-if" is going to get a false, if the length of that array is zero, and it's going to get a true if it's anything else. That takes care of getting rid of elements we don't want if we don't have items in the notes array, but it doesn't give us any sort of feedback about why we're seeing nothing in our notes application.

To do that, I'm just going to add a paragraph down here and it's going to say, "No notes saved." This is great, except even if we have notes, it's still going to say no notes saved. Let me show you really quick.
Now you see, we not only have the paragraph, "No notes saved" but we also have the title of the first note. I need to go back to that paragraph and add a "v-if" to it as well.

I only want to render this paragraph if "notes.length" is false. So I've basically got to invert the condition of my ul v-if, and I do that with a "not" and now you see that paragraph disappear. Let's take our note out and the paragraph should come back. There we go.

The next thing I'm going to do is I want to be able to select the note titles and I want the note contents and title to appear over on the right. In order to do that, I'm first going to surround the note title with a button. That way it will easily be clickable. I need to tell Vue what to do when someone clicks on this button. I do that with another directive, which is "v-on."

This one is a little bit different from the others because it also needs a colon, and then you're going to give it the event that you're listening for. We want to do something here when the button is clicked. Now I use my equal sign, and inside the quotations I'm going to just put what I want to do when this gets clicked and what I'm going to do is I'm going to set "currentNote" to "note".

We're inside our v-for loop, so "note" is just whichever note we're in right now. So this button, as many times as it repeats, it will always set "currentNote" to whatever note is associated with that button. We can use "note" to refer to that current note that the four loop is on currently in the same way that we can use it inside double curly braces to output the value onto the page.

The question now is: I can set a current note. What do I do with that? The most obvious thing I want to be able to do is I want to be able to display that note's contents. Let's just do that in the most quick and dirty way possible. I will start with a paragraph and I'm going to fill it with "currentNote.contents.

Oh, this is bad. Let's look at the console and see what's happening here. "Object error." The really nice thing about CodePen is that you can get up and going with a quick project like this really quickly. The bad part about it is, from what I've found, the error messages in the console are not very helpful.

Let's see if we can get something more useful out of the Chrome developer tools console instead. You can get to developer tools using Command-Option-I on a Mac or Control-Shift-I on Windows. Yeah, this is much more useful. "Reference error: currentNote is not defined."

OK. What I think is happening here is that, when we click and we're supposed to be setting "currentNote" to whatever note this is, "currentNote" doesn't exist, so Vue is basically not doing that and just sort of failing silently.

Simple way to fix that would be to set up a property on our data object that is current note and we'll just start it out with a no value because we're not going to have a current note at that point. Okay. I'm just going to get rid of this code pen console cause it's not being very helpful to us.

"currentNote is not defined." That's what we got before. Now we're getting "this cannot read property contents of null," which makes perfect sense because our app had to refresh when we made these changes. There was no "currentNote" because we hadn't clicked on a button associated with a note. So what this paragraph is trying to do is it's trying to render the contents of "currentNote", which is null and null, doesn't have a property called "contents".

This paragraph is only necessary if we do have a "currentNote", so we can use our "v-if" directive and make sure this paragraph doesn't get rendered at all unless there's a current note. I'm going to wipe our console clean so we can only see fresh messages. Now we have our application back. It looks like things are working.

Let's click on this test and nothing happens. Which is actually what I would expect because "currentNote" once we click is this note the only note we have, and it doesn't have any contents, so that is just nothing. Let's add a contents to it.

OK. Now it has a contents. The app has refreshed. We'll click on the button, and… there we go. There's that typo. Now it has a "contents" instead of just a "content". And if we click, we see the contents appear over on the right. That should be that paragraph. We just put the "v-if" on. Yep. That's what it looks like. Perfect.

I want to put the title over here too, so you can tell which note you're looking at. So I will add an h2 element, and this is going to break everything for a second. It's going to have "currentNote.title" inside it, and spell current right. And I don't want either of these things to appear unless we have a "currentNote". I could just put another "v-if" on this h2, and test for "currentNote" with both of them, but I think instead I'm just going to wrap the whole thing in a div and we'll put a "v-if" on it to test for "currentNote".

I'm also gonna put a class on it. That will make it easier for me to style. In fact, I've already styled it and used that class, and I need to close that div here. And just to keep things neat, I'll indent these one more level and let's get rid of this so we can actually see, since I have the "v-if" on the container div, I don't need it on this paragraph that's inside it. Nothing in there will get rendered if that condition is not met.

Let's try this out now with the title. OK, cool. Clicking on the button gives us both the title and the contents of the note.
If I were going to be using this app, I would also want the button that I've clicked on to somehow show that that is the active note. Let's look at implementing something like that.

This is going to be just sort of a conditional class. If a certain condition is met, and in this case that is the note we're on in the for loop is also the current note. Then we want to apply some sort of class to our button so that we can highlight it or color it a different way or something like that. Something to show that that's the active button.
I can do this by binding an attribute. This is another directive and it's "v-bind". This one also has a colon, and then after it you put the attribute you want to bind. We want to bind a value to the "class" attribute for this button.

Inside the quotes here, I'm going to pass an object. The way that this object works is the key of the object is going to be the class that will be applied. The value associated with that key is the condition we're going to check for. So if the condition that is the value of the object is true, then that class gets applied. If it's false, the class, it doesn't get applied.

For this one, I want to apply a class "active". That's my key in this object, and then the value is going to be my condition. And the condition, like we said before, is that this note is also the current note. So we're going to say "note" which references this note - the note we're on in the for loop - equals "currentNote".

That means if "note" and "currentNote" are one and the same, we're going to get the "active" class on the button. If not, we won't. Let's take it for a quick spin and see if it works.

OK, we'll click on this test note. Something happened. Let's inspect and see if the class got applied. There we go.

Let's go back and refactor what we have right now to use the shorthand directives instead of the ones we have. For the "v-on" directive, you can just take out everything up to and including the colon and just put an at sign. It does the exact same thing as what we had before. It's just the shorthand for "v-on".

For "v-bind", we can take out everything up to the colon. So the first character is going to be the colon, and that's again the exact same thing as what we had before. It's just a shorthand way of typing out. Let's make sure the click and the class still work, and it looks like they do.
Up to this point. We've been using our notes app by manually entering notes objects into our array. That's fine for testing, but our users are not going to do that. We don't want them to do that. We want to make it easy for them to create and edit notes.

Let's start implementing the UI for editing. The first thing I will do is I'm going to get rid of our hard-coded note from here on in. All of the notes will be entered by users through the user interface.
I'm going to start with the contents of the current note. The editing is all going to happen on the current note display. So, to the right of our note navigation.

Vue has this really cool concept called two-way data binding, and what that does is it takes some data in our Javascript and binds it to a user interface element in our HTML. When either of them changes, the other one automatically gets updated. Unfortunately, it doesn't work with most elements. It doesn't work, for example, with the paragraph we have our current note's contents in right now, so I'm going to start by changing this to a text area.

You might think that I would use my double curly braces and put the current note contents inside the text area, but I'm not going to do that because that won't bind the data. Instead, I'm going to use a Vue directive. It is "v-model". What this means is that the text area's contents is always going to be the currentNote.contents and vice versa. So if the text area's content changes, then currentNote.contents will also be updated. That's the "two-way" in the two way data binding: we can change it from either direction and it will update on the other side.

Our users should also be able to set and update the title of their note, and I'm going to implement this in a similar way to the contents. I will replace the title element with an input element and the "v-model" for this input element is the current note's title.

I think I took our sample note out a little prematurely, so let's put that back in just so we can test this and click on the title. OK, now we have a title and contents. Let's say "New title." You can see it updates over in the button. I'm going to add one more so we can switch back and forth and make sure that everything stays in line with our changes. And then let's say "New title," "contents with more data…" Now let's switch to the second note and we'll switch back. And our changes are still there.

These changes won't stay intact when the application refreshes because they're only stored in memory in the application. That means if you hit the refresh button on your browser, or if you close the browser and come back in, you won't have the notes you created before. CodePen helpfully refreshes everything for you when you make changes, so those refreshes will also wipe out your notes. Not a big deal right now. Just something to be aware of.

This is looking much better. Now, users can edit the notes they have, but they still can't create notes and this is really not very useful without that ability.

This time we are going to get rid of the sample notes and they will not come back. We need some way to create a new note and add it onto our notes array. I'm going to do it a really quick and dirty way. I'm just going to set up a note with blank title and contents and then I'm just going to push it onto that notes array.

That's the way you're going to get a new note. Before we can do that and implement it in our UI, we need to create a method on the Vue component that can do those things I just said. Let's go ahead and do that now.
What we want to do here is we want to create a method that we can call from the user interface that's going to add that blank note. To do that, I need to add a new attribute on this options object we're passing into the Vue constructor, and that attribute is going to be "methods" which is itself an object. I'm going to put a method on that object called "createNote".

First, I will set up a new note object, and this is just going to be my blank note. Now I need to push that note on to the notes. All right. When we pass in data to the view constructor, we can then get to that same data through our methods we pass in by using "this". So in this case, "this.notes" is referencing the same array that we passed into "data.notes".

I can stop here with this method and it would create a new note and push it onto the notes array, but it would be a really weird user experience because what it would do is it would create a new blank button that the user would have to click on, and then they'd have to go over to the right and click inside the invisible fields to create the title and the contents of the note.

I don't really want to do it that way because I think that's just a bad user experience. So this next line in the method is going to help a little bit with that. Now I'm going to change the current note to the new note. That means as soon as we create this note, it's going to get pushed onto the notes array and then the current node is going to be set to that same note, which will activate it and display it over on the right of our notes app.

Now we need some way to call this method. I'm just going to stick a button right here next to the notes heading. I'll label it "Create New". I'm going to put a class on it just so it can get some styles I set up for this button. Now we're going to wire it up to that new method. To do that, I need an event binding.

I could use v-bind, but instead I'm going to use the shortcut "@", and the event we're binding is "click" again. When this button is clicked, I want to call the "createNote" method.

Let's see if this works. OK, it sort of works. Actually, it completely works. It just is not great. So far, we have created a new note that became the active note, which is why this empty button is highlighted. It doesn't have a title and it doesn't have contents, so we can't really see anything yet. If we go over here to the right and start clicking around… oh, okay. We will eventually get to the title of the note, which we can update and we can tab into the contents. OK, great. There's another one. We can switch back and forth. That kind of works.

It's still a sketchy user experience. We're not going to make this app amazing. It's just a basic app, but we can still do a little bit better. I think if we could just make it so that when you click on this, "Create New," it focuses the note as the current note, but then it also focuses your insertion point into the title field. That would just make it a lot better. It's still not perfect, but much better.

In order to do that, I need some way inside my Javascript code to be able to refer to this title field. Vue has a pretty simple way to do that called refs.

I'm going to go over here to my HTML, go down to the title field, and add a "ref" attribute. We will call this field "noteTitle". I can now go over here into my Javascript code, and I can reference that field using "this.$refs." and then the name of the reference I created. So in this case "noteTitle." That gives me the element, but what do I actually want to do with it? I want to focus it.

The focus method on an element, at least for an element that takes text entry, just focuses the insertion point inside that element. Let's try that out and see if it works.

Ooh, it looks like it didn't work. I should be focused right here. That's what "this.$ref.noteTitle.focus" should have done for me, but it did not.
Let's try it again. Oh, OK. So this time it did.

OK, this is a pretty tricky problem, but here's what's happening. Our current note title and contents fields do not display unless there's a current note set. That means before there's a current note set, those elements are not even there. It's not that they're hidden, they're not on the page at all.

When we click on "Create Note" and our blank note gets pushed onto the notes array, there is a fraction of a second between the time that the data changes and gets a new blank note and the time that Vue renders those new elements that it needs because it now has a current note set. We are trying to focus on that element before it's actually on the page.

So we need a way basically to tell Vue to wait between the time we add the note onto the array and the time that we try to focus the title input field. Our Vue instance has a method called "$nextTick". You can pass a function into $nextTick, and what it will do is it will hold on to that function until after the DOM has been rendered. Then it will call the function.

I need to call my $nextTick method between the time the data is updated that will cause the new elements to render, and the time I'm trying to focus one of those new elements. That's going to be right after line 11 where the current note gets set to the new note. That's the point at which Vue needs to render those new elements.

So I'll go down here, give myself a new line after 11 ,and I'm going to call this.$nextTick. I'm going to pass it a function that will call my focus method on the note title, and let's see if this works.

Ah, perfect. Now when we create a brand new note, it automatically focuses us into the title field. That way at least you don't have to hunt around with your mouse trying to find where that title field might be.
That's a really simple Vue application for making notes, and in the next video, I'm going to return to this same application and show you how to implement a feature I demonstrated at the very beginning of the video: persisting your notes across refreshes.

We're going to do that using local storage. Be sure to hit subscribe so you don't miss that and I will talk to you then.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .