Create To-do list app using Ethereum Blockchain-Part-2

Hello Amigos !!
In Part-1 we set up the project and created smart contracts. And also deployed on ethereum blockchain using metamask. You may refer to code on Github. In this part, we will see, how we can list out the tasks in the to-do list and create a test for listing tasks.

Step 1: Model a task in solidity

In order to list the tasks inside the smart contract, we’ll need a way to model a task in solidity. Solidity allows you to define your own data types with structs. We’ll use a struct to model the task for our to-do list like this.

pragma solidity ^0.5.0;
 
contract TodoList {
  uint public taskCount = 0;
 
  struct Task {
    uint id;
    string content;
    bool completed;
  }
}

Step 2: Tasks in the to-do list

Now that we’ve modeled a task, we need a place to put all of the tasks on the todo list! We want to put them in the storage on the blockchain so that the state of the smart contract will be persistent. We can access the blockchain’s storage with a state variable, just like we did with task count. We’ll create a task state variable. It will use a special kind of Solidity data structure called a mapping like this.

pragma solidity ^0.5.0;
 
contract TodoList {
  uint public taskCount = 0;
  struct Task {
    uint id;
    string content;
    bool completed;
  }
  mapping(uint => Task) public tasks;
}

Create a function for creating tasks

Now let’s create a function for creating tasks and initialize from the constructor. This function will get run only once, whenever the contract is initialized, i.e., deployed to the blockchain. Inside of this function, we have created one new default task with the string content “initialize create task todo list”.

pragma solidity ^0.5.0;
 
contract TodoList {
  uint public taskCount = 0;
  constructor() public {
        createTask("initialise create task todo list");
      }
  struct Task {
    uint id;
    string content;
    bool completed;
  }
  mapping(uint => Task) public tasks;
  function createTask(string memory _content) public {
    taskCount ++;
    tasks[taskCount] = Task(taskCount, _content, false);
  }
}

Now let’s deploy this smart contract to the blockchain. In order to do this, we must deploy a new copy of our code.

$ truffle migrate --reset

Now we have a new copy of the smart contract on the blockchain. Now let’s list out the tasks in the console.

$ truffle console

Inside the console, let’s get a deployed copy of the new smart contract.

todoList = await TodoList.deployed()

Now we can get the task from the todo list by calling the tasks() function. This will allow us to access values from the task’s mapping by id. We will simply pass in the id of the first task on the list when we call this function.

task = await todoList.tasks(1)

Now that we’ve migrated this smart contract to the blockchain, In the next step, we will create the client-side code to interact with the todo list smart contract. You’ll need to create the following files for your project.

  • bs-config.json
  • src/index.html
  • src/app.js

We are using lite-server to serve all of the project files for the client-side. We’ll need to tell lite-server where all these files are located. so update the browser-sync configuration for lite-server inside the bs-config.json file. Paste this configuration into your project file.

{
  "server": {
    "baseDir": [
      "./src",
      "./build/contracts"
    ],
    "routes": {
      "/vendor": "./node_modules"
    }
  }
}

Now we will add some HTML code to display our todolist.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
    <title>Todo List</title>
 
    <!-- Bootstrap -->
    <link href="vendor/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
 
    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
 
    <style>
      main {
        margin-top: 60px;
      }
 
      #content {
        display: none;
      }
 
      form {
        width: 350px;
        margin-bottom: 10px;
      }
 
      ul {
        margin-bottom: 0px;
      }
 
      #completedTaskList .content {
        color: grey;
        text-decoration: line-through;
      }
    </style>
  </head>
  <body>
    <nav class="navbar navbar-dark fixed-top bg-dark flex-md-nowrap p-0 shadow">
      <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://www.dappuniversity.com/free-download" target="_blank">Todo List</a>
      <ul class="navbar-nav px-3">
        <li class="nav-item text-nowrap d-none d-sm-none d-sm-block">
          <small><a class="nav-link" href="#"><span id="account"></span></a></small>
        </li>
      </ul>
    </nav>
    <div class="container-fluid">
      <div class="row">
        <main role="main" class="col-lg-12 d-flex justify-content-center">
          <div id="loader" class="text-center">
            <p class="text-center">Loading...</p>
          </div>
          <div id="content">
         <!-- <form onSubmit="App.createTask(); return false;">
              <input id="newTask" type="text" class="form-control" placeholder="Add task..." required>
              <input type="submit" hidden="">
            </form> -->
            <ul id="taskList" class="list-unstyled">
              <div class="taskTemplate" class="checkbox" style="display: none">
                <label>
                  <input type="checkbox" />
                  <span class="content">Task content goes here...</span>
                </label>
              </div>
            </ul>
            <ul id="completedTaskList" class="list-unstyled">
            </ul>
          </div>
        </main>
      </div>
    </div>
    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script src="vendor/bootstrap/dist/js/bootstrap.min.js"></script>
    <script src="vendor/truffle-contract/dist/truffle-contract.js"></script>
    <script src="app.js"></script>
  </body>
</html>

Now let’s add some of the JavaScript code for this section. We’ll add code to the newly created app.js file like this

App = {
  loading: false,
  contracts: {},
  load: async () => {
    await App.loadWeb3()
    await App.loadAccount()
    await App.loadContract()
    await App.render()
  },
  loadWeb3: async () => {
    if (typeof web3 !== 'undefined') {
      App.web3Provider = web3.currentProvider
      web3 = new Web3(web3.currentProvider)
      console.log(web3.eth.accounts[0])
    } else {
      window.alert("Please connect to Metamask.")
    }
    // Modern dapp browsers...
    if (window.ethereum) {
      window.web3 = new Web3(ethereum)
      try {
        // Request account access if needed
        await ethereum.enable()
        // Acccounts now exposed
        web3.eth.sendTransaction({/* ... */})
      } catch (error) {
        // User denied account access...
      }
    }
    // Legacy dapp browsers...
    else if (window.web3) {
      App.web3Provider = web3.currentProvider
      window.web3 = new Web3(web3.currentProvider)
      // Acccounts always exposed
      web3.eth.sendTransaction({/* ... */})
    }
    // Non-dapp browsers...
    else {
      console.log('Non-Ethereum browser detected. You should consider trying MetaMask!')
    }
  },
  loadAccount: async () => {
    // Set the current blockchain account
    App.account = web3.eth.accounts[0]
  },
  loadContract: async () => {
    // Create a JavaScript version of the smart contract
    const todoList = await $.getJSON('TodoList.json')
    App.contracts.TodoList = TruffleContract(todoList)
    App.contracts.TodoList.setProvider(App.web3Provider)
 
    // Hydrate the smart contract with values from the blockchain
    App.todoList = await App.contracts.TodoList.deployed()
  },
 render: async () => {
    // Prevent double render
    if (App.loading) {
      return
    }
   // Update app loading state
    App.setLoading(true)
 
    // Render Account
    $('#account').html(App.account)
 
    // Render Tasks
    await App.renderTasks()
 
    // Update loading state
    App.setLoading(false)
  },
renderTasks: async () => {
    // Load the total task count from the blockchain
    const taskCount = await App.todoList.taskCount()
    const $taskTemplate = $('.taskTemplate')
 
    // Render out each task with a new task template
    for (var i = 1; i <= taskCount; i++) {
      // Fetch the task data from the blockchain
      const task = await App.todoList.tasks(i)
      const taskId = task[0].toNumber()
      const taskContent = task[1]
      const taskCompleted = task[2]
      // Create the html for the task
      const $newTaskTemplate = $taskTemplate.clone()
      $newTaskTemplate.find('.content').html(taskContent)
      $newTaskTemplate.find('input')
                      .prop('name', taskId)
                      .prop('checked', taskCompleted)
                      // .on('click', App.toggleCompleted)
 
      // Put the task in the correct list
      if (taskCompleted) {
        $('#completedTaskList').append($newTaskTemplate)
      } else {
        $('#taskList').append($newTaskTemplate)
      }
      // Show the task
      $newTaskTemplate.show()
    }
  },
setLoading: (boolean) => {
    App.loading = boolean
    const loader = $('#loader')
    const content = $('#content')
    if (boolean) {
      loader.show()
      content.hide()
    } else {
      loader.hide()
      content.show()
    }
  }
}
$(() => {
  $(window).load(() => {
    App.load()
  })
})

Load Method

Let’s see in load method what we are doing .

  • loadWeb3() web3.js is a JavaScript library that allows our client-side application to talk to the blockchain. We configure web3 here. This is the default web3 configuration specified by Metamask. Do not worry if you don’t completely understand what is happening here. This is a copy-and-paste implementation that Metamask suggests.
  • loadContract() This is where we load the smart contract data from the blockchain. We create a JavaScript representation of the smart contract wit the Truffle Contract library. Then we load the smart contract data with web3. This will allow us to list the tasks on the todo list.
  • renderTasks() This is where we actually list the tasks in the todo list. Notice that we create a for loop to access each task individually. That is because we cannot fetch the entire task mapping from the smart contract. We must first determine the task count and fetch each task one-by-one.

Now let’s start the web server and ensure that the project will load in the browser.

$ npm run dev

Note make sure your metamask is set up and connected to the localhost .
Once you’re connected with Metamask, you should see all of the contract and account data loaded.

to-do list

Now Let’s do some basic tests to ensure our smart contract working properly. If any of our contract functions that write to the blockchain contain bugs, the account who is calling this function could potentially waste Ether. So it’s a very important step. Let’s create a test file like this.

$ test/TodoList.test.js

Write all our tests in Javascript

We’ll write all our tests in Javascript inside this file with the Mocha testing framework and the Chai assertion library. These come bundled with the Truffle framework. We’ll write all these tests in Javascript to simulate client-side interaction with our smart contract, much as we did in the console. Here is all the code for the tests.

const TodoList = artifacts.require('./TodoList.sol')
contract('TodoList', (accounts) => {
  before(async () => {
    this.todoList = await TodoList.deployed()
  })
  it('deploys successfully', async () => {
    const address = await this.todoList.address
    assert.notEqual(address, 0x0)
    assert.notEqual(address, '')
    assert.notEqual(address, null)
    assert.notEqual(address, undefined)
  })
  it('lists tasks', async () => {
    const taskCount = await this.todoList.taskCount()
    const task = await this.todoList.tasks(taskCount)
    assert.equal(task.id.toNumber(), taskCount.toNumber())
    assert.equal(task.content, 'Check out TODOList list tasks')
    assert.equal(task.completed, false)
    assert.equal(taskCount.toNumber(), 1)
  })
})

In the above code, the first test checks that the contract was deployed to the blockchain properly by inspecting its address.
The next test checks that the smart contract lists tasks properly by checking the default task that we created in the initializer function.
Now let’s run the tests from the command line like this.

$ truffle test
to-do list

Voila !! You successfully list out the tasks in the to-do list and created a test for listing tasks.
Hope you will like this post and stay tuned with us for the last part of this blog series.

Sharing is Caring

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top