Categories
Vue

Build a Drag and Drop App with Vue.js

Drag and drop is a feature of many interactive web apps. It provides an intuitive way for users to manipulate their data. Adding drag and drop feature is easy to add to Vue.js apps.

The App We Are Building

We will create a todo app that has 2 columns — a To Do column and a Done column. You can drag and drop between the two to change the status from to do to done and vice versa. To build the app, we use Vue Material library with the Vue Draggable package to make the app look good and provide the drag and drop ability easily. It will also have a navigation menu and a top bar.

Getting Started

To begin building the app, we start by installing Vue CLI. We install it by running npm i -g @vue/cli. After that, we can create the project. To do this, we run vue create todo-app. Instead of choosing the default, we customize the scaffolding of the app by choosing the alternative option and pick Vue Router, Babel, and CSS preprocessor. The scaffolding should be finished after following the instructions and then we are ready to add some libraries.

We need to add Axios for making HTTP requests, and the Vue Material library with the Vue Draggable packages we mentioned before to make our app prettier and to provide the drag and drop capabilities we desire respectively. In addition, we need the Vee Validate package to let us do form validation in our app. To install these packages, we run npm i axios vuedraggable vue-material vee-validate@2.2.14 .

Building the App

Now we can write some code. To start, we add a mixin to let us make our requests. To do this, we create a mixins folder and add a file called todoMixin.js, and we then put the following into our file:

const axios = require('axios');
const apiUrl = 'http://localhost:3000';

export const todoMixin = {
 methods: {
 getTodos() {
 return axios.get(`${apiUrl}/todos`);
 },

 addTodo(data) {
 return axios.post(`${apiUrl}/todos`, data);
 },

 editTodo(data) {
 return axios.put(`${apiUrl}/todos/${data.id}`, data);
 },

 deleteTodo(id) {
 return axios.delete(`${apiUrl}/todos/${id}`);
 }
 }
}

These functions will be used in our home page to make HTTP requests to do CRUD on our todo list. We will create the home page now. In Home.vue, we replace what we have with the following:

<template>
  <div class="home">
    <div class="center">
      <h1>To Do List</h1>
      <md-button class="md-raised" @click="showDialog = true"
        >Add Todo</md-button
      >
    </div>

    <div class="content">
      <md-dialog :md-active.sync="showDialog">
        <md-dialog-title>Add Todo</md-dialog-title>
        <form @submit="addNewTodo" novalidate>
          <md-field :class="{ 'md-invalid': errors.has('description') }">
            <label for="description">Description</label>
            <md-input
              type="text"
              name="description"
              v-model="taskData.description"
              v-validate="'required'"
            ></md-input>
            <span class="md-error" v-if="errors.has('description')">{{
              errors.first("description")
            }}</span>
          </md-field>

          <md-dialog-actions>
            <md-button class="md-primary" @click="showDialog = false"
              >Close</md-button
            >
            <md-button class="md-primary" @click="showDialog = false"
              >Save</md-button
            >
          </md-dialog-actions>
        </form>
      </md-dialog>
      <div class="lists">
        <div class="left">
          <h2>To Do</h2>
          <draggable v-model="todo" group="tasks" @change="updateTodo">
            <div v-for="t in todo" :key="t.id" class="item">
              {{ t.description }}
              <a @click="deleteTask(t.id)">
                <md-icon>close</md-icon>
              </a>
            </div>
          </draggable>
        </div>
        <div class="right">
          <h2>Done</h2>
          <draggable v-model="done" group="tasks" @change="updateTodo">
            <div v-for="d in done" :key="d.id" class="item">
              {{ d.description }}
              <a @click="deleteTask(d.id)">
                <md-icon>close</md-icon>
              </a>
            </div>
          </draggable>
        </div>
      </div>
    </div>
  </div>
</template>


<script>
// @ is an alias to /src
import draggable from "vuedraggable";
import { todoMixin } from "@/mixins/todoMixin";

export default {
  name: "home",
  components: {
    draggable,
  },
  computed: {
    isFormDirty() {
      return Object.keys(this.fields).some((key) => this.fields[key].dirty);
    },
  },
  mixins: [todoMixin],
  data() {
    return {
      todo: [],
      done: [],
      showDialog: false,
      taskData: {},
    };
  },
  beforeMount() {
    this.getNewTodos();
  },
  methods: {
    async addNewTodo(evt) {
      evt.preventDefault();
      if (!this.isFormDirty || this.errors.items.length > 0) {
        return;
      }
      await this.addTodo(this.taskData);
      this.showDialog = false;
      this.getNewTodos();
    },

    async getNewTodos() {
      const response = await this.getTodos();
      this.todo = response.data.filter((t) => !t.done);
      this.done = response.data.filter((t) => t.done);
    },

    async updateTodo(evt) {
      let todo = evt.removed && evt.removed.element;
      if (todo) {
        todo.done = !todo.done;
        await this.editTodo(todo);
      }
    },

    async deleteTask(id) {
      const todo = await this.deleteTodo(id);
      this.getNewTodos();
    },
  },
};
</script>

<style lang="scss" scoped>
.center {
  text-align: center;
}

.md-dialog {
  width: 70vw;
}

form {
  width: 92%;
}

.md-dialog-title.md-title {
  color: black !important;
}

.lists {
  padding-left: 5vw;
  display: flex;
  align-items: flex-start;
  .left,
  .right {
    width: 45vw;
    padding: 20px;
    min-height: 200px;
    .item {
      padding: 10px;
      border: 1px solid black;
      background-color: white;
      display: flex;
      justify-content: space-between;
      a {
        cursor: pointer;
      }
    }
  }
}
</style>

We added a dialog box with a form to enter the description for our task. The field is required but the user can enter anything. The addTodo function takes the data entered and submit it if it’s valid. The this.fields function is provided by the Vee Validate package and has all the fields in the object so we can check if the fields have been changed or not. Anything in the computed property is computer whenever anything returned by the function changes.

Next, we added 2 lists that we drag between to let us change the status of the tasks to done or not done. The lists are the draggable components on the template. We defined the models in the draggable components in the object returned by the data function. It is important that we have the same group prop so that we can drag between them the 2 draggable components. Whenever drag and drop happens, the change event is raised and the updateTodo function is called. The function will toggle the done flag of the task and make a request to save the task.

Each task also has a button to delete it. When the close button is clicked, the deleteTodo function is called. The id of the task is passed into the function, so we can make the request to delete the task.

Next in App.vue, we add the menu and a left-side navigation bar with the following code:

<template>
  <div id="app">
    <md-toolbar>
      <md-button class="md-icon-button" @click="showNavigation = true">
        <md-icon>menu</md-icon>
      </md-button>
      <h3 class="md-title">Todo App</h3>
    </md-toolbar>
    <md-drawer :md-active.sync="showNavigation" md-swipeable>
      <md-toolbar class="md-transparent" md-elevation="0">
        <span class="md-title">Todo App</span>
      </md-toolbar>

      <md-list>
        <md-list-item>
          <router-link to="/">
            <span class="md-list-item-text">Home</span>
          </router-link>
        </md-list-item>
      </md-list>
    </md-drawer>

    <router-view />
  </div>
</template>

<script>
export default {
  name: "app",
  data: () => {
    return {
      showNavigation: false,
    };
  },
};
</script>

<style>
.center {
  text-align: center;
}

form {
  width: 95vw;
  margin: 0 auto;
}

.md-toolbar.md-theme-default {
  background: #009688 !important;
  height: 60px;
}

.md-title,
.md-toolbar.md-theme-default .md-icon {
  color: #fff !important;
}
</style>

The <router-view /> displays the routes that we will define in router.js, which only consists of the home page.

In main.js,we put:

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import VueMaterial from 'vue-material';
import VeeValidate from 'vee-validate';
import 'vue-material/dist/vue-material.min.css'
import 'vue-material/dist/theme/default.css'

Vue.config.productionTip = false;

Vue.use(VueMaterial);
Vue.use(VeeValidate);

new Vue({
 router,
 store,
 render: h => h(App)
}).$mount('#app')

to include the Vue.js add-on libraries that we use in this app.

And in router.js, we add:

import Vue from "vue";
import Router from "vue-router";
import Home from "./views/Home.vue";

Vue.use(Router);

export default new Router({
  mode: "history",
  base: process.env.BASE_URL,
  routes: [
    {
      path: "/",
      name: "home",
      component: Home
    }
  ]
});

This adds the home page to our list of routes so that it will be displayed to the user when typing in the URL or clicking a link to the page.

Our JSON API will be added without writing any code by using the JSON Server Node.js package, located at https://github.com/typicode/json-server. Data will be saved to a JSON file, so we don’t have to make our own back end add to save some simple data. We install the server by running npm i -g json-server. Then once that is done, go into our project directory then run json-server --watch db.json. In db.json, we put:

{
 "todos": []
}

so that we can use those endpoints for saving data to db.json, which have the same URLs as in todoMixin.

After all the work is done we have the following:

Categories
React Projects

Create a To-Do List App with React and JavaScript

React is an easy to use JavaScript framework that lets us create front end apps.

In this article, we’ll look at how to create a to-do list app with React and JavaScript.

Create the Project

We can create the React project with Create React App.

To install it, we run:

npx create-react-app todo-list

with NPM to create our React project.

We need the uuid to let us create unique IDs for our to-do items easily.

To add it, we run:

npm i uuidv4

Create the To-Do App

To create the to-do list app, we write:

import { useState } from "react";
import { v4 as uuidv4 } from "uuid";

export default function App() {
  const [todo, setTodo] = useState("");
  const [todos, setTodos] = useState([]);

  const submit = (e) => {
    e.preventDefault();
    setTodos((todos) => [
      ...todos,
      {
        id: uuidv4(),
        todo
      }
    ]);
  };

  const deleteTodo = (index) => {
    setTodos((todos) => todos.filter((_, i) => i !== index));
  };

  return (
    <div className="App">
      <form onSubmit={submit}>
        <input value={todo} onChange={(e) => setTodo(e.target.value)} />
        <button type="submit">Add</button>
      </form>
      {todos.map((t, i) => {
        return (
          <div key={t.id}>
            {t.todo}
            <button onClick={() => deleteTodo(i)}>delete</button>
          </div>
        );
      })}
    </div>
  );
}

We have the todo state which is bound to the input box.

The todos state has an array of to-do items.

The submit method is where we add the to-do items to the todos array.

We call e.preventDefault() to stop server-side form submission.

Then we call setTodos with a callback to get the todos and return an array with the original todos items.

After that, we append the new item to it.

It has an id created from the uuidv4 function.

todo has the to-do item text.

The deleteTodo function is called when we click the delete button.

In it, we call the setTodos method with a callback that takes the original todos array.

Then we return an array without the entry with the given index .

We filter that entry out with the filter method.

Then in the return statement, we have a form that has the input element with the value and onChange props to get the input value and set it with the onChange callback respectively.

onSubmit has the submit function as its value. submit is called when we click the Add button.

Below that, we have todos.map callback with a callback that returns a div with the t.todo rendered and a button to delete the item when we click it.

The key prop is set to t.id which is the unique ID.

This helps React identify the rendered array items properly.

Conclusion

We can create a to-do list app easily with React and JavaScript.

Categories
React Projects

Create a Simple Calculator with React and JavaScript

React is an easy to use JavaScript framework that lets us create front end apps.

In this article, we’ll look at how to create a simple calculator with React and JavaScript.

Create the Project

We can create the React project with Create React App.

To install it, we run:

npx create-react-app simple-calculator

with NPM to create our React project.

Also, we’ve to install the mathjs library so that we can evaluate math expressions in strings easily.

To install it, we run:

npm i mathjs

Create the Calculator App

To create the calculator app, we write:

import { useState } from "react";
import { evaluate } from "mathjs";

export default function App() {
  const [expression, setExpression] = useState("");

  const submit = (e) => {
    e.preventDefault();
    setExpression((ex) => evaluate(ex));
  };

  return (
    <div className="App">
      <style>
        {`button {
          width: 20px
        }`}
      </style>
      <div>{expression}</div>
      <form onSubmit={submit}>
        <div>
          <button type="button" onClick={() => setExpression((ex) => `${ex}+`)}>
            +
          </button>
          <button type="button" onClick={() => setExpression((ex) => `${ex}-`)}>
            -
          </button>
          <button type="button" onClick={() => setExpression((ex) => `${ex}*`)}>
            *
          </button>
          <button type="button" onClick={() => setExpression((ex) => `${ex}/`)}>
            /
          </button>
        </div>
        <div>
          <button type="button" onClick={() => setExpression((ex) => `${ex}.`)}>
            .
          </button>
          <button type="button" onClick={() => setExpression((ex) => `${ex}9`)}>
            9
          </button>
          <button type="button" onClick={() => setExpression((ex) => `${ex}8`)}>
            8
          </button>
          <button type="button" onClick={() => setExpression((ex) => `${ex}7`)}>
            7
          </button>
        </div>
        <div>
          <button type="button" onClick={() => setExpression((ex) => `${ex}6`)}>
            6
          </button>
          <button type="button" onClick={() => setExpression((ex) => `${ex}5`)}>
            5
          </button>
          <button type="button" onClick={() => setExpression((ex) => `${ex}4`)}>
            4
          </button>
          <button type="button" onClick={() => setExpression((ex) => `${ex}3`)}>
            3
          </button>
        </div>
        <div>
          <button type="button" onClick={() => setExpression((ex) => `${ex}2`)}>
            2
          </button>
          <button type="button" onClick={() => setExpression((ex) => `${ex}1`)}>
            1
          </button>
          <button type="button" onClick={() => setExpression((ex) => `${ex}0`)}>
            0
          </button>
          <button type="submit">=</button>
        </div>
        <div>
          <button
            type="button"
            style={{ width: 50 }}
            onClick={() => setExpression("")}
          >
            Clear
          </button>
        </div>
      </form>
    </div>
  );
}

We have the expression state which we create with the useState hook.

It’s set to an empty string as its initial value.

Then we return the JSX code with the keypad and the expression display.

We also have a style tag to set button elements to 20px width.

Most buttons except the = button all call setExpression to append a character to expression .

The callback has the ex parameter which is the current value of expression .

We return the new value for expression .

The form element has the onSubmit prop set to the submit function.

In submit , we call e.preventDefault() to prevent server-side form submission.

Then we call setExpression with a callback to return the returned value from mathjs ‘s evaluate method, which returns the evaluated value of the expression string.

The Clear button sets expression back to an empty string when we click it.

Conclusion

We can create a simple calculator easily with React and JavaScript.

Categories
React Projects

Create a Tip Calculator with React and JavaScript

React is an easy to use JavaScript framework that lets us create front end apps.

In this article, we’ll look at how to create a tip calculator with React and JavaScript.

Create the Project

We can create the React project with Create React App.

To install it, we run:

npx create-react-app tip-calculator

with NPM to create our React project.

Create the Tip Calculator App

To create the tip calculator app, we write:

import { useState } from "react";

export default function App() {
  const [subtotal, setSubtotal] = useState(0);
  const [numDiners, setNumDiners] = useState(0);
  const [tipPercentage, setTipPercetnage] = useState(0);
  const [result, setResult] = useState({});

  const submit = (e) => {
    e.preventDefault();
    if (+subtotal <= 0 || +numDiners <= 0 || +tipPercentage <= 0) {
      return;
    }
    const total = +subtotal * (1 + +tipPercentage);
    setResult({ total, totalPerDiner: +total / +numDiners });
  };

  return (
    <div className="App">
      <form onSubmit={submit}>
        <fieldset>
          <label>subtotal</label>
          <input
            value={subtotal}
            onChange={(e) => setSubtotal(e.target.value)}
          />
        </fieldset>

        <fieldset>
          <label>number of people sharing the bill</label>
          <input
            value={numDiners}
            onChange={(e) => setNumDiners(e.target.value)}
          />
        </fieldset>

        <fieldset>
          <label>tip percentage</label>
          <select
            value={tipPercentage}
            onChange={(e) => setTipPercetnage(e.target.value)}
          >
            <option value="0">0%</option>
            <option value="0.05">5%</option>
            <option value="0.1">10%</option>
            <option value="0.15">15%</option>
            <option value="0.2">20%</option>
          </select>
        </fieldset>

        <button type="submit">Calculate</button>
      </form>
      <p>total: {result.total && result.total.toFixed(2)}</p>
      <p>
        total per diner:{" "}
        {result.totalPerDiner && result.totalPerDiner.toFixed(2)}
      </p>
    </div>
  );
}

We have the subtotal , numDiners , tipPercentage , and result states.

subtotal is the subtotal.

numDiners is the number of diners.

tipPercentage has the tip percentage between 0 and 100.

In the submit function, we call e.preventDefault() to prevent server-side form submission.

Then we check if the values are valid with the if block.

Then we compute the total and the result object and call setResult to set it.

In the return statement, we have the input fields with the value and onChange properties to get and set the values respectively.

We get the value from e.target.value .

The form has the onSubmit prop to set the submit event listener.

Below that, we have the result data displayed.

Conclusion

We can create a tip calculator with React and JavaScript.

Categories
React Projects

Create an Image Modal with React and JavaScript

React is an easy to use JavaScript framework that lets us create front end apps.

In this article, we’ll look at how to create an image modal with React and JavaScript.

Create the Project

We can create the React project with Create React App.

To install it, we run:

npx create-react-app image-modal

with NPM to create our React project.

Create the Image Modal App

To create the image modal app, we write:

import { useState, useEffect } from "react";

const images = [
  "https://images.dog.ceo/breeds/pomeranian/n02112018_5091.jpg",
  "https://images.dog.ceo/breeds/mountain-swiss/n02107574_753.jpg",
  "https://images.dog.ceo/breeds/leonberg/n02111129_3028.jpg"
];

export default function App() {
  const [index, setIndex] = useState(0);
  const [displayModal, setDisplayModal] = useState(false);
  const next = () => {
    setIndex((i) => (i + 1) % images.length);
  };
  const prev = () => {
    setIndex(
      (i) => (((i - 1) % images.length) + images.length) % images.length
    );
  };
  const onClickOutside = (e) => {
    if (e.target.localName !== "button") {
      setDisplayModal(false);
    }
  };

  useEffect(() => {
    window.addEventListener("click", onClickOutside);
    return () => window.removeEventListener("click", onClickOutside);
  }, []);

  return (
    <div className="App">
      <button onClick={() => setDisplayModal(true)}>open modal</button>
      {displayModal && (
        <div
          style={{
            position: "absolute",
            top: 20,
            left: 20,
            border: "1px solid gray",
            backgroundColor: "white"
          }}
        >
          <button onClick={prev}>&lt;</button>
          <img src={images[index]} alt="" style={{ width: 200, height: 200 }} />
          <button onClick={next}>&gt;</button>
        </div>
      )}
    </div>
  );
}

We have the images array with URLs of images.

The index state is created with the useState hook to let us get an images entry by its index.

The displayModal state lets us control whether to display the modal or not.

The next and prev methods cycles through the images indexes to rotate the images.

We pass in a callback to the setIndex function to return the latest value based on the current value of index , which is stored in the i parameter.

onClickOutside checks if we clicked on a button.

If we didn’t, then we call setDisplayModal to false to close the modal.

The useEffect callback adds the onClickOutside to listen to the click event of the window .

It returns a callback to let us remove the click listener with removeEventListener when we unmount it.

The empty array in the 2nd argument lets us run the useEffect callback only when we mount the App component.

In the return statement, we have a button that sets the displayModal to true when we click it.

Below that, we have the modal which is displayed with displayModal is true .

We set the modal div’s position property to absolute so that we can display it above other content.

top and left sets the position.

border adds a border.

And backgroundColor sets the background color.

Inside it, we have buttons that call prev and next when we click on them to cycle through the images.

And the img element displays the image.

Conclusion

We can add an image modal with React and JavaScript.