I am using stupidtable (http://joequery.github.io/Stupid-Table-Plugin/) to add sorting to a table. I use a callback to add an appropriate up or down arrow to show which column has been used for the sort and whether it is ascending or descending.
The original event handler used the code:
table.bind('aftertablesort', function (event, data) {
// data.column - the index of the column sorted after a click
// data.direction - the sorting direction (either asc or desc)
var th = $(this).find("th");
th.find(".arrow").remove();
var arrow = data.direction === "asc" ? "↑" : "↓";
th.eq(data.column).append('<span class="arrow">' + arrow +'</span>');
});
This works fine when there is only one row of headers, but if there are multiple rows it doesn't because it finds the first header row not the last.
I'm struggling to find out how to find the bottom row, following another thread I tried:
var th = $(this + ' thead tr:last').find("th");
But this gives a syntax error, I guess because I am not using 'this' properly. Can anyone help?
Thanks,
Steph
you cant combine an object with a string to build a selector.
try this:
var th = $(this).find('thead tr:last').find("th");
this is not a string, but a DOM element.
You could use it as a context selector like this :
var th = $('thead tr:last', this).find("th");
But .find is faster than context, so use this :
var th = $(this).find("thead tr:last th");
if this referes to your table you should use find
var th = $(this).find('thead tr:last')
The following line
$(this + ' thead tr:last')
Will not work, because this is an object and you are mixing with a string, which will result in
$("[object HTMLTableElement] thead tr:last")
You do know that thead is for the header section of a table. So when ask "bottom row" are you sure it's the header you want or the body?
Find the last header row if this is a parent.
var last_header_row = $(this).find("thead").last("tr");
To find the last row in the body if this is a parent.
var last_body_row = $(this).find("tbody").last("tr");
Note, that the this reference is in the context of the callback function for the event. Without knowing what that context is we can't assure that it's a DOM element reference.
If you have a reference from inside the table, where this is a tr or something. Replace the call to find(..) with closest(..)
Related
I've got a JSON string that looks like this:
{
"DocID": "NA2",
"DocType": "Phase1.1 - Visa Documents (This section is applicable for HK work location only)",
"DocSubType": "New Application",
"DocName": "Passport / Travel Document (Soft copy only) *",
"DocDescription": ""
}
In my HTML I have set the data-docid atttribute in a td as the DocID key's value (in another API call)
Now, I want to compare if value.DocID is equal (==) to the docid and insert DocSubType in that particular td.
Here is my code:
$.ajax({
type: "GET",
url: docuViewURL,
success: function(data) {
$.each(data, function(index, value) {
console.log(index);
var a = $("#candidateDetails tbody td a:eq(index)").attr("data-docid");
var b = value.DocID;
if (a == b) {
// then
}
});
}
});
It doesn't work and even the index variable won't give me the output for var a instead I get undefined.
What am I doing wrong and what can I change.
To start, you're not inserting your index variable into your selector.
This is a concatenated string:
"#candidateDetails tbody td a:eq(" + index + ")"
This is a template literal:
`#candidateDetails tbody td a:eq(${index})`
This is a string literal:
"#candidateDetails tbody td a:eq(index)"
You're using a string literal containing the word "index" to select the desired element when you should be using a concatenated string or a template literal to insert the index variable into your selector string.
Another problem is that you're passing a string instead of a number to the :eq() selector.
This is because you're using the $.each function wrong.
You're looping through a (JSON) object, not an array, there is no index, there are keys, and in your case the keys are strings (E.G: "DocID", "DocType", "DocSubType", etc, etc).
So even if you do properly insert the index variable into your selector, these are the selectors you'll end up with:
"#candidateDetails tbody td a:eq(DocID)"
"#candidateDetails tbody td a:eq(DocType)"
"#candidateDetails tbody td a:eq(DocSubType)"
"#candidateDetails tbody td a:eq(DocName)"
"#candidateDetails tbody td a:eq(DocDescription)"
You solve this problem (and save yourself a lot of trouble) by using an attribute selector instead of the :eq() selector, like this:
"#candidateDetails tbody td a[data-docid]"
This prevents you from manually having to specify the index of your elements, giving you more freedom with the positioning of elements in your HTML.
Another thing is that your code doesn't explicitly state that this is a JSON GET request.
Sure jQuery might be smart enough to tell what kind of request this is but as the old saying goes, explicit is better than implicit and that doesn't just apply to Python programs.
I'd advice you to use the jQuery.getJSON function, which is explicitly meant to create JSON GET requests, instead.
Replace your Ajax request with this:
jQuery.getJSON(docuViewURL, function(data) {
$.each(data, function(key, value) {
// NOTE: cache elements not values
var link = $("#candidateDetails tbody td a[data-docid]");
// NOTE: if you're only going to use a value once, there's no need to cache it
// NOTE: use `data("x")` instead of `attr("data-x")` for more concise code
if (link.length > 0 && link.data("docid") == value.DocID) {
// do stuff with the link
}
});
});
You could also get rid of the data() function and use an attribute selector:
jQuery.getJSON(docuViewURL, function(data) {
$.each(data, function(key, value) {
// get the link and check to see if it exists
var link = $("#candidateDetails tbody td a[data-docid='" + value.DocID + "']");
if (link.length > 0) {
// do stuff with the link
}
});
});
NOTE: if you need more information, the jQuery API documentation has a search bar that you can use to find documentation for functions you're struggling with.
NOTE: I'm not trying to be rude or insult you but I think it would go a long way if you read the MDN JavaScript guide which would help you understand the basics and fundamentals of JavaScript.
NOTE: there are also HTML and CSS guides.
Good luck and remember to keep your code DRY, SOLID and Simple.
I'm trying to display data read from a file into a website. I want it to be formatted into a table. Here is the code to set up the table headers:
let table = document.getElementById("outputTable");
table.innerHTML = `<thead><tr>`;
for(const attribute in student_sample){
table.innerHTML += `<th>${attribute}</th>`;
}
table.innerHTML += `</thead></tr>`
However, instead of creating a table row with headers, it creates MULTIPLE rows with one header in each row. It stacks the headers one after the other instead of lining them up in a single row. Any way of how to get over this error? The code works but it's just the formatting of it that is not good.
You cannot add only opening tags to the DOM table.innerHTML = '<thead><tr>'; - if the browser made that the innerHTML it would create an invalid DOM, even if your next lines of code address that.
Instead, create a string, and apply that to table.innerHTML when it's complete:
let table = document.getElementById("outputTable");
let tr = '<thead><tr>';
for(const attribute in student_sample){
tr += `<th>${attribute}</th>`;
}
tr += '</tr></thead>'
table.innerHTML = tr
will work.
The other problem your code had is that you closed thead and tr in the wrong order.
It's safer and more elegant to create elements directly and assign text rather than inserting text into a string that will be parsed as HTML later. I'd use innerHTML only when you're given an HTML string to insert, but I think you shouldn't try to create one when not necessary.
Try soemthing like this instead:
// theads are created automatically:
const thead = document.querySelector('#outputTable > thead');
Object.keys(student_sample).forEach(attribute => {
thead.appendChild(document.createElement('tr'))
.textContent = attribute;
})
you need instead to createElement that creates node into the table which is the thead then you appendChild which is creates an element inside the head then for each attr in the loop creates an element and append it to the tr
So by that you don't need to add innerHTML text and it creates nodes and add them. It helps more if your code became more complex to debug.
let table = document.getElementById("outputTable");
let head = document.createElement("thead");
let row = head.appendChild("tr");
for(const attribute in student_sample){
let th = document.createElement("th");
th.appendChild(${attribute});
row.appendChild(th);
}
$scope.deleteClick = function(e){
e.preventDefault();
var datasource = $scope.mygrid.datasource;
var index = $(e.target).closest("tr")[0].rowIndex;
datasource.remove(datasource.at(index -1));
}
At this code, you will see that I did DOM manipulation, because I did not get row index from parameter e. Please help me to get the row index.
What I can suggest you is to use the removeRow method of the Grid and just pass the TR element (you cannot avoid accessing the TR element).
grid.removeRow($(e.target).closest('tr'))
I'm a noobie that has been learning by his own and messing around with javascript and I stumbled upon that nightmare called 'regular expressions'...I kinda know a little bit about them and I been doing fancy stuff, but I'm stuck and I'd like you clarify this to me:
I've been reading and looking for a way to create matches and I tripped on with great answer:
How to perform a real time search and filter on a HTML table
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
var $rows = $('#table tr');
$('#search').keyup(function() {
var val = '^(?=.*\\b' + $.trim($(this).val()).split(/\s+/).join('\\b)(?=.*\\b') + ').*$',
reg = RegExp(val, 'i'),
text;
$rows.show().filter(function() {
text = $(this).text().replace(/\s+/g, ' ');
return !reg.test(text);
}).hide();
});
http://jsfiddle.net/dfsq/7BUmG/1133/
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
I kinda understand what is going on there but could somebody break that down and 'translate it in javascript' for me so I can catch the idea better, I barely can do cool stuff with jquery since I'm still studying javascript, I know certain things about the jqueries but not enough to fully understand what he did there and enough about regular expressions to know that the guy who wrote taht code is a genius <3
this is what i understand and please correct me:
var $rows = $('#table tr');
it's the scope, the 'target' in which is gonna be looking for the match
pd:that's the first time I see the'$' to declare variables and for what I've looked it sets it as a jQuery object..it's that right?
var val = '^(?=.*\\b' + $.trim($(this).val()).split(/\s+/).join('\\b)(?=.*\\b') + ').*$',
reg = RegExp(val, 'i'),
text;
the '$.trim($(this).val()' is equal to $.trim($("#user_input").val()); .......right?
reg = RegExp(val, 'i')
the reg variable works as a constructor to find the match that will be case-insensitive, but shouldn't it be 'reg = new RegExp(val, 'i')' or I can set as it is as well?
here is when I get confused the most
$rows.show().filter(function() {
text = $(this).text().replace(/\s+/g, ' ');
return !reg.test(text);
}).hide();
what I can understand is that the matches will be shown only if they pass the filter set by the text variable and the ones who don't will be hidden, I haven't the slightest idea of what the $(this) is equivalent to....in the text variable.. and from there on I have no idea what's going on, I found that .test() returns true or false when it finds a match in the regexp object but why does it have the ! at the beginning?
$ is the jQuery object, it's just a variable name that points to jQuery. Don't worry about $rows, that's just another variable name.
var $rows = $('#table tr');
The right-hand side is basically passing a selector to jQuery and telling it to find all DOM elements that match it. If you know CSS this is the same principle, #table is an element with id="table" and combined with tr means select all rows (tr being the table row html tag) within that element.
In pure javascript this could be written as
var $rows = document.querySelectorAll("#table tr");
The result is a list of elements.
Then you find another element, and attach an event listener:
$('#search').keyup(function() { ... });
Notice this is passing another selector to jQuery, which returns the desired element, onto which you attach a keyup event. In JavaScript this might be:
document.getElementById("search").addEventListener("keyup", function() { ... });
When that keyup event is triggered on the element the function is executed, and you are building a string val which contains:
... + $.trim($(this).val()).split(/\s+/).join('\\b)(?=.*\\b') + ...
this in $(this).val() evaluates to the element which was found by the #search selector, in this case an input field.
This might be the following in pure javascript:
... + document.getElementById("search").value.trim().split(/\s+/).join('\\b)(?=.*\\b') + ...
If you evaluate that expression, it 1) trims whitespace, 2) splits the result into an array of strings on every whitespace character and 3) joins that array with the delimiter \\b)(?=.*\\b
Steps 2) and 3) are basically a String.replace(/\s+/, '\\b)(?=.*\\b') but quicker.
Moving onto the last bit, the jQuery show() method is applied to each element in $rows, which was that list of elements (table rows) found at the beginning. This makes every row visible.
Then the filter method is applied to that list, this is a loop through the list of elements calling the function defined within on each element. Notice, this within the filter function now refers to the table row being tested, not the search field.
If that filter function returns true on a list item that item remains in the resulting list, if false it is removed. Here that prepared RegExp is applied, and if it matches the function returns false. So after filtering you have a list of elements/rows which do not match, and finally .hide() is applied which is a jQuery method to hide all elements on which it is called. So you hide the rows that don't match.
The code may look something like this in "pure" javascript (it should work now, I fixed the problem cjsmith wrote about in the comments).
var $rows = document.querySelectorAll("#table tr");
document.getElementById("search").addEventListener("keyup", function(e) {
var val = '^(?=.*\\b' + e.target.value.trim().split(/\s+/).join('\\b)(?=.*\\b') + ').*$';
var reg = RegExp(val, 'i');
Array.prototype.forEach.call($rows, function(row) {
var text = row.textContent.replace(/\s+/g, ' ');
row.style.display = reg.test(text) ? 'table-row' : 'none';
});
});
PS. Happy New Year!
var $rows = $('#table tr');
it's the scope, the 'target' in which is gonna be looking for the
match
pd:that's the first time i see the'$' to declare variables and for
what I've looked it sets it as a jquery object..it's that right?
Yes, $rows is target, but the '$' of sign is actually meaningless, that is an approach of jquery programmers. It's good for remembering a jquery object, "hımmm, this has a '$' at the start, so that must be a jquery object".
In fact var $rows = $('#table tr'); and var rows = $('#table tr'); -> these are same, there aren't any differences between $rows and rows.
-----------------------------------------------------------------------------------------
var val = '^(?=.\b' +
$.trim($(this).val()).split(/\s+/).join('\b)(?=.\b') + ').*$',
reg = RegExp(val, 'i'),
text; the '$.trim($(this).val()' is equal to $.trim($("#user_input").val()); .......right?
In javascript this refers the event's owner. For the example that you shared, this used in keyup callback function of $('#search') so that means this refers the $("#search").
-----------------------------------------------------------------------------------------
reg = RegExp(val, 'i') the reg variable works as a constructor to find
the match that will be case-insensitive, but shouldn't it be 'reg =
new RegExp(val, 'i')' or I can set as it is as well?
There are nice explanations of new keyword for Javascript in this question, you can check it.
-----------------------------------------------------------------------------------------
$rows.show().filter(function() {
text = $(this).text().replace(/\s+/g, ' ');
return !reg.test(text);
}).hide();
Let's explain it step by step
var $rows = $('#table tr');
$rows is an array of tr objects
$rows.show() means show all tr tags which are in a table that's id is #table.
jQuery is chainable, that means $rows.show() returns $rows again
so $rows.show().filter() = $rows.filter
again $rows is still is an array of tr objects, so $rows.filter() loops through this object like a for loop, the callback function is processed for each objects in $rows.
In a callback function this refers the owner, in this example the owner is the object at that time while filter is looping the $rows.
As you said test() returns a bool value.
! is an inverter,
!true = false
!false = true
I'll have a go! Guten tag, nero!
I'll answer your questions from top to bottom and see if we can get it covered. First, you're understanding of the rows variable is correct. It's a jquery object that contains an array of DOM objects that match your search string. In this case, tr's within a table.
You've also got the #user_input part right. $(this) inside the keyup function is a reference to the DOM object that threw the event in the first place. In this case, the user input.
but shouldn't it be 'reg = new RegExp(val, 'i')' or I can set as it is as well?
Using the new keyword seems safer. For further refernce, see here: http://zeekat.nl/articles/constructors-considered-mildly-confusing.html. This may not be necessary if you're relying on just the 'static' bit of a class - but it's a better safe than sorry move I'd say.
Now for the hard part:
$rows.show().filter(function() {
text = $(this).text().replace(/\s+/g, ' ');
return !reg.test(text);
}).hide();
the $rows.show() is going to make visible all the DOM objects in the rows jquery object.
Then on that same set of objects it's going to 'filter' which means it's going to reduce the DOM objects that are in var rows based on a function that returns a boolean. In this case, that function is:
text = $(this).text().replace(/\s+/g, ' ');
return !reg.test(text);
which replaces all whitespace with a single space, and then tests if it matches your regular expression. So here, if it DOESN'T pass your regular expression (! = logical NOT) it stays in the set.
Lastly - this function will hide everything that passed the filter.
So it shows everything and then hides what doesn't match your regular expression.
Hope that helps! Habst ein guten neue Jahre (how's my German?)
how wud u get the next row in the following example? (i am trying to print the next three row/column values of the rowId provided)
function printRowData(rowId)
{
var row=document.getElementById(rowId);
for(i=0; i<3 ; i++)
{
var column=row.getElementsByTagName("td");
alert(column[0].innerText);
//now i want to move to the next row...something like row=row.next()?????
}
}
If you just want the next row, and not subsequent rows, you can do this:
var next = row.parentNode.rows[ row.rowIndex + 1 ];
So your code could look like this:
function printRowData(rowId) {
var row=document.getElementById(rowId);
var idx = row.rowIndex;
for(i=0; i<4 ; i++) {
if( row ) {
alert(row.cells[0].innerText);
var row = row.parentNode.rows[ ++idx ];
}
}
}
From the current row, it gets the .parentNode, then from that, it accesses the rows collection, and increments the .rowIndex property of the original row to select the next.
This takes care of white space issues.
Notice also that instead of getElementsByTagName, I replaced it with row.cells, which is a collection of the cells in the row.
EDIT: Forgot to include the rows property after parentNode. It was included in the description though. Fixed.
To get around the problems with whitespace you can now use
row = row.nextElementSibling
Just make sure to check for null when you get to the end of the table, or if you have a thead, tbody or tfoot it will be at the end of that particular node.
If you're supporting older browsers you may want to use a shim.
Try using the nextSibling property:
row = row.nextSibling
But note that whitespace in your source HTML may turn into text nodes among the rows. You may have to call nextSibling more than once to skip the text nodes.
Have you considered using jQuery? With jQuery, you don't have to worry about the text nodes:
row = $("#" + rowId);
...
row = row.next();
row = row.nextSibling. You might need to do that in a while loop as you may come across whitespace nodes, so you should check to see if the next node has the correct tagName.
If you can use jQuery, then it's becoming really easy.
row.next();
Use document.getElementById(rowId).nextSibling