D3/CSS Dom upward traversal/selection - javascript

I have the following HTML structure to represent a calendar:
<table>
<thead>...</thead>
<tbody>
<tr>...</tr>
<tr>
<td day="4">...</td>
<td day="5">...</td>
<td day="6" class="is-startrange">...</td>
<td day="7">...</td>
<td day="8">...</td>
</tr>
<tr>
<td day="9">...</td>
<td day="10">...</td>
<td day="11">...</td>
<td day="12">
<button class="day" type="button">12</button>
</td>
<td day="13">...</td>
</tr>
</tbody>
</table>
My question is: starting from the button under day 12, how can I traverse up, select all the button elements until a is-startrange class is encountered?
Each table cell is a button representing a date and listeners have been added to all the button elements. When a date is clicked, I will get the selected date as starting point.
I want to add style to all the button elements between the start date and selected date (either add class or through pure CSS).
Is there a way this can be achieved in D3 selection or pure CSS?

As Gerardo Furtado already mentioned in his comment the question is not actually about traversing the DOM upwards, but rather about an iteration of td elements. This can easily be done by using d3.selectAll("td") which will yield a flattened selection of all tds found on the page. Depending on your layout you might need to further narrow the selection down to a specific table which could be done by adjusting the selector to "table.myTable td", "#tableId td" or the like.
Having this selection at hand you can apply a class, say range, by using selection.classed(names[, value]) which can take a function passed in as the second argument value:
If the value is a function, then the function is evaluated for each selected element, in order, being passed the current datum (d), the current index (i), and the current group (nodes), with this as the current DOM element. The function’s return value is then used to assign or unassign classes on each element.
The only task left is to implement a filter function which keeps track, if an element is within the desired or range or not and, thus, determines whether to assign the range class.
The following snippet shows how this could all be put together using a filter function rangeFilter() provided to .classed():
// The day parameter determines the stop criterion
function rangeFilter(day) {
// This property is closed over by the following function to keep track of the
// range. If this is true, this element and following elements belong to the
// range until this property becomes false again once reaching the button's td.
var inRange = false;
// Filter function returning true, if the element belongs to the range.
return function(d) {
element = d3.select(this); // The actual td element of this iteration step.
// Evaluate if the element is still in the range or, in case the range has not
// yet started, check if we reached the td.is-startrange.
inRange = (inRange && element.attr("day") != day)
|| element.classed("is-startrange");
// XOR to exclude the .is-startrange element.
return inRange != element.classed("is-startrange");
}
}
d3.selectAll("button")
.on("click", function() {
// For all tds check if they belong to the range and set the class based
// on the result of the filter function passing in this buttons value.
d3.selectAll("td")
.classed("range", rangeFilter(d3.select(this).text()));
});
.is-startrange {
background-color: limegreen;
}
.range {
background-color: red;
}
<script src="https://d3js.org/d3.v4.js"></script>
<h1>Hit the button</h1>
<table>
<thead>...</thead>
<tbody>
<tr>...</tr>
<tr>
<td day="4">...4...</td>
<td day="5">...5...</td>
<td day="6" class="is-startrange">...6...</td>
<td day="7">...7...</td>
<td day="8">...8...</td>
</tr>
<tr>
<td day="9">...9...</td>
<td day="10">...10...</td>
<td day="11">...11...</td>
<td day="12">
<button class="day" type="button">12</button>
</td>
<td day="13">...13...</td>
</tr>
</tbody>
</table>

Related

How do I filter a table by any matching code/name and not every available field

I'm trying to do the following: I have a table populated with data from the DB. Apart from that, I have an input where you can write something and a button that will filter, only showing the lines that have that string. This is working now!
The thing is, the input should only allow you to filter by foo.name/foo.code (two propertys of my entity).
I'm adding the code I have in case anyone can guide me out, I've tried several things but this are my first experiences with JQuery while I have a strict story-delivery time. Thanks everyone!
<tbody>
<c:forEach var="foo" items="${foo}">
<tr id = "fooInformation" class="mtrow">
<th id="fooName" scope="row">${foo.name}</th>
<td id="fooCode" class="left-align-text">${foo.code}</td>
<td class="left-align-text">${foo.country}</td>
<td class="left-align-text">${foo.region}</td>
<td class="left-align-text">${foo.subregion}</td>
</tr>
</c:forEach>
</tbody>
$("#search").click(function () { -> button id
var value = $("#fooRegionSearch").val(); -> value of the input
var rows = $("#fooRegionTable").find("tr"); -> table id
rows.hide();
rows.filter(":contains('" + value + "')").show();
});
To start with, your HTML is invalid - there cannot be elemenets with duplicate IDs in HTML. Use classes instead of IDs.
Then, you need to identify which TRs pass the test. .filter can accept a callback, so pass it a function which, given a TR, selects its fooName and fooCode children which contain the value using the :contains jQuery selector:
$("#search").click(function() {
var value = $("#fooRegionSearch").val();
var rows = $("#fooRegionTable").find("tr");
rows.hide();
rows.filter(
(_, row) => $(row).find('.fooName, .fooCode').filter(`:contains('${value}')`).length
).show();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table id="fooRegionTable">
<tr id="fooInformation" class="mtrow">
<th class="fooName" scope="row">name1</th>
<td class="fooCode" class="left-align-text">code1</td>
<td class="left-align-text">${foo.country}</td>
<td class="left-align-text">${foo.region}</td>
<td class="left-align-text">${foo.subregion}</td>
</tr>
<tr id="fooInformation" class="mtrow">
<th class="fooName" scope="row">name2</th>
<td class="fooCode" class="left-align-text">code2</td>
<td class="left-align-text">${foo.country}</td>
<td class="left-align-text">${foo.region}</td>
<td class="left-align-text">${foo.subregion}</td>
</tr>
</table>
<button id="search">click</button><input id="fooRegionSearch" />

How to select previous sibling of a parent whose child is last among the input which are not null, using JQuery

I have a little complicated situation here, one which, despite my serious efforts, am unable to find reasonable solution of. So I am placing it here. I have javascript, jQuery and HTML with following details:
var lastDateIndex ='';
function datecheck(){
lastDateIndex = $('td, input[name=date]:not(:empty):last').prev('[name=index]');
alert(lastDateIndex.html());
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id='table1'>
<tr id='row1'>
<th>Index</th>
<th>Date</th>
<th>Description</th>
<th>Payment</th>
<th>Balance</th>
</tr>
<tr id='row2' name='row'>
<td name='index'>1</td>
<td name='rowdate'><input type="date" title="date1" name="date" onblur="datecheck();"></td>
<td name='description'><input title="description1" name="description"></td>
<td name='description'><input title="paymentpay1" name="paymentpay"></td>
<td name='description'><input title="balance1" name="balance"></td>
</tr>
<tr id='row3' name='row'>
<td name='index'>2</td>
<td name='rowdate'><input type="date" title="date2" name="date" onblur="datecheck();"></td>
<td name='description'><input title="description2" name="description"></td>
<td name='description'><input title="paymentpay2" name="paymentpay"></td>
<td name='description'><input title="balance2" name="balance"></td>
</tr>
<tr id='row4' name='row'>
<td name='index'>3</td>
<td name='rowdate'><input type="date" title="date3" name="date" onblur="datecheck();"></td>
<td name='description'><input title="description3" name="description"></td>
<td name='description'><input title="paymentpay3" name="paymentpay"></td>
<td name='description'><input title="balance3" name="balance"></td>
</tr>
</table>
This table has numerous input fields and all of them are to be filled by user as per his/hers need. I need to select the td with name='index' inside of last tr where td with input[name='date'] is not null. In other words, if the user has entered date details in input[name='date'] and [title='date1'] inside tr with id='row2' and has left all remaining rows to be blank, I want to select the html inside of name='index' inside tr with id='row2'.
The function I have written above only alerts 1, even if all the rows except the last one are filled. How can I acheive the answer of the html of name='index' of the last tr with empty name='date'?
As far as I know it can't be done using only selectors, so consider the following:
In case the user writes a value in input[name='date'], update its parent TD and add a class/data-* attribute (for instance: addClass('date-isnt-null')).
Use the following selector:
$('.tr:last-child td.date-isnt-null[name="index"]');
If whenever you call your function you want to output all the rows with a date entered, you can use:
function datecheck() {
$('input[name=date]').each(function(i, el) {
if ($(el).val()) {
console.log($(el).parents('tr').find('[name=index]').html());
}
});
}
(not sure to understand the last index bit).
As for Ofir Baruch suggestion, here is a way to go:
$(function() {
$('input[name=date]').bind('blur', function() {
if ($(this).val()){
$(this).parent('td').addClass("dirty");
} else {
// in case the user removes the date
$(this).parent('td').removeClass("dirty");
}
});
});
function datecheck() {
var html = $('.tr:last-child td.dirty[name="index"]').html();
console.log(html);
}
On input date blur and if the user entered a date, we add the class dirty to its parent.

Jquery displaying id to html and removing when unselected

I am trying to display the id of a selection onto the page and then remove it when it unselected. It does display the selected id, but when I click the same seat to unselect it, it just puts the id up again. I think its a problem with the tempArray.pop.
JQuery
$(this).each(function() {
// Getting the id of each seat
var seatLocation = $(this).attr('id');
// If seatLocation is not inArray - add it - else - pop it off
//$.inArray take (value , name of array)
var index = $.inArray(seatLocation, window.tempArray);
if (index = -1) { // -1 is returned if value is not found in array
window.tempArray.push(seatLocation.replace('_',''));
} else {
window.tempArray.pop(seatLocation.replace('_',''));
}
// Show the ids in a span to the users4
console.log(window.tempArray)
// join() converts an array to a string, putting the argument between each element.
$('#seatLocation').html(window.tempArray.join(', '));
});
HTML to display the seat id
<div id='ticket-options'>
<label>Seats</label>
<span id="seatLocation"></span>
<label>Passengers</label>
<span id='seatsChosen'>0</span> of <span id='numSeats2'>4</span>
</div>
<div class="legroom">
HTML of tables with id
<table>
<tr class="row1">
<td class='n' id='1_A'>T</td>
<td class='n' id='1_B'>F</td>
<td class='n' id='1_C'>T</td>
<td class="row_num">1</td>
<td class='n' id='1_D'>T</td>
<td class='n' id='1_E'>F</td>
<td class='n' id='1_F'>T</td>
<td>Extra Legroom</br>£13.99</td>
</tr>
</table>
</div>
This is an image of the results I get when I select the same seat repeatedly.
If you have two event on the same element.
Add a event.stopPropagation();
Cause the select and unselect are on the same event.
Add this in your function and try... Hope it work for you.

If element is empty hide next element

I want to be able to find a way to hide a preceding <td>'s contents if the one above it is empty. I have my table set up as such:
<tr>
<td class="firsttd">
</td>
</tr>
<tr>
<td class="nexttd">
Hide me if above TD is empty
</td>
</tr>
<tr>
<td class="firsttd">
</td>
</tr>
<tr>
<td class="nexttd">
Hide me if above TD is empty
</td>
</tr>
And so far have:
$(".firsttd").each(function( index ) {
var dotlenght = $(this).html().length;
if (dotlenght < 1){
$(this).next('.nexttd').hide();
}
});
But cannot get it to work correctly. I cannot figure our how to tell JQuery which element to target.
Can anyone point me in the right direction?
You need to use:
$(this).parent().next().find('.nexttd').hide();
|| || ^^------find td `.nexttd`
|| ^^------Traverse to next tr
^^------Traverse to parent tr
Also you do not need to iterate over elements individually. You can target all first td elements that are empty using .filter() function and can narrow down the complete code to:
$( "td.firsttd" ).filter(function(){
return $(this).html() == "";
}).parent().next().find('.nexttd').hide();
There are two issues in your code.
First is the length of the content:
$(this).html().length; // will produce 1
$.trim($(this).html()).length; // will produce 0
The second one is hiding the next row:
$(this).next().find('.nexttd').hide();
should be:
$(this).parent().next().find('.nexttd').hide();

Adding event handlers to each table row that will affect only that table row?

This isn't actually something I'm currently attempting to do; it just occurred to me while working with another table that I have no idea how I'd go about doing it, and the entire time on the train ride home I was puzzling over different solutions, none of which I could imagine working.
Imagine this situation: there is a table of 50 rows. On each row is a button. When clicked, this button should do something to the row it's on -- say, make all its text strikethrough, or make an AJAX call with the first cell's value, or something.
How would you go about binding those event handlers?
My initial thought was something like
buttons = document.getElementsByTagName("input");
rows = document.getElementsByTagName("tr");
for (i=0;i<buttons.length;i++) {
buttons[i].addEventListener('click',function() {
makeAjaxCall(rows[i]);
});
};
That is,
For each button in the document,
Add an event handler to that button,
Which will makeAjaxCall with the data of the row whose number corresponds to the button.
The problem, of course, being that makeAjaxCall will check the value of i when it's invoked, by which time, i is equal to buttons.length, and so the function will only ever work on the final row of the table.
So I suppose you'd need to find some way to actually hard-code the current value of i within the function handler... and that's something I don't even think is possible. Is it? How would you actually do something like this?
You can refer to the button object you are adding the event listener to using 'this'
Given the table in this format
<table>
<tr>
<td> <input type='button'> </td>
<td> 1st <td>
</tr>
<tr>
<td> <input type='button'> </td>
<td> 2nd <td>
</tr>
<tr>
<td> <input type='button'> </td>
<td> 3rd <td>
</tr>
</table>
The following code will allow you to access the data in the next cell, shown using console.log() rather than any ajax calls of course.
buttons = document.getElementsByTagName("input");
for (i=0;i<buttons.length;i++) {
buttons[i].addEventListener('click',function() {
makeAjaxCall(this);
});
};
function makeAjaxCall(btn) {
var sib=btn.parentNode.nextSibling;
while (sib.nodeName !='TD') {
sib=sib.nextSibling;
}
console.log(sib.innerHTML);
}
This can be extended to find any data in the row.
This section
while (sib.nodeName !='TD') {
sib=sib.nextSibling;
}
skips any extraneous characters (white space etc) between cells.
Fiddle Here
Interesting problem. I would go about doing it the way #kennebec suggests in the comment above.
Here is the fiddle: http://jsfiddle.net/u988D/
First, a slight change in markup to add data attributes
HTML
<table>
<tr>
<td data-to-json="id">1</td>
<td data-to-json="text">Lorem ipsum dolor.</td>
<td><button type="button">Click Me</button></td>
</tr>
<tr>
<td data-to-json="id">2</td>
<td data-to-json="text">Lorem ipsum dolor.</td>
<td><button type="button">Click Me</button></td>
</tr>
<tr>
<td data-to-json="id">3</td>
<td data-to-json="text">Lorem ipsum dolor.</td>
<td><button type="button">Click Me</button></td>
</tr>
</table>
Then the javascript. Probably can be optimized a bit more.
JS
var table = document.querySelector("table")
table.addEventListener("click", function(e) {
var element = e.target,
parent
//If the element is a button
if ( element && element.nodeName == "BUTTON" ) {
parent = element.parentNode
//Find the closest parent that is a TR
while ( parent.nodeName !== "TR" ) {
parent = parent.parentNode
}
//Convert Row to JSON
var json = {},
child
for ( var i = 0, _len = parent.children.length; i < _len; i++ ) {
child = parent.children[i]
if ( child.hasAttribute("data-to-json") ) json[child.getAttribute("data-to-json")] = child.innerText
}
// Do your AJAX stuff here
console.log(json)
}
})

Categories

Resources