I'm sorting a table using jQuery, roughly following the code found here. The code sketch is as follows:
$('.sort-table').click(function(e) {
// cache objects
$table = $('#sort-table'), // cache the target table DOM element
$rows = $('tbody > tr', $table); // cache rows from target table body
// sort items
$rows.sort(<my_predicate_function(a,b){...}>);
// assign to table - what is going on?
$rows.each(function(index, row){
$table.append(row); // <-- how come $table remains the same size?
});
});
While the code works fine, I'm puzzeled by the code that appends the rows back to the table, which simply iterates over the sorted rows, appending each at the end of $table.
At no stage did we emtpy $table from its previous children.
Since $table was never emptied, how come $table remains the same size?
Does append() also enforces uniquness in the target container?
This is simply how the DOM works. You can't have an element in two different places. If an element is already in a document and you put it somewhere else, it will be moved from its current position. It is, I suppose, a little like a Set, but that is not how it is specified. So it's not removing duplicate objects, because there never are duplicate objects: it's simply moving the same object, which can only exist in one place.
From the MDN documentation for the underlying method, Node.appendChild:
The Node.appendChild() method adds a node to the end of the list of children of a specified parent node. If the given child is a reference to an existing node in the document, appendChild() moves it from its current position to the new position (there is no requirement to remove the node from its parent node before appending it to some other node).
If you want to duplicate elements, you need to clone them (DOM, jQuery).
As from JQuery doc for .append()
Description: Insert content, specified by the parameter, to the end of each element in the set of matched elements.
As from MDM doc for Set
The Set object lets you store unique values of any type, whether primitive values or object references.
Nothing gets removed.
You only will replace the existing element if it's exactly the same, which is the case in your example.
Related
How come this works:
alert(document.getElementById("tableId").rows[7].cells[7].innerHTML);
But not this:
alert($("#tableId").rows[7].cells[7].innerHTML);
For context, I had used .append to add in cells to the table, and I want to be able to manipulate each cell further in the future (such as using .data, which is why I want to use jQuery.
Alternatively, are there other ways to access the individual cells in the table? Thank you.
The former works because getElementById() returns an HTMLTableElement object which has the rows property. The latter uses a jQuery object which does not have the rows property, so you get an error.
You could solve this by retrieving the underlying Element from the jQuery object in one of two ways:
console.log($("#tableId")[0].rows[7].cells[7].innerHTML); // access by index
console.log($("#tableId").get(0).rows[7].cells[7].innerHTML); // get(0)
Alternatively you could use jQuery selectors to select the cell you want directly:
let html = $('#tableId tr:eq(7) td:eq(7)').html();
For context, I had used .append to add in cells to the table, and I want to be able to manipulate each cell further in the future
If this is the case you can save a reference to the content you add so that you can use it again without having to perform a DOM access operation. In pseudo-code it would look something like this:
let $content = $('<tr><td>Foo</td></tr').appendTo('#tableId');
// in some other function later in the page execution...
$content.find('td').text('bar').data('lorem', 'ipsum');
If you are looking to loop over each row
$('#tableId tr').each(function() {
if (!this.rowIndex) return; // to skip first row if you have heading
console.log(this.cells[0].innerHTML);
});
I'm displaying elements from an arraylist in table on the webpage. I want to make sure that once the user press "delete the data", the element in the table is immediately removed so the user does not have to refresh and wait to see the new table. So I'm currently doing it by removing the element from the arraylist, below is the code:
$scope.list= function(Id) {
var position = $scope.list.indexOf(fooCollection.findElementById({Id:Id}));
fooCollection.delete({Id:Id});
if (position>-1) {
$scope.list.splice(position,1);
}
$location.path('/list');
};
But I the position is always -1, so the last item is always removed from the list no matter which element I delete.
I found it strange you we're operating on two different lists to begin with so I assumed you were taking a copy of the initial list. This enabled me to reproduce your bug. On the following line you're trying to find an object that isn't present in your list.
var position = $scope.list.indexOf(fooCollection.findElementById({Id:Id}));
Eventhough we're talking about the same content, these two objects are not the same because:
indexOf compares searchElement to elements of the Array using strict
equality (the same method used by the ===, or triple-equals,
operator).
So there lies your problem. You can see this reproduced on this plunker.
Fixing it the quick way would mean looping over your $scope.list and finding out which element actually has the id that is being passed.
you can use the splice method of javascript which takes two paramete
arrayObject.splice(param1, param2);
param1 -> from this index elements will start removing
param2 -> no of elements will be remove
like if you want to remove only first element and your array object is arrayObject then we can write code as following
arrayObject.splice(0, 1);
I'm trying to get the values of all selected checkboxes with the following code to insert them in a textarea.
$('input[name="user"]:checked').each(function(){
parent.setSelectedGroup($(this).val()+ "\n");
});
but i always get only one value.
How to write the code in a correct way to get the value of ALL selected checkboxes?
Thanks ahead!
EDIT
1) "parent" because the checkboxes are in a fancybox.iframe.
2) setSelectedGroup in the parent window is
function setSelectedGroup(groupText){
$('#users').val(groupText);
You are getting all the values, simply on each loop through the collection you're passing a new value to setSelectedGroup. I assume that method replaces content rather than appending so you are simply not seeing it happen because its too fast.
parent.setSelectedGroup(
//select elements as a jquery matching set
$('[name="user"]:checked')
//get the value of each one and return as an array wrapped in jquery
//the signature of `.map` is callback( (index in the matching set), item)
.map(function(idx, el){ return $(el).val() })
//We're done with jquery, we just want a simple array so remove the jquery wrapper
.toArray()
//so that we can join all the elements in the array around a new line
.join('\n')
);
should do it.
A few other notes:
There's no reason to specify an input selector and a name attribute, usually name attributes are only used with the input/select/textarea series of elements.
I would also avoid writing to the DOM inside of a loop. Besides it being better technique to modify state fewer times, it tends to be worse for performance as the browser will have to do layout calculations on each pass through the loop.
I strongly recommend almost always selecting the parent element for the parts of the page that you're concerned with. And passing it through as the context parameter for jquery selectors. This will help you scope your html changes and not accidentally modify things in other parts of the page.
I've stumbled upon a tricky one, that I haven't been able to find any references to (except one here on Stackoverflow, that was written quite inefficiently in Plain Old Javascript - where I would like it written in jQuery).
Problem
I need to retrieve all child-elements where the attribute-name (note: not the attribute-value) starts with a given string.
So if I have:
<a data-prefix-age="22">22</a>
<a data-prefix-weight="82">82</a>
meh
My query would return a collection of two elements, which would be the first two with the data-prefix--prefix
Any ideas on how to write up this query?
I was going for something like:
$(document).find("[data-prefix-*]")
But of course that is not valid
Hopefully one of you has a more keen eye on how to resolve this.
Solution
(See accepted code example below)
There is apparently no direct way to query on partial attribute names. What you should do instead (this is just one possible solution) is
select the smallest possible collection of elements you can
iterate over them
and then for each element iterate over the attributes of the element
When you find a hit, add it to a collection
then leave the loop and move on to the next element to be checked.
You should end up with an array containing the elements you need.
Hope it helps :)
Perhaps this will do the trick -
// retrieve all elements within the `#form_id` container
var $formElements = $("form#form_id > *");
var selectedElements = [];
// iterate over each element
$formElements.each(function(index,elem){
// store the JavaScript "attributes" property
var elementAttr = $(this)[0].attributes;
// iterate over each attribute
$(elementAttr).each(function(attIndex,attr){
// check the "nodeName" for the data prefix
if (attr.nodeName.search('data-.*') !== -1){
// we have found a matching element!
if (selectedElements.length < 2){
selectedElements.push(elem);
break;
}else{
if (selectedElements.length == 2){
break(2);
}
}
}
});
});
selectedElements will now hold the first two matching elements.
jsFiddle
You can use jquerys filter() method to have a selections elements being processed by a function. Inside that function you are free to do whatever you want to.
So start with selecting all elements inside the dom tree and filter out everything you dislike. Not especially fast, but working.
I've implemented a routine to sort an xhtml table in place that is based on (= more or less copied from) Nicholas C. Zakas, Professional JavaScript for Web Developers, Ch. 12. It works as advertised, but I have a few questions about how the code works and would be grateful for advice. Here are the relevant bits:
var oldTable = document.getElementById('myTable');
var oldTableBody = oldTable.tBodies[0];
var oldTableRows = oldTableBody.rows;
var newTableArray = new Array();
for (var i = 0, rowCount = oldTableRows.length; i < rowCount; i++) {
newTableArray.push(oldTableRows[i]);
}
newTableArray.sort(chooseSort);
var newFragment = document.createDocumentFragment();
for (var i = 0, rowCount = newTableArray.length; i < rowCount; i++) {
newFragment.appendChild(newTableArray[i]);
}
oldTableBody.appendChild(newFragment);
I understand that I'm adding pointers to the existing table rows when I push new values onto newTableArray, but I'm not sure how the appendChild methods work. Specifically:
When I append each of the array items (pointers to the original rows) to the document fragment, am I just adding a pointer (leaving the row still in the original table), or am I removing the row object from the original table by appending it somewhere else?
When I append newFragment to oldTableBody, if I understand correctly, I'm actually appending not the fragment as an object, but the row objects that I've appended to it, and that's just the way fragments work. Is that correct?
When I append newFragment to oldTableBody, I don't do anything explicit to remove the rows that were originally there. Where did they go? Does appending them anew automatically remove any old traces of them because they can be attached only once? If I'm working with pointers, and not the objects themselves (which I thought was what let me attach them to newTableArray without automatically making them disappear from the table), does this mean that I can have multiple pointers to the same object in some cases (oldTable plus newTableArray) but not others (original rows in the original table plus the new, sorted rows that I'm adding)?
Apologies for the naive questions, but although getting the results that I want is satisfying, not understanding how the code works makes me uneasy.
I think all your questions boil down to asking what appendChild does. It removes the element from where it was in the DOM and places the same element in the new spot.
Your second assertion is correct. You're not creating a pointer, but rather relocating the object into the document fragment.
Your understanding of documenty fragments is correct. It is a generic container that never becomes part of the DOM. Rather its content is appended.
Relates to item one. You removed them from the table when appending them to the fragment. When adding them to the Array, it is just a pointer to the Row Objects getting added. Adding elements to an Array does nothing to manipulate the DOM itself.
As such, your code is ultimately:
doing an iteration to reference each table row into an Array
doing an iteration over the Array to relocate each row into a document fragment
appending the content of the fragment into the original table
...resulting in no noticeable change to the DOM.
If you were hoping to duplicate the rows, you may be interested in the .cloneNode() method.
But no, you can't have a single element appear in several different places in the DOM in such a manner that an update to the element in one location will be reflected in other locations.