Todo list app using ethereum Blockchain-Part-2

In part-1 we have setup project ,created smart contract and deployed on ethereum blockchain using metamask. You may refer code on github.

In this part we will see ,how we can listing out the tasks in the todo list and create test for listing tasks.

Step-1 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 todo list like this:

pragma solidity ^0.5.0;

contract TodoList {
  uint public taskCount = 0;

  struct Task {
    uint id;
    string content;
    bool completed;
  }
}

Step-2 Now that we’ve modeled a task, we need a place to put all of the tasks in the todo list! We want to put them in storage on the blockchain so that the state of the smart contract will be persistent. We can access the blockchain’s storage with with a state variable, just like we did with taskCount. We’ll create a tasks 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;
}

Now let’s create a function for creating tasks and initialise from constructor.This function will get run only once, whenever the contract is initialised, i.e., deployed to the blockchain. Inside of this function, we have created one new default task with the string content “initialise 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 tasks mapping by id. We will simply pass in the id of the first task in the list when we call this function:

task = await todoList.tasks(1)

Now that we’ve migrated this smart contract to the blockchain, In 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 browsersync 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()
  })
})

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 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 conract wit the Truffle Contract library. Then we load the smart contract data with web3. This will allow us to list the tasks in 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 tasks mapping from the smart contract. We must first determine the taskCount 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 makesure your metamask is setup and connected to localhost . If not please setup .

Once you’re connected with Metamask, you should see all of the contract and account data loaded.

Now Let’s do some basic test 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 very important step. Let’s create a test file like this:

$ test/TodoList.test.js

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 like 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 above code ,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 task 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

For project setup and deployment on blockchain refer Part-1

For creating and completing task refer Part -3 and code github .

Leave a Reply

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