DEV Community

Cover image for 1 Backend, 5 Frontends - Todo List with Rails, React, Angular, Vue, Svelte, and jQuery
Alex Merced
Alex Merced

Posted on

1 Backend, 5 Frontends - Todo List with Rails, React, Angular, Vue, Svelte, and jQuery

Why are we doing this?

One of my favorite way to sharpen my skills with different frameworks is to build todo lists. It's easy and quick to build and building the same app in different stacks makes what is the same and what is different between frameworks very clear.

In this tutorial, we will...

  • Create a basic todo list api using rails

  • Create a frontend using React

  • Create a frontend using Vue

  • Create a frontend using Angular

  • Create a frontend using Svelte

Hopefully, by the time you are done, you'll have an appreciation for the three big frontend frameworks and the modularity of working with APIs versus server side rendering.

What you'll need

  • Need Ruby Installed
  • Need Rails Installed
  • Need NodeJS installed
  • Familiarity with the Ruby and Javascript Programming Languages
  • Familiarity with Web Development Concepts

Building the API

SETUP

  1. Create a Folder called "todo" all our apps will be in this folder

  2. Open your terminal in the todo folder

  3. run the command rails new todo_backend --api -d postgresql
    Feel Free to use whichever database you are most comfortable with

Database Setup

head to config/database.yml and set up your database setting to match your local database settings. Below is an example for postgres, keep in mind your postgres username and password may be different.

default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  user: test5
  password: test5
  host: localhost
  port: 5432

## Development and test will pull the info from default to make use of your local postgres server
development:
  <<: *default
  database: todo_backend_development

test:
  <<: *default
  database: todo_backend_test

## If you plan on deploying to Heroku, the setup below will make sure when you deploy to heroku it will be pointing to your heroku database.
production:
  <<: *default
  url: <%= ENV['DATABASE_URL'] %>
Enter fullscreen mode Exit fullscreen mode

Now that your database settings are set we need to create the database by running the command rails db:create. If it is not working make sure that your database settings are correct.

Creating the Todo Model

We can quickly scaffold our todo API using Rails Scaffold generator which will create our migration, model, routes, controller and controller functions for us.

rails g scaffold todo title:string body:string

If you want feel to examine all the files it created but all we have left to do is run our migrations so the todos table is created in our database.

rails db:migrate

Now our API is essentially done, that was pretty easy. Let's next seed then database and make sure our CORS permissions are set to allow requests from other applications.

Seed the Database

Head to the db/seeds.rb and add some seed data


Todo.create({title: "Breakfast", body: "Eat Breakfast"})
Todo.create({title: "Lunch", body: "Eat Lunch"})
Todo.create({title: "Dinner", body: "Eat Dinner"})

p Todo.all

Enter fullscreen mode Exit fullscreen mode

Now run the seed so the database gets populated. rails db:seed

Configuring CORS

If we don't configure CORS headers then our frontend will fail when it tries to make request to our server. So just do the following.

  1. Uncomment gem 'rack-cors' in Gemfile and run bundle install

  2. Then head into config/initializers/cors.rb and make sure it looks like this...


# Be sure to restart your server when you modify this file.

# Avoid CORS issues when API is called from the frontend app.
# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests.

# Read more: https://github.com/cyu/rack-cors

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins '*'

    resource '*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end

Enter fullscreen mode Exit fullscreen mode

Test the API

  • Run the server rails server

  • Open up postman (download it if you don't have it)

  • Make a get request to http://localhost:3000/todos, you should see all the todos we added.

  • Make post request to http://localhost:3000/todos submitting the following json, then do another get request to confirm a new todo was created.

{
  "title": "Brunch",
  "body": "Eating Brunch"
}
Enter fullscreen mode Exit fullscreen mode
  • Make a put request to http://localhost:3000/todos/4 to edit our new todo which should have an ID of 4. Use the JSON below then do another get request to to localhost:3000/todos to confirm it was altered.
{
  "title": "Brunch II",
  "body": "Eating Brunch II"
}
Enter fullscreen mode Exit fullscreen mode
  • Last make a delete request to http://localhost:3000/todos/4 to delete Brunch II then make another get request to localhost:3000/todos to confirm it was deleted.

Complete

Your todo API is complete, you can deploy it or just run it locally so that you can make request to the API from your frontend application.


React Frontend

Setup

  1. Make sure your backend server is deployed or running locally in separate terminal window

  2. Navigate terminal to your todo folder and run the following command...

npx create-react-basic todo_react_frontend

if for some reason npx doesn't work for you then you can clone the same template with the following command.

git clone https://github.com/AlexMercedCoder/react_webpack_basic.git todo_react_frontend

After either command, change directory (cd) into the new todo_react_frontend folder and run 'npm install' to install all the dependencies

Getting Started

Navigate to src/components/App.js and let's create some state to hold the data from our API, function to make a call to our api and save the data in that state, and then call that function from a useEffect call so it grabs the data when the page loads.

import React from "react"

export const App = props => {
  ////////////////
  //STATE
  ////////////////

  // The State we'll save our API Data in
  const [todos, setTodos] = React.useState([])

  ////////////////////////
  // FUNCTIONS
  ////////////////////////

  //Our function to grab the latest list of todos
  const getTodos = async () => {
    //We make a request to our backend server
    const response = await fetch("http://localhost:3000/todos")
    //Convert the response into a javascript object
    const data = await response.json()
    //assign the data to our state
    setTodos(data)
  }

  /////////////////////////
  // useEffects
  /////////////////////////
  //useEffect to initially grab todos when page loads
  React.useEffect(() => {
    getTodos()
  }, [])

  /////////////////////////
  //RETURN JSX
  /////////////////////////

  return <h1>Hello World</h1>
}
Enter fullscreen mode Exit fullscreen mode

Then run the app in the browser with command npm run dev and check the react dev tools to make sure your api data was saved to the state in the App component.

Render the Todos To The Screen

We have the data being saved in the state but if we just render jsx with the data it will error... why?

Because the site will render once before the API call completes so references to the data will cause the app to error, so we need to conditionally render the data.

What we will do is make the JSX we want to conditionally rendered be return by a function and use ternary to only run the function if the todos array is greater than one.

import React from "react"

export const App = props => {
  ////////////////
  //STATE
  ////////////////

  // The State we'll save our API Data in
  const [todos, setTodos] = React.useState([])

  ////////////////////////
  // FUNCTIONS
  ////////////////////////

  //Our function to grab the latest list of todos
  const getTodos = async () => {
    //We make a request to our backend server
    const response = await fetch("http://localhost:3000/todos")
    //Convert the response into a javascript object
    const data = await response.json()
    //assign the data to our state
    setTodos(data)
  }

  //Function that returns JSX to display todos
  const TodosLoaded = () => (
    <h2>
      {todos.map(todo => (
        <div>
          <h2>{todo.title}</h2>
          <h3>{todo.body}</h3>
        </div>
      ))}
    </h2>
  )

  // Variable with JSX to display if no todos exist
  const noTodos = <h1>No Todos</h1>

  /////////////////////////
  // useEffects
  /////////////////////////
  //useEffect to initially grab todos when page loads
  React.useEffect(() => {
    getTodos()
  }, [])

  /////////////////////////
  //RETURN JSX
  /////////////////////////
  //In the JSX below we run the TodosLoaded function if there is at least one todo or render the contents of noTodos if there isn't any.
  return (
    <div>
      <h1>The Todo App</h1>
      {todos.length > 0 ? TodosLoaded() : noTodos}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Being able to create a new todo

Here we need to add a form for adding a new todo. We will create a new state for the create form, we need a handleChange function to update the state as we type in the inputs, a handleCreate function that will be triggered on form submission to create a new todo by making a post request to our server.

import React from "react"

export const App = props => {
  ////////////////
  //STATE
  ////////////////

  //Blank form object to initialize form and reset it
  const blankForm = {
    title: "\"\","
    body: "",
  }

  // The State we'll save our API Data in
  const [todos, setTodos] = React.useState([])

  //State for Our Create Todo Form, initialized with empty strings
  const [createForm, setCreateForm] = React.useState(blankForm)

  ////////////////////////
  // FUNCTIONS
  ////////////////////////

  //Our function to grab the latest list of todos
  const getTodos = async () => {
    //We make a request to our backend server
    const response = await fetch("http://localhost:3000/todos")
    //Convert the response into a javascript object
    const data = await response.json()
    //assign the data to our state
    setTodos(data)
  }

  //Function that returns JSX to display todos
  const TodosLoaded = () => (
    <h2>
      {todos.map(todo => (
        <div>
          <h2>{todo.title}</h2>
          <h3>{todo.body}</h3>
        </div>
      ))}
    </h2>
  )

  // Variable with JSX to display if no todos exist
  const noTodos = <h1>No Todos</h1>

  //Function to update state when people type in create form
  const handleCreateChange = event => {
    //update the create form state determining the key and value based on the form fields name and value properties since it will be the event target.
    setCreateForm({ ...createForm, [event.target.name]: event.target.value })
  }

  const handleCreate = async event => {
    //prevent form from refreshing screen
    event.preventDefault()
    //make post request to our backend server
    const response = await fetch("http://localhost:3000/todos", {
      method: "post",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(createForm),
    })
    //update the list of todos be refetching the list
    await getTodos()
    //reset form
    setCreateForm(blankForm)
  }

  /////////////////////////
  // useEffects
  /////////////////////////
  //useEffect to initially grab todos when page loads
  React.useEffect(() => {
    getTodos()
  }, [])

  /////////////////////////
  //RETURN JSX
  /////////////////////////
  //In the JSX below we run the TodosLoaded function if there is at least one todo or render the contents of noTodos if there isn't any.
  return (
    <div>
      <h1>The Todo App</h1>
      <h1>Create a Todo</h1>
      <form onSubmit={handleCreate}>
        <input
          type="text"
          name="title"
          value={createForm.title}
          onChange={handleCreateChange}
        />
        <input
          type="text"
          name="body"
          value={createForm.body}
          onChange={handleCreateChange}
        />
        <input type="submit" value="Create Todo" />
      </form>
      <h1>Todos</h1>
      {todos.length > 0 ? TodosLoaded() : noTodos}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Being able to Update a Todo

We need another form setup just like create, but we also need a function to pass an existing todo to that forms state, but otherwise pretty much the same as the creating of a todo. We will add an edit button to the JSX in our TodosLoaded Function.

import React from "react"

export const App = props => {
  ////////////////
  //STATE
  ////////////////

  //Blank form object to initialize form and reset it
  const blankForm = {
    title: "\"\","
    body: "",
  }

  // The State we'll save our API Data in
  const [todos, setTodos] = React.useState([])

  //State for Our Create Todo Form, initialized with empty strings
  const [createForm, setCreateForm] = React.useState(blankForm)

  //State for Our Update Todo Form, initialized with empty strings
  const [updateForm, setUpdateForm] = React.useState(blankForm)

  ////////////////////////
  // FUNCTIONS
  ////////////////////////

  //Our function to grab the latest list of todos
  const getTodos = async () => {
    //We make a request to our backend server
    const response = await fetch("http://localhost:3000/todos")
    //Convert the response into a javascript object
    const data = await response.json()
    //assign the data to our state
    setTodos(data)
  }

  //Function that returns JSX to display todos
  const TodosLoaded = () => (
    <>
      {todos.map(todo => (
        <div>
          <h2>{todo.title}</h2>
          <h3>{todo.body}</h3>
          <button onClick={() => setUpdateForm(todo)}>Edit</button>
        </div>
      ))}
    </>
  )

  // Variable with JSX to display if no todos exist
  const noTodos = <h1>No Todos</h1>

  //Function to update state when people type in create form
  const handleCreateChange = event => {
    //update the create form state determining the key and value based on the form fields name and value properties since it will be the event target.
    setCreateForm({ ...createForm, [event.target.name]: event.target.value })
  }

  const handleCreate = async event => {
    //prevent form from refreshing screen
    event.preventDefault()
    //make post request to our backend server
    const response = await fetch("http://localhost:3000/todos", {
      method: "post",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(createForm),
    })
    //update the list of todos be refetching the list
    await getTodos()
    //reset form
    setCreateForm(blankForm)
  }

  //Function to update state when people type in update form
  const handleUpdateChange = event => {
    //update the update form state determining the key and value based on the form fields name and value properties since it will be the event target.
    setUpdateForm({ ...updateForm, [event.target.name]: event.target.value })
  }

  const handleUpdate = async event => {
    //prevent form from refreshing screen
    event.preventDefault()
    //make put request to our backend server
    const response = await fetch(
      "http://localhost:3000/todos/" + updateForm.id,
      {
        method: "put",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(updateForm),
      }
    )
    //update the list of todos be refetching the list
    await getTodos()
    //reset form
    setUpdateForm(blankForm)
  }

  /////////////////////////
  // useEffects
  /////////////////////////
  //useEffect to initially grab todos when page loads
  React.useEffect(() => {
    getTodos()
  }, [])

  /////////////////////////
  //RETURN JSX
  /////////////////////////
  //In the JSX below we run the TodosLoaded function if there is at least one todo or render the contents of noTodos if there isn't any.
  return (
    <div>
      <h1>The Todo App</h1>
      <h1>Create a Todo</h1>
      <form onSubmit={handleCreate}>
        <input
          type="text"
          name="title"
          value={createForm.title}
          onChange={handleCreateChange}
        />
        <input
          type="text"
          name="body"
          value={createForm.body}
          onChange={handleCreateChange}
        />

        <input type="submit" value="Create Todo" />
      </form>
      <h1>Update a Todo</h1>
      <form onSubmit={handleUpdate}>
        <input
          type="text"
          name="title"
          value={updateForm.title}
          onChange={handleUpdateChange}
        />
        <input
          type="text"
          name="body"
          value={updateForm.body}
          onChange={handleUpdateChange}
        />

        <input type="submit" value="Update Todo" />
      </form>
      <h1>Todos</h1>
      {todos.length > 0 ? TodosLoaded() : noTodos}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Deleting a Todo

This one one is fairly simple, we'll add a function to make the delete request and then add a delete button next to our edit button that passed the todo to the deleteTodo function. Then We're done!

import React from "react"

export const App = props => {
  ////////////////
  //STATE
  ////////////////

  //Blank form object to initialize form and reset it
  const blankForm = {
    title: "\"\","
    body: "",
  }

  // The State we'll save our API Data in
  const [todos, setTodos] = React.useState([])

  //State for Our Create Todo Form, initialized with empty strings
  const [createForm, setCreateForm] = React.useState(blankForm)

  //State for Our Update Todo Form, initialized with empty strings
  const [updateForm, setUpdateForm] = React.useState(blankForm)

  ////////////////////////
  // FUNCTIONS
  ////////////////////////

  //Our function to grab the latest list of todos
  const getTodos = async () => {
    //We make a request to our backend server
    const response = await fetch("http://localhost:3000/todos")
    //Convert the response into a javascript object
    const data = await response.json()
    //assign the data to our state
    setTodos(data)
  }

  //Function that returns JSX to display todos
  const TodosLoaded = () => (
    <>
      {todos.map(todo => (
        <div>
          <h2>{todo.title}</h2>
          <h3>{todo.body}</h3>
          <button onClick={() => setUpdateForm(todo)}>Edit</button>
          <button onClick={() => handleDelete(todo)}>Delete</button>
        </div>
      ))}
    </>
  )

  // Variable with JSX to display if no todos exist
  const noTodos = <h1>No Todos</h1>

  //Function to update state when people type in create form
  const handleCreateChange = event => {
    //update the create form state determining the key and value based on the form fields name and value properties since it will be the event target.
    setCreateForm({ ...createForm, [event.target.name]: event.target.value })
  }

  const handleCreate = async event => {
    //prevent form from refreshing screen
    event.preventDefault()
    //make post request to our backend server
    const response = await fetch("http://localhost:3000/todos", {
      method: "post",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(createForm),
    })
    //update the list of todos be refetching the list
    await getTodos()
    //reset form
    setCreateForm(blankForm)
  }

  //Function to update state when people type in update form
  const handleUpdateChange = event => {
    //update the update form state determining the key and value based on the form fields name and value properties since it will be the event target.
    setUpdateForm({ ...updateForm, [event.target.name]: event.target.value })
  }

  const handleUpdate = async event => {
    //prevent form from refreshing screen
    event.preventDefault()
    //make put request to our backend server
    const response = await fetch(
      "http://localhost:3000/todos/" + updateForm.id,
      {
        method: "put",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(updateForm),
      }
    )
    //update the list of todos be refetching the list
    await getTodos()
    //reset form
    setUpdateForm(blankForm)
  }

  const handleDelete = async todo => {
    //prevent form from refreshing screen
    event.preventDefault()
    //make delete request to our backend server
    const response = await fetch("http://localhost:3000/todos/" + todo.id, {
      method: "delete",
    })
    //update the list of todos be refetching the list
    await getTodos()
  }

  /////////////////////////
  // useEffects
  /////////////////////////
  //useEffect to initially grab todos when page loads
  React.useEffect(() => {
    getTodos()
  }, [])

  /////////////////////////
  //RETURN JSX
  /////////////////////////
  //In the JSX below we run the TodosLoaded function if there is at least one todo or render the contents of noTodos if there isn't any.
  return (
    <div>
      <h1>The Todo App</h1>
      <h1>Create a Todo</h1>
      <form onSubmit={handleCreate}>
        <input
          type="text"
          name="title"
          value={createForm.title}
          onChange={handleCreateChange}
        />
        <input
          type="text"
          name="body"
          value={createForm.body}
          onChange={handleCreateChange}
        />

        <input type="submit" value="Create Todo" />
      </form>
      <h1>Update a Todo</h1>
      <form onSubmit={handleUpdate}>
        <input
          type="text"
          name="title"
          value={updateForm.title}
          onChange={handleUpdateChange}
        />
        <input
          type="text"
          name="body"
          value={updateForm.body}
          onChange={handleUpdateChange}
        />

        <input type="submit" value="Update Todo" />
      </form>
      <h1>Todos</h1>
      {todos.length > 0 ? TodosLoaded() : noTodos}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Vue Frontend

Setup

  • Make sure your todo list server is running

  • Navigate to todo folder in terminal

  • run command npx merced-spinup vue todo_vue_frontend

  • cd into todo_vue_frontend folder and run npm install

  • run npm run serve to start dev server on port 8080

Displaying Todos

Our first step is to display todos so we need to make our api call. In a Vue file we export an object that'll contain all our data (state), methods, and lifecycle functions.

We need to create...

  • Data property to hold our todos
  • Method to fetch our todos
  • call the getTodos method in beforeCreate to it fetches when page loads

So head over to src/App.vue and alter it like so...

<template>
  <div></div>
</template>

<script>
  export default {
    //Name property names the component
    name: "App",
    // data property has a function that returns an object with app data
    data: function () {
      return {
        todos: [],
        baseUrl: "http://localhost:3000/todos",
      }
    },
    //methods is an object of functions
    methods: {
      getTodos: async function () {
        const response = await fetch(this.baseUrl)
        const data = await response.json()
        this.todos = data
      },
    },
    //create runs after components is initially created, one of many lifecycle functions
    created: function () {
      this.getTodos()
    },
  }
</script>
Enter fullscreen mode Exit fullscreen mode

The screen will still be blank but if you download the Vue devtools chrome extension you should see the data component has the todos in its data. Now let's edit this components template to over the todos and display them.

<template>
  <div>
    <h1>The Todo App</h1>
    <hr />
    <h3>Todos</h3>
    <hr />
    <ul>
      <li v-for="todo of todos" v-bind:key="todo.id">
        **********************
        <h4>{{todo.title}}</h4>
        <h5>{{todo.body}}</h5>
        **********************
      </li>
    </ul>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

Creating some todos

So now that we can see our todos what we need is some more data properties to hold data from our, and method to run to create our todos. We will use the v-model directive to bind the properties to our form so they keep each other updated (Two way data binding).

<template>
  <div>
    <h1>The Todo App</h1>
    <hr />
    <h1>Create a Todo</h1>
    <form v-on:submit.prevent="createTodo">
      <input type="text" v-model="createTitle" />
      <input type="text" v-model="createBody" />
      <input type="submit" />
    </form>
    <hr />
    <h3>Todos</h3>
    <hr />
    <ul>
      <li v-for="todo of todos" v-bind:key="todo.id">
        **********************
        <h4>{{todo.title}}</h4>
        <h5>{{todo.body}}</h5>
        **********************
      </li>
    </ul>
  </div>
</template>

<script>
  export default {
    //Name property names the component
    name: "App",
    // data property has a function that returns an object with app data
    data: function () {
      return {
        todos: [],
        baseUrl: "http://localhost:3000/todos",
        createTitle: "",
        createBody: "",
      }
    },
    //methods is an object of functions
    methods: {
      getTodos: async function () {
        const response = await fetch(this.baseUrl)
        const data = await response.json()
        this.todos = data
      },
      createTodo: async function () {
        await fetch(this.baseUrl, {
          method: "post",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            title: this.createTitle,
            body: this.createBody,
          }),
        })
        this.createTitle = ""
        this.createBody = ""
        this.getTodos()
      },
    },
    //create runs after components is initially created, one of many lifecycle functions
    created: function () {
      this.getTodos()
    },
  }
</script>
Enter fullscreen mode Exit fullscreen mode

Editing a Todo

Pretty much the same workflow except we'll need an additional method for when users click the edit button.

<template>
  <div>
    <h1>The Todo App</h1>
    <hr />
    <h1>Create a Todo</h1>
    <form v-on:submit.prevent="createTodo">
      <input type="text" v-model="createTitle" />
      <input type="text" v-model="createBody" />
      <input type="submit" />
    </form>
    <hr />
    <h1>Edit a Todo</h1>
    <form v-on:submit.prevent="editTodo">
      <input type="text" v-model="editTitle" />
      <input type="text" v-model="editBody" />
      <input type="submit" />
    </form>
    <hr />
    <h3>Todos</h3>
    <hr />
    <ul>
      <li v-for="todo of todos" v-bind:key="todo.id">
        **********************
        <h4>{{todo.title}}</h4>
        <h5>{{todo.body}}</h5>
        **********************
        <button v-on:click="() => editSelect(todo)">Edit</button>
      </li>
    </ul>
  </div>
</template>

<script>
  export default {
    //Name property names the component
    name: "App",
    // data property has a function that returns an object with app data
    data: function () {
      return {
        todos: [],
        baseUrl: "http://localhost:3000/todos",
        createTitle: "",
        createBody: "",
        editTitle: "",
        editBody: "",
        editId: 0,
      }
    },
    //methods is an object of functions
    methods: {
      getTodos: async function () {
        const response = await fetch(this.baseUrl)
        const data = await response.json()
        this.todos = data
      },
      createTodo: async function () {
        await fetch(this.baseUrl, {
          method: "post",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            title: this.createTitle,
            body: this.createBody,
          }),
        })
        this.createTitle = ""
        this.createBody = ""
        this.getTodos()
      },
      editSelect: function (todo) {
        this.editTitle = todo.title
        this.editBody = todo.body
        this.editId = todo.id
      },
      editTodo: async function () {
        await fetch(this.baseUrl + "/" + this.editId, {
          method: "put",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            title: this.editTitle,
            body: this.editBody,
          }),
        })
        this.editTitle = ""
        this.editBody = ""
        this.editId = 0
        this.getTodos()
      },
    },
    //create runs after components is initially created, one of many lifecycle functions
    created: function () {
      this.getTodos()
    },
  }
</script>
Enter fullscreen mode Exit fullscreen mode

Deleting a Todo

For out final piece of functionality we need to be able to delete a todo. All this requires is for us to add a delete button that calls a deleteTodo method, wallah, we're done!

<template>
  <div>
    <h1>The Todo App</h1>
    <hr />
    <h1>Create a Todo</h1>
    <form v-on:submit.prevent="createTodo">
      <input type="text" v-model="createTitle" />
      <input type="text" v-model="createBody" />
      <input type="submit" />
    </form>
    <hr />
    <h1>Edit a Todo</h1>
    <form v-on:submit.prevent="editTodo">
      <input type="text" v-model="editTitle" />
      <input type="text" v-model="editBody" />
      <input type="submit" />
    </form>
    <hr />
    <h3>Todos</h3>
    <hr />
    <ul>
      <li v-for="todo of todos" v-bind:key="todo.id">
        **********************
        <h4>{{todo.title}}</h4>
        <h5>{{todo.body}}</h5>
        **********************
        <button v-on:click="() => editSelect(todo)">Edit</button>
        <button v-on:click="() => deleteTodo(todo)">Delete</button>
      </li>
    </ul>
  </div>
</template>

<script>
  export default {
    //Name property names the component
    name: "App",
    // data property has a function that returns an object with app data
    data: function () {
      return {
        todos: [],
        baseUrl: "http://localhost:3000/todos",
        createTitle: "",
        createBody: "",
        editTitle: "",
        editBody: "",
        editId: 0,
      }
    },
    //methods is an object of functions
    methods: {
      getTodos: async function () {
        const response = await fetch(this.baseUrl)
        const data = await response.json()
        this.todos = data
      },
      createTodo: async function () {
        await fetch(this.baseUrl, {
          method: "post",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            title: this.createTitle,
            body: this.createBody,
          }),
        })
        this.createTitle = ""
        this.createBody = ""
        this.getTodos()
      },
      editSelect: function (todo) {
        this.editTitle = todo.title
        this.editBody = todo.body
        this.editId = todo.id
      },
      editTodo: async function () {
        await fetch(this.baseUrl + "/" + this.editId, {
          method: "put",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            title: this.editTitle,
            body: this.editBody,
          }),
        })
        this.editTitle = ""
        this.editBody = ""
        this.editId = 0
        this.getTodos()
      },
      deleteTodo: async function (todo) {
        await fetch(this.baseUrl + "/" + todo.id, {
          method: "delete",
        })
        this.getTodos()
      },
    },
    //create runs after components is initially created, one of many lifecycle functions
    created: function () {
      this.getTodos()
    },
  }
</script>
Enter fullscreen mode Exit fullscreen mode

Angular Frontend

Setup

  • Make sure todo API server is running and Navigate to todo folder in terminal

  • Run the command npx merced-spinup angular todo_angular_frontend

  • cd into todo_angular_frontend folder and run npm install

  • run npm start to start up the dev server on port 4200

Displaying Our Todos

There are two primary files we'll be working from...

src/app/app.component.html => this is where the template/html lives for our one and only component. Similar to the template tag in the Vue file.

src/app/app.component.ts => This is the Typescript/Javascript file where our coding logic and variables will live. Similar to the script tag in the Vue file.

So, first things first. We need...

  • Define a variable to hold our todos

  • Create a method that will fetch the todos

  • Call that method with the component initializes using the OnInit method

app.component.ts

import { Component, OnInit } from "@angular/core"

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"],
})
export class AppComponent implements OnInit {
  todos: Array<any> = []
  baseUrl: string = "http://localhost:3000/todos"

  async getTodos() {
    const response = await fetch(this.baseUrl)
    const data = await response.json()
    this.todos = await data
  }

  ngOnInit() {
    this.getTodos()
  }
}
Enter fullscreen mode Exit fullscreen mode

Now let's make those todos visible in the template.

app.component.html

<h1>The Todo App</h1>

<h2>The Todos</h2>
<ul>
  <li *ngFor="let todo of todos">
    <h3>{{todo.title}}</h3>
    <h4>{{todo.body}}</h4>
  </li>
</ul>
Enter fullscreen mode Exit fullscreen mode

Creating Some Todos

First we need to add the Forms Module to our application, make sure your src/app/app.module.ts file looks like this. This is the file where different Angular features get loaded, this is referred as dependency injection. You inject what you need and not what you don't.

import { BrowserModule } from "@angular/platform-browser"
import { NgModule } from "@angular/core"
import { FormsModule } from "@angular/forms"

import { AppRoutingModule } from "./app-routing.module"
import { AppComponent } from "./app.component"
import { HeaderComponent } from "./header/header.component"
import { FooterComponent } from "./footer/footer.component"
import { MainComponent } from "./main/main.component"

@NgModule({
  declarations: [AppComponent, HeaderComponent, FooterComponent, MainComponent],
  imports: [BrowserModule, AppRoutingModule, FormsModule],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

This module will unlock the ability to use the ngModel directive to implement two-way finding on our form inputs like the v-model directive did in Vue.

app.component.ts

import { Component, OnInit } from "@angular/core"

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"],
})
export class AppComponent implements OnInit {
  todos: Array<any> = []

  baseUrl: string = "http://localhost:3000/todos"

  //Properties to Bind with Create Form
  createTitle: string = ""
  createBody: string = ""

  //Function to Grab list of todos
  async getTodos() {
    const response = await fetch(this.baseUrl)
    const data = await response.json()
    this.todos = await data
  }

  //takes data from form and creates new todo
  async createTodo() {
    console.log(this.createTitle, this.createBody)
    await fetch(this.baseUrl, {
      method: "post",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        title: this.createTitle,
        body: this.createBody,
      }),
    })
    //update todo list and reset form
    this.getTodos()
    this.createTitle = ""
    this.createBody = ""
  }

  //this function runs when the component loads
  ngOnInit() {
    this.getTodos()
  }
}
Enter fullscreen mode Exit fullscreen mode

app.component.html

<h1>The Todo App</h1>

<h2>Create a Todo</h2>
<form (submit)="createTodo()">
  <input type="text" [(ngModel)]="createTitle" name="title" #ctrl="ngModel" />
  <input type="text" [(ngModel)]="createBody" name="body" #ctrl="ngModel" />
  <input type="submit" value="create Todo" />
</form>

<h2>The Todos</h2>
<ul>
  <li *ngFor="let todo of todos">
    <h3>{{todo.title}}</h3>
    <h4>{{todo.body}}</h4>
  </li>
</ul>
Enter fullscreen mode Exit fullscreen mode

Let's update todos

So here we need create another form with the same workflow as creating a todo, except we need a function for when an edit button is clicked on.

app.component.ts

import { Component, OnInit } from "@angular/core"

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"],
})
export class AppComponent implements OnInit {
  todos: Array<any> = []

  baseUrl: string = "http://localhost:3000/todos"

  //Properties to Bind with Create Form
  createTitle: string = ""
  createBody: string = ""

  //Properties to Bind with Create Form
  editTitle: string = ""
  editBody: string = ""
  editId: number = 0

  //Function to Grab list of todos
  async getTodos() {
    const response = await fetch(this.baseUrl)
    const data = await response.json()
    this.todos = await data
  }

  //takes data from form and creates new todo
  async createTodo() {
    await fetch(this.baseUrl, {
      method: "post",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        title: this.createTitle,
        body: this.createBody,
      }),
    })
    //update todo list and reset form
    this.getTodos()
    this.createTitle = ""
    this.createBody = ""
  }

  editSelect(todo) {
    this.editId = todo.id
    this.editTitle = todo.title
    this.editBody = todo.body
  }

  //takes data from form and updates new todo
  async updateTodo() {
    await fetch(this.baseUrl + "/" + this.editId, {
      method: "put",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        title: this.editTitle,
        body: this.editBody,
      }),
    })
    //update todo list and reset form
    this.getTodos()
    this.editTitle = ""
    this.editBody = ""
    this.editId = 0
  }

  //this function runs when the component loads
  ngOnInit() {
    this.getTodos()
  }
}
Enter fullscreen mode Exit fullscreen mode

app.component.html

<h1>The Todo App</h1>
<hr />
<h2>Create a Todo</h2>
<form (submit)="createTodo()">
  <input type="text" [(ngModel)]="createTitle" name="title" #ctrl="ngModel" />
  <input type="text" [(ngModel)]="createBody" name="body" #ctrl="ngModel" />
  <input type="submit" value="create Todo" />
</form>
<hr />
<h2>Edit a Todo</h2>
<form (submit)="updateTodo()">
  <input type="text" [(ngModel)]="editTitle" name="title" #ctrl="ngModel" />
  <input type="text" [(ngModel)]="editBody" name="body" #ctrl="ngModel" />
  <input type="submit" value="Edit Todo" />
</form>
<hr />
<h2>The Todos</h2>
<ul>
  <li *ngFor="let todo of todos">
    <h3>{{ todo.title }}</h3>
    <h4>{{ todo.body }}</h4>
    <button (click)="editSelect(todo)">Edit</button>
  </li>
</ul>
Enter fullscreen mode Exit fullscreen mode

Delete a Todo

We just need to a add a delete method, then attach that method to a delete button and ta-da! We are done!

app.component.ts

import { Component, OnInit } from "@angular/core"

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"],
})
export class AppComponent implements OnInit {
  todos: Array<any> = []

  baseUrl: string = "http://localhost:3000/todos"

  //Properties to Bind with Create Form
  createTitle: string = ""
  createBody: string = ""

  //Properties to Bind with Create Form
  editTitle: string = ""
  editBody: string = ""
  editId: number = 0

  //Function to Grab list of todos
  async getTodos() {
    const response = await fetch(this.baseUrl)
    const data = await response.json()
    this.todos = await data
  }

  //takes data from form and creates new todo
  async createTodo() {
    await fetch(this.baseUrl, {
      method: "post",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        title: this.createTitle,
        body: this.createBody,
      }),
    })
    //update todo list and reset form
    this.getTodos()
    this.createTitle = ""
    this.createBody = ""
  }

  editSelect(todo) {
    this.editId = todo.id
    this.editTitle = todo.title
    this.editBody = todo.body
  }

  //takes data from form and updates new todo
  async updateTodo() {
    await fetch(this.baseUrl + "/" + this.editId, {
      method: "put",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        title: this.editTitle,
        body: this.editBody,
      }),
    })
    //update todo list and reset form
    this.getTodos()
    this.editTitle = ""
    this.editBody = ""
    this.editId = 0
  }

  async deleteTodo(todo) {
    await fetch(this.baseUrl + "/" + todo.id, {
      method: "delete",
    })
    //update list of todos
    this.getTodos()
  }

  //this function runs when the component loads
  ngOnInit() {
    this.getTodos()
  }
}
Enter fullscreen mode Exit fullscreen mode

app.component.html

<h1>The Todo App</h1>
<hr />
<h2>Create a Todo</h2>
<form (submit)="createTodo()">
  <input type="text" [(ngModel)]="createTitle" name="title" #ctrl="ngModel" />
  <input type="text" [(ngModel)]="createBody" name="body" #ctrl="ngModel" />
  <input type="submit" value="create Todo" />
</form>
<hr />
<h2>Edit a Todo</h2>
<form (submit)="updateTodo()">
  <input type="text" [(ngModel)]="editTitle" name="title" #ctrl="ngModel" />
  <input type="text" [(ngModel)]="editBody" name="body" #ctrl="ngModel" />
  <input type="submit" value="Edit Todo" />
</form>
<hr />
<h2>The Todos</h2>
<ul>
  <li *ngFor="let todo of todos">
    <h3>{{ todo.title }}</h3>
    <h4>{{ todo.body }}</h4>
    <button (click)="editSelect(todo)">Edit</button>
    <button (click)="deleteTodo(todo)">Delete</button>
  </li>
</ul>
Enter fullscreen mode Exit fullscreen mode

Svelte Frontend

Setup

  • Make sure todo api server is running and navigate terminal to todo folder

  • run the command npx merced-spinup svelte todo_svelte_frontend

  • cd into todo_svelte_frontend folder and run npm install

  • run npm run dev to start the dev server on port 5000

Displaying Our Todos

Svelte is very similar to Vue that everything for one component is in one file. So we'll be working entirely with one component like we've done in the previous runs which will be src/App.svelte.

Just like before we need methods to pull the data and the template to render them like so...

<script>
  import { onMount } from "svelte"

  //Variable to hold todos
  let todos = []

  //base URL
  const baseURL = "http://localhost:3000/todos"

  //Method to pull data
  const getTodos = async () => {
    const response = await fetch(baseURL)
    const data = await response.json()
    todos = await data
  }

  onMount(() => {
    getTodos()
  })
</script>

<main>
  <h1>The Todo App</h1>
  {#each todos as todo}
  <div>
    <h2>{todo.title}</h2>
    <h3>{todo.body}</h3>
  </div>
  {/each}
</main>

<style></style>
Enter fullscreen mode Exit fullscreen mode

Creating a todo

Once again, the same logic as usual

  • create a form
  • bind the form to values
  • function that runs on form submission that makes the post request

App.svelte

<script>
  import { onMount } from "svelte"

  //Variable to hold todos
  let todos = []

  //base URL
  const baseURL = "http://localhost:3000/todos"

  //Method to pull data
  const getTodos = async () => {
    const response = await fetch(baseURL)
    const data = await response.json()
    todos = await data
  }

  //Runs when component loads
  onMount(() => {
    getTodos()
  })

  //properties for create form
  let createTitle
  let createBody

  //create function for form submission
  const createTodo = async event => {
    event.preventDefault()
    await fetch(baseURL, {
      method: "post",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        title: createTitle,
        body: createBody,
      }),
    })

    //refetch todos
    getTodos()

    //reset form
    createTitle = ""
    createBody = ""
  }
</script>

<main>
  <h1>The Todo App</h1>
  <hr />
  <h2>Create a Todo</h2>
  <form on:submit="{createTodo}">
    <input type="text" bind:value="{createTitle}" />
    <input type="text" bind:value="{createBody}" />
    <input type="submit" value="Create Todo" />
  </form>
  <hr />
  <h2>The Todos</h2>
  {#each todos as todo}
  <div>
    <h2>{todo.title}</h2>
    <h3>{todo.body}</h3>
  </div>
  {/each}
</main>

<style></style>
Enter fullscreen mode Exit fullscreen mode

Update a Todo

  • add properties for edit form
  • add edit form
  • add method to select item to edit
  • bind method to edit button

<script>
    import {onMount} from 'svelte'

    //Variable to hold todos
    let todos = []

    //base URL
    const baseURL = "http://localhost:3000/todos"

    //Method to pull data
    const getTodos = async () => {
        const response = await fetch(baseURL)
        const data = await response.json()
        todos = await data
    }

    //Runs when component loads
    onMount(()=>{
        getTodos()
    })

    //properties for create form
    let createTitle;
    let createBody;

    //create function for form submission
    const createTodo = async (event) => {
        event.preventDefault()
        await fetch(baseURL, {
            method: "post",
            headers: {
                "Content-Type":"application/json"
            },
            body: JSON.stringify({
                title: createTitle,
                body: createBody
            })
        })

        //refetch todos
        getTodos()

        //reset form
        createTitle = ""
        createBody = ""
    }

    //properties for edit form
    let editTitle;
    let editBody;
    let editId

    //create function for form submission
    const updateTodo = async (event) => {
        event.preventDefault()
        await fetch(baseURL + "/" + editId, {
            method: "put",
            headers: {
                "Content-Type":"application/json"
            },
            body: JSON.stringify({
                title: editTitle,
                body: editBody
            })
        })

        //refetch todos
        getTodos()

        //reset form
        editTitle = ""
        editBody = ""
        editId = 0
    }

    const editSelect = (todo) => {
        editTitle = todo.title
        editBody = todo.body
        editId = todo.id
    }



</script>




<main>

<h1>The Todo App</h1>
<hr>
<h2>Create a Todo</h2>
    <form on:submit={createTodo}>
        <input type="text" bind:value={createTitle}/>
        <input type="text" bind:value={createBody}/>
        <input type="submit" value="Create Todo"/>
    </form>
<hr>
<h2>Edit a Todo</h2>
    <form on:submit={updateTodo}>
        <input type="text" bind:value={editTitle}/>
        <input type="text" bind:value={editBody}/>
        <input type="submit" value="Update Todo"/>
    </form>
<hr>
<h2>The Todos</h2>
{#each todos as todo}
    <div>
        <h2>{todo.title}</h2>
        <h3>{todo.body}</h3>
        <button on:click={(e) => editSelect(todo)}>Edit</button>
    </div>
{/each}

</main>




<style>
</style>

Enter fullscreen mode Exit fullscreen mode

Delete a Todo

Now we just need to make a delete method and connect it to a delete button with an inline function and we're done!


<script>
    import {onMount} from 'svelte'

    //Variable to hold todos
    let todos = []

    //base URL
    const baseURL = "http://localhost:3000/todos"

    //Method to pull data
    const getTodos = async () => {
        const response = await fetch(baseURL)
        const data = await response.json()
        todos = await data
    }

    //Runs when component loads
    onMount(()=>{
        getTodos()
    })

    //properties for create form
    let createTitle;
    let createBody;

    //create function for form submission
    const createTodo = async (event) => {
        event.preventDefault()
        await fetch(baseURL, {
            method: "post",
            headers: {
                "Content-Type":"application/json"
            },
            body: JSON.stringify({
                title: createTitle,
                body: createBody
            })
        })

        //refetch todos
        getTodos()

        //reset form
        createTitle = ""
        createBody = ""
    }

    //properties for edit form
    let editTitle;
    let editBody;
    let editId

    //create function for form submission
    const updateTodo = async (event) => {
        event.preventDefault()
        await fetch(baseURL + "/" + editId, {
            method: "put",
            headers: {
                "Content-Type":"application/json"
            },
            body: JSON.stringify({
                title: editTitle,
                body: editBody
            })
        })

        //refetch todos
        getTodos()

        //reset form
        editTitle = ""
        editBody = ""
        editId = 0
    }

    const editSelect = (todo) => {
        editTitle = todo.title
        editBody = todo.body
        editId = todo.id
    }

        const deleteTodo = async (todo) => {
        event.preventDefault()
        await fetch(baseURL + "/" + todo.id, {
            method: "delete",
        })

        //refetch todos
        getTodos()
    }



</script>




<main>

<h1>The Todo App</h1>
<hr>
<h2>Create a Todo</h2>
    <form on:submit={createTodo}>
        <input type="text" bind:value={createTitle}/>
        <input type="text" bind:value={createBody}/>
        <input type="submit" value="Create Todo"/>
    </form>
<hr>
<h2>Edit a Todo</h2>
    <form on:submit={updateTodo}>
        <input type="text" bind:value={editTitle}/>
        <input type="text" bind:value={editBody}/>
        <input type="submit" value="Update Todo"/>
    </form>
<hr>
<h2>The Todos</h2>
{#each todos as todo}
    <div>
        <h2>{todo.title}</h2>
        <h3>{todo.body}</h3>
        <button on:click={(e) => editSelect(todo)}>Edit</button>
        <button on:click={(e) => deleteTodo(todo)}>Delete</button>
    </div>
{/each}

</main>




<style>
</style>

Enter fullscreen mode Exit fullscreen mode

jQuery Frontend

So let's do something a little different, how about jQuery using webpack!

Setup

  • Make sure your todo api is running and navigate to your todo folder in terminal

  • run the command npx merced-spinup jquerywebpack todo_jquery_frontend

  • cd into todo_jquery_frontend folder and run npm install

  • npm run dev to start dev server

Displaying Todos

  • create a function to pull todos from api

  • create a function to take those todos and render lis

  • call that latter function

src/index.js

import $ from "jquery"
import _ from "lodash"

//Adding the initial HTML to the body
$("body").append(`
<h1>The Todo App</h1>
<hr>
<h2>The Todos</h2>
<ul id="todolist">

</ul>
`)

//The UL for the Todo List
const $todoList = $("#todolist")

const baseURL = "http://localhost:3000/todos"

//function to get todos
const fetchTodos = async () => {
  const response = await fetch(baseURL)
  const data = await response.json()
  //return promise of data
  return data
}

//render todos to DOM
const renderTodos = async () => {
  const todos = await fetchTodos()

  todos.forEach(todo => {
    const $li = $("<li>")

    $li.html(`
        <h3>${todo.title}</h3>
        <h4>${todo.body}</h4>
        `)

    $todoList.append($li)
  })
}

// Initial Fetch of Todos
renderTodos()
Enter fullscreen mode Exit fullscreen mode

Creating a Todo

  • Create a Form and Variables to hold form and inputs
  • Create a Function for when the form is submitted
import $ from "jquery";
import _ from "lodash";

//Adding the initial HTML to the body
$("body").append(`
<h1>The Todo App</h1>
<hr>
<h2>Create a Todo</h2>
<form id="createForm">
<input type="text" name="createTitle"/>
<input type="text" name="createBody"/>
<input type="submit" value="Create Todo">
</form>
<hr>
<h2>The Todos</h2>
<ul id="todolist">

</ul>
`);

//The UL for the Todo List
const $todoList = $("#todolist");

//Create Form Variables
const $createForm = $("#createForm");
const $createTitle = $('input[name="createTitle');
const $createBody = $('input[name="createBody');
const baseURL = "http://localhost:3000/todos";

//function to get todos
const fetchTodos = async () => {
  const response = await fetch(baseURL);
  const data = await response.json();
  //return promise of data
  return data;
};

//render todos to DOM
const renderTodos = async () => {
  const todos = await fetchTodos();
  $todoList.empty();

  todos.forEach((todo) => {
    const $li = $("<li>");

    $li.html(`
        <h3>${todo.title}</h3>
        <h4>${todo.body}</h4>
        `);

    $todoList.append($li);
  });
};

//Function to Create a to do
const createTodo = async (event) => {
  event.preventDefault();
  await fetch(baseURL, {
    method: "post",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      title: $createTitle.val(),
      body: $createBody.val(),
    }),
  });
  renderTodos();
  $createTitle.val("");
  $createBody.val("");
};

//Add Event Listener to Form
$createForm.on("submit", createTodo);

// Initial Fetch of Todos
renderTodos();
Enter fullscreen mode Exit fullscreen mode

Update a Todo

  • add an edit button
  • add an edit form
  • create variables for form and input
  • create function to set variables for selected todo
  • create function to make post request when form submitted.

import $ from "jquery";
import _ from "lodash";

//Adding the initial HTML to the body
$("body").append(`
<h1>The Todo App</h1>
<hr>
<h2>Create a Todo</h2>
<form id="createForm">
<input type="text" name="createTitle"/>
<input type="text" name="createBody"/>
<input type="submit" value="Create Todo">
</form>
<hr>
<form id="editForm">
<input type="text" name="editTitle"/>
<input type="text" name="editBody"/>
<input type="submit" value="Update Todo">
</form>
<hr>
<h2>The Todos</h2>
<ul id="todolist">

</ul>
`);

//The UL for the Todo List
const $todoList = $("#todolist");

//Create Form Variables
const $createForm = $("#createForm");
const $createTitle = $('input[name="createTitle"]');
const $createBody = $('input[name="createBody"]');

//Create Form Variables
const $editForm = $("#editForm");
const $editTitle = $('input[name="editTitle"]');
const $editBody = $('input[name="editBody"]');
let editId = 0

//API URL
const baseURL = "http://localhost:3000/todos";

//function to get todos
const fetchTodos = async () => {
  const response = await fetch(baseURL);
  const data = await response.json();
  //return promise of data
  return data;
};

//render todos to DOM
const renderTodos = async () => {
  const todos = await fetchTodos();
  $todoList.empty();

  todos.forEach((todo) => {
    const $li = $("<li>");

    $li.html(`
        <h3>${todo.title}</h3>
        <h4>${todo.body}</h4>
        <button id="${todo.id}editbutton">Edit</button>
        `);

    $todoList.append($li);

    $(`#${todo.id}editbutton`).on('click', () => {
        $editTitle.val(todo.title)
        $editBody.val(todo.body)
        editId = todo.id
    })
  });
};

//Function to Create a to do
const createTodo = async (event) => {
  event.preventDefault();
  await fetch(baseURL, {
    method: "post",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      title: $createTitle.val(),
      body: $createBody.val(),
    }),
  });
  renderTodos();
  $createTitle.val("");
  $createBody.val("");
};

//Function to update a to do
const updateTodo = async (event) => {
    event.preventDefault();
    await fetch(baseURL + "/" + editId, {
      method: "put",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        title: $editTitle.val(),
        body: $editBody.val(),
      }),
    });
    renderTodos();
    $editTitle.val("");
    $editBody.val("");
  };

//Add Event Listener to Form
$createForm.on("submit", createTodo);

//Add Event Listener to Form
$editForm.on("submit", updateTodo);

// Initial Fetch of Todos
renderTodos();11

Enter fullscreen mode Exit fullscreen mode

Delete Todos

Finally... our last function in our last build. We just need to create a delete button that triggers a delete function and we're done! We'll define the function when we add the listener in the renderTodos function so the todo is in scope.

import $ from "jquery";
import _ from "lodash";

//Adding the initial HTML to the body
$("body").append(`
<h1>The Todo App</h1>
<hr>
<h2>Create a Todo</h2>
<form id="createForm">
<input type="text" name="createTitle"/>
<input type="text" name="createBody"/>
<input type="submit" value="Create Todo">
</form>
<hr>
<form id="editForm">
<input type="text" name="editTitle"/>
<input type="text" name="editBody"/>
<input type="submit" value="Update Todo">
</form>
<hr>
<h2>The Todos</h2>
<ul id="todolist">

</ul>
`);

//The UL for the Todo List
const $todoList = $("#todolist");

//Create Form Variables
const $createForm = $("#createForm");
const $createTitle = $('input[name="createTitle"]');
const $createBody = $('input[name="createBody"]');

//Create Form Variables
const $editForm = $("#editForm");
const $editTitle = $('input[name="editTitle"]');
const $editBody = $('input[name="editBody"]');
let editId = 0

//API URL
const baseURL = "http://localhost:3000/todos";

//function to get todos
const fetchTodos = async () => {
  const response = await fetch(baseURL);
  const data = await response.json();
  //return promise of data
  return data;
};

//render todos to DOM
const renderTodos = async () => {
  const todos = await fetchTodos();
  $todoList.empty();

  todos.forEach((todo) => {
    const $li = $("<li>");

    $li.html(`
        <h3>${todo.title}</h3>
        <h4>${todo.body}</h4>
        <button id="${todo.id}editbutton">Edit</button>
        <button id="${todo.id}deletebutton">Delete</button>
        `);

    $todoList.append($li);

    //add function to edit button
    $(`#${todo.id}editbutton`).on('click', () => {
        $editTitle.val(todo.title)
        $editBody.val(todo.body)
        editId = todo.id
    })

    //add function to delete button
    $(`#${todo.id}deletebutton`).on('click', async () => {
        await fetch(baseURL + "/" + todo.id, {
            method: "delete"
        })
        renderTodos()
    })
  });
};

//Function to Create a to do
const createTodo = async (event) => {
  event.preventDefault();
  await fetch(baseURL, {
    method: "post",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      title: $createTitle.val(),
      body: $createBody.val(),
    }),
  });
  renderTodos();
  $createTitle.val("");
  $createBody.val("");
};

//Function to update a to do
const updateTodo = async (event) => {
    event.preventDefault();
    await fetch(baseURL + "/" + editId, {
      method: "put",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        title: $editTitle.val(),
        body: $editBody.val(),
      }),
    });
    renderTodos();
    $editTitle.val("");
    $editBody.val("");
  };

//Add Event Listener to Form
$createForm.on("submit", createTodo);

//Add Event Listener to Form
$editForm.on("submit", updateTodo);

// Initial Fetch of Todos
renderTodos();
Enter fullscreen mode Exit fullscreen mode

Congrats

You've just taken a single api and built 5 seperate frontend applications! Hopefully this gives you a deeper appreciation for the different way to build out your frontend applications and the modularity of APIs.


Top comments (5)

Collapse
 
k776 profile image
Kieran Pilkington

This is nice comparison of frontend frameworks. Of course, I realise that there are many aspects not covered here, each framework is powerful for a certain purpose. For the above described example (simple todo app), I think my preference would be Svelte.

To me, React's use of useState and JSX makes things more complicated than things need to be. Angular's very specific function name and html tag attr names means I'll constantly be looking up the reference guide. I've heard good things about Vue, and it would probably my first choice, but the need for a specific export structure was enough to tip it into second place.

I think Svelte with its regular HTML, no specific state obj (all variables hold state), simple function use (on:submit and on:click), and easy to understand bind system takes the top spot.

One thing not clear, does Svelte know which parts of the DOM need updating like the other frameworks? e.g. if you add a todo at the end of the array, or update one, does it re-render the entire html tree, just the each loop contents, or just append/replace a single list item? Frameworks like React use the shadow dom to calculate changes, and Vue use "v-bind:key" to help the renderer update only those elements. My guess is Svelte would be slower here?

Collapse
 
alexmercedcoder profile image
Alex Merced • Edited

I don't think Svelte has VirtualDOM but it is Reactive, so the bundle size is WAAAAY smaller but wouldn't it be as performant in larger applications, not sure.

Collapse
 
staycreativedesign profile image
Gus P

this clearly shows svelte as the winner honestly. React is too much crazy with all that needs typing.

Collapse
 
alexandrusimandi profile image
Alexandru Simandi

I think sharpening is a wrongly picked word. Imagine saying in an interview that you sharpen your skill by making hello world in all frameworks.

Collapse
 
alexmercedcoder profile image
Alex Merced

Agreed, that wouldn't be the way I'd frame it. But don't exercises like this have really helped me understand the difference between what javascript brings to the table vs the framework and a deeper appreciation for approaches to creating my own abstractions.