ToDo List Ethereum Dapp (Step8) | Create Smart Contract Data From The Frontend

This tutorial is part of the series ToDo List Ethereum Dapp. This is the Step 8. Tutorials already published:

In the last tutorial (step7) of this series we read tasks from the frontend using jQuery, Web3 and TruffleContract. We also refactored our code by creating an App object and separating configuration and bootstrapping code. Now our code looks better!

However, we are still only reading dummy data from the smart contract, instead of real data.

How can we create tasks from the frontend and read them? Read this tutorial until the end and you will know how to do that! We will continue to use jQuery, Web3 and TruffleContract.

Setup project

(If you have been following this tutorial series for a while this section can get a bit repetitive… feel free to skip it if you remember how to get started for each step).

Copy and paste the folder of the last episode (step7) and rename it to step8:

cp -r special-episode-1/step7 special-episode-1/step8

Step into step8 folder and install npm dependencies:

cd special-episode-1/step8
npm install

In a new terminal, launch a local Ethereum blockchain:

ganache-cli

In a new terminal, start the nodejs server:

npm run dev-back

In a new terminal, start the webpack watcher for the frontend:

npm run dev-front

Just to recap, you should have created a total of 3 terminal windows:

  • Terminal 1: for ganache-cli, the local Ethereum Blockchain
  • Terminal 2: for npm run dev-back, the nodejs server
  • Terminal 3: for npm run dev-front, the webpack watcher

Awesome! You are ready to get started.

Setup the HTML

Let’s create a form to create new tasks. Open app/index.html inside <div class="container"></div>. At the very top, add this HTML:

<div class="container">
  <!-- Create Task -->
  <div class="card">
    <!-- Create Tasks button -->
    <div class="row">
      <div class="col-sm-12">
        <h2 class="orange">Create Task</h2>
      </div>
    </div>
    <div class="row">
      <form id="new-task" class="col-sm-12">
        <div class="form-group">
          <label for="task-content">Content</label>
          <input id="task-content" type="text" class="form-control"></input>
        </div>
        <div class="form-group">
          <label for="task-author">Author</label>
          <input id="task-author" type="text" class="form-control"></input>
        </div>
        <button type="submit" class="btn btn-primary">Submit</button>
      </form>
    </div><!--./row-->
  </div><!--./card-->
   ...
</div><!--./container-->

You should see this in your browser:

We can click on the Submit button, but it doesn’t do much at the moment. How can we make the form actually create a task in our smart contract?

Attach an event handler to the Submit button with jQuery

The first step to making our Submit button operational is to be able to respond to user clicks and trigger a specific action. We will use jQuery to do that.

Open app/js/lib/app.js. In the setup() method, add this just below this.$tasks = $('#tasks'):

this.$newTask = $('#new-task'); 
this.$taskContent = $('#task-content'); 
this.$taskAuthor = $('#task-author');

We are grabbing the DOM nodes of the create task form with jQuery. The first one is the submit button, and the 2 other are the form elements that contain the data of the new task.

At the top of the init() method add this:

this.$newTask.on('submit', (event) => {
  event.preventDefault();
  console.log('create task!');
});

When the user click on the Submit button, we detect it thanks to the submit event and trigger a function (this kind of function is called an event handler). We temporarily only put a console.log() statement in it to make sure it works properly.

A word of caution for frontend beginner: the fact that the text of the button is Submit has nothing to do with the naming of the submit event. We could put whatever text we want in the button and still be able to capture clicks with the submit event.

Open your web browser at http://localhost:3000 and check that in your dev console you see the console.log() statement when you click on the Submit button:

We are making progress. Next, we still need to actually to trigger the creation of the task in the smart contract from our event handler.

Get the address (account) to send our transaction from

Note: Ethereum has the concept of accounts, where each address has not only an associated balance but also some data. Since each account is referenced by its address, we often use address and account interchangably.

In previous episodes, we already called smart contract functions without spending any ether. It was because we did not modify data on the Blockchain, but just reading it.

This time it’s different. We will call a function of our smart contract that actually modifies data on the Blockchain. When we do this, we need to pay some ether. Don’t panic, we are just using a local ethereum Blockchain with fake ether, so you don’t need to spend any money to follow this tutorial 🙂

In Ethereum terms, we will send to the Blockchain a data structure called a “transaction”. The term transaction can be confusing here, especially since Ethereum is able to perform financial transactions. In this context, a transaction is a signed message that says:

  • Call this function in this smart contract
  • If the execution result in any date changes, persist them in the Blockchain

Because transaction change data and cost money to the sender, we need to make sure the address owner actually sent the transaction. That’s why transactions need to be signed.

But how do we sign a transaction?

With the private keys of your address. The local Blockchain we use, ganache-cli, provides you with 10 pre-funded addresses and their private keys. To send our transaction, we will use one of this address. But first, we need to let the frontend know what is this address.

app.js starts to be a bit beefy, so let’s create a new file to host the code that will get our address. We Will call this file app/js/lib/actions.js. Put this inside:

const getAccount = (web3) => {
  return new Promise((resolve, reject) => {
    web3.eth.getAccounts((error, accounts) => {
      if(typeof error === null) {
        return reject(error);
      } 
      resolve(accounts[0]);
    });
  });
};

export {
  getAccount,
}

We use web3.eth.getAccounts() to get a list of all the pre-funded accounts of ganache-cli. We return the first one. Note that we also transform the (old school) callback-style to a (more modern) promise.

Let’s make use of our new function. In app/js/lib/app.js lets call this in the setup() function.

First add this import statement at the top of the file:

import { getAccount } from './actions';

Then, in the setup() function replace this:

return new Promise((resolve, reject) => {
  Todo.at(address)
  .then((todo) => {
    this.todo = todo;
    resolve(todo);
  })
  .catch((error) => {
    reject(error);
  });
});

By this:

return new Promise((resolve, reject) => {
  getAccount(this.web3)
  .then((account) => {
    this.account = account;
    return Todo.at(address);
  })
  .then((todo) => {
    this.todo = todo;
    resolve(todo);
  })
  .catch((error) => {
    reject(error);
  });
});

Now that the frontend knows which address to send transaction from, We are finally ready to create a task in our smart contract!

Create a task in the smart contract from the event handler

We are going to call the createTask() function of our smart contract from the event handler we just created.

In app/js/lib/app.js, replace this:

this.$newTask.on('submit', (event) => {
  event.preventDefault();
  console.log('create task!');
});

By this:

this.$newTask.on('submit', (event) => {
  event.preventDefault();
  this.todo.createTask(
    this.$taskContent.val(), 
    this.$taskAuthor.val(), 
    {from: this.account, gas: 1000000}
  ).then(() => {
    console.log('Task created!');
  })
  .catch((error) => {
    console.log(`Oops... There was an error: ${error}`);
  });
});

Go to frontend of the Dapp in your browser, create a task, and check that you see this console.log statement after you click on submit:

We are finally able to create some blockchain data from the frontend. That’s awesome! But we still can’t see if from the frontend. Remember, in the last episode we made a function that return some dummy data to the frontend, and that’s what we see currently in the list of tasks. We need to change that!

Reading the ACTUAL tasks of the smart contract

Approach 1

The most simple would be to have a function in our smart contract that returns an array of our tasks:

function getTasks() returns(Task[])
...

However, our tasks are represented as struct (Task) and its currently not possible to return an array of structs in Solidity.

Approach 2

Another approach would be to return a tuple of arrays, each representing a field of our struct:

function getTasks() returns(
  uint[] ids, 
  uint[] dates, 
  string[] content,
  string[] author,
  bool[] done
)
...

However it’s not possible to return an array of string (string[]) in Solidity yet. We could just use a simple string and concatenate the content / author of all tasks into it, but its not trivial to do so.

Approach 3

The solution we will take involve doing multiple calls instead of just one:

  • From the frontend, first we will get the list of all task ids by calling getTaskIds() on the smart contract
  • Then we will get each task one-by-one by calling getTask() on the smart contract for each id.

In app/js/lib/actions.js, add this function:

const getTasks = (todo) => {
  return new Promise((resolve, reject) => {
    todo.getTaskIds()
    .then((taskIds) => {
      const promises = [];
      taskIds.forEach((taskId) => {
        promises.push(todo.getTask(taskId));
      });
      return Promise.all(promises);
    })
    .then((tasks) => {
      resolve(tasks);
    })
    .catch((error) => {
      reject(error);
    });
  });
};

Export the function:

export { 
  getAccount,
  getTasks
};

Then at top of app/js/lib/app.js, import getTasks():

import { getAccount, getTasks } from './actions';

And make use of it by replacing this in init():

return new Promise((resolve, reject) => { 
  this.todo.getTaskFixtures(0)
  .then((tasks) => { 
    renderTasks(this.$tasks, [task]);  
  }); 
}); 

By this:

return new Promise((resolve, reject) => { 
   getTasks(this.todo)
  .then((tasks) => { 
    renderTasks(this.$tasks, tasks);  
  }); 
}); 

Go to the frontend of the Dapp in your browser, create a few tasks, reload the page, and you should see the tasks you just created showing in the list of tasks:

Fantastic! You made it!

The end

Where to go from there? There are different features / improvements that can be made:

  • We still cant toggle the done status of a task
  • We have no feedback message after we create a task
  • After we create a new task, we still need to refresh manually the page to see the new task appearing
  • The last 2 points can get considerably complex when you consider that for production deployments (mainnet) the latency between the moment you send a transaction and the moment it gets mined is at least 15s. Worst, the transaction can get rejected. How to poll the network to see when the transaction gets mined? How to handle failure cases? What to show to the user?

You will find the answers to all these questions by subscribing to EatTheBlocks Pro. Every week I will release a professionally edited video that goes in the deep topic that will allow you to produce a production-ready Dapp.

Leave a Reply

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