iT邦幫忙

第 11 屆 iThome 鐵人賽

1
Blockchain

又LAG的EOS.IO技術筆記系列 第 31

EOS智能合約範例-TODO工作事項管理合約(網頁前台)

本文中完整程式可以在GitHub找到。

本篇接續EOS智能合約範例-TODO工作事項管理合約,還沒完成合約的建議先去看看。

不得不說,雖然完成了,但是雷區還不少。
我原本用npm安裝Scatter.js需要的相關套件,但是卻在一個地方卡住,後來使用 CDN 提供的檔案,就又正常了。只能說...使用當下,最新版的可能存在Bug。/images/emoticon/emoticon03.gif

接下來使用 Parcel 建置網頁頁面,不知道是什麼的可以參考Parcel入門使用。不過這次避免太多看不懂,不會使用Pug模板。(不過你還需要有一點Vue的概念...)

引入所需要的套件

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://cdn.scattercdn.com/file/scatter-cdn/js/latest/scatterjs-core.min.js"></script>
    <!-- <script src="https://cdn.scattercdn.com/file/scatter-cdn/js/latest/scatterjs-plugin-eosjs.min.js"></script> -->
    <script src="https://cdn.scattercdn.com/file/scatter-cdn/js/latest/scatterjs-plugin-eosjs2.min.js"></script

這些套件,因為我編寫的時候,在js裡import會有一些錯誤,像是ScatterJS如果這麼用:

import ScatterJS from '@scatterjs/core';
import ScatterEOS from '@scatterjs/eosjs';
import {JsonRpc, Api} from 'eosjs';
import config from './config.js';

ScatterJS.plugins( new ScatterEOS() );
const network = ScatterJS.Network.fromJson(config.config.network);
const rpc = new JsonRpc(network.fullhost());
const api = new Api({ rpc, signatureProvider:ScatterJS.scatter.eos, textDecoder: new TextDecoder(), textEncoder: new TextEncoder() });
const eos = ScatterJS.scatter.eos(network, Api, {rpc});

會在最後ScatterJS.scatter.eos莫名報錯,貌似Api型態不對,但應該是Bug。不過你還是可以參考該項目說明試試看。至於...Vue,單純是我試了之後,資料渲染全給我白的,可能要放到$(document).ready()裡使用。不清楚,我可能還要問問熟悉Vue.js的朋友。

資料顯示頁面

些著看看顯示資料的地方。Vue的部份我會跳過(Bootstrap、jQuery也是),所以只要知道之後邏輯版定在idconnectloginlogoutrefresh和類別為btn_completebtn_delete。頁面不含操作邏輯,所以基本上我會跳過。不過可以先知道Scatter的流程。會先連結網路、登入驗證身份,最後才去交易送資料。不過其實單純取資料不需要登入,可以直接用get_table_rows()。(後面會看到)

https://ithelp.ithome.com.tw/upload/images/20191017/20112470JcN3FfeFHJ.png

    <div class="container" id="todo_app">
      <div class="btn btn-primary" id="connect">連線</div>
      <div class="btn btn-primary" id="login" v-show="!account">登入</div>
      <div class="btn btn-danger" id="logout" v-show="account">登出</div>
      <div v-if="account">登入狀態:{{account.name}}@{{account.authority}}</div>
      <div v-else>登入狀態:(未登入)</div>
      <h1>代辦清單</h1>
      <div id="refresh_setting">
        <label for="refresh_index">從:</label>
        <input id="refresh_index" type="number" value="1" v-model="task_index"/>
        <label for="refresh_limit">數量:</label>
        <input id="refresh_limit" type="number" value="50" v-model="task_show_limit"/>
        <div class="btn btn-primary" id="refresh">刷新任務</div>
      </div>
      <ul class="list-group" v-for="task in tasks">
        <li class="list-group-item"> {{ task.id }}. [{{ task.status }}] {{ task.task }}
          <div class="btn btn-success btn_complete" v-bind:data-id="task.id">完成</div>
          <div class="btn btn-danger btn_delete" v-bind:data-id="task.id">刪除</div>
        </li>
      </ul>
      <div v-if="task_more">還有其他任務</div>
      <div id="create_task_setting">
        <h2>新增任務</h2>
        <label for="new_task_content">任務內容:</label>
        <input id="new_task_content" v-model="new_task" placeholder="任務內容" type="text" value=""/>
        <input class="btn btn-primary" id="btn_create_task" type="button" value="新增"></input>
      </div>

網路設定部份

需要設定部份獨立出來。hostportprotocolchainId應該不陌生,dapp則是部署之前那份合約的合約帳號。

let config = {
  network:{
    blockchain:'eos',
    chainId:'cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f',
    host:'localhost',
    port:8888,
    protocol:'http'
  },
  dapp:'dapp1',
};

合約操作實現

首先要先引入而外需要使用的東西,eosjs是EOS.IO官方提供的套件,要注意版本有1.16以前的,以及2.0之後的差別;config.js就是上面的設定檔案:

引入套件

import {JsonRpc, Api} from 'eosjs';

import config from './config.js';

基本設定

ScatterJS.plugins( new ScatterEOS() );
const network = ScatterJS.Network.fromJson(config.config.network);
const rpc = new JsonRpc(network.fullhost());
const api = new Api({ rpc, signatureProvider:ScatterJS.scatter.eos, textDecoder: new TextDecoder(), textEncoder: new TextEncoder() });
const eos = ScatterJS.scatter.eos(network, Api, {rpc});
  • 首先需要告知ScatterJS需要使用eosio.js 2.0+。你還可以多重設定,Scatter是支援的。
  • 然後設定網路參數
  • 接著兩個是eosio.js本身的東西。rpcapi。(該項目連結
  • 最最後,建立一個可以實際簽章、送出交易的實例eos

實際操作

連結網路&登入驗證身份

首先要先透過ScatterJS.connect()連線網路,然後會有ScatterJS.login()方法來登入帳號。登入執行時,Scatter會跳出來要求確認。然後有ScatterJS.logout()方法登出帳號。登入後取得的結果,可以透過result.accounts[0]取得帳號,會包含帳號名稱和權限等級。

async function connect(){
  await ScatterJS.connect('todoapp', {network}).then(connected => {
    if(!connected)
      return false;
  });
}

function login(){
  let login = ScatterJS.login();
  login.then((success)=>{
    console.log("Login Success");
    app.account = success.accounts[0];
  },(fail)=>{
    console.log("Login Fail");
  });
}

讀取任務

讀取資料無須登入,比較單純,先來看看。

function getTasks(){
  // https://eosio.github.io/eosjs/latest/classes/json_rpc.jsonrpc/#get_table_rows
  rpc.get_table_rows({
    code: config.config.dapp,
    lower_bound: app.task_index,
    limit: app.task_show_limit,
    table: 'tasks',
    scope: config.config.dapp,
  }).then(result=>{
    console.group("Tasks");
    console.debug("The Resulte:");
    console.table(result);
    console.debug("tasks:");
    console.table(result.rows);
    app.tasks = result.rows;
    app.task_more = result.more,
    console.groupEnd();
  });
}

rpc.get_table_rows的用法可以看看這個頁面。其給定的參數表示:

  • code: 合約帳號
  • table: 資料表
  • scope: 範圍。在撰寫合約時,直接將範圍設定為合約名稱。如果你有改寫合約,建立多的不同的範圍,讀取時就是在這裡設定,會讀到不同的資料。
  • lower_bound: 從哪開始讀取任務。預設是從頭1
  • limit: 讀取數量。預設是10

之後結果可以透過result.rows讀取。

建立任務

建立任務也是一筆交易,會使用到transact。雖然用ScatterJS包裝起來了,但用法一樣,可以參考這個頁面。一個交易(transaction)是可以包含數個活動(action)的,每個活動的參數,機本要設定合約帳號(account)、執行活動(name)、執行權限(authorization),以及對應活動需要的而外參數(data),如果知道順序,參數也可以用list

// https://eosio.github.io/eosjs/latest/#sending-a-transaction
async function createTask(task_content){
  let account = await ScatterJS.login();
  let permission = account.accounts[0].authority;
  account = account.accounts[0].name;

  let result = await eos.transact({
    actions: [{
      account: config.config.dapp,
      name: 'create',
      authorization: [{
        actor: account,
        permission: permission,
      }],
      data: {
        owner: account,
        task: task_content
      },
    }]
  }, {
    blocksBehind: 3,
    expireSeconds: 30,
  });
}

新版似乎一定要加上{blocksBehind: 3,expireSeconds: 30}。之前使用1.16版本類似下面這樣:

// NOTE: this is an example for eosio.js v1.16

      eos.transaction({
        actions: [
          {
            account: dapp, //has to be the smart contract name of the token you want to transfer - eosio for EOS or eosjackscoin for JKR for example
            name: "deleteact",
            authorization: [{
              actor: scatter.identity.accounts[0].name,
              permission: scatter.identity.accounts[0].authority
            }],data: {
              organizer: organizer,
              activity_id: activity_id
            }
          }]
      }).then(res => {
        console.log(res);
      })

完成任務&刪除任務

完成任務刪除任務也就大同小異了:


async function completeTask(task_id){
  let account = await ScatterJS.login();
  let permission = account.accounts[0].authority;
  account = account.accounts[0].name;


  let result = await eos.transact({
    actions: [{
      account: config.config.dapp,
      name: 'update',
      authorization: [{
        actor: account,
        permission: permission,
      }],
      data: {
        id: task_id,
        new_status: "DONE",
      },
    }]
  }, {
    blocksBehind: 3,
    expireSeconds: 30,
  });
}


async function deleteTask(task_id){
  let account = await ScatterJS.login();
  let permission = account.accounts[0].authority;
  account = account.accounts[0].name;

  let result = await eos.transact({
    actions: [{
      account: config.config.dapp,
      name: 'remove',
      authorization: [{
        actor: account,
        permission: permission,
      }],
      data: {
        id: task_id,
      },
    }]
  }, {
    blocksBehind: 3,
    expireSeconds: 30,
  });
}

綁定事件

最後綁定事件完工:


$('#login').click(login);

$('#connect').click(connect);

$('#refresh').click(getTasks);

$('.btn_complete').click(async e=>{
  console.group("Complete Task");
  let id = e.target.getAttribute('data-id');
  console.debug("complete task id: #",id);
  await completeTask(id);
  console.groupEnd();

  getTasks();
});

$('.btn_delete').click(async e=>{
  console.group("Delete Task");
  let id = e.target.getAttribute('data-id');
  console.debug("Delete task id: #",id);
  await deleteTask(id);
  console.groupEnd();

  getTasks();
});

$('#btn_create_task').click(async e=>{
  console.group("Create Task");
  let task_content = app.new_task;
  //console.debug("Create task id: #",id);
  await createTask(task_content);
  console.groupEnd();

  getTasks();
});

建置App

parcel build -d public src/index.html --public-url ./

本文中完整程式可以在GitHub找到。


上一篇
Scatter 錢包管理管理 - 設定
系列文
又LAG的EOS.IO技術筆記31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

1
阿展展展
iT邦好手 1 級 ‧ 2020-02-02 07:56:56

恭喜完賽 /images/emoticon/emoticon42.gif

我要留言

立即登入留言