I'm building a todo list using express handlebars, mongoose, mongodb, google oauth. I'm having trouble with rendering using handlebars. A todo has a mongoose attribute of done. If done true, then a class of complete is applied, which is text-decoration: line-through. The problem is that done is always rendered as true. When I click on the todo, it toggles between true/false in mongodb but doesn't show in hbs.
hbs:
<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.0" />
<link rel="stylesheet" href="css/style.css" />
<title>To Do</title>
</head>
<body>
<div class="container">
<header class="flexContainer">
<h1 class="title main-font center">Welcome {{name}}</h1>
</header>
<form class="center" action="/todos/addTodo" method="POST">
<input type="text" placeholder="Add a To Do" name="todoItem" />
<button type="submit" class="submitButton">
<span>Add Todo</span>
</button>
</form>
<div class="to-do-list flexContainer">
<ul class="task-list center">
{{#each todo}}
<li data-id="{{_id}}">
// Problem is this block of code here.
{{done}} // Added this line just to show the done attribute is toggling between true/false. Status renders here correctly.
{{#if done}} // This if statement doesn't work. All todos are rendered with the complete class.
<span class="complete">{{todo}}</span>
{{else}}
<span class="incomplete">{{todo}}</span>
{{/if}}
<span class="fa fa-trash delete-todo"></span>
</li>
{{/each}}
</ul>
</div>
<h4 class="main-font center">Left to do: {{left}}</h4>
</div>
<script type="text/javascript" src="js/main.js"></script>
</body>
</html>
controller todo.js
const Todo = require("../models/todos");
module.exports = {
getTodos: async (req, res) => {
try {
const todoItems = await Todo.find({ googleId: req.user.googleId }).lean();
const itemsLeft = await Todo.countDocuments({
googleId: req.user.googleId,
done: false,
});
res.render("todos.hbs", {
todo: todoItems,
left: itemsLeft,
name: req.user.firstName,
// done: done, <-- I'm not sure if I have to pass in a variable for done. The variable done seems to work in the hbs file anyways.
});
} catch (err) {
console.error(err);
}
},
addTodo: async (req, res) => {
console.log(req.body);
try {
await Todo.create({
todo: req.body.todoItem,
done: false,
googleId: req.user.googleId,
});
console.log("To do has been added!");
res.redirect("/");
} catch (err) {
console.error(err);
}
},
deleteTodo: async (req, res) => {
try {
await Todo.findOneAndDelete({ _id: req.body.todoId });
console.log("Deleted Todo");
res.json("Deleted Todo");
} catch (err) {
console.log(err);
}
},
markComplete: async (req, res) => {
console.log("markComplete");
try {
await Todo.findOneAndUpdate(
{ _id: req.body.todoId },
{
done: true,
}
);
console.log("complete");
res.json("Marked Complete");
} catch {
console.log(err);
}
},
markIncomplete: async (req, res) => {
try {
await Todo.findOneAndUpdate(
{ _id: req.body.todoId },
{
done: false,
}
);
console.log("Marked Incomplete");
res.json("Marked Incomplete");
} catch {
console.log(err);
}
},
};
main.js:
const deleteBtn = document.querySelectorAll(".delete-todo");
const todoIncomplete = document.querySelectorAll(".incomplete");
const todoComplete = document.querySelectorAll(".complete");
Array.from(deleteBtn).forEach((e) => {
e.addEventListener("click", deleteToDo);
});
Array.from(todoIncomplete).forEach((e) => {
e.addEventListener("click", markComplete);
});
Array.from(todoComplete).forEach((e) => {
e.addEventListener("click", markIncomplete);
});
async function deleteToDo() {
const todoId = this.parentNode.dataset.id;
try {
const response = await fetch("todos/deleteTodo", {
method: "delete",
headers: { "Content-type": "application/json" },
body: JSON.stringify({
todoId: todoId,
}),
});
const data = await response.json();
console.log(data);
location.reload();
} catch (err) {
console.log(err);
}
}
async function markComplete() {
const todoId = this.parentNode.dataset.id;
console.log(todoId);
try {
const response = await fetch("todos/markComplete", {
method: "put",
headers: { "Content-type": "application/json" },
body: JSON.stringify({
todoId: todoId,
}),
});
const data = await response.json();
console.log(data);
location.reload();
} catch (err) {
console.log(err);
}
}
async function markIncomplete() {
const todoId = this.parentNode.dataset.id;
try {
const response = await fetch("todos/markIncomplete", {
method: "put",
headers: { "Content-type": "application/json" },
body: JSON.stringify({
todoId: todoId,
}),
});
const data = await response.json();
console.log(data);
location.reload();
} catch (err) {
console.log(err);
}
}
Routes:
const express = require("express");
const router = express.Router();
const todosController = require("../controllers/todos");
const { ensureAuth, EnsureGuest } = require("../middleware/auth");
router.get("/", ensureAuth, todosController.getTodos);
router.post("/addTodo", todosController.addTodo);
router.put("/markComplete", todosController.markComplete);
router.put("/markIncomplete", todosController.markIncomplete);
router.delete("/deleteToDo", todosController.deleteTodo);
module.exports = router;
According to #76484, it is a string and not boolean. I have fixed it.
Mongoose model schema:
const toDoSchema = new mongoose.Schema({
todo: {
type: String,
required: true,
},
done: {
type: Boolean, // previously string here
required: true,
},
googleId: {
type: String,
required: true,
},
});
Related
#SOLVED [from first comment answer]
I am try to build paypal advance payment and card save during payment with nodejs. I successfully build paypal advance payment and store card in vault. but the problem is while I try to pay with previously saved card, it fails. Order is created successfully then the error occurred.
checkout html (ejs)
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" type="text/css"
href="https://www.paypalobjects.com/webstatic/en_US/developer/docs/css/cardfields.css" />
<script src="https://www.paypal.com/sdk/js?components=buttons,hosted-fields&client-id=<%= clientId %>"
data-client-token="<%= clientToken %>"></script>
</head>
<body>
<div id="paypal-button-container" class="paypal-button-container"></div>
<div class="card_container">
<form id="card-form">
<label for="card-number">Card Number</label>
<div id="card-number" class="card_field"></div>
<div style="display: flex; flex-direction: row;">
<div>
<label for="expiration-date">Expiration Date</label>
<div id="expiration-date" class="card_field"></div>
</div>
<div style="margin-left: 10px;">
<label for="cvv">CVV</label>
<div id="cvv" class="card_field"></div>
</div>
</div>
<label for="card-holder-name">Name on Card</label>
<input type="text" id="card-holder-name" name="card-holder-name" autocomplete="off"
placeholder="card holder name" />
<div>
<label for="card-billing-address-street">Billing Address</label>
<input type="text" id="card-billing-address-street" name="card-billing-address-street" autocomplete="off"
placeholder="street address" />
</div>
<div>
<label for="card-billing-address-unit"> </label>
<input type="text" id="card-billing-address-unit" name="card-billing-address-unit" autocomplete="off"
placeholder="unit" />
</div>
<div>
<input type="text" id="card-billing-address-city" name="card-billing-address-city" autocomplete="off"
placeholder="city" />
</div>
<div>
<input type="text" id="card-billing-address-state" name="card-billing-address-state" autocomplete="off"
placeholder="state" />
</div>
<div>
<input type="text" id="card-billing-address-zip" name="card-billing-address-zip" autocomplete="off"
placeholder="zip / postal code" />
</div>
<div>
<input type="text" id="card-billing-address-country" name="card-billing-address-country" autocomplete="off"
placeholder="country code" />
</div>
<div>
<input type="checkbox" id="save" name="save">
<label for="save">Save your card</label>
</div>
<br /><br />
<button value="submit" id="submit" class="btn">Pay</button>
</form>
</div>
<script src="app.js"></script>
</body>
</html>
app.js
paypal
.Buttons({
// Sets up the transaction when a payment button is clicked
createOrder: function (data, actions) {
return fetch("/api/orders", {
method: "post"
// use the "body" param to optionally pass additional order information
// like product ids or amount
})
.then((response) => response.json())
.then((order) => order.id);
},
// Finalize the transaction after payer approval
onApprove: function (data, actions) {
return fetch(`/api/orders/${data.orderID}/capture`, {
method: "post",
})
.then((response) => response.json())
.then((orderData) => {
// Successful capture! For dev/demo purposes:
console.log(
"Capture result",
orderData,
JSON.stringify(orderData, null, 2)
);
const transaction = orderData.purchase_units[0].payments.captures[0];
alert(`Transaction ${transaction.status}: ${transaction.id}
See console for all available details
`);
// When ready to go live, remove the alert and show a success message within this page. For example:
// var element = document.getElementById('paypal-button-container');
// element.innerHTML = '<h3>Thank you for your payment!</h3>';
// Or go to another URL: actions.redirect('thank_you.html');
});
},
})
.render("#paypal-button-container");
// If this returns false or the card fields aren't visible, see Step #1.
if (paypal.HostedFields.isEligible()) {
let orderId;
// Renders card fields
paypal.HostedFields.render({
// Call your server to set up the transaction
createOrder: () => {
return fetch("/api/orders", {
method: "post",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
save: document.querySelector('#save').checked,
card_name: document.getElementById("card-holder-name").value,
billing_address: {
address_line_1: document.getElementById("card-billing-address-street").value,
address_line_2: document.getElementById("card-billing-address-unit").value,
admin_area_2: document.getElementById("card-billing-address-city").value,
admin_area_1: document.getElementById("card-billing-address-state").value,
postal_code: document.getElementById("card-billing-address-zip").value,
country_code: document.getElementById("card-billing-address-country").value
}
})
// use the "body" param to optionally pass additional order information like
// product ids or amount.
})
.then((res) => res.json())
.then((orderData) => {
console.log("==============>create order response<==============");
console.log(orderData);
orderId = orderData.id; // needed later to complete capture
return orderData.id;
});
},
styles: {
".valid": {
color: "green",
},
".invalid": {
color: "red",
},
},
fields: {
number: {
selector: "#card-number",
placeholder: "4111 1111 1111 1111",
},
cvv: {
selector: "#cvv",
placeholder: "123",
},
expirationDate: {
selector: "#expiration-date",
placeholder: "MM/YY",
},
},
}).then((cardFields) => {
document.querySelector("#card-form").addEventListener("submit", (event) => {
event.preventDefault();
cardFields
.submit({
save: document.querySelector('#save').checked,
// Cardholder's first and last name
cardholderName: document.getElementById("card-holder-name").value,
// Billing Address
billingAddress: {
// Street address, line 1
streetAddress: document.getElementById(
"card-billing-address-street"
).value,
// Street address, line 2 (Ex: Unit, Apartment, etc.)
extendedAddress: document.getElementById(
"card-billing-address-unit"
).value,
// State
region: document.getElementById("card-billing-address-state").value,
// City
locality: document.getElementById("card-billing-address-city")
.value,
// Postal Code
postalCode: document.getElementById("card-billing-address-zip")
.value,
// Country Code
countryCodeAlpha2: document.getElementById(
"card-billing-address-country"
).value,
},
})
.then(() => {
fetch(`/api/orders/${orderId}/capture`, {
method: "post",
})
.then((res) => res.json())
.then((orderData) => {
console.log("==============>capture order response<==============");
console.log(orderData);
// Two cases to handle:
// (1) Other non-recoverable errors -> Show a failure message
// (2) Successful transaction -> Show confirmation or thank you
// This example reads a v2/checkout/orders capture response, propagated from the server
// You could use a different API or structure for your 'orderData'
const errorDetail =
Array.isArray(orderData.details) && orderData.details[0];
if (errorDetail) {
var msg = "Sorry, your transaction could not be processed.";
if (errorDetail.description)
msg += "\n\n" + errorDetail.description;
if (orderData.debug_id) msg += " (" + orderData.debug_id + ")";
return alert(msg); // Show a failure message
}
// Show a success message or redirect
alert("Transaction completed!");
});
})
.catch((err) => {
alert("Payment could not be captured! " + JSON.stringify(err));
});
});
});
} else {
// Hides card fields if the merchant isn't eligible
document.querySelector("#card-form").style = "display: none";
}
paypal-api.js
import fetch from "node-fetch";
// set some important variables
const { CLIENT_ID, APP_SECRET } = process.env;
const base = "https://api-m.sandbox.paypal.com";
// call the create order method
export async function createOrder(body) {
const purchaseAmount = "100.00"; // TODO: pull prices from a database
const accessToken = await generateAccessToken();
const url = `${base}/v2/checkout/orders`;
const response = await fetch(url, {
method: "post",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: body ? JSON.stringify({
intent: "CAPTURE",
purchase_units: [
{
amount: {
currency_code: "USD",
value: purchaseAmount,
},
},
],
payment_source: {
card: {
name: body.card_name,
billing_address: body.billing_address,
attributes: {
vault: {
store_in_vault: 'ON_SUCCESS'
}
}
}
}
}) : undefined,
});
return handleResponse(response);
}
// capture payment for an order
export async function capturePayment(orderId) {
const accessToken = await generateAccessToken();
const url = `${base}/v2/checkout/orders/${orderId}/capture`;
const response = await fetch(url, {
method: "post",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
});
return handleResponse(response);
}
// generate access token
export async function generateAccessToken() {
const auth = Buffer.from(CLIENT_ID + ":" + APP_SECRET).toString("base64");
const response = await fetch(`${base}/v1/oauth2/token`, {
method: "post",
body: "grant_type=client_credentials",
headers: {
Authorization: `Basic ${auth}`,
},
});
const jsonData = await handleResponse(response);
return jsonData.access_token;
}
// generate client token
export async function generateClientToken() {
const accessToken = await generateAccessToken();
const response = await fetch(`${base}/v1/identity/generate-token`, {
method: "post",
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Language": "en_US",
"Content-Type": "application/json",
},
body: JSON.stringify({
customer_id: 'kxIaZbNwOZ'
})
});
const jsonData = await handleResponse(response);
return jsonData.client_token;
}
async function handleResponse(response) {
if (response.status === 200 || response.status === 201) {
return response.json();
}
const errorMessage = await response.text();
throw new Error(errorMessage);
}
server.js
import "dotenv/config";
import express from "express";
import * as paypal from "./paypal-api.js";
const app = express();
app.set("view engine", "ejs");
app.use(express.static("public"));
app.use(express.json());
// render checkout page with client id & unique client token
app.get("/", async (req, res) => {
const clientId = process.env.CLIENT_ID;
try {
const clientToken = await paypal.generateClientToken();
res.render("checkout", { clientId, clientToken });
} catch (err) {
res.status(500).send(err.message);
}
});
// create order
app.post("/api/orders", async (req, res) => {
try {
const order = await paypal.createOrder(req.body);
res.json(order);
} catch (err) {
res.status(500).send(err.message);
}
});
// capture payment
app.post("/api/orders/:orderID/capture", async (req, res) => {
const { orderID } = req.params;
try {
const captureData = await paypal.capturePayment(orderID);
res.json(captureData);
} catch (err) {
res.status(500).send(err.message);
}
});
app.listen(8888, () => console.log("App Started."));
Error:
{
"err": "Error: Shipping address requested for card payment\n...",
"timestamp": "1673098647388",
"referer": "www.sandbox.paypal.com",
"sdkCorrelationID": "04103b485b692",
"sessionID": "uid_82aff76087_mtm6mzu6mzk",
"clientID": "AUUbkZiQ1akU7ChP0UxbWN9U8-wR0pYmOUF8gzkXtsrcbn9GXDLB0fjOT14vG-rvWYwGmM7Lzj8LDK-C",
"env": "sandbox",
"buttonSessionID": "uid_05c2d7e1ec_mtm6mzc6mtq",
"buttonCorrelationID": "a17486c9208f5",
"time": "1673098647388",
"user_id": "uid_05c2d7e1ec_mtm6mzc6mtq",
"token": "2VY75292WT2863902"
}
Uncaught Error: Shipping address requested for card payment
at https://www.sandbox.paypal.com/smart/buttons?sdkVersion=5.0.344&style.layout=vertical&style.color=gold&style.shape=rect&style.tagline=false&style.menuPlacement=below&components.0=buttons&components.1=hosted-fields&locale.country=US&locale.lang=en&sdkMeta=eyJ1cmwiOiJodHRwczovL3d3dy5wYXlwYWwuY29tL3Nkay9qcz9jb21wb25lbnRzPWJ1dHRvbnMsaG9zdGVkLWZpZWxkcyZjbGllbnQtaWQ9QVVVYmtaaVExYWtVN0NoUDBVeGJXTjlVOC13UjBwWW1PVUY4Z3prWHRzcmNibjlHWERMQjBmak9UMTR2Ry1ydldZd0dtTTdMemo4TERLLUMiLCJhdHRycyI6eyJkYXRhLXVpZCI6InVpZF9laWVpbXFteW9tY3F4am1xeWd4dGxsZWlld2FvcmIifX0&clientID=AUUbkZiQ1akU7ChP0UxbWN9U8-wR0pYmOUF8gzkXtsrcbn9GXDLB0fjOT14vG-rvWYwGmM7Lzj8LDK-C&clientAccessToken=A21AALuiYv6v0yKLvGRKk4KoMModxjNd_Ph185k19xRLIjbptX_pGsMmYPvM9pTdyuKBwJbDm33J9zeRmkwl9_FbdGY1WtwIw&sdkCorrelationID=04103b485b692&storageID=uid_e93c1270fe_mtm6mzu6mzk&sessionID=uid_82aff76087_mtm6mzu6mzk&buttonSessionID=uid_05c2d7e1ec_mtm6mzc6mtq&env=sandbox&buttonSize=huge&fundingEligibility=eyJwYXlwYWwiOnsiZWxpZ2libGUiOnRydWUsInZhdWx0YWJsZSI6dHJ1ZX0sInBheWxhdGVyIjp7ImVsaWdpYmxlIjp0cnVlLCJwcm9kdWN0cyI6eyJwYXlJbjMiOnsiZWxpZ2libGUiOmZhbHNlLCJ2YXJpYW50IjpudWxsfSwicGF5SW40Ijp7ImVsaWdpYmxlIjpmYWxzZSwidmFyaWFudCI6bnVsbH0sInBheWxhdGVyIjp7ImVsaWdpYmxlIjp0cnVlLCJ2YXJpYW50IjpudWxsfX19LCJjYXJkIjp7ImVsaWdpYmxlIjp0cnVlLCJicmFuZGVkIjpmYWxzZSwiaW5zdGFsbG1lbnRzIjpmYWxzZSwidmVuZG9ycyI6eyJ2aXNhIjp7ImVsaWdpYmxlIjp0cnVlLCJ2YXVsdGFibGUiOnRydWV9LCJtYXN0ZXJjYXJkIjp7ImVsaWdpYmxlIjp0cnVlLCJ2YXVsdGFibGUiOnRydWV9LCJhbWV4Ijp7ImVsaWdpYmxlIjp0cnVlLCJ2YXVsdGFibGUiOnRydWV9LCJkaXNjb3ZlciI6eyJlbGlnaWJsZSI6dHJ1ZSwidmF1bHRhYmxlIjp0cnVlfSwiaGlwZXIiOnsiZWxpZ2libGUiOmZhbHNlLCJ2YXVsdGFibGUiOmZhbHNlfSwiZWxvIjp7ImVsaWdpYmxlIjpmYWxzZSwidmF1bHRhYmxlIjp0cnVlfSwiamNiIjp7ImVsaWdpYmxlIjpmYWxzZSwidmF1bHRhYmxlIjp0cnVlfX0sImd1ZXN0RW5hYmxlZCI6ZmFsc2V9LCJ2ZW5tbyI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJpdGF1Ijp7ImVsaWdpYmxlIjpmYWxzZX0sImNyZWRpdCI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJhcHBsZXBheSI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJzZXBhIjp7ImVsaWdpYmxlIjpmYWxzZX0sImlkZWFsIjp7ImVsaWdpYmxlIjpmYWxzZX0sImJhbmNvbnRhY3QiOnsiZWxpZ2libGUiOmZhbHNlfSwiZ2lyb3BheSI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJlcHMiOnsiZWxpZ2libGUiOmZhbHNlfSwic29mb3J0Ijp7ImVsaWdpYmxlIjpmYWxzZX0sIm15YmFuayI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJwMjQiOnsiZWxpZ2libGUiOmZhbHNlfSwiemltcGxlciI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJ3ZWNoYXRwYXkiOnsiZWxpZ2libGUiOmZhbHNlfSwicGF5dSI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJibGlrIjp7ImVsaWdpYmxlIjpmYWxzZX0sInRydXN0bHkiOnsiZWxpZ2libGUiOmZhbHNlfSwib3h4byI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJtYXhpbWEiOnsiZWxpZ2libGUiOmZhbHNlfSwiYm9sZXRvIjp7ImVsaWdpYmxlIjpmYWxzZX0sImJvbGV0b2JhbmNhcmlvIjp7ImVsaWdpYmxlIjpmYWxzZX0sIm1lcmNhZG9wYWdvIjp7ImVsaWdpYmxlIjpmYWxzZX0sIm11bHRpYmFuY28iOnsiZWxpZ2libGUiOmZhbHNlfSwic2F0aXNwYXkiOnsiZWxpZ2libGUiOmZhbHNlfX0&platform=desktop&experiment.enableVenmo=false&experiment.enableVenmoAppLabel=false&flow=purchase¤cy=USD&intent=capture&commit=true&vault=false&renderedButtons.0=paypal&renderedButtons.1=paylater&debug=false&applePaySupport=false&supportsPopups=true&supportedNativeBrowser=false&experience=&allowBillingPayments=true:1492:261589
at n.dispatch (https://www.sandbox.paypal.com/smart/buttons?sdkVersion=5.0.344&style.layout=vertical&style.color=gold&style.shape=rect&style.tagline=false&style.menuPlacement=below&components.0=buttons&components.1=hosted-fields&locale.country=US&locale.lang=en&sdkMeta=eyJ1cmwiOiJodHRwczovL3d3dy5wYXlwYWwuY29tL3Nkay9qcz9jb21wb25lbnRzPWJ1dHRvbnMsaG9zdGVkLWZpZWxkcyZjbGllbnQtaWQ9QVVVYmtaaVExYWtVN0NoUDBVeGJXTjlVOC13UjBwWW1PVUY4Z3prWHRzcmNibjlHWERMQjBmak9UMTR2Ry1ydldZd0dtTTdMemo4TERLLUMiLCJhdHRycyI6eyJkYXRhLXVpZCI6InVpZF9laWVpbXFteW9tY3F4am1xeWd4dGxsZWlld2FvcmIifX0&clientID=AUUbkZiQ1akU7ChP0UxbWN9U8-wR0pYmOUF8gzkXtsrcbn9GXDLB0fjOT14vG-rvWYwGmM7Lzj8LDK-C&clientAccessToken=A21AALuiYv6v0yKLvGRKk4KoMModxjNd_Ph185k19xRLIjbptX_pGsMmYPvM9pTdyuKBwJbDm33J9zeRmkwl9_FbdGY1WtwIw&sdkCorrelationID=04103b485b692&storageID=uid_e93c1270fe_mtm6mzu6mzk&sessionID=uid_82aff76087_mtm6mzu6mzk&buttonSessionID=uid_05c2d7e1ec_mtm6mzc6mtq&env=sandbox&buttonSize=huge&fundingEligibility=eyJwYXlwYWwiOnsiZWxpZ2libGUiOnRydWUsInZhdWx0YWJsZSI6dHJ1ZX0sInBheWxhdGVyIjp7ImVsaWdpYmxlIjp0cnVlLCJwcm9kdWN0cyI6eyJwYXlJbjMiOnsiZWxpZ2libGUiOmZhbHNlLCJ2YXJpYW50IjpudWxsfSwicGF5SW40Ijp7ImVsaWdpYmxlIjpmYWxzZSwidmFyaWFudCI6bnVsbH0sInBheWxhdGVyIjp7ImVsaWdpYmxlIjp0cnVlLCJ2YXJpYW50IjpudWxsfX19LCJjYXJkIjp7ImVsaWdpYmxlIjp0cnVlLCJicmFuZGVkIjpmYWxzZSwiaW5zdGFsbG1lbnRzIjpmYWxzZSwidmVuZG9ycyI6eyJ2aXNhIjp7ImVsaWdpYmxlIjp0cnVlLCJ2YXVsdGFibGUiOnRydWV9LCJtYXN0ZXJjYXJkIjp7ImVsaWdpYmxlIjp0cnVlLCJ2YXVsdGFibGUiOnRydWV9LCJhbWV4Ijp7ImVsaWdpYmxlIjp0cnVlLCJ2YXVsdGFibGUiOnRydWV9LCJkaXNjb3ZlciI6eyJlbGlnaWJsZSI6dHJ1ZSwidmF1bHRhYmxlIjp0cnVlfSwiaGlwZXIiOnsiZWxpZ2libGUiOmZhbHNlLCJ2YXVsdGFibGUiOmZhbHNlfSwiZWxvIjp7ImVsaWdpYmxlIjpmYWxzZSwidmF1bHRhYmxlIjp0cnVlfSwiamNiIjp7ImVsaWdpYmxlIjpmYWxzZSwidmF1bHRhYmxlIjp0cnVlfX0sImd1ZXN0RW5hYmxlZCI6ZmFsc2V9LCJ2ZW5tbyI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJpdGF1Ijp7ImVsaWdpYmxlIjpmYWxzZX0sImNyZWRpdCI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJhcHBsZXBhe
Expecting the payment success
Am trying to display json response after calling an API using fetch, I can see the response in the response tab of chrome, but I can't find it in fetch response object
Client side
import React from 'react';
import './App.css';
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
query: '',
properties: []
}
this.search = this.search.bind(this);
this.handleChange = this.handleChange.bind(this)
}
handleChange(event) {
const { name, value } = event.target;
// const { query } = this.state.query;
this.setState({
[name]: value
});
}
search() {
console.log('fetching data')
try {
fetch('http://localhost:3000/property/find', {
method: 'POST',
mode: 'CORS',
body: JSON.stringify({ "query": this.state.query }),
headers: {
'Content-Type': 'application/json'
}
}).then(res => res.json())
.then((data) => {
console.log(data)
this.setState({ properties: data.result });
})
}
catch (err) {
return err;
}
}
render() {
const { properties } = this.state;
return (
<div className="App" >
<input type="text" name="query" onChange={this.handleChange}></input>
<div className="form-group">
<button className="btn btn-primary" onClick={this.search}>Search</button>
</div>
<div className="row text-center">
{properties.items &&
properties.items.map((property, index) =>
<div className="col-lg-3 col-md-6 mb-4" key={index}>
<div className="card h-100">
<img className="card-img-top" src="http://placehold.it/500x325" alt="" />
<div className="card-body">
<h4 className="card-title"> {property.details.description}</h4>
{/* <p className="card-text">{property.biography}</p> */}
</div>
<div className="card-footer">
Find Out More!
</div>
</div>
</div>
)
}
</div>
</div>
)
}
}
export default App;
Server side
var app = express();
const server = http.createServer(app);
const io = socketIo(server);
var db = require('./db');
var property = require('./endpoint/property');
// var authController = require('./auth/AuthController');
app.use(function (req, res, next) {
// Website you wish to allow to connect
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3001');
// Request methods you wish to allow
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
// Request headers you wish to allow
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
next();
});
//allow OPTIONS on just one resource
// app.post('*', cors())
app.use(cors())
app.use('/property', property);
End point response
var express = require('express');
var router = express.Router();
var bodyParser = require('body-parser');
router.use(bodyParser.urlencoded({ extended: true }));
router.use(bodyParser.json());
var Model = require('../model/propertyModel');
// GETS A SINGLE USER FROM THE DATABASE
router.post('/find',function (req, res) {
var query = req.body.query
console.log(query)
Model.find( { $text: { $search: query }} , { score: { $meta: "textScore" } }).sort( { score: { $meta: "textScore" } } ).then((data)=>{
if(data.length>0){
res.status(200).json({"result":data});
}
if (data.length==0){
Model.find({ "details.description": {$regex:query} }).sort( { score: { $meta: "textScore" } } ).then((data)=>{
if(data){
res.status(200).json({"result":data});
}
if (data.length==0) return res.status(404).send("No properties found.");
})
}
})
});
Inside your render method, if you change this:
{properties.items &&
properties.items.map((property, index) =>
...to this:
{properties &&
properties.map((property, index) =>
That should resolve this for you.
Within the render method, it looks like properties.items is expected to be an array. But in the network tab response screenshot, the result field inside the JSON response is an array.
Calling this.setState({ properties: data.result }); will lead to properties being the field you should be mapping over in the render method, instead of properties.items
I'm in a web programming class and we are writing web server API's. I've currently implemented POST and GET, but I'm having some trouble implementing DELETE. What am I doing wrong here? I would appreciate a thorough explanation of what I'm doing wrong because I'm really trying to learn the material(unless of course it is simply formatting errors).
There are lots of portions that have not been implemented yet, so I'm sure there are many errors. Mostly I'm concerned with DELETE, and anything else apparent from my code that I don't understand.
I've replaced my domain name with fakeDomainName.com
index.html and script.js are in a public folder. start.js in my main directory.
We are creating a simple voting type api to practice, using a Node.js server and MongoDB database to store the information. Currently, POST and GET are working as expected, but DELETE is giving me these errors:
spread.js:25 DELETE http://fakeDomainName.com:3010/api/candidates/undefined 500 (Internal Server Error)
(anonymous) # spread.js:25
e.exports # spread.js:25
e.exports # spread.js:25
Promise.then (async)
r.request # spread.js:25
r.<computed> # spread.js:25
(anonymous) # axios.min.js:477
deleteItem # script.js:65
invokeWithErrorHandling # vue.js:1855
invoker # vue.js:2173
original._wrapper # vue.js:7416
spread.js:25 Uncaught (in promise) Error: Request failed with status code 500
at e.exports (spread.js:25)
at e.exports (spread.js:25)
at XMLHttpRequest.d.onreadystatechange (spread.js:25)
e.exports # spread.js:25
e.exports # spread.js:25
d.onreadystatechange # spread.js:25
XMLHttpRequest.send (async)
(anonymous) # spread.js:25
e.exports # spread.js:25
e.exports # spread.js:25
Promise.then (async)
r.request # spread.js:25
r.<computed> # spread.js:25
(anonymous) # axios.min.js:477
deleteItem # script.js:65
invokeWithErrorHandling # vue.js:1855
invoker # vue.js:2173
original._wrapper # vue.js:7416
Here is my code in "start.js", which is what I'm using to start the server.
const express = require('express');
const bodyParser = require("body-parser");
const multer = require('multer');
const upload = multer({
dest: './public/images/',
limits: {
fileSize: 10000000
}
});
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: false
}));
app.use(express.static('public'));
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/voting', {
useNewUrlParser: true
});
var candidateSchema = new mongoose.Schema({
name: String,
bio: String,
numVotes: String,
});
var Candidate = mongoose.model('Candidate', candidateSchema);
//add a candidate to the list
app.post('/api/candidates', async(req, res) => {
console.log("initiated post request");
const candidate = new Candidate({
name: req.body.name,
bio: req.body.bio,
numVotes: req.body.numVotes,
});
this.addItem = candidate.data;
try {
await candidate.save();
}
catch (error) {
console.log(error);
res.sendStatus(500);
}
});
// Get a list of all of the candidates.
app.get('/api/candidates', async(req, res) => {
console.log("initiated get request");
try {
let candidate = await Candidate.find();
res.send(candidate);
}
catch (error) {
console.log(error);
res.sendStatus(500);
}
});
//delete a candidate from the list
app.delete('/api/candidates/:id', async(req, res) => {
console.log("initiated delete request");
Candidate.deleteOne({ _id: req.params.id }, function(err) {
if (err) res.sendStatus(500);
else {
console.log(req.params.id, "deleted successfully");
res.sendStatus(200);
}
});
});
//edit a candidate
app.put('/api/candidates/:id', async(req, res) => {
console.log("initiated put(edit) request");
try {
let candidate = await Candidate.findOne({ _id: req.params.id });
candidate.name = req.body.name;
candidate.bio = req.body.bio;
candidate.numVotes = req.body.numVotes;
candidate.save();
res.sendStatus(200);
}
catch (error) {
console.log(error);
res.sendStatus(500);
}
});
app.listen(3010, () => console.log('Server listening on port 3010!'));
and here is my code in script.js, which is linked to my index.html page:
var app = new Vue({
el: '#app',
data: {
name: "",
bio: "",
numVotes: "",
file: null,
addItem: null,
items: [],
findName: "",
findItem: null,
},
created() {
this.getItems();
},
computed: {
suggestions() {
return this.items.filter(item => item.title.toLowerCase().startsWith(this.findTitle.toLowerCase()));
}
},
methods: {
async postItem(item) {
console.log("initiated");
try {
let response = await axios.post('/api/candidates', {
name: this.name,
bio: this.bio,
numVotes: this.numVotes,
});
this.addItem = response.data;
}
catch (error) {
console.log(error);
}
},
async getItems() {
try {
let response = await axios.get("/api/candidates");
this.items = response.data;
return true;
}
catch (error) {
console.log(error);
}
},
selectItem(item) {
this.findName = "";
this.findItem = item;
},
async deleteItem(item) {
try {
let response = axios.delete("/api/candidates/" + item._id);
this.findItem = null;
this.getItems();
return true;
} catch (error) {
console.log(error);
}
},
async editItem(item) {
try {
let response = await axios.put("/api/candidates/" + item._id, {
name: this.findItem.name,
bio: this.findItem.bio,
numVotes: this.findItem.numVotes,
});
this.findItem = null;
this.getItems();
return true;
} catch (error) {
console.log(error);
}
},
},
});
and finally, here is the code I'm using in index.html:
<!DOCTYPE HTML>
<html>
<head>
<title></title>
</head>
<body>
<h2>Hello World</h2>
<div id="app">
<div>
<div>
<input v-model="name" placeholder="Name">
<p></p>
<input v-model="bio" placeholder="Bio">
<p></p>
<input v-model="numVotes" placeholder="Number of votes">
<button #click="postItem">Upload</button>
<button #click="deleteItem">Delete</button>
</div>
<div v-if="addItem">
<h2>{{addItem.name}}</h2>
<h2>{{addItem.bio}}</h2>
<h2>{{addItem.NumVotes}}</h2>
</div>
</div>
<h2>Candidates:</h2>
<div v-for="item in items">
<div #click="selectItem">
<h2>Name: {{item.name}}</h2>
<h2>Bio: {{item.bio}}</h2>
<h2>Number of Votes: {{item.numVotes}}</h2>
</div>
</div>
</div>
<!--Vue and axios-->
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.2/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="/script.js"></script>
</body>
</html>
As you can see in url you are not getting params id its showing as undefined
//fakeDomainName.com:3010/api/candidates/undefined
due to which it is giving error if you will pass with as id it will work as well
//delete a candidate from the list
app.delete('/api/candidates/:id', async(req, res) => {
console.log("initiated delete request");
Candidate.findOneAndDelete({ _id: req.params.id }, function(err) {
if (err) res.sendStatus(500);
else {
console.log(req.params.id, "deleted successfully");
res.sendStatus(200);
}
});
});```
I tested your node side with postman. "Delete" is fine. But as you see in the error itself
DELETE http://fakeDomainName.com:3010/api/candidates/undefined the parameter item id is being sent undefined, hence cannot delete.
In you POST request (on server/node side), you can perhaps add a sendStatus(200) to avoid post request being hung up.Also, You can get id of the saved item while saving the Candidate
Like this:
app.post('/api/candidates', async(req, res) => {
console.log("initiated post request");
const candidate = new Candidate({
name: req.body.name,
bio: req.body.bio,
numVotes: req.body.numVotes,
});
this.addItem = candidate.data;
try {
console.log("Await save...");
let data = await candidate.save();
console.log("Done");
res.statusCode = 200;
res.setHeader('content-type','application/json');
res.end(JSON.stringify(data));
}
catch (error) {
console.log(error);
res.sendStatus(500);
}
});
The response would be like :
{
"_id": "5dd38cb16f47912b40a1deef",
"name": "Ken Adams",
"bio": "bio",
"numVotes": "10",
"__v": 0
}
I have two directories where vue, node exist. And I have the vue build file in the node folder.
I am currently processing requests from nodes in the vue. However, the event occurs but the data does not cross.
I have the following code, I sent form data via create, but the return data is empty. Also, in mongodb, title and content are require: true, so I get an error like the title.
Please help me.
node/routes/api
...
const Post = require('../db/post');
router.post('/new', (req, res) => {
const post = new Post({
title: req.body.title,
content: req.body.content
});
post.save((err) => {
if (err) {
console.error(err);
res.json({ result: 0 });
return;
}
res.json({ result: 1 });
});
});
...
vue/src/component/new
<template>
<div id="form-group">
name : <input v-model="post.title">
content : <input v-model="post.content">
<button v-on:click="new" >New</button>
</div>
</template>
<script>
export default {
data: function () {
return {
post: {}
}
},
methods: {
new: function (evt) {
this.$http.post('/api/post/new', {
post: this.post
})
.then((response) => {
if (response.data.result === 0) {
alert('Error')
}
if (response.data.result === 1) {
alert('Success')
this.$router.push('/')
}
})
.catch(function (error) {
alert('error')
})
}
}
}
</script>
Disclaimer: I am new to React and Nodejs and have of course looked at all the current similar posts but none of them were able to help me fully so please don't mark as duplicate.
In my Nodejs index.js I have:
app.get('/list-certs', function (req, res) {
fs.readFile("certs.json", 'utf8', function (err, data) {
console.log( data );
res.send( data );
});
})
app.post('/list-certs', function (req, res) {
res.send('POST request: ' + req.body.domain-input);
console.log('post is working!')
});
And in my React App.js I have:
componentDidMount() {
this.callApi()
.then(res => {
this.setState({
json: res.map(x => x),
})
})
.catch(err => console.log(err));
}
callApi = async () => {
const response = await fetch('/list-certs');
const body = await response.json();
if (response.status !== 200) throw Error(body.message);
return body;
};
handleChange(event) {
this.setState({domainInputValue: event.target.value});
}
click() {
}
render() {
return (
<div className="App">
<form action="http://127.0.0.1:8000/list-certs" className="add-cert-form" method="post">
<h1>Add new domain</h1>
<h5 className="domain-label">Name:</h5>
<Input className="domain-input" value={this.state.domainInputValue} onChange={(e) => {this.handleChange(e)}}></Input>
<Button onClick={this.click} className="domain-button" type='primary'>Add</Button>
</form>
.
.
.
}
How do I send the data in my input field domain-input to my back-end when the button domain-button is clicked? I know something needs to go in the click() but I'm not sure what.
Any help is appreciated!
Your click function will look like it:
async click() {
const { domainInputValue } = this.state;
const request = await fetch('/echo/json', {
headers: {
'Content-type': 'application/json'
},
method: 'POST',
body: { domainInputValue }
});
}