在本系列关于使用以太坊构建DApps教程的第6部分中,咱们经过添加投票,黑名单,股息分配和撤销来完成DAO合约,同时投入一些额外的辅助函数以实现良好的标准。在本教程中,咱们将构建一个用于与咱们的故事Story交互的Web界面,不然咱们没法统计用户如何参与。因此这是咱们故事Story发布以前的最后一部分。javascript
因为这不是一个Web应用程序教程,咱们将保持很是简单。下面的代码不是生产就绪的,只是做为如何将JavaScript链接到区块链的概念证实。但首先,让咱们添加一个新的迁移。php
如今,当咱们部署代币和DAO时,它们位于区块链上但不进行交互。为了测试咱们构建的内容,咱们须要手动将代币全部权和余额转移到DAO,这在测试期间可能很乏味。css
让咱们写一个新的迁移,为咱们作这件事。建立文件4_configure_relationship.js
并将如下内容放在其中:html
var Migrations = artifacts.require("./Migrations.sol");
var StoryDao = artifacts.require("./StoryDao.sol");
var TNSToken = artifacts.require("./TNSToken.sol");
var storyInstance, tokenInstance;
module.exports = function (deployer, network, accounts) {
deployer.then(function () {
return TNSToken.deployed();
}).then(function (tIns) {
tokenInstance = tIns;
return StoryDao.deployed();
}).then(function (sIns) {
storyInstance = sIns;
return balance = tokenInstance.totalSupply();
}).then(function (bal) {
return tokenInstance.transfer(storyInstance.address, bal);
})
.then(function (something) {
return tokenInstance.transferOwnership(storyInstance.address);
});
}
复制代码
这是这段代码的做用。首先,你会注意到它是基于promise的。它充满了各类调用。这是由于咱们在调用下一个数据以前依赖于返回一些数据的函数。全部合约调用都是基于promise的,这意味着它们不会当即返回数据,由于Truffle须要向节点请求信息,所以promise在未来返回数据。咱们强制代码等待这些数据,使用then
关键词并提供全部then
调用函数,这些函数在最终给出时将使用此结果调用。前端
因此,按顺序:java
运行truffle migrate --reset
如今应该产生这样的输出:node
前端是一个常规的静态HTML页面,其中包含一些JavaScript用于与区块链和一些CSS进行通讯以使页面变得不那么难看。python
让咱们在子文件夹public
建立一个文件index.html
,并为其提供如下内容:android
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>The Neverending Story</title>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta name="description" content="The Neverending Story is an community curated and moderated Ethereum dapp-story">
<link rel="stylesheet" href="assets/css/main.css"/>
</head>
<body>
<div class="grid-container">
<div class="header container">
<h1>The Neverending Story</h1>
<p>A story on the Ethereum blockchain, community curated and moderated through a Decentralized Autonomous Organization (DAO)</p>
</div>
<div class="content container">
<div class="intro">
<h3>Chapter 0</h3>
<p class="intro">It's a rainy night in central London.</p> </div> <hr> <div class="content-submissions"> <div class="submission"> <div class="submission-body">This is an example submission. A proposal for its deletion has been submitted.</div> <div class="submission-submitter">0xbE2B28F870336B4eAA0aCc73cE02757fcC428dC9</div> <div class="submission-actions"> <div class="deletionproposed" data-votes="3024" data-deadline="1531607200"></div> </div> </div> <div class="submission"> <div class="submission-body">This is a long submission. It has over 244 characters, just we can see what it looks like when rendered in the UI. We need to make sure it doesn't break anything and the layout also needs to be maintained, not clashing with actions/buttons etc.</div>
<div class="submission-submitter">0xbE2B28F870336B4eAA0aCc73cE02757fcC428dC9</div>
<div class="submission-actions">
<div class="delete"></div>
</div>
</div>
<div class="submission">
<div class="submission-body">This is an example submission. A proposal for its deletion has been submitted but is looking like it'll be rejected.</div> <div class="submission-submitter">0xbE2B28F870336B4eAA0aCc73cE02757fcC428dC9</div> <div class="submission-actions"> <div class="deletionproposed" data-votes="-790024" data-deadline="1531607200"></div> </div> </div> </div> </div> <div class="events container"> <h3>Latest Events</h3> <ul class="eventlist"> </ul> </div> <div class="information container"> <p>Logged in / out</p> <div class="avatar"> <img src="http://placeholder.pics/svg/200/DEDEDE/555555/avatar" alt="avatar"> </div> <dl> <dt>Contributions</dt> <dd>0</dd> <dt>Deletions</dt> <dd>0</dd> <dt>Tokens</dt> <dd>0</dd> <dt>Proposals submitted</dt> <dd>0</dd> <dt>Proposals voted on</dt> <dd>0</dd> </dl> </div> </div> <script src="assets/js/web3.min.js"></script> <script src="assets/js/app.js"></script> <script src="assets/js/main.js"></script> </body> </html> 复制代码
注意:这是一个很是基本的框架,仅用于演示集成。请不要把这当成最终产品!程序员
你可能缺乏web3
文件夹中的dist
文件夹。该软件仍处于测试阶段,所以仍有可能出现轻微漏洞。要解决此问题并使用dist
文件夹,请运行npm install ethereum/web3.js --save
。
对于CSS,让咱们在public/assets/css/main.css
一些基本内容:
@supports (grid-area: auto) {
.grid-container{
display: grid;
grid-template-columns: 6fr 5fr 4fr;
grid-template-rows: 10rem ;
grid-column-gap: 0.5rem;
grid-row-gap: 0.5rem;
justify-items: stretch;
align-items: stretch;
grid-template-areas:
"header header information"
"content events information";
height: 100vh;
}
.events {
grid-area: events;
}
.content {
grid-area: content;
}
.information {
grid-area: information;
}
.header {
grid-area: header;
text-align: center;
}
.container {
border: 1px solid black;
padding: 15px;
overflow-y: scroll;
}
p {
margin: 0;
}
}
body {
padding: 0;
margin: 0;
font-family: sans-serif;
}
复制代码
而后做为JS,咱们将在public/assets/js/app.js
:
var Web3 = require('web3');
var web3 = new Web3(web3.currentProvider);
console.log(web3);
复制代码
这里发生了什么?
既然咱们假设全部用户都安装了MetaMask,而且MetaMask将本身的Web3实例注入到任何访问过的网页的DOM中,咱们基本上能够访问咱们网站上MetaMask的wallet provider
。实际上,若是咱们在页面打开时登陆MetaMask,咱们将在控制台中看到:
注意MetamaskInpageProvider
是如何激活的。实际上,若是咱们在控制台中键入web3.eth.accounts
,咱们将经过MetaMask访问的全部账户都将打印出来:
可是,这个特殊账户默认添加到我本身的我的Metamask中,所以余额为0eth。它不是咱们运行的Ganache或PoA链的一部分:
请注意,若是要求咱们的MetaMask活动账户的余额产生0,同时要求咱们的一个私有区块链账户的余额产生100以太(在个人状况下它是Ganache,因此全部账户都用100以太初始化)。
你会注意到这些调用的语法看起来有点奇怪:
web3.eth.getBalance("0x35d4dCDdB728CeBF80F748be65bf84C776B0Fbaf", function(err, res){console.log(JSON.stringify(res));});
复制代码
为了读取区块链数据,大多数MetaMask用户不会在本地运行节点,而是从Infura或其余远程节点请求它。所以,咱们实际上能够依靠回调。所以,一般不支持同步方法。相反,一切都是经过promise或回调来完成的——就像本文开头的部署步骤同样。这是否意味着你须要很是熟悉为以太坊开发JS的promise?不,这意味着如下内容。在DOM中进行JS调用时......
error
,而后是result
。因此,基本上,只需考虑延迟响应就能够了。当节点响应数据时,你定义为回调函数的函数将由JavaScript调用。是的,这意味着你不能期望你的代码在编写时逐行执行!
有关promises,回调和全部async jazz
的更多信息,请参阅此文章。
若是咱们打开上面提到的网站骨架,咱们获得这样的东西:
让咱们用真实数据填充关于账户信息的最右侧列。
当用户未登陆其MetaMask扩展名时,账户列表将为空。若是甚至没有安装MetaMask,则提供程序将为空(未定义)。当他们登陆MetaMask时,接口将可用并提供账户信息以及与链接的以太坊节点(live或Ganache或其余)的交互。
提示:要进行测试,你能够经过单击右上角的头像图标而后选择注销来注销MetaMask。若是用户界面看起来不像下面的屏幕截图,你可能须要经过打开菜单并单击“试用Beta”来激活Beta用户界面。
首先,若是用户已注销,请将该状态列的全部内容替换为用户的消息:
<div class="information container">
<div class="logged out">
<p>You seem to be logged out of MetaMask or MetaMask isn't installed. Please log into MetaMask - to learn more, see <a href="https://bitfalls.com/2018/02/16/metamask-send-receive-ether/">this tutorial</a>.</p> </div> <div class="logged in" style="display: none"> <p>You are logged in!</p> </div> </div> 复制代码
处理它的JS看起来像这样(在public/assets/js/main.js
):
var loggedIn;
(function () {
loggedIn = setLoggedIn(web3.currentProvider !== undefined && web3.eth.accounts.length > 0);
})();
function setLoggedIn(isLoggedIn) {
let loggedInEl = document.querySelector('div.logged.in');
let loggedOutEl = document.querySelector('div.logged.out');
if (isLoggedIn) {
loggedInEl.style.display = "block";
loggedOutEl.style.display = "none";
} else {
loggedInEl.style.display = "none";
loggedOutEl.style.display = "block";
}
return isLoggedIn;
}
复制代码
第一部分——(function () { -
包含一旦网站加载就要执行的逻辑。所以,当页面准备就绪时,内部的任何内容都会当即执行。调用单个函数setLoggedIn
并将条件传递给它条件是:
若是这些条件一块儿评估为true
,则setLoggedIn
函数使“Logged out”消息不可见,而且“Logged In”消息可见。
全部这些都具备可以使用任何其余web3提供商的额外优点。若是最终出现MetaMask替代方案,它将当即与此代码兼容,由于咱们并未明确指望任何地方的MetaMask。
由于以太坊钱包的每一个私钥都是惟一的,因此它可用于生成独特的图像。这是你在MetaMask的右上角或使用MyEtherWallet时看到的彩色化身,尽管Mist,MyEtherWallet和MetaMask都使用不一样的方法。让咱们为登陆用户生成一个并显示它。
Mist中的图标是使用Blockies库生成的——是自定义的,由于原始文件具备损坏的随机数生成器,而且能够为不一样的键生成相同的图像。所以,要安装此文件,请将此文件下载到assets/js
文件夹中。而后,在index.html
咱们在main.js
以前包含它:
<script src="assets/js/app.js"></script>
<script src="assets/js/blockies.min.js"></script>
<script src="assets/js/main.js"></script>
</body>
复制代码
咱们还应该升级logged.in
容器:
<div class="logged in" style="display: none">
<p>You are logged in!</p>
<div class="avatar">
</div>
</div>
复制代码
在main.js,咱们启动该功能。
if (isLoggedIn) {
loggedInEl.style.display = "block";
loggedOutEl.style.display = "none";
var icon = blockies.create({ // All options are optional
seed: web3.eth.accounts[0], // seed used to generate icon data, default: random
size: 20, // width/height of the icon in blocks, default: 8
scale: 8, // width/height of each block in pixels, default: 4
});
document.querySelector("div.avatar").appendChild(icon);
复制代码
所以,咱们升级JS代码的登陆部分以生成图标并将其粘贴到头像部分。咱们应该在渲染以前将它与CSS稍微对齐:
div.avatar { width: 100%; text-align: center; margin: 10px 0; }
复制代码
如今,若是咱们在登陆MetaMask时刷新页面,咱们应该会看到生成的头像图标。
如今让咱们输出一些账户余额信息。
咱们拥有一系列只读功能,咱们专门为此目的而开发。因此让咱们查询区块链并询问一些信息。为此,咱们须要经过如下步骤调用智能合约功能。
获取咱们正在调用的函数的合约的ABI。ABI包含函数签名,所以咱们的JS代码知道如何调用它们。在此处了解有关ABI的更多信息。
你能够经过在编译后打开项目文件夹中的build/TNSToken.json
和build/StoryDao.json
文件并仅选择abi
部分来获取TNS代币和StoryDAO的ABI([
和]
方括号之间的部分):
咱们将这个ABI放在咱们的JavaScript代码的顶部,进入main.js
以下所示:
请注意,上面的屏幕截图显示了个人代码编辑器(Microsoft Visual Code)折叠的缩写插入。若是你查看行号,你会注意到令牌的ABI是400行代码,而DAO的ABI是另外1000行,因此将它粘贴到本文中是没有意义的。
if (loggedIn) {
var token = TNSToken.at('0x3134bcded93e810e1025ee814e87eff252cff422');
var story = StoryDao.at('0x729400828808bc907f68d9ffdeb317c23d2034d5');
token.balanceOf(web3.eth.accounts[0], function(error, result) {console.log(JSON.stringify(result))});
story.getSubmissionCount(function(error, result) {console.log(JSON.stringify(result))});
//...
复制代码
咱们使用Truffle给咱们的地址调用每一个合约,并分别为每一个token
和story
建立一个实例。而后,咱们简单地调用函数(与之前同样异步)。控制台给咱们两个零,由于MetaMask中的账户有0个代币,由于如今故事story中有0个提交。
最后,咱们可使用咱们提供的信息填充用户的我的资料数据。
让咱们更新咱们的JavaScript:
var loggedIn;
(function () {
loggedIn = setLoggedIn(web3.currentProvider !== undefined && web3.eth.accounts.length > 0);
if (loggedIn) {
var token = TNSToken.at('0x3134bcded93e810e1025ee814e87eff252cff422');
var story = StoryDao.at('0x729400828808bc907f68d9ffdeb317c23d2034d5');
token.balanceOf(web3.eth.accounts[0], function(error, result) {console.log(JSON.stringify(result))});
story.getSubmissionCount(function(error, result) {console.log(JSON.stringify(result))});
readUserStats().then(User => renderUserInfo(User));
}
})();
async function readUserStats(address) {
if (address === undefined) {
address = web3.eth.accounts[0];
}
var User = {
numberOfSubmissions: await getSubmissionsCountForUser(address),
numberOfDeletions: await getDeletionsCountForUser(address),
isWhitelisted: await isWhitelisted(address),
isBlacklisted: await isBlacklisted(address),
numberOfProposals: await getProposalCountForUser(address),
numberOfVotes: await getVotesCountForUser(address)
}
return User;
}
function renderUserInfo(User) {
console.log(User);
document.querySelector('#user_submissions').innerHTML = User.numberOfSubmissions;
document.querySelector('#user_deletions').innerHTML = User.numberOfDeletions;
document.querySelector('#user_proposals').innerHTML = User.numberOfProposals;
document.querySelector('#user_votes').innerHTML = User.numberOfVotes;
document.querySelector('dd.user_blacklisted').style.display = User.isBlacklisted ? 'inline-block' : 'none';
document.querySelector('dt.user_blacklisted').style.display = User.isBlacklisted ? 'inline-block' : 'none';
document.querySelector('dt.user_whitelisted').style.display = User.isWhitelisted ? 'inline-block' : 'none';
document.querySelector('dd.user_whitelisted').style.display = User.isWhitelisted ? 'inline-block' : 'none';
}
async function getSubmissionsCountForUser(address) {
if (address === undefined) {
address = web3.eth.accounts[0];
}
return new Promise(function (resolve, reject) {
resolve(0);
});
}
async function getDeletionsCountForUser(address) {
if (address === undefined) {
address = web3.eth.accounts[0];
}
return new Promise(function (resolve, reject) {
resolve(0);
});
}
async function getProposalCountForUser(address) {
if (address === undefined) {
address = web3.eth.accounts[0];
}
return new Promise(function (resolve, reject) {
resolve(0);
});
}
async function getVotesCountForUser(address) {
if (address === undefined) {
address = web3.eth.accounts[0];
}
return new Promise(function (resolve, reject) {
resolve(0);
});
}
async function isWhitelisted(address) {
if (address === undefined) {
address = web3.eth.accounts[0];
}
return new Promise(function (resolve, reject) {
resolve(false);
});
}
async function isBlacklisted(address) {
if (address === undefined) {
address = web3.eth.accounts[0];
}
return new Promise(function (resolve, reject) {
resolve(false);
});
}
复制代码
让咱们更改我的资料信息部分:
<div class="logged in" style="display: none">
<p>You are logged in!</p>
<div class="avatar">
</div>
<dl>
<dt>Submissions</dt>
<dd id="user_submissions"></dd>
<dt>Proposals</dt>
<dd id="user_proposals"></dd>
<dt>Votes</dt>
<dd id="user_votes"></dd>
<dt>Deletions</dt>
<dd id="user_deletions"></dd>
<dt class="user_whitelisted">Whitelisted</dt>
<dd class="user_whitelisted">Yes</dd>
<dt class="user_blacklisted">Blacklisted</dt>
<dd class="user_blacklisted">Yes</dd>
</dl>
</div>
复制代码
你会注意到咱们在获取数据时使用了promises,即便咱们的函数当前只是模拟函数:它们会当即返回平面数据。这是由于每一个函数都须要不一样的时间来获取咱们要求它获取的数据,所以咱们将在填充User
对象以前等待它们完成,而后将其传递给render
函数,该函数更新了屏幕。
若是您对JS承诺不熟悉并但愿了解更多信息,请参阅此帖子。
如今,咱们全部的功能都是嘲笑; 咱们须要先作一些写操做才能阅读。 但首先咱们须要准备好注意那些写做的发生!
为了可以跟踪合约发出的事件,咱们须要监听它们——不然咱们将全部这些emit
语句都放入代码中。咱们构建的模拟UI的中间部分用于保存这些事件。
如下是咱们如何监听区块链发出的事件:
// Events
var WhitelistedEvent = story.Whitelisted(function(error, result) {
if (!error) {
console.log(result);
}
})
复制代码
这里咱们在StoryDao合约的story
实例上调用Whitelisted
函数,并将回调传递给它。每当触发此给定事件时,将自动调用此回调。所以,当用户被列入白名单时,代码将自动将该事件的输出记录到控制台。
可是,这只会获取网络挖掘的最后一个块的最后一个事件。所以,若是从第1块到第10块触发了几个白名单事件,它只会向咱们展现第10块中的那些事件,若是有的话。更好的方法是使用这种方法:
story.Whitelisted({}, { fromBlock: 0, toBlock: 'latest' }).get((error, eventResult) => {
if (error) {
console.log('Error in myEvent event handler: ' + error);
} else {
// eventResult contains list of events!
console.log('Event: ' + JSON.stringify(eventResult[0].args));
}
});
复制代码
注意:将上面的内容放在JS文件底部的一个单独的部分,一个专门用于事件。
在这里,咱们使用get
函数,它容许咱们定义从中获取事件的块范围。咱们使用0到最新,这意味着咱们能够获取此类型的全部事件。可是这增长了与上述监听方法发生冲突的可能。监听方法输出最后一个块的事件,get
方法输出全部这些事件。咱们须要一种方法来使JS忽略双重事件。不要写那些你已经从历史中获取的东西。咱们会进一步作到这一点,但就目前而言,让咱们来处理白名单。
最后,让咱们进行一些写操做。
第一个也是最简单的一个是白名单。请记住,要得到白名单,账户须要向DAO的地址发送至少0.01以太。你将在部署时得到此地址。若是你的Ganache/PoA链在本课程的各个部分之间从新启动,那不要紧,只需使用truffle migrate --reset
从新运行,你就能够得到代币和DAO的新地址。在个人例子中,DAO的地址是0x729400828808bc907f68d9ffdeb317c23d2034d5
,个人代币是0x3134bcded93e810e1025ee814e87eff252cff422
。
设置完全部内容后,让咱们尝试向DAO地址发送必定数量的以太。让咱们尝试0.05以太只是为了好玩,因此咱们能够看看DAO是否为咱们提供额外的计算代币,以支付超额费用。
注意:不要忘记自定义gas量——只需在21000限制之上再拍一个零——使用标记为红色的图标。为何这有必要?由于由简单的ether发送(回调函数)触发的函数执行超过21000的额外逻辑,这对于简单发送就足够了。因此咱们须要达到极限。不要担忧:超出此限制的任何内容都会当即退款。有关gas如何工做的入门读物,请参见[www.sitepoint.com/ethereum-tr…]。
在交易确认后(你将在MetaMask中将其视为“已确认”),咱们能够在MetaMask账户中检查代币金额。咱们首先须要将自定义代币添加到MetaMask中,以便跟踪它们。根据下面的动画,过程以下:选择MetaMask菜单,向下滚动到Add Tokens
,选择Custom Token
,粘贴Truffle
在迁移时给你的代币地址,点击Next
,查看余额是否为ok,而后选择添加Add Tokens
。
对于0.05 eth,咱们应该有400k令牌,咱们这样作。
可是这个事件怎么样?咱们收到了这个白名单的通知吗?咱们来看看控制台吧。
实际上,完整的数据集就在那里——发出事件的地址,块数和挖掘它的hash,等等。其中包括args
对象,它告诉咱们事件数据:addr
是被列入白名单的地址,状态是它是添加到白名单仍是从中删除。成功!
若是咱们如今刷新页面,则事件再次出如今控制台中。可是怎么样?咱们没有将任何新人列入白名单。为何事件会发生警告?EVM中的事件是它们不像JavaScript那样是一次性的事情。固然,它们包含任意数据并仅用做输出,但它们的输出永远在区块链中注册,由于致使它们的交易也永久地在区块链中注册。所以事件将在发出以后保留,这使咱们没必要将它们存储在某处并在页面刷新时调用它们!
如今让咱们将其添加到UI中的事件屏幕!编辑JavaScript文件的Events
部分,以下所示:
// Events
var highestBlock = 0;
var WhitelistedEvent = story.Whitelisted({}, { fromBlock: 0, toBlock: "latest" });
WhitelistedEvent.get((error, eventResult) => {
if (error) {
console.log('Error in Whitelisted event handler: ' + error);
} else {
console.log(eventResult);
let len = eventResult.length;
for (let i = 0; i < len; i++) {
console.log(eventResult[i]);
highestBlock = highestBlock < eventResult[i].blockNumber ? eventResult[i].blockNumber : highestBlock;
printEvent("Whitelisted", eventResult[i]);
}
}
});
WhitelistedEvent.watch(function(error, result) {
if (!error && result.blockNumber > highestBlock) {
printEvent("Whitelisted", result);
}
});
function printEvent(type, object) {
switch (type) {
case "Whitelisted":
let el;
if (object.args.status === true) {
el = "<li>Whitelisted address "+ object.args.addr +"</li>";
} else {
el = "<li>Removed address "+ object.args.addr +" from whitelist!</li>";
}
document.querySelector("ul.eventlist").innerHTML += el;
break;
default:
break;
}
}
复制代码
哇,变得很快,是吧?不用担忧,咱们会澄清。
highestBlock
变量将记住从历史记录中获取的最新块。咱们建立了一个事件的实例,并为它附加了两个监听器。一个是get
,它从历史记录中获取全部事件并记住最新的块。另外一个是watch
,watch
事件“实时”并在最近一个块中出现新事件时触发。只有当刚刚进入的块大于咱们记忆中最高的块时,观察者才会触发,确保只有新事件被附加到事件列表中。
咱们还添加了一个printEvent
函数执行打印事件的操做; 咱们也能够将它重复用于其余类型的事件!
若是咱们如今测试它,确实,咱们能够很好地打印出来。
如今尝试本身作这个,咱们的故事Story能够发出的全部其余事件!看看你是否能够弄清楚如何一次处理它们,而没必要为每一个都写出这个逻辑。(提示:在数组中定义它们的名称,而后遍历这些名称并动态注册事件!)
你还能够经过在MyEtherWallet中打开并调用其whitelist
函数来手动检查StoryBAO的白名单和全部其余公共参数。
你会注意到,若是咱们检查刚刚发送白名单金额的账户,咱们将得到true
回复,代表此账户确实存在于whitelist
映射中。
在将其添加到Web UI以前,使用此相同的功能菜单来试验其余功能。
最后,让咱们从UI进行正确的写函数调用。这一次,咱们将在故事Story中提交一个条目。首先,咱们须要清除咱们在开始时放在那里的示例条目。编辑HTML到这个:
<div class="content container">
<div class="intro">
<h3>Chapter 0</h3>
<p class="intro">It's a rainy night in central London.</p> </div> <hr> <div class="submission_input"> <textarea name="submission-body" id="submission-body-input" rows="5"></textarea> <button id="submission-body-btn">Submit</button> </div> ... 复制代码
还有一些基本的CSS:
.submission_input textarea {
width: 100%;
}
复制代码
咱们添加了一个很是简单的textarea,用户能够经过它提交新条目。
咱们如今来作JS部分吧。
首先,让咱们准备经过添加一个新事件并修改咱们的printEvent
函数来接受这个事件。咱们还能够对整个事件部分进行一些重构,以使其更具可重用性。
// Events
var highestBlock = 0;
var WhitelistedEvent = story.Whitelisted({}, { fromBlock: 0, toBlock: "latest" });
var SubmissionCreatedEvent = story.SubmissionCreated({}, { fromBlock: 0, toBlock: "latest" });
var events = [WhitelistedEvent, SubmissionCreatedEvent];
for (let i = 0; i < events.length; i++) {
events[i].get(historyCallback);
events[i].watch(watchCallback);
}
function watchCallback(error, result) {
if (!error && result.blockNumber > highestBlock) {
printEvent(result.event, result);
}
}
function historyCallback(error, eventResult) {
if (error) {
console.log('Error in event handler: ' + error);
} else {
console.log(eventResult);
let len = eventResult.length;
for (let i = 0; i < len; i++) {
console.log(eventResult[i]);
highestBlock = highestBlock < eventResult[i].blockNumber ? eventResult[i].blockNumber : highestBlock;
printEvent(eventResult[i].event, eventResult[i]);
}
}
}
function printEvent(type, object) {
let el;
switch (type) {
case "Whitelisted":
if (object.args.status === true) {
el = "<li>Whitelisted address "+ object.args.addr +"</li>";
} else {
el = "<li>Removed address "+ object.args.addr +" from whitelist!</li>";
}
document.querySelector("ul.eventlist").innerHTML += el;
break;
case "SubmissionCreated":
el = "<li>User " + object.args.submitter + " created a"+ ((object.args.image) ? "n image" : " text") +" entry: #" + object.args.index + " of content " + object.args.content+"</li>";
document.querySelector("ul.eventlist").innerHTML += el;
break;
default:
break;
}
}
复制代码
如今咱们须要作的就是添加一个全新的事件来实例化它,而后为它定义一个case。
接下来,让咱们提交。
document.getElementById("submission-body-btn").addEventListener("click", function(e) {
if (!loggedIn) {
return false;
}
var text = document.getElementById("submission-body-input").value;
text = web3.toHex(text);
story.createSubmission(text, false, {value: 0, gas: 400000}, function(error, result) {
refreshSubmissions();
});
});
function refreshSubmissions() {
story.getAllSubmissionHashes(function(error, result){
console.log(result);
});
}
复制代码
在这里,咱们向提交表单添加一个事件监听器,一旦提交,首先拒绝全部用户未登陆的内容,而后抓取内容并将其转换为十六进制格式(这是咱们须要将值存储为bytes
)。
最后,它经过调用createSubmission
函数并提供两个参数来建立交易:条目的文本和false标记(意思即不是图像)。第三个参数是交易设置:值表示要发送多少以太,而gas表示你想要默认的gas限制量。这能够在客户端(MetaMask)中手动更改,但这是一个很好的起点,以确保咱们不会遇到限制。最后一个参数是咱们如今已经习惯的回调函数,这个回调函数将调用一个刷新函数来加载故事Story的全部提交。目前,此刷新功能仅加载故事story哈希并将它们放入控制台,以便咱们检查一切是否正常。
注意:以太量为0,由于第一个条目是免费的。进一步的条目将须要添加以太币。咱们将动态计算留给你看成业。提示:为此目的,咱们的DAO中有一个calculateSubmissionFee
函数。
此时,咱们须要在JS的顶部更改一些在页面加载时自动执行的函数:
if (loggedIn) {
token.balanceOf(web3.eth.accounts[0], function(error, result) {console.log(JSON.stringify(result))});
story.getSubmissionCount(function(error, result) {console.log(JSON.stringify(result))});
web3.eth.defaultAccount = web3.eth.accounts[0]; // CHANGE
readUserStats().then(User => renderUserInfo(User));
refreshSubmissions(); // CHANGE
} else {
document.getElementById("submission-body-btn").disabled = "disabled";
}
复制代码
更改标记为//CHANGE
:第一个容许咱们设置执行交易的默认账户。这可能会在将来的Web3
版本中默认使用。第二个刷新页面加载时提交的内容,所以咱们在网站打开时得到一个完整的故事story。
若是你如今尝试提交条目,MetaMask应在你单击“提交”后当即打开,并要求你确认提交。
你还应该在事件部分中看到事件打印出来。
控制台应该回显这个新条目的哈希值。
注意:MetaMask目前在私有网络和nonce
方面存在问题。它在这里描述并将很快修复,但若是nonce
在提交条目时在JavaScript控制台中收到错误,那么目前的权宜之计解决方案是从新安装MetaMask(禁用和启用将不起做用)。请记住首先备份你的种子SEED:你须要它来从新导入你的MetaMask账户!
最后,让咱们获取这些条目并显示它们。让咱们从一些CSS开始:
.content-submissions .submission-submitter { font-size: small; }
复制代码
如今让咱们更新一下这个refreshSubmissions
功能:
复制代码
咱们浏览全部提交内容,获取它们的哈希值,获取每一个哈希值,而后在屏幕上输出。若是提交者与登陆用户相同,则打印“你”而不是地址。
让咱们添加另外一个条目进行测试。
在这一部分中,咱们为DApp开发了基本前端的开端。
因为开发完整的前端应用程序也能够成为它本身的一个过程,咱们将做为家庭做业留给你进一步的发展。只需调用所演示的函数,将它们绑定到常规JavaScript流程中(经过像VueJS这样的框架或普通的旧jQuery或像咱们上面所作的原生JS)并将它们绑定在一块儿。它实际上就像与标准服务器API交谈。若是你遇到困难,请查看代码的项目仓库!
能够执行的其余升级:
在下一部分和最后一部分中,咱们将专一于将咱们的项目部署到实时互联网。敬请关注!
======================================================================
分享一些以太坊、EOS、比特币等区块链相关的交互式在线编程实战教程:
- java以太坊开发教程,主要是针对java和android程序员进行区块链以太坊开发的web3j详解。
- python以太坊,主要是针对python工程师使用web3.py进行区块链以太坊开发的详解。
- php以太坊,主要是介绍使用php进行智能合约开发交互,进行帐号建立、交易、转帐、代币开发以及过滤器和交易等内容。
- 以太坊入门教程,主要介绍智能合约与dapp应用开发,适合入门。
- 以太坊开发进阶教程,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。
- C#以太坊,主要讲解如何使用C#开发基于.Net的以太坊应用,包括帐户管理、状态与交易、智能合约开发与交互、过滤器和交易等。
- EOS教程,本课程帮助你快速入门EOS区块链去中心化应用的开发,内容涵盖EOS工具链、帐户与钱包、发行代币、智能合约开发与部署、使用代码与智能合约交互等核心知识点,最后综合运用各知识点完成一个便签DApp的开发。
- java比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Java代码中集成比特币支持功能,例如建立地址、管理钱包、构造裸交易等,是Java工程师不可多得的比特币开发学习课程。
- php比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Php代码中集成比特币支持功能,例如建立地址、管理钱包、构造裸交易等,是Php工程师不可多得的比特币开发学习课程。
- tendermint区块链开发详解,本课程适合但愿使用tendermint进行区块链开发的工程师,课程内容即包括tendermint应用开发模型中的核心概念,例如ABCI接口、默克尔树、多版本状态库等,也包括代币发行等丰富的实操代码,是go语言工程师快速入门区块链开发的最佳选择。
汇智网原创翻译,转载请标明出处。这里是原文以太坊构建DApps系列教程(七):为DAO合约构建Web3 UI