AJAX incorrectly GETs and POSTs multiple data - javascript

I am working on a feature of my webapp that allows a user to add a place to a list. The current path is to open a modal which shows the user's lists and allows them to choose which list to add the data to.
The problem I have is two fold:
For some reason when the user click's a list it tries to send the data multiple times depending on how many lists there are (eg. if there are 2 lists it tries to send the data to the chosen list twice.) This is causing all sorts of headaches at my database.
The modal doesn't seem to reset or something meaning every time I reopen the modal 2 more lists are added meaning there are all these duplicates (example).
I have tried adding and removing event handlers etc. but I can't seem to debug the issue myself. Thank you in advance for any support you can offer.
Here is the JS function
const addToListModal = function(venueName) {
$('#userlist-modal').modal('show');
$.ajax({
type: 'GET',
url: '/api/userlist/',
data: {
'username': userName
},
success: function(data) {
data.forEach(item => {
var listName = item.list_name;
var listId = item.id;
var listItem = $("#userListsModal").append(
`<li class="userlistModal" id="${listName}" data-name="${listName}" data-pk="${listId}">
${listName}
</li>`)
$(listItem).on('click', function(e) {
if (e.target && e.target.matches("li.userlistModal")) {
var listname = e.target.getAttribute('data-name');
var listId = e.target.getAttribute('data-pk');
addVenueToList(listId, venueName);
e.preventDefault();
}
})
})
}
});
};
AJAX Post:
const addVenueToList = function(listId, venue) {
$.ajax({
type: "POST",
url: '/api/uservenue/',
dataType: 'json',
data: {
csrfmiddlewaretoken: document.querySelector('input[name="csrfmiddlewaretoken"]').value,
'user_list': listId,
'venue': venue
},
success: function(data) {
console.log('User added: ' + data)
},
});
}
And here is the pertinent HTML
<div class="modal" id="userlist-modal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content rounded-0">
<div class="modal-header">
<h5 class="modal-title" id="ModalLabel"></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div id="lists-column" class="mt-1 ml-2 mb-1 col-2" style="height: 367px;">
<ul id="userListsModal" class="list-group list-group-flush" style="width: 250px">
{% csrf_token %}
{{ form.as_p }}
</ul>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>

$(listItem).on('click', function(e) { …
listItem is not what you think it is here.
You did
var listItem = $("#userListsModal").append(`<li class="userlistModal" …
before - but that does not return a reference to the newly appended item, it returns the jQuery object you started with (that is necessary, for method chaining to work).
So what you are doing here, is adding a click handler for #userListsModal. And since this happens in a loop over your list items, you are adding this same click handler twice (here in this situation), resp. as many times, as you have list items.
Your check
if (e.target && e.target.matches("li.userlistModal")) {
inside the callback function still works - because when you click on any of the list items, the event bubbles up the DOM tree. You are handling it when it reaches #userListsModal, but the event target is still the list item you clicked on.
You need to either add these event handlers to the individual new list items you just appended, or – probably better in a case like this – use event delegation, set up one handler for this, outside of the loop that creates the list items.

Try using jQuery's one() method: one
So, instead: $(listItem).on('click', function(e) {...})
Do this: $(listItem).one('click', function(e) {...})

I think the problem here is that the list (of lists) is not cleared every time you open the modal dialog. So every time the same items are appended and new 'click' event handlers are added. Try to call jQuery empty() method before running AJAX '/api/userlist' (it will remove all event handlers as well)
const addToListModal = function(venueName) {
$("#userListsModal").empty();
$('#userlist-modal').modal('show');
$.ajax({
// ...

Related

How to append big HTML snippets into DOM with Javascript?

I have an app which uses EJS templating to populate data.
In order to add new projects I have made a simple input form which takes all required parameters.
After an input completed an Ajax request being sent, on success I want to inject this snippet into DOM.
In simple words - After new project added I want to display play instantly by injecting into DOM without reloading the page
Is there an elegant way of inserting this div ladder as a template into DOM? It works,
<div class="projects">
<div class="projectHeader">
<div class="projectTitle">
<span>
<a data-toggle="modal" data-target="#editDeadLineModal">
<i data-id="<%=project.id%>" class="projectDeadline far fa-calendar-alt fa-2x" data-toggle="tooltip" data-placement="top" title="Set Deadline"></i>
</a>
</span>
<h5 class="projectName <%=project.id%>" data-toggle="tooltip" data-placement="top" title="Deadline <%=deadline%>" style="align-items: center;">
<%=project.name%>
</h5>
<%}%>
<div class="projectButtons">
<span data-toggle="tooltip" data-placement="top" title="Edit Project Title">
<a data-toggle="modal" data-target="#editProjectTitleModal">
<i id="editProjectName" class="editProject fas fa-pencil-alt" data-name="<%=project.name%>" data-id="<%=project.id%>"></i>
</a>
</span>
<i class="separatorDash fas fa-minus"></i>
<span data-toggle="tooltip" data-placement="top" title="Delete Project">
<a data-toggle="modal" data-target="#deleteProjectModal">
<i id="deleteProject>" class="deleteProject far fa-trash-alt" data-id="<%=project.id%>"></i>
</a>
</span>
</div>
</div>
</div>
</div>
What I have tried is recreating the entire div ladder in string and append it to parent node.
Like this:
// Add new Project
$('#addNewProjectBtn').on("click", function() {
$("#newProjectModal").on('show.bs.modal', function() {
$(".confirmNewList").on("click", function(event) {
var url = '/addNewList';
var newListTitle = $("#newListNameInput").val()
event.preventDefault();
$.post({
url: url,
data: {
listName: newListTitle
},
success: function(result) {
$("#newProjectModal").modal('hide')
$("#newListNameInput").val('');
var id = result.data.id
var name = result.data.name
//Append new project
$(".projects").append("<div class='project col-4' id='project" + id + "'> <div class='projectHeader'> <div class='projectTitle'> ...and so on until the end")
},
error: function(err) {
console.log(err);
}
})
}
}
})
})
})
In simple words - After new project added I want to display play instantly by injecting into DOM without reloading the page
Is there an more elegant and specially efficient way of inserting this div ladder as a template into DOM?
The method which I have tried above - works, But on attempt to interact with it by calling modals - modals do not get it's data-*, as well the bootstrap tooltips don't work.
you can try create new html file instead, and append like this in your page
$.get("yourfile.html", function (data) {
$("#appendToThis").append(data); // or use .html();
});
OR you can directly pass this HTML structure from your backend, so you can directly use append function.
After some research I discovered that the event handler can be delegated.
Since the html snippet I am trying to append to projects is added after document.ready (document is loaded and handlers are bind) the only thing required to make the new snippet be active on event is to delegate the event handler to the parent of element that is appended.
Like this :
$("body").delegate("#addNewProjectBtn", "click", function() {
$("#newProjectModal").on('show.bs.modal', function() {
$(".confirmNewList").on("click", function(event) {
var url = '/addNewList';
var newListTitle = $("#newListNameInput").val()
event.preventDefault();
$.post({
url: url,
data: {
listName: newListTitle
},
success: function(result) {
$("#newProjectModal").modal('hide')
$("#newListNameInput").val('');
var id = result.data.id
var name = result.data.name
//Append new project
$(".projects").append("<div class='project col-4' id='project" + id + "'> <div class='projectHeader'> <div class='projectTitle'> ...and so on until the end")
},
error: function(err) {
console.log(err);
}
})
}
}
})
})
})
By delegating the event handler to it's parent - the event handler is preserved in the parent.

Javascript Issue with aria-hidden popup

I have this ajax code that gets a res.json(event) from the server and then creates an object based on the value received.
Here is part of that code:
html += `<div class="card-header" id="headingOne-${i}">` +
`<div class="event-time"><time class="published" datetime="2017-03-24T18:18">${data[i].events.targetReminder} | ${data[i].events.targetAmPM}</time><div class="more"> <svg class="olymp-three-dots-icon"><use xlink:href="svg-icons/sprites/icons.svg#olymp-three-dots-icon"></use> </svg><ul class="more-dropdown"><li>Mark as Completed</li> <li>Delete Event </li></ul></div></div>` +
`<h5 class="mb-0 title"><a href="" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne" >${data[i].events.title}<i class="fa fa-angle-down" aria-hidden="true"></i>` +
` <span class="event-status-icon" data-toggle="modal" data-target="#public-event"><svg class="olymp-calendar-icon" data-toggle="tooltip" data-placement="top" id ="uncomplet-${i}"data-original-title="UNCOMPLETED"><use xlink:href="svg-icons/sprites/icons.svg#olymp-calendar-icon"></use></svg></span></a></h5></div>` +
`<div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#headingOne-${i}"><div class="card-body" id="${data[i].events._id}" onClick="reply_click()">${data[i].events.caption}</div><div class="place inline-items"><svg class="olymp-add-a-place-icon"><use xlink:href="svg-icons/sprites/icons.svg#olymp-add-a-place-icon"></use></svg><span>${data[i].events.location}</span></div></div></div>`;
And here is the output of the code:
The aria-hidden that I'm talking about is this one
<div class="modal fade" id="public-event" tabindex="-1" role="dialog" aria-labelledby="public-event" aria-hidden="true">
What I have tried and already know it's not a proper solution is here
Code test
I created a script that change aria-hidden => true or false but I don't know how to link it with my box
<script type="text/javascript">
function reply_click()
{
document.getElementById('public-event').setAttribute('aria-hidden', 'false');
}
</script>
another failed try:
<script type="text/javascript">
function reply_click()
{
alert('this function is called')
$(`#mark-${i}`).onclick = function() {
document.getElementById('public-event').setAttribute('aria-hidden', 'false');
};
}
</script>
I also added in the HTML this function onClick="reply_click()" but nothing is happening. I only get the alert('this function is called')
Could you suggest me an idea, please?
By looking at your question and discussion on the comment section I think that you are trying to add click event on your dynamically generated div section(html part) and open the pop up modal.
We can achieve that by using $('#id' OR '.class').modal('show') in jquery.
So why don't you add a class any where inside your div section of html and bind a click function using jquery. Suppose you have added a class name showModal on the very first div after card-header like, div="card-header showModal"
$('#eventCard').on('click', '.showModal', function(){
$('#public-event').modal('show');
});
We cannot directly use $('.showModal').click...... because document structure is change after appending the html section after #eventCard which was initially was not present. Hpe this works.
could this be your problem ? you create an item dynamically with javascript, but the item you want to select, click event, is not actually created at that time. Once the item you want to export is created, you can select it and want to make the changes you want.
udpated: I added a snippet of what I meant. in order for me to select the H1 tag that occurs after I click on the button, I have to write a function that will occur after it occurs.
const button = document.querySelector(".clickme");
const container = document.querySelector(".container");
button.addEventListener("click", () => {
container.innerHTML += `
<h1 class = "change-modal"> ı cant select this</h1>
<div class="modal fade" id="public-event" tabindex="-1" role="dialog" aria-labelledby="public-event"
aria-hidden="true"> `
})
const changeModal = document.querySelector(".change-modal")
console.log(changeModal)
<button class="clickme">Click me</button>
<div class="container" style="background-color: red;">
</div>
<h1 class="change-modal">ı select this</h1>
updated2 :
for (let i = 0; i < data.length; i++) {
html += // your html
$("eventCard").append(html);
//after this, you can select the item and type the function because the items are created here.
}
Updated 3!!! : I explained how to do things with javascript without using jquery. Hopefully you know what I mean.
async getData() { // fetch operations using javascript
const data = await fetch(url); // your api url
const jsonToData = await data.json(); // here you can Request api and obtain the data
return jsonToData;
}
getData().then((data) => {
console.log(data) // ıts probably an array.
for (let data = 0; data < jsonToData.length; data++) {
// data operations, what if you want to
html += // you printed document items,
}
})
.then(() => {
//!!! IMPORTANT!!! this is where you need to perform the operation of selecting the element function. you can also write a function that can work for the code here, but I've written it one by one for now.
const clickedElement = //type whatever element you want to click on. !!
clickedElement.addEventListener("click", function() {
const elementToChange = document.getElementById(".public-event");
elementToChange.setAttribute('aria-hidden', 'false');
})
})
.catch(err => console.log(err))

Node.js - Can't reach handlebars element from jquery using $(this)

I set a jquery function for whenever someone clicks on one of the avaliable "Delete" buttons it sends a request to "/burgers/delete/" + ("data-id" button attribute).
The problems is I can't reach the button "data-id" attribute. Is seems the "$(this)" is pointing to the window object.
app.js
$("button[id='delete']").on("click", (event) => {
event.preventDefault()
$("button[id='delete']").attr("disabled","disabled")
const id = $(this).attr("data-id")//I need the dynamc id here!
$.ajax({
url:"/burgers/delete/"+id,
method: "DELETE",
data: id
}).then((id) => {
deleteBurgerSuccess(id)
}).catch((erro) => {
deleteBurgerFails(erro)
})
})
burgers.handlebars
{{#each burgers}}
{{#unless is_favorite}}
<div class="card mt-2 mx-auto" style="width: 30rem" id="burger">
<div class="card-body">
<h4>{{burger_name}}</h4>
<br>
<button data-id="{{id}}" class="btn btn-danger" id="delete">Delete</button>
</div>
</div>
{{else}}
{{/unless}}
{{else}}
{{/each}}
My id keeps always being undefined giving me the browser console error:
DELETE http://localhost:9001/burgers/delete/undefined 500 (Internal Server Error)
I expect reaching the dynamic element which called this jquery function.
Thank you in advance.
$("button[id='delete']").on("click", (event) => {
event.preventDefault()
$("button[id='delete']").attr("disabled","disabled")
const id = $(event.target).attr("data-id")//I need the dynamc id here!
$.ajax({
url:"/burgers/delete/"+id,
method: "DELETE",
data: id
}).then((id) => {
deleteBurgerSuccess(id)
}).catch((erro) => {
deleteBurgerFails(erro)
})
})
Modify your code as shown above. $(this) has context bound to document object.You will need to access your element through event.target property.

Build input dynamically from View Model in an ajax response

I have a controller action that sends down a collection of ApiViewModel Types. Each view model represents a different API that can be executed server-side, and output the response in the browser through an ajax call using jquery. The server generates the HTML so all I have to do is insert the server-side HTML into the current page.
Some of the APIs can only execute if they are given some parameters. I'm trying to do this in a generic fashion. When the user clicks the run button, I display a model Bootstrap dialog. Within this dialog I'd like to provide the input options for the parameters on the API selected.
This is my HTML for the modal dialog
<div class="modal fade"
id="appParameters"
role="dialog"
aria-labelledby="appParametersLabel">
<div class="modal-dialog"
role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<h4 class="modal-title" id="appParametersLabel"></h4>
</div>
<div class="modal-body" id="appDialogBody">
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</div>
i have enough information to send to the server, letting the server know what API is going to be executed, and what View Model needs to go with it. What I'm not sure of though is how should I put together the HTML on the server side, so that I can send the HTML to the client and have the MVC validation attributes still work for client-side validation?
The javascript I'm using to send the data to the server, and add the servers HTML to the DOM is this. If no View Model is required, I just make a request to the server to execute the app and output the server-side response HTML. I think I don't need to do anything in regards to my Java Script below to handle the validation stuff; not sure though.
$('.btn-success').click(function () {
var button = $(this);
var appId = $(this).data("app");
var vmRequired = $(this).data("vm-required");
if (!vmRequired) {
var url = "/Home/RunApp?appId=" + appId;
$.get(url, function (data) {
$("div[data-app='" + appId + "']").html(data);
var buttonColumn = button.parent();
var appRow = buttonColumn.parent();
var hiddenRow = appRow.next()
hiddenRow.removeClass("hidden");
appRow.click(function () {
var hiddenColumn = hiddenRow.children().first();
var resultsDiv = hiddenColumn.children().first();
resultsDiv.empty();
hiddenRow.addClass("hidden");
$(this).off();
hiddenRow.off();
})
hiddenRow.click(function () {
var hiddenColumn = $(this).children().first();
var resultsDiv = hiddenColumn.children().first();
resultsDiv.empty();
$(this).addClass("hidden");
appRow.off();
$(this).off();
})
});
return;
}
var appName = $(this).data("app-name");
$('#appParametersLabel').html(appName);
$('#appParameters').modal({
keyboard: true,
backdrop: "static",
show: false,
}).on('show', function () {
$.get(url, function (data) {
$('#appDialogBody').html(data);
})
});
});
Do I just generate the HTML on the server side, like I would normally in the view? When the HTML is inserted into the DOM, will the validation all work correctly while using unobtrusive jquery validation?

Knockout error - Unable to process binding “foreach”

I'm having a bit of trouble seeing what's wrong with my code. More likely with the Knockout.js part... It's giving me the following error:
Message: Unable to process binding "attr: function (){return {href:website()} }"
HTML
<div class="demo-card-square mdl-card mdl-shadow--2dp" data-bind="foreach: favoriteSpot">
<div class="mdl-card__title mdl-card--expand">
<h2 class="mdl-card__title-text">Update</h2>
</div>
<div class="mdl-card__supporting-text" data-bind="text:name"></div>
<div class="mdl-card__supporting-text" data-bind="text:location"></div>
<a data-bind="attr: {href: website()}">Website</a>
</div>
JS
var favoriteSpotsList = [{
venueName: "name",
venueLocation: "address",
website: "url",
image: "<img src='img'",
}];
var favoriteSpot = function(data) {
this.name = ko.observable(data.venueName);
this.address = ko.observable(data.venueLocation);
this.website = ko.observable(data.website);
this.image = ko.observable(data.img);
};
var AppViewModel = function() {
var self = this;
/* Create array of hotspot locations. */
this.hotSpotList = ko.observableArray([]);
favoriteSpotsList.forEach(function(spot) {
self.hotSpotList.push(new favoriteSpot(spot));
});
};
ko.applyBindings(new AppViewModel());
As #saj and #haim770 mentioned in comment, there is no favoriteSpot property on the view-model. So, the data bind should loop the hotSpotList to get the website property in it. Like below.,
data-bind="foreach: hotSpotList"
There is an easy way to identify these kind of issues, specifically while performing bindings in view
You just need add a button with click binding, The Button should be placed before the exception line.
<button data-bind="click: function () { console.log($context); }"> Context Log </button>
The above code will log the entire context in the browser console(F12). As usual you will get the exception. And this code will not resolve the issue. But this will be very helpful to identify the issue.
The above code will log the entire context of the current operation. Which holds object, property with the value.
Below are common scenarios where as you can exactly find your binding object has exceptions.
1. Properties are present/missing due to the scope level problems?
2. Whether it has case sensitive problem?
3. Your object comes under where? Is it a parent, child / Alone?
4. Human error which makes exception while binding.
There are few other ways to find the object/data in view:
1. Logs the root:
<button data-bind="click: function () { console.log($root); }"> Root Log </button>
2. Logs the Current scope data:
<button data-bind="click: function () { console.log($data); }"> Current Data Log </button>
3. Logs the parent data: (specifically helpful when we do looping)
<button data-bind="click: function () { console.log($parent); }"> Parent Log </button>
4. Logs the list of parent data: (specifically helpful when we do looping with different types of parents)
<button data-bind="click: function () { console.log($parents); }"> Parents Log </button>
5. Logs the list of parent data: (specifically helpful when we do looping and access different types of parents)
<button data-bind="click: function () { console.log(objectName.propertyName); }">Property Log </button>
For Example in your case you can do like below:
<!-- Added this button before the exception -->
<button data-bind="click: function () { console.log(favoriteSpot); }">Log </button>
<div class="demo-card-square mdl-card mdl-shadow--2dp" data-bind="foreach: favoriteSpot">
<div class="mdl-card__title mdl-card--expand">
<h2 class="mdl-card__title-text">Update</h2>
</div>
<div class="mdl-card__supporting-text" data-bind="text:name">
</div>
<div class="mdl-card__supporting-text" data-bind="text:location">
</div>
<a data-bind="attr: {href: website()}">Website</a>
</div>
When you click the button, obviously the message will be logged as undefined in console.
Hope this helps.,

Categories

Resources