构建由区块链提供支持的待办事项列表 Web 应用程序
在这里,我们将构建一个将数据保存在区块链中的待办事项列表应用程序。这个应用的区块链部分也可以理解为一个数据库。首先,我们将创建一个智能合约,然后创建 Web 应用程序本身。我们将使用 Bloc 作为应用程序名称,但首先让我们看一下组件。
Bloc 应用程序中的组件
- Ganache - 本地以太坊区块链。
- Web3 JS-使应用程序能够与区块链通信。
- Bootstrap-用于应用程序的前端。
- Solidity-用于编译智能合约。
- JQuery -用于 DOM 操作。
什么是智能合约?
为了能够与区块链通信,我们需要编写一个智能合约。智能合约也可以理解为联系区块链的后端脚本。智能合约将允许我们将待办事项列表任务存储在区块链中。
为了编写和开发智能合约,我们将使用 REMIX IDE。
注意:确保您使用的是 Http 站点而不是 Https。 Http 站点将允许我们在本地区块链中部署我们的智能合约。
单击加号图标以创建 bloc.sol 文件。
智能合约的第一行必须声明solidity 版本来编译我们的智能合约,我们将编写以下内容-
pragma solidity ^0.5.1;
为了告诉编译器我们的智能合约,我们将定义一个合约块。像 OOP 中的类一样的合约,其中包含所有字段和方法:
pragma solidity ^0.5.1;
contract Bloc{
}
要存储任务,我们需要执行以下操作:
1. 为您的任务创建一个结构体:结构体允许创建用户定义的数据类型。它将有一个字符串作为任务和一个布尔值来判断任务是否完成。
pragma solidity ^0.5.1;
contract Bloc{
struct Task{
string task;
bool isDone;
}
}
2. 创建一个映射来存储我们的任务数组和关联的用户地址:映射就像一个哈希表,在这里我们创建一个地址作为键,值将是一个任务结构数组。将此映射设置为访问修饰符的私有映射。地址是一种强调帐户地址的可靠数据类型。
pragma solidity ^0.5.1;
contract Bloc{
struct Task{
string task;
bool isDone;
}
mapping (address => Task[]) private Users;
}
在合约中操作我们的任务的方法:
1. 创建任务:此方法将创建一个任务。
- addTask 方法接受一个字符串作为参数。
- calldata设置字符串参数的数据位置。
- external使该方法在通过 web3js 调用时可用。
- msg.sender为我们提供了调用该方法的用户的地址。
- 将任务添加到映射的push 方法。
pragma solidity ^0.5.1;
contract Bloc{
struct Task{
string task;
bool isDone;
}
mapping (address => Task[]) private Users;
function addTask(string calldata _task) external{
Users[msg.sender].push(Task({
task:_task,
isDone:false
}));
}
}
2. 读取任务:此方法有助于读取任务中的值。
- 要从 getTask 方法返回 Task结构,我们需要添加第 2 行。
- getTask 方法接收任务索引并给出任务。
- memory是要返回的 Task 的数据位置。
- view告诉该函数不会修改区块链的状态。
pragma solidity ^0.5.1;
contract Bloc{
struct Task{
string task;
bool isDone;
}
mapping (address => Task[]) private Users;
function addTask(string calldata _task) external{
Users[msg.sender].push(Task({
task:_task,
isDone:false
}));
}
function getTask(uint _taskIndex) external view returns (Task memory){
Task storage task = Users[msg.sender][_taskIndex];
return task;
}
}
3. 更新任务:此方法将更新任务中的值。
- 此方法将任务设置为选中或取消选中。
- updateStatus方法将获取要更新的任务索引和状态。
- 通过taskIndex ,我们将能够访问任务结构,因此我们将 isDone 设置为已通过的状态。
pragma solidity ^0.5.1;
contract Bloc{
struct Task{
string task;
bool isDone;
}
mapping (address => Task[]) private Users;
function addTask(string calldata _task) external{
Users[msg.sender].push(Task({
task:_task,
isDone:false
}));
}
function getTask(uint _taskIndex) external view returns (Task memory){
Task storage task = Users[msg.sender][_taskIndex];
return task;
}
function updateStatus(uint256 _taskIndex,bool _status) external{
Users[msg.sender][_taskIndex].isDone = _status;
}
}
4. 删除任务: deleteTask 方法将获取任务索引,然后从数组中删除元素,就像在 C 中一样。
pragma solidity ^0.5.1;
contract Bloc{
struct Task{
string task;
bool isDone;
}
mapping (address => Task[]) private Users;
function addTask(string calldata _task) external{
Users[msg.sender].push(Task({
task:_task,
isDone:false
}));
}
function getTask(uint _taskIndex) external view returns (Task memory){
Task storage task = Users[msg.sender][_taskIndex];
return task;
}
function updateStatus(uint256 _taskIndex,bool _status) external{
Users[msg.sender][_taskIndex].isDone = _status;
}
function deleteTask(uint256 _taskIndex) external{
delete Users[msg.sender][_taskIndex];
}
}
5. 获取任务数:可以获取任务数作为任务数组长度。
加上上面所有处理任务的方法后,完整的solidity程序是这样的:
Solidity
pragma solidity ^0.5.1;
// Creating a contract
contract Bloc{
// Defining a structure to
// store a task
struct Task
{
string task;
bool isDone;
}
mapping (address => Task[]) private Users;
// Defining function to add a task
function addTask(string calldata _task) external
{
Users[msg.sender].push(Task({
task:_task,
isDone:false
}));
}
// Defining a function to get details of a task
function getTask(uint _taskIndex) external view returns (Task memory)
{
Task storage task = Users[msg.sender][_taskIndex];
return task;
}
// Defining a function to update status of a task
function updateStatus(uint256 _taskIndex,bool _status) external
{
Users[msg.sender][_taskIndex].isDone = _status;
}
// Defining a function to delete a task
function deleteTask(uint256 _taskIndex) external
{
delete Users[msg.sender][_taskIndex];
}
// Defining a function to get task count.
function getTaskCount() external view returns (uint256)
{
return Users[msg.sender].length;
}
}
HTML
Bloc
BLOC
0 Task
New Task
Javascript
// Connect to Ganache Make sure you enter the address you noted earlier here //
web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:7545'));
// getAccount() will get the first account from ganache and will set it as defaultAccount for our contract operations ////
async function getAccount() {
let accounts = await web3.eth.getAccounts();
web3.eth.defaultAccount = accounts[0];
console.log(web3.eth.defaultAccount + ' account detected');
return web3.eth.defaultAccount;
}
Javascript
$(document).ready(createTaskList());
// Auto focus on input of add task modal //
$('#add-task-container').on('shown.bs.modal', function () {
$('#new-task').trigger('focus');
});
/**
* createTaskList() set the contract object and gets the number
* of tasks of the user and then calls addTaskToList() to add
* them to HTML one after the other after all task are added to
* HTML then calls updateTaskCount()
* @author Gupta Shrinath
*/
async function createTaskList() {
// Get account from the Ganache EVM //
try {
await getAccount();
// Set contract and set gas //
contract = new web3.eth.Contract(contractABI, contractAddress);
try {
numberOfTask = await contract.methods.getTaskCount().call({ from: web3.eth.defaultAccount });
/* The actual number of task may differ because
when an task is removed the task element is
removed and the index value now has nothing.
*/
console.log('Number of Tasks are ' + numberOfTask);
// If there are task present //
if (numberOfTask != 0) {
// Fetch one task after the other until no task remain //
console.log('Start fetching task ...');
let taskIterator = 0;
while (taskIterator < numberOfTask) {
try {
let task = await contract.methods.getTask(taskIterator).call({ from: web3.eth.defaultAccount });
if (task[0] != '') {
// addTaskToList add this task as children to the ul tag //
addTaskToList(taskIterator, task[0], task[1]);
}
else {
console.log('The index ' + taskIterator + ' is empty');
}
} catch {
console.log('Failed to get Task ' + taskIterator);
}
taskIterator++;
}
// Update the task count in HTML //
updateTasksCount();
}
} catch {
console.log('Failed to get task count from blockchain');
}
} catch {
console.log('Failed to get the acount');
}
}
/**
* addTaskToList() takes the task attributes and adds them to
* the HTML
* @author Gupta Shrinath
* @param {number} id
* @param {string} name
* @param {boolean} status
*/
function addTaskToList(id, name, status) {
console.log('addTaskToList(): Add Task ' + (id) + ' ' + [name, status]);
/* Get the id of ul element so to be able to
add children to it
*/
let list = document.getElementById('list');
/* Create a li element and add the class
required to make look good and
set the id of it
*/
let item = document.createElement('li');
item.classList.add('list-group-item', 'border-0', 'd-flex', 'justify-content-between', 'align-items-center');
item.id = 'item-' + id;
// Create a text to add it to the li element//
let task = document.createTextNode(name);
/* Create a checkbox and set its id and checked
value to add it to the li element
*/
var checkbox = document.createElement("INPUT");
checkbox.setAttribute("type", "checkbox");
checkbox.setAttribute("id", "item-" + id + "-checkbox");
checkbox.checked = status;
/* if status is true then add task-done class to li
element so that the text gets a linethrough
*/
if (status) {
item.classList.add("task-done");
}
// Add the li element to ul element //
list.appendChild(item);
/* Set a ondblclick event to able to remove the
item when double clicked on it */
item.ondblclick = function () {
removeTask(item.id);
}
// Append the text of task //
item.appendChild(task);
// Append the checkbox for task //
item.appendChild(checkbox);
// Add onclick to the checkbox //
checkbox.onclick = function () { changeTaskStatus(checkbox.id, id); };
}
/**
* removeTask() remove the task from blockchain and then from
* the HTML using JQuery
* Note: The taskIndex is the li element id {item-taskIndex}
* @author Gupta Shrinath
* @param {string} taskIndex
*/
async function removeTask(taskIndex) {
console.log("removeTask(): Remove Task " + taskIndex);
// Create the selector for the Task //
let taskSelector = '#' + taskIndex;
// Make taskIndex to have only task index number
taskIndex = taskIndex.replace('item-', '');
try {
await contract.methods.deleteTask(taskIndex).send({ from: web3.eth.defaultAccount });
console.log('Remove Task ' + taskIndex + ' from the blockchain');
// Remove the task from the HTML //
$(taskSelector).remove();
// Update the task count in HTML//
updateTasksCount();
} catch {
console.log('Issue occured while removing task item-' + taskIndex);
}
}
/**
* changeTaskStatus() change the status of task in blockchain and
* then in the HTML
* Note: The id is the checkbox id {item-taskIndex-checkbox}
* @author Gupta Shrinath
* @param {string} id
* @param {number} taskIndex
*/
async function changeTaskStatus(id, taskIndex) {
// Get checkbox element //
let checkbox = document.getElementById(id);
// Get the id of the li element //
let textId = id.replace('-checkbox', '');
// Get the li element //
let text = document.getElementById(textId);
try {
await contract.methods.updateStatus(taskIndex, checkbox.checked).send({ from: web3.eth.defaultAccount });
console.log('changeTaskStatus(): Change status of task ' + textId + ' to ' + checkbox.checked);
if (checkbox.checked) {
text.classList.add("task-done");
} else {
text.classList.remove("task-done");
}
} catch (error) {
console.log('Failed to change Status of task ' + textId + ' in blockchain');
}
}
/**
* updateTaskCount() update the number of task in HTML by counting
* the number of item in the ul element
* @author Gupta Shrinath
*/
function updateTasksCount() {
// Get the element of ul tag //
let list = document.getElementById('list');
// Get the count of the ul element //
let taskCount = list.childElementCount;
console.log('updateTaskCount(): The number of task are ' + taskCount);
// Set the count to the taskCount id element //
let count = document.getElementById('taskCount');
count.innerText = taskCount + " Task";
}
/**
* addTask() add the task to the HTML via adddTasktoList() and then
* add it to blockchain and update the count via updateTaskCount()
* @author Gupta Shrinath
* @param {string} name
*/
async function addTask(name) {
// Get the form element containing the new task //
let form = document.getElementById('add-task-form');
// Check if the input is valid and then add it//
if (form.checkValidity()) {
console.log('Get the number of task from blockchain');
// Set blank value for text in the addtask modal //
document.getElementById('new-task').value = '';
// Remove the mentioned class because it might be
// present if a task was added before
form.classList.remove('was-validated');
// Get the number of task from blockchain //
contract.methods.getTaskCount().call({ from: web3.eth.defaultAccount }).then(numberOfTask => {
// Add the task to the HTML //
addTaskToList(numberOfTask, name, false);
// Update the task count in HTML//
updateTasksCount();
}, err => {
console.log('Failed to get the number of task in blockchain ' + err);
});
try {
await contract.methods.addTask(name).send({ from: web3.eth.defaultAccount });
console.log('Add task ' + name + ' to blockchain');
} catch {
console.log('Failed to add task to EVM');
}
} else {
form.addEventListener('submit', function (event) {
// Stop all events //
event.preventDefault();
event.stopPropagation();
// Add the mentioned class to able to display
// error to user
form.classList.add('was-validated');
// Set blank value for text in the addtask modal //
document.getElementById('new-task').value = '';
}, false);
}
}
单击 Solidity 左侧导航栏选项下的编译按钮。
单击部署中的Deploy and Run Transaction ,在已部署的合约中,您可以找到所有方法。
有了这个,我们就完成了创建一个基本的智能合约。我们将在下一篇文章中使用此智能合约与 Web 应用程序进行通信。现在,我们将创建一个 Web 应用程序,该应用程序可以与我们在上面创建的智能合约进行通信。
在我们开始构建 Web 应用程序之前,我们需要以太坊区块链,它将拥有我们的应用程序的智能合约和数据。我们将使用 Ganache 继续下载并安装它。
安装后,打开 Ganache 并单击快速启动 Ethereum,然后记下 RPC Server 下标记的地址(应该类似于 http://127.0.0.1:7545)。
现在构建 Web 应用程序
HTML
Bloc
BLOC
0 Task
New Task
该网页将如下所示 -
获取账户.js
Javascript
// Connect to Ganache Make sure you enter the address you noted earlier here //
web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:7545'));
// getAccount() will get the first account from ganache and will set it as defaultAccount for our contract operations ////
async function getAccount() {
let accounts = await web3.eth.getAccounts();
web3.eth.defaultAccount = accounts[0];
console.log(web3.eth.defaultAccount + ' account detected');
return web3.eth.defaultAccount;
}
配置文件
- 转到 Remix IDE 并确保您拥有上一教程中的 bloc.sol(确保确保 HTTP 站点,而不是 https)。
- 转到位于左侧面板中的 Solidity 编译器,然后单击 compile bloc.sol。在 ABI 单击它时,您会发现一个带有复制图标和文本的按钮。将其粘贴到 js/config.js 第 1 行。
let contractABI = COPIED TEXT;
The copied text will be enclosed in [].
- 在环境选择 Web3 Provider 下部署和运行事务。
- 输入您从 Ganache 复制的地址并粘贴,点击 OK。
- 现在可以看到一个部署按钮,点击它。
- 在下面你会发现 Deployed Contracts 标签,现在会有一个复制图标按钮点击它。
- 并粘贴到 js/config.js 第 2 行。
let contractAddress = 'COPIED text';
The copied text might look like this 0xF3017acEDd45526aC6153FBBCfcA8096173D245a.
The contractABI helps the web3js with our smart contract
The contractAddress tells the web3js about where on blockchain is our smart contract
应用程序.js
Javascript
$(document).ready(createTaskList());
// Auto focus on input of add task modal //
$('#add-task-container').on('shown.bs.modal', function () {
$('#new-task').trigger('focus');
});
/**
* createTaskList() set the contract object and gets the number
* of tasks of the user and then calls addTaskToList() to add
* them to HTML one after the other after all task are added to
* HTML then calls updateTaskCount()
* @author Gupta Shrinath
*/
async function createTaskList() {
// Get account from the Ganache EVM //
try {
await getAccount();
// Set contract and set gas //
contract = new web3.eth.Contract(contractABI, contractAddress);
try {
numberOfTask = await contract.methods.getTaskCount().call({ from: web3.eth.defaultAccount });
/* The actual number of task may differ because
when an task is removed the task element is
removed and the index value now has nothing.
*/
console.log('Number of Tasks are ' + numberOfTask);
// If there are task present //
if (numberOfTask != 0) {
// Fetch one task after the other until no task remain //
console.log('Start fetching task ...');
let taskIterator = 0;
while (taskIterator < numberOfTask) {
try {
let task = await contract.methods.getTask(taskIterator).call({ from: web3.eth.defaultAccount });
if (task[0] != '') {
// addTaskToList add this task as children to the ul tag //
addTaskToList(taskIterator, task[0], task[1]);
}
else {
console.log('The index ' + taskIterator + ' is empty');
}
} catch {
console.log('Failed to get Task ' + taskIterator);
}
taskIterator++;
}
// Update the task count in HTML //
updateTasksCount();
}
} catch {
console.log('Failed to get task count from blockchain');
}
} catch {
console.log('Failed to get the acount');
}
}
/**
* addTaskToList() takes the task attributes and adds them to
* the HTML
* @author Gupta Shrinath
* @param {number} id
* @param {string} name
* @param {boolean} status
*/
function addTaskToList(id, name, status) {
console.log('addTaskToList(): Add Task ' + (id) + ' ' + [name, status]);
/* Get the id of ul element so to be able to
add children to it
*/
let list = document.getElementById('list');
/* Create a li element and add the class
required to make look good and
set the id of it
*/
let item = document.createElement('li');
item.classList.add('list-group-item', 'border-0', 'd-flex', 'justify-content-between', 'align-items-center');
item.id = 'item-' + id;
// Create a text to add it to the li element//
let task = document.createTextNode(name);
/* Create a checkbox and set its id and checked
value to add it to the li element
*/
var checkbox = document.createElement("INPUT");
checkbox.setAttribute("type", "checkbox");
checkbox.setAttribute("id", "item-" + id + "-checkbox");
checkbox.checked = status;
/* if status is true then add task-done class to li
element so that the text gets a linethrough
*/
if (status) {
item.classList.add("task-done");
}
// Add the li element to ul element //
list.appendChild(item);
/* Set a ondblclick event to able to remove the
item when double clicked on it */
item.ondblclick = function () {
removeTask(item.id);
}
// Append the text of task //
item.appendChild(task);
// Append the checkbox for task //
item.appendChild(checkbox);
// Add onclick to the checkbox //
checkbox.onclick = function () { changeTaskStatus(checkbox.id, id); };
}
/**
* removeTask() remove the task from blockchain and then from
* the HTML using JQuery
* Note: The taskIndex is the li element id {item-taskIndex}
* @author Gupta Shrinath
* @param {string} taskIndex
*/
async function removeTask(taskIndex) {
console.log("removeTask(): Remove Task " + taskIndex);
// Create the selector for the Task //
let taskSelector = '#' + taskIndex;
// Make taskIndex to have only task index number
taskIndex = taskIndex.replace('item-', '');
try {
await contract.methods.deleteTask(taskIndex).send({ from: web3.eth.defaultAccount });
console.log('Remove Task ' + taskIndex + ' from the blockchain');
// Remove the task from the HTML //
$(taskSelector).remove();
// Update the task count in HTML//
updateTasksCount();
} catch {
console.log('Issue occured while removing task item-' + taskIndex);
}
}
/**
* changeTaskStatus() change the status of task in blockchain and
* then in the HTML
* Note: The id is the checkbox id {item-taskIndex-checkbox}
* @author Gupta Shrinath
* @param {string} id
* @param {number} taskIndex
*/
async function changeTaskStatus(id, taskIndex) {
// Get checkbox element //
let checkbox = document.getElementById(id);
// Get the id of the li element //
let textId = id.replace('-checkbox', '');
// Get the li element //
let text = document.getElementById(textId);
try {
await contract.methods.updateStatus(taskIndex, checkbox.checked).send({ from: web3.eth.defaultAccount });
console.log('changeTaskStatus(): Change status of task ' + textId + ' to ' + checkbox.checked);
if (checkbox.checked) {
text.classList.add("task-done");
} else {
text.classList.remove("task-done");
}
} catch (error) {
console.log('Failed to change Status of task ' + textId + ' in blockchain');
}
}
/**
* updateTaskCount() update the number of task in HTML by counting
* the number of item in the ul element
* @author Gupta Shrinath
*/
function updateTasksCount() {
// Get the element of ul tag //
let list = document.getElementById('list');
// Get the count of the ul element //
let taskCount = list.childElementCount;
console.log('updateTaskCount(): The number of task are ' + taskCount);
// Set the count to the taskCount id element //
let count = document.getElementById('taskCount');
count.innerText = taskCount + " Task";
}
/**
* addTask() add the task to the HTML via adddTasktoList() and then
* add it to blockchain and update the count via updateTaskCount()
* @author Gupta Shrinath
* @param {string} name
*/
async function addTask(name) {
// Get the form element containing the new task //
let form = document.getElementById('add-task-form');
// Check if the input is valid and then add it//
if (form.checkValidity()) {
console.log('Get the number of task from blockchain');
// Set blank value for text in the addtask modal //
document.getElementById('new-task').value = '';
// Remove the mentioned class because it might be
// present if a task was added before
form.classList.remove('was-validated');
// Get the number of task from blockchain //
contract.methods.getTaskCount().call({ from: web3.eth.defaultAccount }).then(numberOfTask => {
// Add the task to the HTML //
addTaskToList(numberOfTask, name, false);
// Update the task count in HTML//
updateTasksCount();
}, err => {
console.log('Failed to get the number of task in blockchain ' + err);
});
try {
await contract.methods.addTask(name).send({ from: web3.eth.defaultAccount });
console.log('Add task ' + name + ' to blockchain');
} catch {
console.log('Failed to add task to EVM');
}
} else {
form.addEventListener('submit', function (event) {
// Stop all events //
event.preventDefault();
event.stopPropagation();
// Add the mentioned class to able to display
// error to user
form.classList.add('was-validated');
// Set blank value for text in the addtask modal //
document.getElementById('new-task').value = '';
}, false);
}
}
现在应用程序完全正常工作