Jquery calculating sum with dynamic IDs - javascript

I have a google map and when a user adds a marker, it calculates the distance between that marker and the previous marker. I am adding the distances to a table via jQuery. The distances work fine. I want to have an "accumulated distance column where it adds the distances up to that point. So it looks like this:
I am dynamically assigning ids to the spans on each row. I am not sure how to go about creating the accumulated distance column. This is what I have done so far:
var currentIndex=0;
var distancenm = Math.round((((google.maps.geometry.spherical.computeDistanceBetween(FromLocation, ToLocation))/1000)) * 0.539957);
var accdistance = $("#distance_0").val();
var accdistance2 = $("#distance_1").val();
var accdistancetotal = (accdistance+accdistance2);
$('#table').append('<tr id="table_row_'+(currentIndex-1)+'">'+
'<td><span id="distance_'+(currentIndex-1)+'" name="distance_[]" class="distance">'+distancenm+' </span></td>'+
'<td><span id="accdistance_'+(currentIndex-1)+'" name="accdistance_[]" class="accumulateddistance">'+accdistancetotal+' </span></td>'+
'</td>'
I also don't know how to select the ID without directly referencing it like this:
$("#distance_0").val();
because I won't know how many points the user will add. So I need to have it in such a way that each row's accumulated distance column is just the sum of the current row's distance column + the previous row's distance column.
EDIT: It looks like this after Mario's suggestion:

To sum all your current distances you need to do that:
var accDistance = 0;
$('.distance').each(function() {
accDistance += parseInt($(this).text());
});
accDistance += distancenm;
I think it will work because in your example you are using val() in a <span> tag, it is incorrect.

Related

Highlight slices of text with Javascript

Problem
Suppose that in the backend of my Web application I have a generic string of letters:
seq = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
and an array of positions in such a string:
pos = [(0, 2), (4, 8)]
I need to render this sequence in the frontend by splitting it every n characters. Then when a user clicks a button I need to highlight the sequence between two parameters (taken from pos) for which the button refers to.
My solution
I solve this by implementing a Javascript function formatSequence which splits seq every n characters and iterates through the pos array in order to wrap each substring inside a span tag. The result is something like this:
<pre>
<span class="A">AA</span>AA<span class="B">A</span>
<span class="B">AAA</span>AA
AAAAA
</pre>
When the user clicks the button referring to the class A I simply change the CSS background rule for class A.
It works :) But the function formatSequence is way too complicated imho. It was a pain dealing with multiple lines. I prefer not posting the code since I am looking for other approaches not changing the code of such function.
A better solution?
I think that a (better?) solution would be to implement a function that given two parameters start and end it dynamically highlights the text between them. But it appears to be even more complicated than the previous one (remember that the sequence must be split every n characters and thus the highlight must be multilines).
Any suggestions? Better approach to solve this?
One simple solution would be just to print the full seq multiple times into the HTML and hide every row you don't need at the time. When a user clicks on a button, another row would be displayed (and the first one would be hidden).
HTML:
<div class="rows"></div>
<div class="buttons"></div>
JavaScript (depending on jQuery):
(function generateRowsAndButtons() {
var sequence = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
var position = [ [0,2], [4,8] ];
var $rows = $('.rows');
var $buttons = $('.buttons');
for(var i = 0; i < position.length; i++) {
if(position[i].length !== 2 || position[i][0] > position[i][1]) {
console.log("every position array needs exactly two values with the second larger than the first one");
continue;
}
// the index is used for mapping the button the highlight position
var row = '<div class="row" data-index="' + i + '" style="display: none;">';
// you should add some checks here, if position larger then the length of the string to avoid some misbehaviors. this is of course only necessary if you aren't validating the values on another place.
row += sequence.substring(0, position[i][0]);
row += '<span class="highlighted">';
row += sequence.substring(position[i][0], position[i][1]);
row += '</span>';
row += sequence.substring(position[i][1]);
row += '</div>';
var $row = $(row);
$rows.append($row);
// a button needs the index to find the link the highlighted value
var $button = $('<button data-index="' + i + '">' + position[i] + '</button>');
$buttons.append($button);
}
$buttons.find('button').click(function() {
var index = $(this).data('index');
// hide every row, except the one with the correct index
$rows.find('.row').hide().filter('[data-index="' + index + '"]').show();
});
})();
CSS:
.row .highlighted {
background: yellow;
}
Here is a jsFiddle: https://jsfiddle.net/y8uoou1L/2

Prevent overlapping while positioning element at height of another

Inside a long text document there are some "special words" to which I want to display notes/annotations on the left. Each note should be as close as possible to the level of the word it is refering to.
The HTML for this is organised in a table. Each paragraph is one table row, consisting on annotations in the left and main text in the right table column. the notes/annotations go to the left. However, unfortunately, there are also some other elements/text nodes in there.
<table>
<tr>
<td class"comments">
<span id="dog" class="note">Note for dog</span>
<span id="cat" class="note">Note for cat</span>
<span id="horse" class="note">Note for horse</span>
Somethin else than a note.
</td>
<td>[Text...]
<span id="dog_anchor" class="reference">Dog</span>
<span id="cat_anchor" class="reference">Cat</span>
<span id="horse_anchor" class="reference">Horse</span>
[Text...]
</td>
</tr>
</table>
It's easy to change the "note"-spans to absolute and positioned them on the level of their reference:
$('span[class*="note"]').each(function (index, value) {
var my_id = $(this).attr('id');
var element_ref = document.getElementById(my_id + "_anchor"); // get reference element
var pos_of_ref = element_ref.offsetTop; // get position of reference element
$(this).css('top', pos_of_ref); // set own position to position of reference element
});
However, life is not so simple here. Since there could be a lot of reference words in one line (while on other there are none of them) I need a rather sophisticated way to distribute the notes so that they are as close as possible to their references without destroying anything in the layout (e.g. being placed outside of the table cell or overlapping with other elements).
Furthermore, the height of the table cells could not be changed. Elements which are not notes must not be moved. (Note elements are always in the order they appear in the main text. That's not the problem.)
So, I need an algorithm like this:
Take all notes in a table cell.
Analyse blank space in that table cell: Which areas are blank, which are blocked?
Distribute the notes in the table cell so that each note is as close as possible to its reference word without any element colliding with any other item in the table cell.
Is there any fast and elegant way to do this without having to write hundreds of lines of code?
Here is a JSfiddle: https://jsfiddle.net/5vLsrLa7/7/
[Update on suggested solutions]
Simply setting the position of the side notes to relative or just moving notes down won't work, because in this case, the side notes will just go downwards relative to their desired position which results in side notes way to far from their reference words. After all, for a neat solution I need to side notes spread in both directions: up and down.
[Update]
The expected result would be something like this:
As you see, it's never possible to place all the notes at the height of their reference. However, the free space is used to position them as close as possible, moving them up and down.
I changed move() function as follows:
function move(){
var prev_offset = 0;
$('span.note').each(function (index, value){
var my_id = $(this).attr('id');
var element_ref = document.getElementById(my_id + "_anchor"); // get reference element
var pos_of_ref = element_ref.offsetTop; // get position of reference element
if (prev_offset >= pos_of_ref){
pos_of_ref = prev_offset + 30;
}
$(this).css('top', pos_of_ref); // set own position to position of reference element
prev_offset = pos_of_ref;
});
}
I'm assuming that your element's notes will be in the correct order always
I made some changes to your javascript:
function move()
{
var arrayTops = [];
$('span[class*="note"]').each(function (index, value)
{
var my_id = $(this).attr('id');
var element_ref = document.getElementById(my_id + "_anchor"); // get reference element
var pos_of_ref = element_ref.offsetTop; // get position of reference element
pos_of_ref = getCorrectTopPosition(arrayTops,pos_of_ref);
$(this).css('top', pos_of_ref); // set own position to position of reference element
arrayTops.push(pos_of_ref);
});
}
function getCorrectTopPosition(arrayTops, newOffsetTop)
{
var notesHeight = 18;
var marginBetweenNotes = 3;
var noteheightWithMargin = notesHeight + marginBetweenNotes;
var lastTop = arrayTops[arrayTops.length-1];
if((lastTop + noteheightWithMargin) >= newOffsetTop)
return lastTop + noteheightWithMargin;
return newOffsetTop;
}
Thanks for all the answers and comments. I was finally able to figure out at least a partical solution which works for me.
First of all, I was able to restructure my HTML, so that now the "non note" elements in the left td are all wrapped in one div which is now the very first element in the td. So, now there is nothing between notes, maybe something before them.
The idea of my solution is not to give the notes a new position but to set a new margin-top to each of them. The maximum amount of margin-top values to be added within a table cell is calculated before (called "roaming space"), being the space below the last note in a table cell. Thus, the table layout is not destroyed.
function move_notes() {
$('tr').each(function (index, value) {
var current_tr = $(this);
var last_app_element_in_tr = $(this).find('span[class*="note"]').last();
if ($(last_app_element_in_tr).length) /* Only preceed if there is at least one note in the table row */ {
var tr_height = $(this).height();
var tr_offset = $(this).offset().top;
var bottom_of_tr = tr_offset + tr_height;
var bottom_of_last_app_el = $(last_app_element_in_tr).offset().top + $(last_app_element_in_tr).height();
var roaming_space = bottom_of_tr - bottom_of_last_app_el; // Calculate the amount of pixels which are "free": The space below the very last note element
$(this).find('span[class*="note"]').each(function (index, value) {
var my_id = $(this).attr('id');
var element_ref = $(current_tr).find("#" + my_id + "_anchor");
var pos_of_ref = $(element_ref).offset().top;
var new_margin_top;
/* Calculate the new margin top: The note should be at the same level as the reference element.
When loading, in most cases the notes are placed too high. So, the margin top of the note should equal
the amount of pixels which the note is "too high". So we subtract the height and the offset of the element
before the current note from the offset of the reference. */
var previous_note = $(this).prev();
// not just notes, but every element in the td in general
if (! $(previous_note).length) // If there is no previous_note, than take the table cell
{
closest_td = $(this).closest("td");
new_margin_top = pos_of_ref - $(closest_td).offset().top;
} else {
new_margin_top = pos_of_ref - $(previous_note).offset().top - $(previous_note).height();
}
var difference_to_previous = $(this).css('marginTop').replace(/[^-\d\.]/g, '') - new_margin_top; // Calculate the difference between the old and the new margin top
if (new_margin_top > 0 && Math.abs(difference_to_previous) > 2) // Only move, if the new margin is greater than zero (no negative margins!) if the difference is greater than 2px (thus preventing ugly "micro moving".
{
var new_roaming_space = roaming_space - difference_to_previous;
if (new_roaming_space > 0) /* if there is still room to move */ {
var margin_top_ready = new_margin_top + "px";
$(this).css('margin-top', margin_top_ready);
roaming_space = new_roaming_space;
} else /* If there is no more space to move: */ {
var margin_top_ready = roaming_space + "px"; // take the rest of the "roaming space" left as margin top
$(this).css('margin-top', margin_top_ready);
return false; // Stop the execution because there is nothing left to do.
}
}
});
}
});
}
window.onload = function () {
move_notes();
};
$(window).resize(function () {
move_notes();
});
As you will notice, one of my main concerns is still not addressed: Notes are only moved down, never up. Because of various problems with my real world webpage I didn't implement that yet. However, an algorith could be something like: If the new margin top is greater than the height of the current note and the difference between the offet of the current note anchor and the following note anchor is less than the height of the current note, than subtract the height of the current note from the new margin.
Still, two problems remain:
If the window is maximized or quickly resized from a rather thin width to a greater width, the adjustment of the note positions won't work. I don't know why.
The performance could be better. As a user, you can see the notes jump down. (Because of strange and unpredictable behaviour in Firefox, I had to move the event handler from document.ready to window.onload)

Calculate the total of values in text boxes

I have a screen on my ASP.Net page, which, depending on a selection by the user, they get offered between 1 and 5 text boxes. These boxes allow the user to type in an amount.
When the user enters the screen, based on certain criteria, a certain number of these edit boxes are displayed. They are not hidden... if I want 3 boxes, then only 3 boxes are on the screen.
In javascript, as the user types in an amount, a 'Total' edit box is displayed, summing up the values from each box.
So, as the user types, the value in the total updates with the total calculated for all text boxes:
I use:
onkeyup="calculateTotal()"
on the editbox.
To do that, I use the code below:
function calculateTotal() {
var amt0 = $('.txtAmount0').val();
var amt1 = $('.txtAmount1').val();
var amt2 = $('.txtAmount2').val();
var amt3 = $('.txtAmount3').val();
var amt4 = $('.txtAmount4').val();
var amt = Number(amt0);
if (!isNaN(amt1))
amt += Number(amt1);
if (!isNaN(amt2))
amt += Number(amt2);
if (!isNaN(amt3))
amt += Number(amt3);
if (!isNaN(amt4))
amt += Number(amt4);
$('.txtTotalAmount').text('$' + amt);
}
The first issue is that it seems I am losing some precision in the calculation:
I am not sure why I am getting a strange rounding issue, when all I am doing is summing up.
Additionally, it seems a bit dirty, if I added more boxes, I need to modify the code.
Is there a better way for this code to work?
You can something like this:
var sum = 0;
$("input[class*='txtAmount']").each(function() {
if ($.isNumeric($(this).val()) ) {
sum += Number($(this).val());
}
});
$('.txtTotalAmount').text('$' + sum.toFixed(2));

Need to mark points over a custom map which I have as image

I have a hand drawn map and have to mark points on those map based on an item selected. To implement this, I thought that best would be to ensure that the size of the image is maintained and based on that coordinates of the points are decided and stored in a database. Now when a particular item is selected, I can get the respective coordinates from the database and can mark it on the page where the image will be in background and hence it will appear as the mark is on the image. Can you please tell me how I can create these markers on the image given the coordinates.
Please do let me know or point me to some better ways of implementing this also.
Here is a demonstration concept. Once you have the coordinates of the point you want, you can create a marker point and position it with CSS. Of course, there's a lot more work to do such as validation, possible mapping of coordinate sets, etc.
http://jsfiddle.net/tonicboy/L87E5/
JS:
$('#plot_form').submit(function(ev) {
ev.preventDefault();
var map = $('#map_wrapper'),
point = $('<div class="map-point"></div>'),
x = $('#lon').val(),
y = $('#lat').val();
point.css({
left: x + "px",
top: y + "px"
});
point.appendTo(map);
});
$('#reset').click(function() {
$('.map-point').remove();
$('#lat').val('');
$('#lon').val('');
});
Check this fiddle
HTML
<!-- Add database stored coordinates as value in textarea like below -->
<textarea id="coordinateVals">230|100,30|100,130|30,100|260</textarea>
In the above Coordinate values of points are seperated by " , " . X & Y of each point are seperated by " | " respectively
Javascript
var s=document.getElementById('coordinateVals').value;
var g=s.split(',');
var frag = document.createDocumentFragment(),
element = 'div',
clsName = 'sections';
for (var i=0; i<g.length; i++) {
var box = document.createElement(element);
box.className = clsName
var coordinates = g[i].split("|");
box.setAttribute('style','top:'+coordinates[0]+'px;left:'+coordinates[1]+'px;');
// Append the child into the Fragment
frag.appendChild(box);
}
// Finally, append the fragment into the real DOM
document.getElementsByTagName('body')[0].appendChild(frag.cloneNode(true));

Updating Table Totals with Javascript

I recently added some functionality to a table using jQuery to allow users to add/delete table rows on the fly which is working well. However in doing so I broke the automatic calculation of totals based on the values enter into some cells in the table rows and for the life of me I can't work out how I broke it nor how to fix it.
I've also noticed that deleting a table row doesn't update the totals either (this was the new feature that I added).
Here's the script that runs that calculates the average number of hours per week for a single row - it should also update the total moderate or high field at the bottom of the table at the same time:
$(window).load(function(){
//Sum Table Cell and Map
$('#lastYear')
.on('change', 'select', calc)
.on('keyup', 'input', calc);
function calc(){
$('#lastYear tr:has(.risk)').each(function(i,v){
var $cel = $(v.cells);
var $risk = $cel.eq(1).find('option:selected').val();
var $numb = $cel.eq(2).find('input').val();
var $weeks = $cel.eq(3).find('input').val();
var $avg = ($numb * $weeks) / 52;
var $avgRounded = Math.round( $avg * 10 ) / 10;
$cel.eq(4).find('input').val($avgRounded);
});
var tot = {high:0,moderate:0};
$('#lastYear tr:has(.risk) option:selected')
.map(function(i){
var el = $(this).val();
var qty = parseFloat($('#lastYear tr:has(.risk)').eq(i).find('td:last').prev().find('input').val());
if (!tot.hasOwnProperty(el)) {
tot[el] = 0;
}
tot[el] += qty
return tot;
}).get();
// console.log(tot);
$('#textfield4').val(tot.moderate.toFixed(1));
$('#textfield5').val(tot.high.toFixed(1));
}
});//]]>
I've setup a jsFiddle that shows the current functionality - you can see if generates the averages but generates a 'NaN' result for the totals when entering data, and when you click the Delete button it should also update the totals too.
Update: I think problem is that the script is looking for the last table cell then going to the previous one to get the value. This used to work when the average cell was the 2nd last cell, but not it's the 3rd last cell and I don't know how to look for that cell by name/id etc. Any ideas?
I'm new to Javascript and have spent almost a day trying to work this out so really appreciate some assistance with this.
Working jsFiddle Demo
You have 2 td after your desired input. So you must call prev method twice:
var qty = parseFloat(
$('#lastYear tr:has(.risk)').eq(i).find('td:last')
.prev().prev() // call prev method twice
.find('input').val()
);
Mark your cells that store the average with eg a CSS class, than use that to find the cell within the row (rather than relying on td:last).
eg:
var row = $('#lastYear tr:has(.risk)').eq(i); // if this is the TR you want..
var avgCell = row.find('td.averageCell'); // find a <TD class="averageCell">.
var avgValue = parseFloat( avgCell.val());
In general, relying on positions to find things is brittle & fairly difficult to maintain; it's better to use IDs (where there is only one) or CSS classes (where there may be multiple.) Both of these are efficient, as well.
Hope this helps!

Categories

Resources