Vue Basics Tutorial
In this tutorial, we will be building a website for a bookstore. This will focus on the front-end part of the application.
Setup
We will be using @vue/cli
to develop this website.
Begin by opening a terminal and navigating to the directory where you want to save your code. Then, we will use @vue/cli
to create the application. We
The dev server will automatically update when we change files, so we can leave that running. We will still be working in the terminal, so you will want to open up a new terminal window/tab and navigate to the application's directory. Once you have done that, there are a few packages that need to be installed.
The @hickory/browser
package will be used to create an object that interacts with the browser to power navigation (e.g. updates the URI in the address bar when you click a link). @curi/router
provides the function to actually create the router. @curi/vue
gives us a plugin for Vue and some Vue components that interact with the router.
History and Locations
URIs can be broken into parts to identify a location. With a single-page application, we don't care about the URI's protocol (http, https) or its hostname (www.example.com). The properties we care about are the pathname
, hash
, and query
.
The routes define what the application renders for a particular location, but we also need to define how the application navigates. When we create the router, we will pass it a history function that will be used to enable navigation.
Curi uses the Hickory library for its history. There are a few Hickory packages to choose from for different environments. For most websites, the @hickory/browser
is the right choice for the front end.
We can import the browser
function from @hickory/browser
in our index file (src/index.js
, which create-react-app
created for us).
Defining the Routes
Routes are JavaScript objects that define the valid locations for a router. They have a name
and a path
.
A route's name
needs to be unique. We will use route names when we navigate within the application. A route's path
describes the location pathname that it should match.
Path basics
Route paths are strings describing the pathname segments they should match.
Paths never begin with a slash.
Paths can include dynamic parameters. These are specified with a string that starts with a colon (:
) followed by the name of the params.
Routes can be nested using the children
property of a route. A nested route inherits the path from its ancestor route(s), so its path
is only the additional part of the pathname that should be matched.
The website will start with four routes.
name | path | Description |
---|---|---|
Home | "" | Lists books available for purchase |
Book | "book/:id" | Details about an individual book |
Checkout | "checkout" | "Buy" the books in the shopping cart |
Catch All | "(.*)" | Display a not found page for all other locations |
Inside of the src
directory, we will create a routes.js
file where we can define the application's routes.
We can create an array of routes using the above names and paths.
@curi/router
provides a prepareRoutes
function, which is used to setup routes for the router. We will pass the routes array to prepareRoutes
and export the result of that function call.
The Router
With the history object created and the routes defined, we are ready to create the router. Back in the src/index.js
file, we should import the createRouter
function from @curi/router
as well as our routes from src/routes.js
. Creating the router is done by calling the createRouter
function and passing it the history function and the routes
array.
The router is now ready and we can render the application, but first we should do something really important: make the site more accessible.
Announcing Navigation
In a multi-page application, a screen reader will announce navigation to users. This happens automatically when a new Document is loaded. A single-page application reuses its Document, which is great for removing unnecessary server requests, but also means that the navigation is no longer automatically announced.
Curi has a concept of "side effects". These are functions that are called after a navigation happens and are passed an object with data about the navigation.
The @curi/router
package provides a few side effects that are useful for websites. For now, we will focus on the announce
side effect. The announce
side effect returns a string, which sets the text content of a ARIA Live region. Screen readers will detect the changed text and read it to the users.
Let's go ahead and add the announce
side effect to the router. We will have it return a string of the response's pathname
.
The Vue Plugin
We will add router support to the Vue application using a plugin. This plugin does a couple of things. First, it makes some Curi components available within the application. The only one of these components that we will be using is the curi-link
. Second, it makes router related values accessible to the components in the application. The router is available as this.$router
and the response
and navigation
(we will cover these next) are grouped under this.$curi
. When the CuriPlugin
is installed, the router
as passed in the options object.
Rendering with Vue
We can now render our application. We will re-use the provide App.vue
file.
Responses and Navigation
Whenever Curi receives a location, it matches its routes against it and generates a response. This is an object with data related to the route that matched the location. Later on we will modify this data ourselves, but for now the important thing to know is that the response lets us know about the current route.
The router uses an observer model to let functions subscribe to be called when a new response is generated. The CuriPlugin
sets up an observer so that it can trigger a re-render whenever there is a new one.
The navigation
object contains additional information about a navigation that doesn't make sense to include in the response object. This includes the navigation's "action" (push
, pop
, or replace
) and the previous response object. This can be useful for animation and modals.
Most of the time, the response is the only property you will need to use to render, but the other two may occasionally be useful.
How do we use the response to render? Any way you want. Based on the sample response above, the name
stands out as the best way to identify which route matched. We can make this even easier by adding another property to the response: body
.
Earlier it was mentioned that response objects can be modified. This is done by returning an object from a route's respond
function. respond
receives an object with a whole bunch of properties that we can use to help determine how to modify the response, but for the time being, we don't care about any of those. All we need to know is that if we return an object with a body
property, that value will be set on our response object.
If the return object's body
property is a Vue component, we can render it using <Component :is>
.
We haven't actually defined components for our routes yet, so we should throw together some placeholders.
These components can be imported in src/routes.js
and attached to their respective routes.
We can now update App.vue
to render response.body
as a component, which as mentioned above is available through this.$curi
.
We can also remove the HelloWorld
component.
At this point in time our app is rendering, but is isn't very interesting because we cannot navigate between locations.
Navigating between locations
The CuriPlugin
makes a curi-link
component available with the appliaction. We can use that to navigate between locations within our application.
The <curi-link> Component
Navigation isn't done by manually typing the pathname of the location the link should navigate to. Instead, we specify the name of the route using the name
prop.
If a route has params, we provide these to the curi-link
as a params
object. For a nested route, we would also need to provide params for any ancestor routes.
The curi-link
is only for in-app navigation. If you want to link to pages outside of the application, use an anchor.
If you need to attach query or hash data to a curi-link
, use the query
and hash
props.
A Navigation Menu
We will start with creating a navigation menu component with links to our home page and checkout page.
We can import that in our App.vue
file and add it to our template. This is a good opportunity to also add some structure to the elements in the template.
Linking to Books
We want to be able to link to individual books from the home page. First, we need data about the books. For now, we're going to hard-code the books in the src/books.js
module.
You can copy+paste or modify the data, but the structure of the provided data should stay the same.
The data can be imported in the Home
component. We will iterate over the books with a curi-link
to each one.
Now that we can navigate to the books, we should fill out the UI for the Book
component. We will once again import the books.js
data. We can use params.id
to select the correct book. params.id
is a string, so we will need to parse it into an integer. Sometimes there won't be a valid book for the params.id
. In that case, we will also want to display a message that the requested book could not be found.
Let's go shopping
We want to be able to add books to our shopping cart. Since this is a play site, we will store the cart data in memory.
The shopping cart implementation will be a JavaScript Map
. We can call its set
method to add books, its clear
method to reset the cart, and iterate over its entries
with a for...of
loop.
As stated above, we can access our router
in the Book
component using this.$router
. The router's navigate
function can be used to navigate to a new location. This means that when the user clicks a button to add a book to their shopping cart, we can automatically navigate to the checkout page.
The Router's URL & Navigate Methods
The router has a url
method that is used to generate a URL string using the name of a route and an object of the route's params.
The router's navigate
method is used to navigate; it takes a URL (such as one defined using router.url
). The function can also take a method
type for the navigation: push
, replace
, or anchor
.
push
pushes a new location after the current index, removing any locations after the current location.
replace
replaces the location at the current index.
anchor
is a mix between push
and replace
. It mimics the behavior of clicking on links, so if you navigate to the same location as the current one it will replace, and if you navigate to a new location it will push.
If method.navigate
is called without a navigation method
, it will default to anchor
.
We also want to import our shopping cart API so that we can add a book to the cart.
Finally, we can update our Checkout
component to display the books in the shopping cart. To do this, we will import our cart and books. Our cart only stores book id
s, so we will need to merge the book data with the cart data.
When a user "buys" the books in their shopping cart, we need to clear out the cart. We will also replace the current location with one whose location.hash
is the string "thanks". When that is present in the URI, we can render a "Thanks for your purchase" message to "confirm" the purchase.
What's next?
We now have a functional website built with Vue and Curi. What should you do next? Build another site! You can also check out the guides for information on advanced techniques.