Last active
April 7, 2019 17:51
-
-
Save Maxcutex/cfd840ae6cde059e21ef7f7fc834f7ff to your computer and use it in GitHub Desktop.
Election Voting App
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<form onSubmit="App.castVote(); return false;"> | |
<div class="form-group"> | |
<label for="candidatesSelect">Select Candidate</label> | |
<select class="form-control" id="candidatesSelect"> | |
</select> | |
</div> | |
<button type="submit" class="btn btn-primary">Vote</button> | |
<hr /> | |
</form> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
App = { | |
web3Provider: null, | |
contracts: {}, | |
account: '0x0', | |
init: function() { | |
return App.initWeb3(); | |
}, | |
initWeb3: function() { | |
if (typeof web3 !== 'undefined') { | |
// If a web3 instance is already provided by Meta Mask. | |
App.web3Provider = web3.currentProvider; | |
web3 = new Web3(web3.currentProvider); | |
} else { | |
// Specify default instance if no web3 instance provided | |
App.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545'); | |
web3 = new Web3(App.web3Provider); | |
} | |
return App.initContract(); | |
}, | |
initContract: function() { | |
$.getJSON("Election.json", function(election) { | |
// Instantiate a new truffle contract from the artifact | |
App.contracts.Election = TruffleContract(election); | |
// Connect provider to interact with contract | |
App.contracts.Election.setProvider(App.web3Provider); | |
return App.render(); | |
}); | |
}, | |
render: function() { | |
var electionInstance; | |
var loader = $("#loader"); | |
var content = $("#content"); | |
loader.show(); | |
content.hide(); | |
// Load account data | |
web3.eth.getCoinbase(function(err, account) { | |
if (err === null) { | |
App.account = account; | |
$("#accountAddress").html("Your Account: " + account); | |
} | |
}); | |
// Load contract data | |
App.contracts.Election.deployed().then(function(instance) { | |
electionInstance = instance; | |
return electionInstance.candidatesCount(); | |
}).then(function(candidatesCount) { | |
var candidatesResults = $("#candidatesResults"); | |
candidatesResults.empty(); | |
for (var i = 1; i <= candidatesCount; i++) { | |
electionInstance.candidates(i).then(function(candidate) { | |
var id = candidate[0]; | |
var name = candidate[1]; | |
var voteCount = candidate[2]; | |
// Render candidate Result | |
var candidateTemplate = "<tr><th>" + id + "</th><td>" + name + "</td><td>" + voteCount + "</td></tr>" | |
candidatesResults.append(candidateTemplate); | |
}); | |
} | |
loader.hide(); | |
content.show(); | |
}).catch(function(error) { | |
console.warn(error); | |
}); | |
} | |
}; | |
$(function() { | |
$(window).load(function() { | |
App.init(); | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
assert.equal(receipt.logs.length, 1, "an event was triggered"); | |
assert.equal(receipt.logs[0].event, "votedEvent", "the event type is correct"); | |
assert.equal(receipt.logs[0].args._candidateId.toNumber(), candidateId, "the candidate id is correct"); | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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"> | |
<title>Election Results</title> | |
<!-- Bootstrap --> | |
<link href="css/bootstrap.min.css" rel="stylesheet"> | |
</head> | |
<body> | |
<div class="container" style="width: 650px;"> | |
<div class="row"> | |
<div class="col-lg-12"> | |
<h1 class="text-center">Election Results</h1> | |
<hr/> | |
<br/> | |
<div id="loader"> | |
<p class="text-center">Loading...</p> | |
</div> | |
<div id="content" style="display: none;"> | |
<table class="table"> | |
<thead> | |
<tr> | |
<th scope="col">#</th> | |
<th scope="col">Name</th> | |
<th scope="col">Votes</th> | |
</tr> | |
</thead> | |
<tbody id="candidatesResults"> | |
</tbody> | |
</table> | |
<hr/> | |
<p id="accountAddress" class="text-center"></p> | |
</div> | |
</div> | |
</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="js/bootstrap.min.js"></script> | |
<script src="js/web3.min.js"></script> | |
<script src="js/truffle-contract.js"></script> | |
<script src="js/app.js"></script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var Election = artifacts.require("./Election.sol"); | |
contract("Election", function(accounts) { | |
var electionInstance; | |
it("initializes with two candidates", function() { | |
return Election.deployed().then(function(instance) { | |
return instance.candidatesCount(); | |
}).then(function(count) { | |
assert.equal(count, 2); | |
}); | |
}); | |
it("it initializes the candidates with the correct values", function() { | |
return Election.deployed().then(function(instance) { | |
electionInstance = instance; | |
return electionInstance.candidates(1); | |
}).then(function(candidate) { | |
assert.equal(candidate[0], 1, "contains the correct id"); | |
assert.equal(candidate[1], "Candidate 1", "contains the correct name"); | |
assert.equal(candidate[2], 0, "contains the correct votes count"); | |
return electionInstance.candidates(2); | |
}).then(function(candidate) { | |
assert.equal(candidate[0], 2, "contains the correct id"); | |
assert.equal(candidate[1], "Candidate 2", "contains the correct name"); | |
assert.equal(candidate[2], 0, "contains the correct votes count"); | |
}); | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
listenForEvents: function() { | |
App.contracts.Election.deployed().then(function(instance) { | |
instance.votedEvent({}, { | |
fromBlock: 0, | |
toBlock: 'latest' | |
}).watch(function(error, event) { | |
console.log("event triggered", event) | |
// Reload when a new vote is recorded | |
App.render(); | |
}); | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity 0.4.2; | |
contract Election { | |
// Read/write candidate | |
string public candidate; | |
// Constructor | |
function Election () public { | |
candidate = "Candidate 1"; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
it("allows a voter to cast a vote", function() { | |
return Election.deployed().then(function(instance) { | |
electionInstance = instance; | |
candidateId = 1; | |
return electionInstance.vote(candidateId, { from: accounts[0] }); | |
}).then(function(receipt) { | |
return electionInstance.voters(accounts[0]); | |
}).then(function(voted) { | |
assert(voted, "the voter was marked as voted"); | |
return electionInstance.candidates(candidateId); | |
}).then(function(candidate) { | |
var voteCount = candidate[2]; | |
assert.equal(voteCount, 1, "increments the candidate's vote count"); | |
}) | |
}); | |
// throws exception | |
it("throws an exception for invalid candidates", function() { | |
return Election.deployed().then(function(instance) { | |
electionInstance = instance; | |
return electionInstance.vote(99, { from: accounts[1] }) | |
}).then(assert.fail).catch(function(error) { | |
assert(error.message.indexOf('revert') >= 0, "error message must contain revert"); | |
return electionInstance.candidates(1); | |
}).then(function(candidate1) { | |
var voteCount = candidate1[2]; | |
assert.equal(voteCount, 1, "candidate 1 did not receive any votes"); | |
return electionInstance.candidates(2); | |
}).then(function(candidate2) { | |
var voteCount = candidate2[2]; | |
assert.equal(voteCount, 0, "candidate 2 did not receive any votes"); | |
}); | |
}); | |
// | |
it("throws an exception for double voting", function() { | |
return Election.deployed().then(function(instance) { | |
electionInstance = instance; | |
candidateId = 2; | |
electionInstance.vote(candidateId, { from: accounts[1] }); | |
return electionInstance.candidates(candidateId); | |
}).then(function(candidate) { | |
var voteCount = candidate[2]; | |
assert.equal(voteCount, 1, "accepts first vote"); | |
// Try to vote again | |
return electionInstance.vote(candidateId, { from: accounts[1] }); | |
}).then(assert.fail).catch(function(error) { | |
assert(error.message.indexOf('revert') >= 0, "error message must contain revert"); | |
return electionInstance.candidates(1); | |
}).then(function(candidate1) { | |
var voteCount = candidate1[2]; | |
assert.equal(voteCount, 1, "candidate 1 did not receive any votes"); | |
return electionInstance.candidates(2); | |
}).then(function(candidate2) { | |
var voteCount = candidate2[2]; | |
assert.equal(voteCount, 1, "candidate 2 did not receive any votes"); | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
castVote: function() { | |
var candidateId = $('#candidatesSelect').val(); | |
App.contracts.Election.deployed().then(function(instance) { | |
return instance.vote(candidateId, { from: App.account }); | |
}).then(function(result) { | |
// Wait for votes to update | |
$("#content").hide(); | |
$("#loader").show(); | |
}).catch(function(err) { | |
console.error(err); | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
render: function() { | |
var electionInstance; | |
var loader = $("#loader"); | |
var content = $("#content"); | |
loader.show(); | |
content.hide(); | |
// Load account data | |
web3.eth.getCoinbase(function(err, account) { | |
if (err === null) { | |
App.account = account; | |
$("#accountAddress").html("Your Account: " + account); | |
} | |
}); | |
// Load contract data | |
App.contracts.Election.deployed().then(function(instance) { | |
electionInstance = instance; | |
return electionInstance.candidatesCount(); | |
}).then(function(candidatesCount) { | |
var candidatesResults = $("#candidatesResults"); | |
candidatesResults.empty(); | |
var candidatesSelect = $('#candidatesSelect'); | |
candidatesSelect.empty(); | |
for (var i = 1; i <= candidatesCount; i++) { | |
electionInstance.candidates(i).then(function(candidate) { | |
var id = candidate[0]; | |
var name = candidate[1]; | |
var voteCount = candidate[2]; | |
// Render candidate Result | |
var candidateTemplate = "<tr><th>" + id + "</th><td>" + name + "</td><td>" + voteCount + "</td></tr>" | |
candidatesResults.append(candidateTemplate); | |
// Render candidate ballot option | |
var candidateOption = "<option value='" + id + "' >" + name + "</ option>" | |
candidatesSelect.append(candidateOption); | |
}); | |
} | |
return electionInstance.voters(App.account); | |
}).then(function(hasVoted) { | |
// Do not allow a user to vote | |
if(hasVoted) { | |
$('form').hide(); | |
} | |
loader.hide(); | |
content.show(); | |
}).catch(function(error) { | |
console.warn(error); | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function vote (uint _candidateId) public { | |
// require that they haven't voted before | |
require(!voters[msg.sender]); | |
// require a valid candidate | |
require(_candidateId > 0 && _candidateId <= candidatesCount); | |
// record that voter has voted | |
voters[msg.sender] = true; | |
// update candidate vote Count | |
candidates[_candidateId].voteCount ++; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment