Submitting multiple forms with one button - javascript

I've looked at all the threads that already exist on this topic and have not been able to come up with a solution for my case.
I have multiple forms rendering with the help of Handlebars like this:
<ul>
{{#each listRecords}}
<form id="form{{id}}" action="/expand-list-records-save/{{../listId}}/{{id}}" method="POST">
<div class="record-box">
<li>{{recordTitle}} by {{artistName}} ({{releaseYear}})
<br>
<div>
<label>Paste media embed code here:</label>
<textarea class="form-control form-control-lg" name="embeddedmedia" cols="30" rows="10">{{embeddedMedia}}</textarea>
</div>
<br>
<br>
</li>
</div>
</div>
</form>
{{/each}}
</ul>
<input id="submit" class="btn btn-secondary btn-block" type="submit" value="Submit embed code" >
<script>
$(document).ready(() => {
$('#submit').click(function submitAllForms() {
for (var i=0; i < document.forms.length; i++) {
console.log(`submitting ${document.forms[i].id}`)
document.forms[i].submit();
}
})
})
</script>
my Node.js + Express.js route looks like this
router.route('/expand-list-records-save/:listId/:recordId')
.post((req, res) => {
// console.log(req)
Record.findOne({
where: {
id: req.params.recordId
}
}).then(result => {
// console.log(req.body)
result.update({
embeddedMedia: req.body.embeddedmedia
})
}).then(() => {
console.log("sending list to view")
sendListDataToView({ params: {id: req.params.listId} }, res, 'view-list')
})
})
I'm having a few problems. First of all, this logic only executes a POST request for the item that the very last form on the page is for. Why is it that the console.log works for every single instance in my loop when iterating through all the document forms? From what I know, I think I need to use AJAX here somehow to execute all the POST requests. And the second main thing that I don't think is giving me problems at this point, but will once I get the first issue solved, is that my route is not written to handle a batch of requests like I need it to.
UPDATE
Upon a recommendation in comments, I decided try and write an Ajax request to post all of the forms to a separate route which will handle it from there. How do I pass an array of forms to the data parameter? I get the Uncaught RangeError: Maximum call stack size exceeded error this way:
$(document).ready(() => {
$('#submit').click(function submitAllForms() {
$.ajax({
type: 'POST',
url: window.location.origin + $('h3')[0].innerText,
data: document.forms,
success: (data) => {
console.log(data)
}
})
})
})
After going through some other examples, I tried rewriting original submit script like this. And, in this case, it does not pick up the action attribute.
$(document).ready(() => {
$('#submit').click(function submitAllForms() {
$('form').each(() => {
var that = $(this);
$.post(that.attr('action'), that.serialize())
})
})
})

So, I have finally come up with a solution, if anyone is interested. Perhaps not prettiest, but it works.
<script>
$(document).ready(() => {
$('#submit').click(function submitAllForms() {
var counter = 0;
var totalForms = $('form').length;
$('form').each((i, form) => {
const redirectIfDone = () => {
if (counter === totalForms) {
alert("all forms updated successfully")
window.location.replace(window.location.origin + '/list/' + $('h3')[0].innerText)
}
}
if (!($(form).find('textarea').val())) {
counter++
redirectIfDone();
return true;
} else {
$.ajax({
type: 'POST',
url: $(form).attr('action'),
data: $(form).serialize(),
success: (data) => {
counter++;
redirectIfDone();
}
})
}
})
})
})
</script>
Virtually no changes to the route. Overall, I'm still interested in seeing other possible solutions.
router.route('/expand-list-records-save/:listId/:recordId')
.post((req, res) => {
Record.findOne({
where: {
id: req.params.recordId
}
}).then(result => {
result.update({
embeddedMedia: req.body.embeddedmedia
})
res.end()
})
})

Related

How to sort object by key value in javascript?

I'm making a code to fetch content from contentful using AJAX. I've success retrieve data and display it, but something is not quite what I want. Because the content that I get is not in the same order as the contentful cms, so I add another field called sequence. So in my code I added a sort() and Object.keys() function before forEach(), but there is no error and data not appears ,does anyone know why data not appears?
If you want to try debugging, you can look at This Codepen.
function renderContentBySection(sectionName, appendElement, numberOfSkeleton, elementAttribute, elementClass){
$.ajax({
url : 'https://cdn.contentful.com/spaces/r5mgd95bqsb5/environments/master/entries/1bI13SpZBBvgOgIk4GhYEg?access_token=CVel_r57GUqeTeaLyIsseXEAM1z1f-spXNKR-a2-huA',
type: 'GET',
success: function(data){
const getData = data.fields
if(getData[sectionName]) {
if(getData[sectionName] && getData[sectionName].length) {
getData[sectionName].forEach((item, index) => {
getSingleEntry(item.sys.id)
});
}
}
}
});
}
function getSingleEntry(contentId){
$.ajax({
url : `https://cdn.contentful.com/spaces/r5mgd95bqsb5/environments/master/entries/${contentId}?access_token=CVel_r57GUqeTeaLyIsseXEAM1z1f-spXNKR-a2-huA`,
type: 'GET',
success: function(dataKat){
getAssetData(dataKat.fields.image.sys.id, dataKat.fields.sequence)
$('.data-banner').append(JSON.stringify(dataKat.fields, null, 4))
$('.data-banner').append('<br>');
}
});
}
function getAssetData(assetsId, sequenceId){
$.ajax({
url : `https://cdn.contentful.com/spaces/r5mgd95bqsb5/environments/master/assets/${assetsId}?access_token=CVel_r57GUqeTeaLyIsseXEAM1z1f-spXNKR-a2-huA`,
type: 'GET',
success: function(getAssetsData){
$('.data-image').append(JSON.stringify(getAssetsData.fields, null, 4))
$('.data-image').append('<br>');
}
});
}
$(document).ready(function(){
renderContentBySection('mainBannerImage', '#carousel-inner', 1, 'banner', 'main-banner-item');
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<pre class="data-banner">
<h4>Get Data Main Banner:</h4>
</pre>
<br>
<pre class="data-image">
<h4>Get Data for Each Image in Main Banner:</h4>
</pre>
Because you completely changed the criteria, I will provide an answer for your second ask.
The key to working with multiple batches of asynchronous requests is to gather all the requests, and then listen for them all to complete. Then, do the same thing again with the next batch of requests.
Otherwise, your HTML will print in the order the responses are returned and it will seem random.
Once you have gathered all the completed requests, you can sort() them, then do a forEach through them.
function listenForEntries(arrAllEntryRequests) {
//set up a listener for when all requests have finished, using "spread operator" (...) to send all requests as parameters to when():
$.when(...arrAllEntryRequests).done(
//done:
function (...arrAllEntryResponses) {
let arrAllEntries = [];
//console.log("arrAllEntryResponses", arrAllEntryResponses);
arrAllEntryResponses.forEach((e) => {
arrAllEntries.push(e[0].fields);
});;
//all images loaded, sort:
arrAllEntries.sort((a, b) => (a.sequence < b.sequence ? -1 : 1));
//sorting done, get asset data for each. This is also asyncronous so you need to do the same thing as above:
let arrAllAssetRequests = [];
arrAllEntries.forEach((entryData) => {
//console.log("entryData", entryData);
$('.data-sequence').append(`
<ul>
<li>
Sequence ID: ${entryData.sequence}<br>
Title Banner: ${entryData.title}
</li>
</ul>`)
let assetReqObj = getAssetData(entryData.image.sys.id, entryData.sequence);
arrAllAssetRequests.push(assetReqObj);
});
listenForAssets(arrAllAssetRequests);
}
);
}
function listenForAssets(arrAllAssetRequests) {
$.when(...arrAllAssetRequests).done(
//done:
function (...arrAllAssetResponses) {
//all assets loaded, sort:
arrAllAssetResponses.sort((a, b) => (a[2].sequence < b[2].sequence ? -1 : 1));
arrAllAssetResponses.forEach((assetData) => {
//console.log("assetData", assetData);
if(assetData.length > 0) {
$('.data-assets').append(`
<ul>
<li>
Sequence ID: ${assetData[2].sequence}<br>
Title Banner: ${assetData[0].fields.title}<br>
<img class="img-fluid" src="${assetData[0].fields.file.url}">
</li>
</ul>`);
} else {
console.error("Something wrong with assetData", assetData);
}
});
}
);
}
function renderContentBySection(sectionName, appendElement, numberOfSkeleton, elementAttribute, elementClass) {
$.ajax({
url: 'https://cdn.contentful.com/spaces/r5mgd95bqsb5/environments/master/entries/1bI13SpZBBvgOgIk4GhYEg?access_token=CVel_r57GUqeTeaLyIsseXEAM1z1f-spXNKR-a2-huA',
type: 'GET',
success: function (data) {
const getData = data.fields
//move array to inside this function as it's the only place it will be used:
let arrAllEntryRequests = [];
if (getData[sectionName]) {
if (getData[sectionName] && getData[sectionName].length) {
getData[sectionName].forEach((item, index) => {
arrAllEntryRequests.push(getSingleEntry(item.sys.id));
});
//once array of requests is created, listen for it to finish:
listenForEntries(arrAllEntryRequests);
}
}
}
});
}
function getSingleEntry(contentId) {
return $.ajax({
url: `https://cdn.contentful.com/spaces/r5mgd95bqsb5/environments/master/entries/${contentId}?access_token=CVel_r57GUqeTeaLyIsseXEAM1z1f-spXNKR-a2-huA`,
type: 'GET',
success: function (dataKat) {
//do nothing
}
});
}
function getAssetData(assetsId, sequenceId) {
let $xhr = $.ajax({
url: `https://cdn.contentful.com/spaces/r5mgd95bqsb5/environments/master/assets/${assetsId}?access_token=CVel_r57GUqeTeaLyIsseXEAM1z1f-spXNKR-a2-huA`,
type: 'GET',
success: function (assetData) {
//do nothing
}
});
$xhr.sequence = sequenceId; //store the sequence for later
return $xhr;
}
$(document).ready(function () {
renderContentBySection('mainBannerImage', '#carousel-inner', 1, 'banner', 'main-banner-item');
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container-fluid">
<div class="row">
<div class="col-6">
<div class="data-sequence">
<span> This is sequence data:</span>
</div>
</div>
<div class="col-6">
<div class="data-assets">
<span> This is assets data:</span>
</div>
</div>
</div>
</div>
Because your data is loaded asyncronously, you will need to create a queue of your requests, and listen for them to all finish.
I have commented my code below so you can understand how it works.
First, you need to use the spread operator a lot ..., to work with an unknown number of array elements.
(https://stackoverflow.com/a/35169449/1410567)
Second, you need to use $.when(...array).done(function(...results) { to know when the requests have finished.
(https://blog.kevinchisholm.com/javascript/jquery/using-jquery-deferred-to-manage-multiple-ajax-calls/)
Third, you need to use Array.sort() to sort the array of objects, comparing their sequence, and returning 1 or -1 to sort them.
(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
//create an empty array to hold the queue:
let allImageRequests = [];
function listenForImages() {
//set up a listener for when all requests have finished, using "spread operator" (...) to send all requests as parameters to when():
$.when(...allImageRequests).done(
//done:
function (...arrAllImagesResp) {
let arrAllImages = [];
console.log("arrAllImagesResp", arrAllImagesResp);
arrAllImagesResp.forEach((e) => {
console.log(e);
arrAllImages.push(e[0].fields);
});;
//all images loaded, sort:
arrAllImages.sort((a, b) => (a.sequence < b.sequence ? -1 : 1));
console.log("done", arrAllImages);
//sorting done, display results:
$('.data-image').append("\n\n<strong>All Images Sorted:</strong> \n\n" + JSON.stringify(arrAllImages, null, 4));
}
);
}
$.ajax({
url: 'https://cdn.contentful.com/spaces/r5mgd95bqsb5/environments/master/entries/1bI13SpZBBvgOgIk4GhYEg?access_token=CVel_r57GUqeTeaLyIsseXEAM1z1f-spXNKR-a2-huA',
type: 'GET',
success: function (data) {
console.log("got data", data);
const getData = data.fields.mainBannerImage
$('.data-banner').append(JSON.stringify(getData, null, 4))
$('.data-banner').append('<br>');
getData.forEach((item, index) => {
//add requests to our queue:
allImageRequests.push(getImageAssets(item.sys.id));
});
listenForImages();
}
})
function getImageAssets(assetId) {
//I added a return here, so the XHR objects will be push()'d to the allImageRequests queue array:
return $.ajax({
url: `https://cdn.contentful.com/spaces/r5mgd95bqsb5/environments/master/entries/${assetId}?access_token=CVel_r57GUqeTeaLyIsseXEAM1z1f-spXNKR-a2-huA`,
type: 'GET',
success: function (assetsData) {
const getAssetsData = assetsData.fields
$('.data-image').append(JSON.stringify(getAssetsData, null, 4))
$('.data-image').append('<br>');
}
})
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<pre class="data-banner">
<h4>Get Data Main Banner:</h4>
</pre>
<br>
<pre class="data-image">
<h4>Get Data for Each Image in Main Banner:</h4>
</pre>

Send true or false to database wether checkbox is checked or not

i got an issue regarding checkboxes with nedb. I want to send true or false if the checkbox is checked or not to the database i cannot solve this issue. i am working with node.js and nedb. please help!
client js eventlistener:
var taskDone = document.querySelectorAll('.taskDone');
taskDone.forEach(btn => {
btn.addEventListener('click', (e) => {
var done = e.target.attributes[1].value;
let id = e.target.getAttribute('data-id');
let isDone = document.querySelector(`input[data-id=${id}]`).value;
console.log(isDone + "isdone")
if ($(taskDone).is(':checked')) {
$('.text').addClass('line-through')
console.log("trues")
$.ajax({
url: 'http://localhost:3000/done/' + id,
type: 'PUT',
data: { isDone }
}).done(function (data) {
//location.reload()
console.log(data)
})
} else {
console.log('falses')
$('.text').removeClass('line-through')
}
})
})
update function to nedb:
function taskIsDone (id, done) {
return new Promise((resolve, reject) => {
db.update({ _id: id }, { $set: done }, { returnUpdatedDocs: true }, (err, num, updateDocs) => {
if (err) {
reject(err)
} else {
resolve(updateDocs)
}
})
})
}
server:
app.put('/done/:_id', async(req, res) => {
try {
var id = req.params._id;
let done = {
title: req.body.isDone,
}
const updateToDo = await taskIsDone(id, done)
console.log(updateToDo + " Todo done");
res.json(updateToDo);
} catch (error) {
res.json({error: error.message});
}
})
html/ejs:
<% for ( var i = 0; i < row.length; i++) { %>
<div class="edit-container" >
<input type="text" name="editTask" value="<%=row[i].title %>" data-id="<%=row[i]._id %>">
<button name="<%= row[i]._id %>" class="edit" data-id="<%=row[i]._id %>">save edit</button>
</div>
<div>
<input type="checkbox" name="isDone" class="taskDone" data-id="<%=row[i]._id %>">
<span class="text"><%= row[i].title %></span>
<button class="delete" name="<%= row[i]._id %>">delete</button>
</div>
<br>
<% } %>
i could really need some help with this! thanks
I have recreated a minimal example of what you are trying to do with checkbox checked state. I have added three checkboxes with same class name .taskDone
And i have using a change function not a click function. Every-time you clicked on the checkbox and check it will show the console log with checked and the data-id of that checkbox as well.
To get the data-id you can simply use .data function of jQuery and just specify what you want after the data-** to get it stored value.
In addition, do not use fat arrow - => function with jQuery. Use normal function statements so you can access you things by using $(this) instead of specifying each class or id
Live Working Demo:
let taskDone = document.querySelectorAll('.taskDone'); //get all the chechbox with same class .taskDone
taskDone.forEach(function(btn) { //use normal function
btn.addEventListener('change', function() {
let id = $(this).data('id') //get the data id of checkbox
if ($(this).is(':checked')) { //check if the clicked checkbox is checked or not
console.log(id + ' is Checked - Updating neDB') //console.log
$.ajax({
url: 'http://localhost:3000/done/' + id,
type: 'PUT',
data: 'isDone'
}).done(function(data) {
console.log(data)
})
} else {
console.log("Not Checked")
}
})
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input type="checkbox" name="isDone" class="taskDone" data-id="1">
<input type="checkbox" name="isDone" class="taskDone" data-id="2">
<input type="checkbox" name="isDone" class="taskDone" data-id="3">

Decode href URL link and Redirect Page with Vue.js after POST

I have an example Vue.js setup of two pages. A list of products and then an order form.
https://listorder.netlify.com
ISSUE 1 - The URL passed from products to order page input gets encoded. I have tried to decode with decodeURI() but it still outputs encoded.
<a class="btn btn-primary btn-pill" v-bind:href="'order.html?product=' + decodeURI(item.title) + '&' ?price=' + decodeURI(item.price)" style="color:white;">Buy Now</a>
ISSUE 2 - After POST has completed, I need to redirect to a Paypal page appending data from the "Price" field on the order page. Not sure whether Vue will be required here or to add into the existing javascript.
Paypal page to redirect to https://www.paypal.me/wereallcatshere/USD then append the "price" field
JAVASCRIPT
form.addEventListener('submit', e => {
e.preventDefault()
showLoadingIndicator()
fetch(scriptURL, { method: 'POST', body: new FormData(form) })
.then(response => showSuccessMessage(response))
.catch(error => showErrorMessage(error))
})
function showSuccessMessage(response) {
console.log('Success!', response)
setTimeout(() => {
successMessage.classList.remove('is-hidden')
loading.classList.add('is-hidden')
}, 500)
}
VUE
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
items: []
},
created: function () {
fetch('listorder.json')
.then(resp => resp.json())
.then(items => {
this.items = items;
})
},
methods: {
redirect: function () {
window.location.href = "https://www.paypal.me/wereallcatshere/USD" + item.price;
}
}
});

Implementing search in js

I've been trying to implement some searching features. I need the program to read search area and redirect the user to the page with results. Here's my search.ejs
<script>
function executeSearch($this) {
console.log('button clicked');
let request_data = document.getElementById('search-area').value;
console.log("Request data : " + request_data);
$.post("search", {subtitle: request_data}, function(json) {
console.log('requested access completed');
})
}
</script>
<input id="search-area" type="search" value="" placeholder="Enter movie title" />
<button onclick="executeSearch(this)" class="details-button">Search</button>
<ul class="search-list">
<% for(let i=0; i< movieList.length; i++) { %>
<li class="single-search-result">
<h3 class="text-light">
<%=movieList[i].title%>
</h3>
<br>
</li>
<% } %>
</ul>
Here's the code handling the request :
app.post('/search', (req, res) => {
movie_controller.getBySubTitle(req.body.subtitle)
.then(result => {
res.render('search', {
movieList: result
});
})
.catch(err => {
console.log(err);
res.render(500);
})});
By using console.log, I've determined that there are no issues in transferring required data, however I can't render 'search' page after getting data from getBySubtitle(). Any ideas what can be causing this? Thank you in advance.

How to retrieve data from form using a POST route?

I am trying to retrieve data from a Bootstrap form element, and save it to a PostgresSQL database using Express and Knex. There are no errors when I run the route; however, the data from the form is saved as null. Here is my form element (I'm using React):
render() {
return (
<form>
<div className ="form-group">
<label>Add a Note:</label>
<textarea className="form-control" name="note" rows="5">
</textarea>
</div>
<button onClick={this.handleClick} className="btn btn-primary"
type="submit">Submit</button>
</form>
)
}
Here is my fetch to the POST route:
handleClick(e) {
e.preventDefault()
fetch('/create-note', {
method: 'POST'
})
}
Here is my Express POST route (app.use(bodyParser.json()) is included in this file):
app.post('/create-note', (req, res) => {
postNote(req.body.note)
.then(() => {
res.sendStatus(201)
})
})
Here is the Knex postNote function:
export function postNote(newNote) {
const query = knex
.insert({ note_content: newNote })
.into('notes')
return query
}
Any help would be appreciated!
With POST requests you may have to wait for data body to be ready. Try this
app.post('/create-note', (req, res) => {
var body = '';
request.on('data',function(data) { body += data; });
request.on('end', function(data) {
postNote(body)
.then(() => {
res.sendStatus(201)
})
});
})
try the following in your markup, and forgo using fetch
...
<form method="POST" action="/create-note" enctype='application/json'>
...
</form>
...
or since the default encoding for a form is application/x-www-form-encoded (doc), add the following middleware to your express app..
...
app.use(bodyParser.urlencoded({ extended: true }));
...
also you could try...
...
<button ref="form" onClick={this.handleClick} className="btn btn-primary"
type="submit">Submit</button>
...
along with
handleClick(e) {
e.preventDefault();
const data = new FormData(this.refs.form);
fetch('/create-note', {
method: 'POST',
body: data
})
}
I found a solution and want to post it incase anyone else runs into a similar issue. The problem was I wasn't querying textarea's value correctly, so I was passing an undefined variable to the database to save.
Here's the solution I came up with:
handleSubmit(e) {
const data = new FormData(e.target)
const text = {note: data.get('note')}
fetch('/create-note', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(text)
})
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<div className ="form-group">
<label>Add a Note:</label>
<textarea className="form-control" name="note" rows="5">
</textarea>
<button ref="textarea" className="btn btn-primary"
type="submit">Submit</button>
</div>
</form>
)
}
I put a onSubmit event listener on the form, and created a new FormData instance with the form. Then I created an object containing the value of the textarea to pass into the fetch call.

Categories