Clicking anchor unexpectedly calls alert() - javascript

I have some code that retrieves users from a server (ajax) and I use some <a> tags to display it, and when you click on an <a> tag with a user, it's supposed to add it to an array in_group. The first one works, the second one goes to the alert() function AND also adds the user to the array, which confuses me. The remove button doesn't work either. What am I doing wrong? I want the user to be added to the in_group array only if doesn't exist, and to be deleted when the button is pressed.
var in_group = [];
$("#students-body").on('click', 'a', function() {
var modal = $("#manageGroupMembers");
var student_id = $(this).attr('student-id');
var student_name = $(this).html();
var student = {
id: student_id,
name: student_name
};
console.log(in_group.length);
if (in_group.length > 0)
{
for (i = 0; i < in_group.length; i++)
{
console.log(in_group[i].id);
if (in_group[i].id === student_id)
{
alert('in grp');
return;
}
else
{
in_group.push(student);
}
}
}
else
{
in_group.push(student);
}
RefreshGroup();
//modal.modal('hide');
});
function RefreshGroup()
{
var students_group = $("#students-group");
var html = "";
if (in_group.length > 0)
{
for (i = 0; i < in_group.length; i++)
{
html += "<span>"+in_group[i].name+"</span>";
html += "<button class='btn btn-danger' onclick='event.preventDefault();RemoveFromGroup("+i+")'>x</button>";
}
students_group.append(html);
}
}
function RemoveFromGroup(index) {
in_group.splice(index, 1);
RefreshGroup();
}
Html:
<div class="form-group">
<label for="group_members">Members</label>
<button class="btn btn-primary" style="display: block;" onclick="event.preventDefault()" id="add-student-btn">Add Member</button>
<div id="students-group"></div>
</div>
Modal:
<!-- Modal -->
<div class="modal fade" id="manageGroupMembers" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title">Add Member to Group</h4>
</div>
<div class="modal-body">
<div id="students-body"></div>
<div id="pagination-students"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>

That's because you are inserting the id in a for loop. Change your code:
...your code before this....
var student_id = $(this).attr('student-id');
var student_name = $(this).html();
var student = {
id: student_id,
name: student_name
};
var index = in_group.findIndex(function(std) {
return std.id == student.id;
});
if(index === -1) {
in_group.push(student);
} else {
alert('student was already in array')
}
This way, you check if the student is inside the array. If not (-1) you insert it. Else you alert

In this section of your code, you’re looping the in_group array to find a student entry with a specific ID.
if (in_group.length > 0)
{
for (i = 0; i < in_group.length; i++)
{
console.log(in_group[i].id);
if (in_group[i].id === student_id)
{
alert('in grp');
return;
}
else
{
in_group.push(student);
}
}
}
else
{
in_group.push(student);
}
RefreshGroup();
If you find it, you alert it, and then you stop the loop and the entire function. If you don’t find it, you push it, but you don’t break the loop. You keep looping, the check in_group[i].id === student_id is re-executed and the alert is executed as well.
You could add a return; after calling RefreshGroup, however, why not get rid of the loops and make things easier?
let isInGroup = in_group.find(studentEntry => studentEntry.id === student_id);
if(isInGroup){
alert("in grp");
}
else{
in_group.push(student);
RefreshGroup();
}
This will replace the above section completely and it will work.
As to why the removal doesn’t work:
event isn’t defined. It’ll likely produce a ReferenceError. (Same problem for your other <button>.)
Instead of inline event attributes, look into event delegation and standard event listeners (jQuery has the click method—use it!)
Look into data- attributes or jQuery’s data to hold the value of the index.
Event delegation could look like this:
var students_group = $("#students-group");
students_group.on("click", "button", function(e){
RemoveFromGroup($(this).attr("data-index"));
});
And then, when generating the HTML:
html += "<button class='btn btn-danger' data-index='" + i + "'>x</button>";
I’d also recommend reading about Array.prototype methods, particularly the iteration methods.

Related

Is there a way to uniquely identity a dynamically added element?

I am trying to dynamically load a bunch of posts from a API and then implement a like button for each of them.
function load_allposts(){
fetch("/posts")
.then(response => response.json())
.then(posts => {
var enc = document.createElement('div');
enc.className = "post-enc";
let s = ``;
posts.forEach(element => {
s += `<div class="p-container">
<div>
<button type="button" class="btn btn-link" class="profile-btn" data-id=${element[0].author_id}> ${element[0].author_name} </button>
</div>
<div class="post-body">
${element[0].body}
</div>
<div class="p1">
<span class="like-status">${element[0].likes}</span> people like this
<button class="like-btn">${element[1]}</button>
</div>
<div class="post-time">
${element[0].timestamp}
</div>
</div>`;
});
enc.innerHTML = s;
document.querySelector('#all-posts').appendChild(enc);
});
}
I would to like to modify the <span class="like-status"> element when I click the <button class="like-btn">. The only way that I can think of to get a reference to <span class="like-status"> is by adding a ID to it by implementing some kind of counter, which I feel is more like a hack rather than real solution.
I tried googling but almost all solutions involved JQuery, which I am not familiar with. Any help would be appreciated.
You can use delegate event binding document.addEventListener('click', function(event) { to trigger click event for dynamically added button.
It will raise click on every element inside document you need to find if it is one which you expect with event.target.matches('button.like-btn').
Then you can find your span with getting parent and then finding span.like-status using querySelector.
Try it below. For demo modified load_allposts. You do not need to do any change in it.
load_allposts();
document.addEventListener('click', function(event) {
if (event.target.matches('button.like-btn')) {
let span = event.target.parentElement.querySelector('span.like-status');
span.innerText = 'Modified';
}
});
function load_allposts() {
let posts = [1]
var enc = document.createElement('div');
enc.className = "post-enc";
let s = ``;
posts.forEach(element => {
s += `<div class="p-container">
<div>
<button type="button" class="btn btn-link" class="profile-btn" data-id=element[0].author_id> element[0].author_name </button>
</div>
<div class="post-body">
element[0].body
</div>
<div class="p1">
<span class="like-status">element[0].likes</span> people like this
<button class="like-btn">element[1]</button>
</div>
<div class="post-time">
element[0].timestamp
</div>
</div>`;
});
enc.innerHTML = s;
document.querySelector('#all-posts').appendChild(enc);
}
<div id='all-posts'>
</div>
Note event delegation have extra overhead so alternatively you can use below code.
Here added two functions added as below and added one line bindClickEvent(enc); at end of load_allposts function.
likeClick - perform custom logic to update span.like-status
bindClickEvent - bind click event to all button.like-btn inside div
Call bindClickEvent(enc); at end of load_allposts function.
Try it below.
load_allposts();
// perform custom logic to update span.like-status
function likeClick(event) {
// querySelector will return first matching element
let span = event.target.parentElement.querySelector('span.like-status');
span.innerText = 'Modified';
}
// bind click event to all button.like-btn inside div
function bindClickEvent(enc) {
// querySelectorAll will return array of all matching elements
let buttons = enc.querySelectorAll('button.like-btn');
// loop over each button and assign click function
for (let i = 0; i < buttons.length; i++) {
buttons[i].onclick = likeClick;
}
}
function load_allposts() {
let posts = [1]
var enc = document.createElement('div');
enc.className = "post-enc";
let s = ``;
posts.forEach(element => {
s += `<div class="p-container">
<div>
<button type="button" class="btn btn-link" class="profile-btn" data-id=element[0].author_id> element[0].author_name </button>
</div>
<div class="post-body">
element[0].body
</div>
<div class="p1">
<span class="like-status">element[0].likes</span> people like this
<button class="like-btn">element[1]</button>
</div>
<div class="post-time">
element[0].timestamp
</div>
</div>`;
});
enc.innerHTML = s;
document.querySelector('#all-posts').appendChild(enc);
// assign click event to buttons inside enc div.
bindClickEvent(enc);
}
<div id='all-posts'>
</div>

sessionStorage return null

Im trying to achieve this piece of code but in my console it says thing is null which is weird because when I look in the console, sessionStorage isn't empty...
$(".btn-alert").click(function(){
var identifierOfSpan = $(this > "span").text();
for(var prop in sessionStorage){
var thing = JSON.parse(sessionStorage.getItem(prop))
if(thing.id == identifierOfSpan){
sessionStorage.removeItem(prop);
}
}
$(this).closest(".voyages").remove();
if(sessionStorage.length == 0){
alert("Message!");
location.href="reservation.html"
}
});
the button is supposed to delete the div and the sessionStorage item which looks like this
Html :
<div class="voyages">
<button class="btn btn-alert btn-md mr-2" tabindex="-1">delete the flight</button>
<span>ID : 4224762</span>
<div class="infos">
<img src="img/angleterre.jpg" alt="maroc">
<div>
<ul>
<li><h5>Angleterre, Londres (LON)</h5></li>
<li><h5>2 adulte(s)</h5></li>
<li><h5> Aucun enfants </h5></li>
<li><h5>Type : Couple</h5></li>
</ul>
</div>
</div>
<hr>
<h3>Options</h3>
<ul>
<li>voiture : 0</li>
<li>Hotel : 0 </li>
</ul>
<hr>
<h3>Prix :3713$</h3>
If I'm reading your question correctly, you want to...
Click on a button
Find the first sibling <span> element and parse a number out of its text content
Remove all sessionStorage items (JSON serialized objects) with matching id properties
For the ID, I highly recommend adding some data directly to the <button> to help you identify the right record. If you can, try something like
<button class="btn btn-alert btn-md mr-2" data-voyage="4224762"...
Try something like this
$('.btn-alert').on('click', function() {
const btn = $(this)
const id = btn.data('voyage')
// or, if you cannot add the "data-voyage" attribute
const id = btn.next('span').text().match(/\d+$/)[0]
// for index-based removal, start at the end and work backwards
for (let i = sessionStorage.length -1; i >= 0; i--) {
let key = sessionStorage.key(i)
let thing = JSON.parse(sessionStorage.getItem(key))
if (thing.id == id) {
sessionStorage.removeItem(key)
}
}
// and the rest of your code
btn.closest(".voyages").remove();
if(sessionStorage.length === 0) {
alert("Message!");
location.href = 'reservation.html'
}
})
The problem with using a for..in loop on sessionStorage is that you not only get any item keys added but also
length
key
getItem
setItem
removeItem
clear

Unable to sort div Elements using javascript

This is the HTML elements which i am trying to sort
<div id="notStarted-tasks">
<div id="K1512844566066" class="row">
<div class="span4 offset4 alert alert-warning" style="margin: auto; display: table;"><span style="font-size: 170%">Breakfast</span>
<button href="#" type="button" class="btn btn-warning">Start</button>
</div>
</div>
<div id="I1512844569230" class="row">
<div class="span4 offset4 alert alert-warning" style="margin: auto; display: table;"><span style="font-size: 170%">dinner</span>
<button href="#" type="button" class="btn btn-warning">Start</button>
</div>
</div>
</div>
And this is my sort function in javascript, in the first "if" block i am just returning if the list is empty. In the second "if" i am doing a descending sort and in the "else" ascending sort.
const todoSort = () =>{
if(notStartedElement.length==0)
return;
if(todoCurrent=="asc"){
document.getElementById("todoSort").setAttribute("src","src/image/desc.png");
todoCurrent="desc";
const ele = document.getElementById("notStarted-tasks");
const divEle = ele.getElementsByClassName("row");
divEle.sort(function(a,b){
let spana = a.getElementsByTagName("span")[0];
let spanb = b.getElementsByTagName("span")[0];
return spana.innerHTML == spanb.innerHTML? 0 : (spana.innerHTML > spanb.innerHTML ? -1 : 1);
});
ele.innerHTML="";
for(let i = 0;i<divEle.length;i++)
ele.appendChild(divEle[i]);
}
else{
document.getElementById("todoSort").setAttribute("src","src/image/asc.png");
todoCurrent="asc";
const ele = document.getElementById("notStarted-tasks");
const divEle = ele.getElementsByClassName("row");
divEle.sort(function(a,b){
let spana = a.getElementsByTagName("span")[0];
let spanb = b.getElementsByTagName("span")[0];
return spana.innerHTML == spanb.innerHTML? 0 : (spana.innerHTML > spanb.innerHTML ? 1 : -1);
});
ele.innerHTML="";
for(let i = 0;i<divEle.length;i++)
ele.appendChild(divEle[i]);
}
}
When i click on my sort button i get this message "Uncaught TypeError: divEle.sort is not a function". And i need this to be done only in javascript and i am not allowed to use jquery. So, can anybody point me out what am i doing wrong here ?
Thank you
getElementsByClassName returns a HTMLCollection and you need a Array to use sort.
You can easily make it into an array like this:
var collection = document.getElementsByClassName("some-class");
var array = [];
for (var x = 0; x < collection.length; x++) {
array.push(collection[x]);
}
array.sort(...);

How to efficiently build a document fragment [duplicate]

I'm currently using AJAX with Django Framework.
I can pass asynchronous POST/GET to Django, and let it return a json object.
Then according to the result passed from Django, I will loop through the data, and update a table on the webpage.
The HTML for the table:
<!-- Modal for Variable Search-->
<div class="modal fade" id="variableSearch" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
<h4 class="modal-title" id="myModalLabel">Variable Name Search</h4>
</div>
<div class="modal-body">
<table id="variableSearchTable" class="display" cellspacing="0" width="100%">
<thead>
<tr>
<th> Variable Name </th>
</tr>
</thead>
</table>
<p>
<div class="progress">
<div class="progress-bar progress-bar-striped active" id="variableSearchProgressBar" role="progressbar" aria-valuenow="45" aria-valuemin="0" aria-valuemax="100" style="width: 45%">
<span class="sr-only">0% Complete</span>
</div>
</div>
</p>
<p>
<div class="row">
<div class="col-lg-10">
<button class="btn btn-default" type="button" id="addSearchVariable" >Add</button>
</div>
</div>
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" id="variableSearchDataCloseButton" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
Basically it is a bootstrap 3 modal, with jQuery DataTable, and with a progress bar to show the user the current progress.
The Javascript that is used to get Django results:
$('#chartSearchVariable').click(function(event)
{
$('#chartConfigModal').modal("hide");
$('#variableSearch').modal("show");
var csrftoken = getCookie('csrftoken');
var blockname = document.getElementById('chartConfigModalBlockname').value;
$('#variableSearchProgressBar').css('width', "0%").attr('aria-valuenow', "0%");
event.preventDefault();
$.ajax(
{
type:"GET",
url:"ajax_retreiveVariableNames/",
timeout: 4000000,
data:
{
'csrfmiddlewaretoken':csrftoken,
'blockname':blockname
},
success: function(response)
{
if(response.status == "invalid")
{
$('#chartConfigModal').modal("hide");
$('#variableSearch').modal("hide");
$('#invalid').modal("show");
}
else
{
configurationVariableChart.row('').remove().draw(false);
for (i = 0 ; i < response.variables.length; i++)
{
configurationVariableChart.row.add(
$(
'<tr>' +
'<td>' + response.variables[i] + '</td>' +
'<tr>'
)[0]);
}
configurationVariableChart.draw();
$('#variableSearchProgressBar').css('width', "100%").attr('aria-valuenow', "100%");
}
},
failure: function(response)
{
$('#chartConfigModal').modal("hide");
$('#variableSearch').modal("hide");
$('#invalid').modal("show");
}
});
return false;
});
$('#addSearchVariable').click(function(event)
{
$('#variableSearch').modal("hide");
$('#chartConfigModal').modal("show");
document.getElementById('chartConfigModalVariable').value = currentVariableNameSelects;
});
$('#variableSearchDataCloseButton').click(function(event)
{
$('#variableSearch').modal("hide");
$('#chartConfigModal').modal("show");
});
The problem is with the updating table part:
configurationVariableChart.row('').remove().draw(false);
for (i = 0 ; i < response.variables.length; i++)
{
configurationVariableChart.row.add(
$(
'<tr>' +
'<td>' + response.variables[i] + '</td>' +
'<tr>'
)[0]);
}
configurationVariableChart.draw();
$('#variableSearchProgressBar').css('width', "100%").attr('aria-valuenow', "100%");
Since the response.variables can be over 10k, and it will freeze the web browser, even though it is still drawing.
I'm pretty new to Web Design (less than 4 months), but I assume it's because they are all running on the same thread.
Is there a way in Javascript to do threading/async? I had a search, and the results were deferred/promise which seems very abstract at the moment.
Try processing retrieved data incrementally.
At piece below , elements generated in blocks of 250 , primarily utilizing jQuery deferred.notify() and deferred.progress().
When all 10,000 items processed , the deferred object is resolved with the collection of 10,000 elements. The elements are then added to document at single call to .html() within deferred.then()'s .done() callback ; .fail() callback cast as null .
Alternatively , could append elements to the document in blocks of 250 , within deferred.progress callback ; instead of at the single call within deferred.done , which occurs upon completion of the entire task.
setTimeout is utilized to prevent "freeze the web browser" condition .
$(function() {
// 10k items
var arr = $.map(new Array(10000), function(v, k) {
return v === undefined ? k : null
});
var len = arr.length;
var dfd = new $.Deferred();
// collection of items processed at `for` loop in blocks of 250
var fragment = [];
var redraw = function() {
for (i = 0 ; i < 250; i++)
{
// configurationVariableChart.row.add(
// $(
fragment.push('<tr>' +
'<td>' + arr[i] + '</td>' +
'</tr>')
// )[0]);
};
arr.splice(0, 250);
console.log(fragment, arr, arr.length);
return dfd.notify([arr, fragment])
};
$.when(redraw())
// `done` callbacks
.then(function(data) {
$("#results").html(data.join(","));
delete fragment;
}
// `fail` callbacks
, null
// `progress` callbacks
, function(data) {
// log , display `progress` of tasks
console.log(data);
$("progress").val(data[1].length);
$("output:first").text(Math.floor(data[1].length / 100) + "%");
$("output:last").text(data[1].length +" of "+ len + " items processed");
$("#results").html("processing data...");
if (data[0].length) {
var s = setTimeout(function() {
redraw()
}, 100)
} else {
clearTimeout(s);
dfd.resolve(data[1]);
}
})
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<progress min="0" max="10000"></progress><output for="progress"></output>
<output for="progress"></output><br />
<table id="results"></table>
jsfiddle http://jsfiddle.net/guest271314/ess28zLh/
Deferreds/promises won't help you here. JS in the browser is always single-threaded.
The trick is not to build up DOM elements via JS. That is always going to be expensive and slow. Rather than passing data in JSON from Django and building up a DOM dynamically, you should get Django to render a template fragment on the server side and pass that whole thing to the front-end, where the JS can simply insert it at the relevant point.

Server side pagination with tablesorter - How to refresh it?

I have added a server side pagination with table sorter successfully. I just would like to know how can I refresh it? I would like to create a button to call a refresh function. Does anyone know if there is any method to do it? I do not want to reload the page for it.
UPDATE:
ajaxProcessing: function(data){
if (data && data.hasOwnProperty('rows')) {
var r, row, c, d = data.rows,
total = data.total_rows,
headers = data.headers,
rows = [],
len = d.length;
for ( r=0; r < len; r++ ) {
row = []; // new row array
// cells
for (c in d[r]) {
if (typeof(c) === "string") {
row.push(d[r][c]); //add each table cell data to row array
}
}
rows.push(row); // add new row array to rows array
}
var items="";
$("#tabelaTickets tr:has(td)").remove();
if (rows!==null && rows.length!== 0) {
$.each(rows,function(index,item) {
$("#tabelaTickets").append('<tr class="danger"><td align="center" style="width: 70px"><a type="button" class="btn btn-primary btn-xs" data-placement="right" title="Visualizar ticket" data-toggle="modal" class="btn btn-primary" href="visualizar.php?ticket='+item[3]+'"> #' + item[3] + '</a></td><td><div style="text-overflow:ellipsis;overflow:hidden;width:250px">' + item[4] + '</div></td><td><div style="text-overflow:ellipsis;overflow:hidden;width:350px;">' + item[5] + '</div></td><td><div style="text-overflow:ellipsis;overflow:hidden;width:250px;">' + item[6] + '</div></td><td><div style="text-overflow:ellipsis;overflow:hidden;width:60px;">' + item[7] + '</div></td><td><div style="text-overflow:ellipsis;overflow:hidden;width:70px;">' + item[8] + '</div></td></tr>');
});
}else{
$("#tabelaTickets").append('<tr><td colspan = "6" align="center">SEM RESULTADO A SER EXIBIDO</td></tr>');
}
$("#tabelaTickets").trigger("update");
$("#tabelaTickets").trigger("appendCache");
$("#pleaseWaitDialog").modal('hide');
// in version 2.10, you can optionally return $(rows) a set of table rows within a jQuery object
return [ total];
}
},
Thanks since now,
Erik
your repsonse is JSON, it's easy with a little AJAX function.
example your HTML is look like :
<div class="wrapper">
<div class="item">
<span>item 01</span>
</div>
<div class="item">
<span>item 02</span>
</div>
<div class="item">
<span>item 03 </span>
</div>
</div>
<button class="btn refresh-btn" type="submit"></button>
your response JSON maybe look like :
response = {
{ content : item11 },
{ content : item12 },
{ content : item13 }
};
your HTML render function with AJAX will be look like :
$('.refresh-btn').on('click', function() {
var url = 'yourUrl/?param=refresh&example=true';
var $wrapper = $('.wrapper'); // a div that wrap your new HTML.
$.get(url, {}) //call AJAX GET new item.
.done(function(data) {
$wrapper.html(''); // clear old list;
var $template = $('<div/>', {class : 'item'} ); // create item's HTML.
data.arrayItemList.forEach(function(item) {
var itemTemplate = $template.clone();
itemTemplate.append($('<span/>').text(item.content));
$wrapper.append(itemTemplate); // add new item in list.
});
});
})
that's mean : you create new HTML, and fill it with your data, everything worked fine.
Some time I create a empty template some where in view and clone it.
<div class="sample-template">
<div class="item">
<span> </span>
</div>
</div>
when I need it, I call the jQuery var $template = $('.sample-template').clone(); then fill data with $template.find('span').text(item.content);

Categories