I am building a Django e-commerce website and as I was working on my cart functionality I noticed that every time I try to change the quantity of a specific item in the cart, all the items in the cart are re-sorted in a different order. I was wondering if there's any way I can sort the items in the backend before they are displayed.
** I am getting this error only when the user is "Authenticated" ** Guest checkout is working correctly
This is my cart Views.py
def cart(request):
# Authenticated Checkout
if request.user.is_authenticated:
customer = request.user.customer
order, created = Order.objects.get_or_create(customer=customer, complete=False)
cartItems = order.get_cart_items
items = order.orderitem_set.all()
if cartItems == 0:
context = {"items": items, "order": order, "cartItems": cartItems}
return render(request, "cart_empty.html", context)
#Guest Checkout
else:
data = cartData(request)
cartItems = data["cartItems"]
order = data["order"]
items = data["items"]
if cartItems == 0:
context = {"items": items, "order": order, "cartItems": cartItems}
return render(request, "cart_empty.html", context)
context = {"items":items, "order": order, "cartItems":cartItems}
return render(request, "cart.html", context)
def update_cart(request):
data = json.loads(request.body)
productId = data["productId"]
action = data["action"]
customer = request.user.customer
product = Product.objects.get(id=productId)
order, created = Order.objects.get_or_create(customer=customer, complete=False)
orderItem, created = OrderItem.objects.get_or_create(order=order, product=product)
if action == "add":
orderItem.quantity = (orderItem.quantity + 1)
elif action == "reduce":
orderItem.quantity = (orderItem.quantity - 1)
orderItem.save()
if action == "remove":
orderItem = orderItem.delete()
return JsonResponse("item was added", safe=False)
This is My JavaScript File
// Getting the cart cookie Value stored in the browser
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for(var i=0; i<ca.length; i++) {
var c = ca[i];
while (c.charAt(0)==' ') c = c.substring(1);
if (c.indexOf(name) == 0) return c.substring(name.length,c.length);
}
return "";
}
var cart = JSON.parse(getCookie("cart"));
// Finding all the Minus buttons on the page and adding a click event listener to each one
const reduce_btn = document.getElementsByClassName("btn minus1 update-cart")
for (i = 0; i < reduce_btn.length; i++) {
reduce_btn[i].addEventListener("click", function(event){
var productId = this.dataset.id
var action = this.dataset.action
if (user === "AnonymousUser"){
addCookieItem(productId, action)
} else {
updateUserOder(productId,action)
}
})
}
// Finding all the Plus buttons on the page and adding a click event listener to each one
const increase_btn = document.getElementsByClassName("btn add1 update-cart")
for (i = 0; i < increase_btn.length; i++){
increase_btn[i].addEventListener("click", function(event){
var productId = this.dataset.id
var action = this.dataset.action
if (user === "AnonymousUser"){
addCookieItem(productId, action)
} else {
updateUserOder(productId,action)
}
})
}
// Finding all the remove buttons on the page and adding a click event listener to each one
const removeButton = document.getElementsByClassName("removeButton")
for (i = 0; i < removeButton.length; i++) {
removeButton[i].addEventListener("click", function(event){
var productId = this.dataset.id
var action = this.dataset.action
if (user === "AnonymousUser"){
removeFromCart(productId, action)
} else {
updateUserOder(productId,action)
}
})
}
// Removing the product from the order if the remove button is clicked
function removeFromCart(productId, action) {
if (action == 'remove'){
delete cart[productId];
}
document.cookie ='cart=' + JSON.stringify(cart) + ";domain=;path=/"
location.reload()
}
// (Guest Checkout) Updating the order of the customer if he is not authenticated
function addCookieItem(productId, action) {
console.log("not logged in..........")
if (action == 'add'){
cart[productId]['quantity'] += 1
}
if (action == 'reduce'){
cart[productId]['quantity'] -= 1
}
document.cookie ='cart=' + JSON.stringify(cart) + ";domain=;path=/"
location.reload()
}
// csrf token
var csrftoken = localStorage.getItem("csrftoken")
// (Authenticated Checkout) Updating the order of the customer if he is Authenticated
function updateUserOder(productId, action){
var url = "http://127.0.0.1:8000/update_cart/"
fetch(url, {
method: "POST",
headers: {
"Content-Type":"application/json",
"X-CSRFToken":csrftoken,
},
body:JSON.stringify({"productId": productId, "action": action})
})
.then((response) =>{
return response.json();
})
.then((data) => {
location.reload()
})
}
This is my HTML File
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="UTF-8" name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cart</title>
<link rel="stylesheet" type="text/css" href="{% static 'css/cart.css' %}">
<script type="text/javascript">
var user = "{{request.user}}"
</script>
</head>
<body>
<!-- navigation menu -->
{% include "topnav.html" %}
<div class="maincontent">
<h1 class="hhh1" style="font-size:50px; margin:0px; margin-bottom:30px">Your Cart</h1>
<div class="centerblock">
<!-- DYNAMIC CONTENT GOES HERE -->
<div class="cart-row">
<span class="cart-item cart-header cart-column">ITEM</span>
<span class="cart-header cart-column">PRICE</span>
<span class="cart-quantity cart-header cart-column">QUANTITY</span>
</div>
<div class="cart-items">
{% for item in items %}
<div>
<div class="cart-item cart-column">
<img class="cart-item-image" src="{{item.product.imageURL}}" width="100" height="100">
<span class="cart-item-title">{{item.product.title}}</span>
</div>
<span class="cart-price cart-column" data-price="{{item.product.price}}" >£{{item.product.price}}</span>
<div class="cart-quantity cart-column">
<div class="quantity">
<button data-id={{item.product.id}} data-action="reduce" id="minus" class="btn minus1 update-cart" >-</button>
<input class="cart-quantity-input quantity" type="number" id="id_form-0-quantity" name="quantity" min="1" max="5" value="{{item.quantity}}">
<button data-id={{item.product.id}} data-action="add" id="plus" class="btn add1 update-cart" >+</button>
<button data-id={{item.product.id}} data-action="remove" class="removeButton" type="button">REMOVE</button>
</div>
</div>
</div>
{% endfor %}
<div class="cart-total">
<strong class="cart-total-title">Total</strong>
<span class="cart-total-price">£{{order.get_cart_total}}</span>
</div>
<div class="ordersummary">
<form action="checkout-info" method="post">
{% csrf_token %}
<input type="submit" value="CHECKOUT">
</form>
</div>
</div>
</div>
</div>
{% include "footer.html" %}
<script src="{% static 'js/test2.js' %}" ></script>
</body>
</html>
Assuming you want them sorted in the reverse order they were added to the cart, you can sort them by their id field:
items = order.orderitem_set.order_by('-id')
Related
I'm trying to learn this tutorial, the custom payment flow last bit to integrate stripe with Django
https://justdjango.com/blog/django-stripe-payments-tutorial
in my views.py, I have these views
class StripeIntentView(View):
def post(self, request, *args, **kwargs):
try:
req_json = json.loads(request.body)
customer = stripe.Customer.create(email=req_json['email'])
price = Price.objects.get(id=self.kwargs["pk"])
intent = stripe.PaymentIntent.create(
amount=price.price,
currency='usd',
customer=customer['id'],
metadata={
"price_id": price.id
}
)
return JsonResponse({
'clientSecret': intent['client_secret']
})
except Exception as e:
return JsonResponse({'error': str(e)})
class CustomPaymentView(TemplateView):
template_name = "custom_payment.html"
def get_context_data(self, **kwargs):
product = Product.objects.get(name="Test Product")
prices = Price.objects.filter(product=product)
context = super(CustomPaymentView, self).get_context_data(**kwargs)
context.update({
"product": product,
"prices": prices,
"STRIPE_PUBLIC_KEY": settings.STRIPE_PUBLIC_KEY
})
return context
and in my urls I have
from django.contrib import admin
from django.urls import path
from products.views import stripe_webhook
from products.views import StripeIntentView, CustomPaymentView
urlpatterns = [
path('admin/', admin.site.urls),
path('create-payment-intent/<pk>/', StripeIntentView.as_view(), name='create-payment-intent'),
path('custom-payment/', CustomPaymentView.as_view(), name='custom-payment')
and in my custom_payment.html I have
{% load static %}
<!DOCTYPE html>
<html>
<head>
<title>Custom payment</title>
<script src="https://polyfill.io/v3/polyfill.min.js?version=3.52.1&features=fetch"></script>
<script src="https://js.stripe.com/v3/"></script>
<link rel="stylesheet" href="{% static 'products/global.css' %}">
</head>
<body>
<section>
<div class="product">
<div class="description">
<h3>{{ product.name }}</h3>
<hr />
<select id='prices'>
{% for price in prices %}
<option value="{{ price.id }}">${{ price.get_display_price }}</option>
{% endfor %}
</select>
</div>
<form id="payment-form">{% csrf_token %}
<input type="text" id="email" placeholder="Email address" />
<div id="card-element">
<!--Stripe.js injects the Card Element-->
</div>
<button id="submit">
<div class="spinner hidden" id="spinner"></div>
<span id="button-text">Pay</span>
</button>
<p id="card-error" role="alert"></p>
<p class="result-message hidden">
Payment succeeded, see the result in your
Stripe dashboard. Refresh the page to
pay again.
</p>
</form>
</div>
</section>
<script>
var csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
var stripe = Stripe("{{ STRIPE_PUBLIC_KEY }}");
document.querySelector("button").disabled = true;
var elements = stripe.elements();
var style = {
base: {
color: "#32325d",
fontFamily: 'Arial, sans-serif',
fontSmoothing: "antialiased",
fontSize: "16px",
"::placeholder": {
color: "#32325d"
}
},
invalid: {
fontFamily: 'Arial, sans-serif',
color: "#fa755a",
iconColor: "#fa755a"
}
};
var card = elements.create("card", { style: style });
// Stripe injects an iframe into the DOM
card.mount("#card-element");
card.on("change", function (event) {
// Disable the Pay button if there are no card details in the Element
document.querySelector("button").disabled = event.empty;
document.querySelector("#card-error").textContent = event.error ? event.error.message : "";
});
var form = document.getElementById("payment-form");
form.addEventListener("submit", function(event) {
event.preventDefault();
var selectedPrice = document.getElementById("prices").value
// Complete payment when the submit button is clicked
fetch(`/create-payment-intent/${selectedPrice}/`, {
method: "POST",
headers: {
"Content-Type": "application/json",
'X-CSRFToken': csrftoken
},
body: JSON.stringify({
email: document.getElementById('email').value
})
})
.then(function(result) {
return result.json();
})
.then(function(data) {
payWithCard(stripe, card, data.clientSecret);
});
});
// Calls stripe.confirmCardPayment
// If the card requires authentication Stripe shows a pop-up modal to
// prompt the user to enter authentication details without leaving your page.
var payWithCard = function(stripe, card, clientSecret) {
loading(true);
stripe
.confirmCardPayment(clientSecret, {
payment_method: {
card: card
}
})
.then(function(result) {
if (result.error) {
// Show error to your customer
showError(result.error.message);
} else {
// The payment succeeded!
orderComplete(result.paymentIntent.id);
}
});
};
/* ------- UI helpers ------- */
// Shows a success message when the payment is complete
var orderComplete = function(paymentIntentId) {
loading(false);
document
.querySelector(".result-message a")
.setAttribute(
"href",
"https://dashboard.stripe.com/test/payments/" + paymentIntentId
);
document.querySelector(".result-message").classList.remove("hidden");
document.querySelector("button").disabled = true;
};
// Show the customer the error from Stripe if their card fails to charge
var showError = function(errorMsgText) {
loading(false);
var errorMsg = document.querySelector("#card-error");
errorMsg.textContent = errorMsgText;
setTimeout(function() {
errorMsg.textContent = "";
}, 4000);
};
// Show a spinner on payment submission
var loading = function(isLoading) {
if (isLoading) {
// Disable the button and show a spinner
document.querySelector("button").disabled = true;
document.querySelector("#spinner").classList.remove("hidden");
document.querySelector("#button-text").classList.add("hidden");
} else {
document.querySelector("button").disabled = false;
document.querySelector("#spinner").classList.add("hidden");
document.querySelector("#button-text").classList.remove("hidden");
}
};
</script>
</body>
</html>
The tutorial was missing a csrf token so I added that and the card element loaded up, and also I had to add an id of prices to the select
Then I got this error
(index):1 Uncaught (in promise) IntegrationError: Missing value for stripe.confirmCardPayment intent secret: value should be a client_secret string.
at X ((index):1)
at Q ((index):1)
at uo ((index):1)
at (index):1
at (index):1
at e.<anonymous> ((index):1)
at e.confirmCardPayment ((index):1)
at payWithCard ((index):104)
at (index):94
Can anyone help me with this ? Thanks
i would suggest adding an additional line to check what is the value of data (and data.clientSecret). It looks like clientSecret may not have a value, or may not be a string.
.then(function(data) {
console.log(data);
payWithCard(stripe, card, data.clientSecret);
});
You would then need to trace why clientSecret does not have the expected value.
I found difficult to change the color of the like button when the button hit and increase by +1 (in likes) in Django using ajax
my html template
<form method="POST" action="{% url 'video:like' video.pk %}" id="my-like-form">
{% csrf_token %}
<input type="hidden" class="likin" name="next" value="{{ request.path }}">
<button class="remove-default-btn" type="submit" id="openPopup" class="like-btn{{ request.path }}" style="border: none; ">
<i class="fa fa-thumbs-up" aria-hidden="true"><span>{{ video.likes.all.count }}</span></i>
Like
</button>
JavaScript
$("#my-like-form").submit(function(e){
e.preventDefault(); // Prevent form submission
let form = $(this);
let url = form.attr("action");
let res
const likes = $(`.like-btn{{ request.path }}`).text();// this code stopping the function of like button from work
const trimCount = parseInt(likes)
$.ajax({
type: "POST",
url: url,
data: form.serialize(),
dataType: "json",
success: function(response) {
selector = document.getElementsByName(response.next);
if(response.liked==true){
$(selector).css("color","blue");
res = trimCount - 1
} else if(response.liked==false){
$(selector).css("color","black");
res = trimCount + 1
}
}
})
})
Instead of using jinja code inside jquery code you can simple use $(this).find("button[class*=like-btn] span") this will give you span tag which have your total likes count then using .text() add new count to span tag.
Demo Code :
//suppose this return from server
var response = {
"liked": true
}
$("#my-like-form").submit(function(e) {
e.preventDefault();
let form = $(this);
let url = form.attr("action");
let res
//get like button and then find span to get total value
const likes = $(this).find("button[class*=like-btn] span").text();
const trimCount = parseInt(likes)
console.log(trimCount)
var selector = $(this).find("button[class*=like-btn]") //to select that button
/* $.ajax({
type: "POST",
url: url,
data: form.serialize(),
dataType: "json",
success: function(response) {
*/
if (response.liked == true) {
$(selector).css("color", "blue");
res = trimCount + 1
} else if (response.liked == false) {
$(selector).css("color", "black");
res = trimCount - 1
}
$(selector).find("span").text(res) //add that value inside span
/*}
})*/
})
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.2/css/all.min.css" integrity="sha512-HK5fgLBL+xu6dm/Ii3z4xhlSUyZgTT9tuc/hSrtw6uzJOvgRr2a9jyxxT1ely+B+xFAmJKVSTbpM/CuL7qxO8w==" crossorigin="anonymous" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form method="POST" action="{% url 'video:like' video.pk %}" id="my-like-form">
{% csrf_token %}
<input type="hidden" class="likin" name="next" value="{{ request.path }}">
<!--you have two clas attribute merge that in one here `1` is just for demo replace that with like-btn{{ request.path }} -->
<button class="remove-default-btn like-btn1" type="submit" id="openPopup" style="border: none; ">
<i class="fa fa-thumbs-up" aria-hidden="true"><span>23</span></i>
Like
</button>
</form>
i have a formset inside my form on django and i need to do a button add to offer to the user the possibility to fill the fields of the formset as many time as he wants. i used a javascript code to do that and i do all the following steps to link the js file with the project, but when i click on the button nothing happens, i don't know why. please if anyone could help me.
i add staticfiles on my settings
STATIC_URL = '/static/'
STATICFILES_DIRS = (BASE_DIR / "static",)
part of my html file
{% load static %}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ordonnancement </title>
...
<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
</head>
<body class="bg-gray-100">
<form action="/" method="post" class="join-form mt-8 m-auto">
{% csrf_token %}
...
{% for form_data in formset %}
<div class="mt-4 w-1/2 mx-1">
{{ form_data.nom_qualité }}
</div>
<div class="mt-4 w-1/2 mx-1">
{{ form_data.id_qualité }}
</div>
<div class="mt-4 w-1/2 mx-1">
{{ form_data.date_début }}
</div>
<div class="mt-4 w-1/2 mx-1">
{{ form_data.date_fin }}
</div>
{% endfor %}
<button type="button" class="btn btn-sm btn-success add-form-row"
id="{{ formset.prefix }}">
ajouter
</button>
{{ formset.management_form }}
...
</form>
<script type="text/javascript" src="{% static 'js/formset.js' %}">
</script>
js file
function updateElementIndex(el, prefix, ndx) {
var id_regex = new RegExp('(' + prefix + '-\\d+-)');
var replacement = prefix + '-' + ndx + '-';
if ($(el).attr("for")) $(el).attr("for", $(el).attr("for").replace(id_regex,
replacement));
if (el.id) el.id = el.id.replace(id_regex, replacement);
if (el.name) el.name = el.name.replace(id_regex, replacement);
}
function addForm(btn, prefix) {
var formCount = parseInt($('#id_' + prefix + '-TOTAL_FORMS').val());
if (formCount < 1000) {
// Clone a form (without event handlers) from the first form
var row = $(".item:last").clone(false).get(0);
// Insert it after the last form
$(row).removeAttr('id').hide().insertAfter(".item:last").slideDown(300);
// Remove the bits we don't want in the new row/form
// e.g. error messages
$(".errorlist", row).remove();
$(row).children().removeClass("error");
// Relabel or rename all the relevant bits
$(row).find('.formset-field').each(function () {
updateElementIndex(this, prefix, formCount);
$(this).val('');
$(this).removeAttr('value');
$(this).prop('checked', false);
});
// Add an event handler for the delete item/form link
$(row).find(".delete").click(function () {
return deleteForm(this, prefix);
});
// Update the total form count
$("#id_" + prefix + "-TOTAL_FORMS").val(formCount + 1);
} // End if
return false;
}
function deleteForm(btn, prefix) {
var formCount = parseInt($('#id_' + prefix + '-TOTAL_FORMS').val());
if (formCount > 1) {
// Delete the item/form
var goto_id = $(btn).find('input').val();
if( goto_id ){
$.ajax({
url: "/" + window.location.pathname.split("/")[1] + "/formset-data-delete/"+ goto_id +"/
next="+ window.location.pathname,
error: function () {
console.log("error");
},
success: function (data) {
$(btn).parents('.item').remove();
},
type: 'GET'
});
}else{
$(btn).parents('.item').remove();
}
var forms = $('.item'); // Get all the forms
// Update the total number of forms (1 less than before)
$('#id_' + prefix + '-TOTAL_FORMS').val(forms.length);
var i = 0;
// Go through the forms and set their indices, names and IDs
for (formCount = forms.length; i < formCount; i++) {
$(forms.get(i)).find('.formset-field').each(function () {
updateElementIndex(this, prefix, i);
});
}
} // End if
return false;
}
$("body").on('click', '.remove-form-row',function () {
deleteForm($(this), String($('.add-form-row').attr('id')));
});
$("body").on('click', '.add-form-row',function () {
return addForm($(this), String($(this).attr('id')));
});
I am trying to make a form which includes the functionality of Add/Delete Row. And I am following this tutorial.
The problem that I am facing is that I am unable to show the Add or remove button as well as the input fields in the form.
Screenshot:
Here's the template:
<html>
<head>
<title>gffdfdf</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<form action="" method="post" class="">
{% csrf_token %}
<h2> Team</h2>
{% for field in form %}
{{ field.errors }}
{{ field.label_tag }} : {{ field }}
{% endfor %}
{{ form.players.management_form }}
<h3> Product Instance(s)</h3>
<table id="table-product" class="table">
<thead>
<tr>
<th>player name</th>
<th>highest score</th>
<th>age</th>
</tr>
</thead>
{% for player in form.players %}
<tbody class="player-instances">
<tr>
<td>{{ player.pname }}</td>
<td>{{ player.hscore }}</td>
<td>{{ player.age }}</td>
<td><input id="input_add" type="button" name="add" value=" Add More "
class="tr_clone_add btn data_input"></td>
</tr>
</tbody>
{% endfor %}
</table>
<button type="submit" class="btn btn-primary">save</button>
</form>
</div>
<script>
var i = 1;
$("#input_add").click(function () {
$("tbody tr:first").clone().find(".data_input").each(function () {
if ($(this).attr('class') == 'tr_clone_add btn data_input') {
$(this).attr({
'id': function (_, id) {
return "remove_button"
},
'name': function (_, name) {
return "name_remove" + i
},
'value': 'Remove'
}).on("click", function () {
var a = $(this).parent();
var b = a.parent();
i = i - 1
$('#id_form-TOTAL_FORMS').val(i);
b.remove();
$('.player-instances tr').each(function (index, value) {
$(this).find('.data_input').each(function () {
$(this).attr({
'id': function (_, id) {
var idData = id;
var splitV = String(idData).split('-');
var fData = splitV[0];
var tData = splitV[2];
return fData + "-" + index + "-" + tData
},
'name': function (_, name) {
var nameData = name;
var splitV = String(nameData).split('-');
var fData = splitV[0];
var tData = splitV[2];
return fData + "-" + index + "-" + tData
}
});
})
})
})
} else {
$(this).attr({
'id': function (_, id) {
var idData = id;
var splitV = String(idData).split('-');
var fData = splitV[0];
var tData = splitV[2];
return fData + "-" + i + "-" + tData
},
'name': function (_, name) {
var nameData = name;
var splitV = String(nameData).split('-');
var fData = splitV[0];
var tData = splitV[2];
return fData + "-" + i + "-" + tData
}
});
}
}).end().appendTo("tbody");
$('#id_form-TOTAL_FORMS').val(1 + i);
i++;
});
</script>
</body>
</html>
Please help me figure out what the code is missing, I tried the solution given in comments too but that only solves the input fields problem
You can do this by looping through the last row added to the table and then updating all id & name attributes with new incremented i value like:
$("tbody tr:last :input").each(function() {
$(this).attr({
'id': function(_, id) {
return id.replace(/\d/g, i)
},
'name': function(_, name) {
return name.replace(/\d/g, i)
}
})
})
Also as Paul mentioned change players to player
I haven't reproduced your code so I can't be sure, but when I look at your models I read:
class PlayerForm(forms.Form):
pname = forms.CharField()
hscore= forms.IntegerField()
age = forms.IntegerField()
PlayerFormset= formset_factory(PlayerForm)
class TeamForm(forms.Form):
tname= forms.CharField()
player= PlayerFormset()
You pass the Teamform to the template in the variable form. That means you can access the individual player forms with:
{% for player in form.player %}
...
{% endfor %}
In your code I read form.players instead of form.player. Also at the top I read form.players.management_form. Django will not recognize these variables so return empty values. You can check this by looking at the HTML elements in your browser. In the example in the comments in the link you provided it says correctly form.player. I am not sure why in that case the add / remove button would not show.
I am trying to make a voting app using Blockchain on truffle framework. The data from the network is not rendered on the webpage.
Only loading is displayed but the actual content is not displayed even though I have connected by Blockchain accounts from Ganache to my metamask extension.
Here is my code:
Election.sol
pragma solidity ^0.5.0;
contract Election {
// Model a Candidate for first past the post system
struct Candidatepost {
uint id;
string name;
uint voteCount;
}
// Model as candidate for proportional party system
struct Candidateparty {
uint id;
string name;
uint voteCount;
}
// Store accounts that have voted
mapping(address => bool) public voters;
// Store Candidates
// Fetch Candidate
mapping(uint => Candidatepost) public cand_post;
mapping(uint => Candidateparty) public cand_party;
// Store Candidates Count
uint public partyCount;
uint public postCount;
uint[] public candidatesCount = [postCount,partyCount];
constructor () public {
addpostCandidate("Candidate 1");
addpostCandidate("Candidate 2");
addpartyCandidate("Candidate 1");
addpartyCandidate("Candidate 2");
candidatesCount = [postCount,partyCount];
}
function addpostCandidate (string memory _name) private {
postCount ++;
cand_post[postCount] = Candidatepost(postCount, _name, 0);
}
function addpartyCandidate (string memory _name) private {
partyCount ++;
cand_party[partyCount] = Candidateparty(partyCount, _name, 0);
}
// voted event
event votedEvent (
uint indexed _candidateId1,
uint indexed _candidateId2
);
function vote (uint _candidateId1, uint _candidateId2) public {
// require that they haven't voted before
require(!voters[msg.sender]);
// require a valid candidate
require(_candidateId1 > 0 && _candidateId1 <= postCount && _candidateId2 > 0 && _candidateId2 <= partyCount);
// record that voter has voted
voters[msg.sender] = true;
// update candidate vote Count
cand_post[_candidateId1].voteCount ++;
cand_party[_candidateId2].voteCount ++;
// trigger voted event
emit votedEvent(_candidateId1, _candidateId2);
}
}
App.js
App = {
web3Provider: null,
contracts: {},
account: '0x0',
init: function() {
return App.initWeb3();
},
initWeb3: function() {
// TODO: refactor conditional
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);
App.listenForEvents();
return App.render();
});
},
// Listen for events emitted from the contract
listenForEvents: function() {
App.contracts.Election.deployed().then(function(instance) {
// Restart Chrome if you are unable to receive this event
// This is a known issue with Metamask
// https://github.com/MetaMask/metamask-extension/issues/2393
instance.votedEvent({}, {
fromBlock: 0,
toBlock: 'latest'
}).watch(function(error, event) {
console.log("event triggered", event)
// Reload when a new vote is recorded
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(1) {
var postcandidatesResults = $("#postcandidatesResults");
postcandidatesResults.empty();
var partycandidatesResults = $("#partycandidatesResults");
partycandidatesResults.empty();
var postcandidatesSelect = $('#postcandidatesSelect');
postcandidatesSelect.empty();
var partycandidatesSelect = $('#partycandidatesSelect');
partycandidatesSelect.empty();
for (var i = 1; i <= 2; i++) {
electionInstance.cand_post(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>";
postcandidatesResults.append(candidateTemplate);
// Render candidate ballot option
var candidateOption = "<option value='" + id + "' >" + name + "</ option>";
postcandidatesSelect.append(candidateOption);
});
}
for (var j = 1; j <= 2; j++) {
electionInstance.cand_party(i).then(function(candidate) {
var id2 = candidate[0];
var name2 = candidate[1];
var voteCount2 = candidate[2];
// Render candidate Result
var candidateTemplate2 = "<tr><th>" + id2 + "</th><td>" + name2 + "</td><td>" + voteCount2 + "</td></tr>";
partycandidatesResults.append(candidateTemplate2);
// Render candidate ballot option
var candidateOption2 = "<option value='" + id2 + "' >" + name2 + "</ option>";
partycandidatesSelect.append(candidateOption2);
});
}
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);
});
},
castVote: function() {
var candidateId1 = $('#postcandidatesSelect').val();
var candidateId2 = $('#partycandidatesSelect').val();
App.contracts.Election.deployed().then(function(instance) {
return instance.vote(candidateId1, candidateId2, { from: App.account });
}).then(function(result) {
// Wait for votes to update
$("#content").hide();
$("#loader").show();
}).catch(function(err) {
console.error(err);
});
}
};
$(function() {
$(window).load(function() {
App.init();
});
});
Index.html
<!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">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<title>Election Results</title>
<!-- Bootstrap -->
<link href="css/bootstrap.min.css" rel="stylesheet">
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div class="container" style="width: 650px;">
<div class="row">
<div class="col-lg-12">
<h1 class="text-center">
<div class="row d-flex justify-content-center" style="border:none;background:white;">
<div class="col-md-1 col-3">
<img class="mx-auto d-block img-fluid" src="images/logo.png" style="" alt="">
</div>
</div></h1>
<hr/>
<br/>
<h1 class="text-center">National Election-2075</h1>
<h1 class="text-center">Election Updates</h1>
<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="postcandidatesResults">
</tbody>
</table>
<hr/>
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Votes</th>
</tr>
</thead>
<tbody id="partycandidatesResults">
</tbody>
</table>
<hr/>
<form onSubmit="App.castVote(); return false;">
<div class="form-group">
<label for="postcandidatesSelect">Select Candidate</label>
<select class="form-control" id="postcandidatesSelect">
</select>
</div>
<div class="form-group">
<label for="partycandidatesSelect">Select Candidate</label>
<select class="form-control" id="partycandidatesSelect">
</select>
</div>
<button type="submit" class="btn btn-primary">Vote</button>
<hr />
</form>
<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>
Any call to the blockchain is async(returns a promise). You need to either handle the promise with a .then() or you can use async await. Basically anything chained after electionInstance is async. For example:
electionInstance.candidatesCount();
electionInstance.cand_party
electionInstance.voters