I have an HTML Table consisting of several rows. I am trying to insert rows at a specific position in the table. For example if I mark an already existing row and click insert a new row should be inserted below. At the moment adding rows at the bottom of the table works. What I need is to insert the already build row at a certain position.
Here is my code:
function addLine(lineNumberGlobal,columnnumber,insertion)
{
var newrow = document.createElement("tr");
newrow.setAttribute('id', globalObj.lineNumberGlobal);
newrow.setAttribute('onmousedown', "setCurIdG(getAttribute(\"id\"))");
for (j = 1; j <= globalObj.monthdays + 1; j++)
{
//build row cells with content
}
if (insertion == true)
{
var newrowinsert = globalObj.bodyGlobal.insertRow(globalObj.currenIdGlobal);
//this actually inserts a new empty row but I want to append my existing row "newrow"
}
else if (insertion == false)
{
globalObj.bodyGlobal.appendChild(newrow);
}
}
Any help would be appreciated ...
You can use the insertBefore DOM method. If you want to insert after the row that you have marked, you would need to use that row's nextSibling to find the row after it, and then insert before that.
If I assume that globalObj.currenIdGlobal is the ID of the row you want to insert after, that would look like this:
var refElement = document.getElementById(globalObj.currenIdGlobal);
if (refElement) { // Being defensive here, you probably know it _does_ exist
globalObj.bodyGlobal.insertBefore(newrow, refElement.nextSibling);
}
That assumes that your HTML is structured with no whitespace or similar between rows (since nextSibling will return the next node after the row, which can be a text node rather than an element). If you need to be more defensive:
function findNextSiblingElement(elm, tagName) {
do {
elm = elm.nextSibling;
} while (elm && (elm.nodeType != 1 || elm.tagName != tagName));
return elm;
}
and then change the above to:
var refElement = document.getElementById(globalObj.currenIdGlobal);
if (refElement) { // Being defensive here, you probably know it _does_ exist
globalObj.bodyGlobal.insertBefore(newrow, findNextSiblingElement(refElement, 'TR'));
}
Note that if you pass null as the second argument to insertBefore, it appends to the end.
FWIW, operations like these can be made a bit easier if you use a library like Prototype, jQuery, Closure, or any of several others.
Use insertRow to create the row.
Also: Don't use setAttribute, it's broken in IE. And an event handler requires a function reference and not a string.
function addLine(lineNumberGlobal,columnnumber,insertion)
{
var newrow = globalObj.bodyGlobal.insertRow(insertion ? globalObj.currenIdGlobal : -1);
newrow.id = globalObj.lineNumberGlobal;
newrow.onmousedown = function() { setCurIdG(this.id); };
for (j = 1; j <= globalObj.monthdays + 1; j++)
{
//build row cells with content
}
}
BTW, you seem to be using the id to "re-find" the table rows. Consider keeping a reference to the row instead.
Related
I'm trying to use an insertion sort in javascript in order to sort tr tags inside a tbody tag.
Each tr tag is an alert and has multiple td tags with various informations about the alarm like the person who send the alarm, the one that takes care of it, etc... but the one I need for the sorting is the td with the duration of the alert put as a string inside of it (for example: 00h 01m 42s) and it increases each two seconds.
Each tr tag also has a class between these three: "success", "warning" and "danger". If the class is "success" the duration time is displayed as for example: "Done at: 00h 02m 10s" and it doesn't increase.
I need to sort the tr tags by the duration time from newest to oldest or oldest to newest and put the "success" class tags at the end in order to have "warning" and "danger" class tags (no matter the order for those two) at the top of the tbody tag when I sort my tags by the duration of the alarms.
I have already been able to do the sorting from newest to oldest and I have a function that puts the tags with a "success" class at the end but the sorting from oldest to newest doesn't work and for some reason the "success" class tags are not put at the end when that kind of sorting is chosen.
I tried to do the sorting from oldest to newest by doing the reverse of what I do with the sorting from newest to oldest but it doesn't work.
Here is the sortByTime() function I created in order to sort the tags by the duration, I use document.getElementById("tableBody") in order to get the tbody to which I have given "tableBody" as an id attribute on my page:
function sortByTime(order) {
var rows, tableBody, i, j, sortTime, temp;
tableBody = document.getElementById("tableBody");
rows = tableBody.rows;
sortTime=function (temporary, tabIndex, mode) {
if(mode>0){
return temporary<tabIndex;
}else {
return temporary>tabIndex;
}
};
switch (order) {
case "newest":
for(i=1;i<rows.length;i++){
temp=rows[i];
j=i-1;
while (sortTime(temp.childNodes[4].innerHTML, rows[j].childNodes[4].innerHTML,1)&& j>0){
rows[i].parentNode.insertBefore(rows[j+1], rows[j]);
j--;
}
}
sortAlerts("put success last");
break;
case "oldest":
for(i=rows.length;i===0;i--){
temp=rows[i];
j=i+1;
while (sortTime(temp.childNodes[4].innerHTML, rows[j].childNodes[4].innerHTML,0)&& j>0){
if(rows[i].getAttribute("class")!=="success"){
rows[i].parentNode.insertBefore(rows[j+1], rows[j]);
}
j++;
}
}
sortAlerts("put success last older");
break;
}
}
And here is the sortAlerts() function that I created in order to sort the tags by their classes by using a switch. But in order to put the "success" class at the end, I made a case "put success last" in order to differentiate it from the "success" case that puts the "success" class tags on top:
function sortAlerts(sortWord) {
if(alerts.length>0){
var rows, rowToMove, tableBody, i;
tableBody = document.getElementById("tableBody");
rows = tableBody.rows;
if(prevSortWord===sortWord){
return;
}
prevSortWord=sortWord;
if(sortWord==="put success last older"){
sortWord="put success last";
}
switch (sortWord) {
case "danger":
for (i = 0; i < rows.length; i++) {
rowToMove = rows[i].getAttribute("class");
if (rowToMove === "danger") {
rows[i].parentNode.insertBefore(rows[i], rows[0]);
}
}
break;
case "warning":
for (i = 0; i < rows.length; i++) {
rowToMove = rows[i].getAttribute("class");
if (rowToMove === "warning") {
rows[i].parentNode.insertBefore(rows[i], rows[0]);
}
}
break;
case "success":
for (i = 0; i < rows.length; i++) {
rowToMove = rows[i].getAttribute("class");
if (rowToMove === "success") {
rows[i].parentNode.insertBefore(rows[i], rows[0]);
}
}
break;
case "put success last":
for (i = 0; i < rows.length; i++) {
rowToMove = rows[i].getAttribute("class");
if (rowToMove === "success") {
rows[i].parentNode.insertBefore(rows[i], rows[rows.length-1]);
}
}
break;
}
}
}
The prevSortWord variable is a variable from outside the function which only serves to prevent that the sorting can't be done by calling the function two times in a row because otherwise the tags would end up being sorted in reverse with the cases that aren't "put success last".
I need a way to do the reverse sorting of the "newest" case in the sortByTime() function for the "oldest" case because all that the "oldest" case does is to sort the tags in a manner I don't really understand because when I try to debug it, each line of the code that is read apparently does the swapping of places inside the tbody tag even if the line that is supposed to do it isn't read.
I had a bit of difficulty reasoning the above functions, but what I suspect to be the case is that:
The sortByTime function is not actually checking the time in the table row, and
there could be something up with the order that element insertion happens
I wrote a similar function that behaves slightly differently. Hopefully it will be of some use for you:
// Returns the sort-comparable timestamp (you'll want to change this)
function getTimeInt (timeStr) {
return timeStr.substring(timeStr.lastIndexOf(' ') + 1)
}
function sortAlerts (word, _reverse = false) {
const tableBody = document.querySelector('#tableBody')
const reverse = _reverse ? -1 : 1
// Sort the rows, and add them back to the tableBody
Array.apply(null, tableBody.querySelectorAll('tr'))
.sort(function(rowA, rowB) {
// Change this function return to do different things, like putting success at bottom
return (
(rowA.classList.contains(word) - rowB.classList.contains(word)) ||
((getTimeInt(rowA.innerText) - getTimeInt(rowB.innerText)) * reverse)
)
})
.forEach(function (row) {
row.parentNode.removeChild(row)
tableBody.appendChild(row)
})
}
// Put danger at top, and reverse the search
sortAlerts('danger', true)
And here's the jsfiddle:
https://jsfiddle.net/r6sxmko7/
I'm sorry I couldn't be more helpful.
I had a quick script to search for some text (a unique code) in a div and hide a different element if it exists:
var itemCode = ["000001"];
if( $(".itemCode").text().indexOf(itemCode) >= 0) {
$(".codeBox").addClass("hideElement");
}
however I wanted to expand this so it finds one of multiple texts (codes) and if any of them exist then hide the element:
var itemCode = ["000001", "000003", "000008"];
if( $(".itemCode").text().indexOf(itemCode) >= 0) {
$(".codeBox").addClass("hideElement");
}
and this isn't working. I'm sure It's probably something simple and I'm supposed to add a .each() somewhere but I'm just not getting it working when I experiment trying things, what am I missing?
Might be slighty quicker if you have a few item codes in your array
var itemCode = ["000001", "000003", "000008"];
var regExpPattern = itemCode.join('|');
if($(".itemCode").text().match(new RegExp(regExpPattern, 'i'))) {
$(".codeBox").addClass("hideElement");
}
});
indexOf takes only one (mandatory) argument, so you'll have to iterate over your list to find element(s) matching your condition :
var itemCode = ["000001", "000003", "000008"];
var contains = itemCode.some(function(code) {
return $(".itemCode").text().indexOf(code) >= 0;
});
if (contains) {
$(".codeBox").addClass("hideElement");
}
I tried to make a function that would generate a number of list items based on the user input from a prompt. It does not work although I believe it should.
I'm looking for an explanation of what's wrong with my code even if an alternate solution is also provided, if possible.
On the HTML side I have entered <div class="freshList"></div> in the body so that it can be picked up by the function and have the list placed in that location
Code is below:
function makeAList()
{
var freshList = document.getElementsByClassName("freshList");
var listLength = prompt("Enter number of list items");
var listString = "<ul>";
for (var i=0; i < listLength; i++)
{
listString+= "<li>"+"</li>"
}
listString += "</ul>"
document.innerHTML = listString;
}
makeAList();
// end code
Now the only way I have been able to get this to work was by accident when using the document.Write method at various points in the code to see what was working (I tried console log first which said that the function was called and the loop was proceeding but no output was coming so I switched to doc.write instead). I used document.Write(listString); and this was able to forcibly print the bullet points onto the screen but that is not my desire. I want it in the HTML not just printed on the screen (so that I can manipulate it with other functions I have made).
Altogether I wanted to make a series of functions to perform the following action: Ask if the user would like to make a new list. Call the makeNewList function which would prompt the user for the number of items. Then ask the user if they would like to edit the list and call the editList function with new prompts for each list item. Finally leaving an output of # of bullet points with user input on each point. I am sure this is a ridiculous idea that nobody would use but it was more a lesson for myself to try an idea I had rather than something functional. Full (attempted) code below:
function makeAList()
{
var freshList = document.getElementsByClassName("freshList");
var listLength = prompt("Enter number of list items");
var listString = "<ul>";
for (var i=0; i < listLength; i++)
{
listString+= "<li>"+"</li>"
}
listString += "</ul>"
document.innerHTML = listString;
}
makeAList();
function editAList() {
var list = document.getElementsByTagName("li");
for (var i = 0; i < list.length; i++)
{
list[i].innerHTML = prompt("Place list text below","")
}
function checkList(){
var resp1 = confirm("Would you like to make a new list?")
if(resp1 == true)
{
makeAList();
}
else
{
}
if(resp1 === false){
var resp2 = prompt("Would you like to edit an existing list instead?")
}
else if(resp2 === true){
editAList();
}
else{
alert("You have chosen not to make a new list or edit an existing one")
}
}
checkList();
My friend looked at my code and made some changes as well as detailed comments with the places I went wrong. For anyone who views this question in the future here is his response. All credit to him but I don't know his stack overflow handle to tag him.
Here is his js bin updated and heavily commented code
Code below in case that link dies:
// hi
// i've changed a few things, i've left the original code in comments (//)
function makeAList()
{
// what does the following code return? a single element? a list of elements?
//var freshList = document.getElementsByClassName("freshList")
var freshList = document.getElementById("freshList");
var listLength = prompt("Enter number of list items");
// var listString = "<ul>";
// you can create a 'ul' element and append the list string later
// https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/append
var ul = document.createElement('ul');
ul.setAttribute('id', 'theList');
// there's an even shorter way of doing all this, but since you're starting out, we can save that for later
for (var i=0; i < listLength; i++)
{
//i would probably append here too, but just demonstrating insertAdjacent
ul.insertAdjacentHTML('beforeend', '<li></li>');
}
// document.innerHtml = listString //this was the reason why this function didn't work
// document has no inner html, instead, you want to append the list to the .freshList div that you created
// and then append that to the listOfLists that you queried
// the reason why we don't want to manually set innerHTML is because the DOM has to be reparsed and recreated
// every time innerHTML is set. if you have 1000s of lists, this would be extremely slow
// there are DOM apis that create and insert html elements much more faster and efficient (appendChild)
// if you want to create html elements as strings, as you have done previously, use insertAdjacentHTML: https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML
// it is faster and more efficient
freshList.appendChild(ul);
}
makeAList();
function editAList() {
var list = document.getElementsByTagName("li");
// there's a much more efficient way to do this, but keep this here for now
var insertText = function(i) {
var input = prompt("Place list text below", "");
console.log(i);
list[i].append(input);
}
for (var i = 0; i < list.length; i++)
{
// why would we use settimeout? http://www.w3schools.com/jsref/met_win_settimeout.asp
setTimeout(insertText.bind(null, i), 1000); // why bind? https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
}
}
editAList();
// function checkList(){
// var resp1 = confirm("Would you like to make a new list?")
// if(resp1 == true)
// {
// makeAList();
// }
// else
// {
// }
// if(resp1 === false){
// var resp2 = prompt("Would you like to edit an existing list instead?")
// }
// else if(resp2 === true){
// editAList();
// }
// else{
// alert("You have chosen not to make a new list or edit an existing one")
// }
// }
// checkList();
I have a selected row, and by clicking some button (currently I use space via AngularHotkeys.js) I want to deselect current row and select the one that is next after the currently selected one.
The thing is complicated more knowing that I can sort the table with different columns. So, it would be great to know index of the current row with the current sorting applied.
From where do I start with this problem?
Any suggestions are appreciated.
You can get the array of rows, in their sorted and filtered state, from $scope.gridApi.grid.renderContainers.body.visibleRowCache. There's also a bunch of trickiness to deal with when you have the entity and when you have the gridRow, so the code gets a little complex.
Your code would be something like:
$scope.selectNextRow = function() {
var currentRowIndex;
var selectedRows = $scope.gridApi.selection.getSelectedRows();
if( selectedRows.length < 1 ){
// if nothing selected, we'll select the top row
currentRowIndex = -1;
} else {
// if there are multiple selected, we use the first one
var selectedRow = selectedRows[0];
var gridRow = $scope.gridApi.grid.getRow(selectedRow);
currentRowIndex = $scope.gridApi.grid.renderContainers.body.visibleRowCache.indexOf(gridRow);
}
$scope.gridApi.selection.clearSelectedRows();
$scope.gridApi.selection.selectRow($scope.gridApi.grid.renderContainers.body.visibleRowCache[currentRowIndex + 1].entity);
};
Refer http://plnkr.co/edit/Z7HCjVY6oxGJzyjLI6Qo?p=preview
If I understand you correctly you should be able to accomplish what you want with something like that. (Did not test that)
$scope.scrollTo = function( rowIndex ) {
$scope.gridApi.core.scrollTo( $scope.gridOptions.data[rowIndex], 0);
};
$scope.nextRow() {
var current = $scope.gridApi.selection.getSelectedRows();
scrollTo(current + 1);
}
Cheers
I'm trying to re-sort the child elements of the tag input by comparing
their category attribute to the category order in the Javascript
variable category_sort_order. Then I need to remove divs whose category attribute
does not appear in category_sort_order.
The expected result should be:
any
product1
product2
download
The code:
<div id="input">
<div category="download">download</div>
<div category="video">video1</div>
<div category="video">video2</div>
<div category="product">product1</div>
<div category="any">any</div>
<div category="product">product2</div>
</div>
<script type="text/javascript">
var category_sort_order = ['any', 'product', 'download'];
</script>
I really don't even know where to begin with this task but if you could please provide any assistance whatsoever I would be extremely grateful.
I wrote a jQuery plugin to do this kind of thing that can be easily adapted for your use case.
The original plugin is here
Here's a revamp for you question
(function($) {
$.fn.reOrder = function(array) {
return this.each(function() {
if (array) {
for(var i=0; i < array.length; i++)
array[i] = $('div[category="' + array[i] + '"]');
$(this).empty();
for(var i=0; i < array.length; i++)
$(this).append(array[i]);
}
});
}
})(jQuery);
and use like so
var category_sort_order = ['any', 'product', 'download'];
$('#input').reOrder(category_sort_order);
This happens to get the right order for the products this time as product1 appears before product2 in the original list, but it could be changed easily to sort categories first before putting into the array and appending to the DOM. Also, if using this for a number of elements, it could be improved by appending all elements in the array in one go instead of iterating over the array and appending one at a time. This would probably be a good case for DocumentFragments.
Just note,
Since there is jQuery 1.3.2 sorting is simple without any plugin like:
$('#input div').sort(CustomSort).appendTo('#input');
function CustomSort( a ,b ){
//your custom sort function returning -1 or 1
//where a , b are $('#input div') elements
}
This will sort all div that are childs of element with id="input" .
Here is how to do it. I used this SO question as a reference.
I tested this code and it works properly for your example:
$(document).ready(function() {
var categories = new Array();
var content = new Array();
//Get Divs
$('#input > [category]').each(function(i) {
//Add to local array
categories[i] = $(this).attr('category');
content[i] = $(this).html();
});
$('#input').empty();
//Sort Divs
var category_sort_order = ['any', 'product', 'download'];
for(i = 0; i < category_sort_order.length; i++) {
//Grab all divs in this category and add them back to the form
for(j = 0; j < categories.length; j++) {
if(categories[j] == category_sort_order[i]) {
$('#input').append('<div category="' +
category_sort_order[i] + '">'
+ content[j] + '</div>');
}
};
}
});
How it works
First of all, this code requires the JQuery library. If you're not currently using it, I highly recommend it.
The code starts by getting all the child divs of the input div that contain a category attribute. Then it saves their html content and their category to two separate arrays (but in the same location.
Next it clears out all the divs under the input div.
Finally, it goes through your categories in the order you specify in the array and appends the matching child divs in the correct order.
The For loop section
#eyelidlessness does a good job of explaining for loops, but I'll also take a whack at it. in the context of this code.
The first line:
for(i = 0; i < category_sort_order.length; i++) {
Means that the code which follows (everything within the curly brackets { code }) will be repeated a number of times. Though the format looks archaic (and sorta is) it says:
Create a number variable called i and set it equal to zero
If that variable is less than the number of items in the category_sort_order array, then do whats in the brackets
When the brackets finish, add one to the variable i (i++ means add one)
Then it repeats step two and three until i is finally bigger than the number of categories in that array.
A.K.A whatever is in the brackets will be run once for every category.
Moving on... for each category, another loop is called. This one:
for(j = 0; j < categories.length; j++) {
loops through all of the categories of the divs that we just deleted from the screen.
Within this loop, the if statement checks if any of the divs from the screen match the current category. If so, they are appending, if not the loop continues searching till it goes through every div.
Appending (or prepending) the DOM nodes again will actually sort them in the order you want.
Using jQuery, you just have to select them in the order you want and append (or prepend) them to their container again.
$(['any', 'product', 'video'])
.map(function(index, category)
{
return $('[category='+category+']');
})
.prependTo('#input');
Sorry, missed that you wanted to remove nodes not in your category list. Here is the corrected version:
// Create a jQuery from our array of category names,
// it won't be usable in the DOM but still some
// jQuery methods can be used
var divs = $(['any', 'product', 'video'])
// Replace each category name in our array by the
// actual DOM nodes selected using the attribute selector
// syntax of jQuery.
.map(function(index, category)
{
// Here we need to do .get() to return an array of DOM nodes
return $('[category='+category+']').get();
});
// Remove everything in #input and replace them by our DOM nodes.
$('#input').empty().append(divs);
// The trick here is that DOM nodes are selected
// in the order we want them in the end.
// So when we append them again to the document,
// they will be appended in the order we want.
I thought this was a really interesting problem, here is an easy, but not incredibly performant sorting solution that I came up with.
You can view the test page on jsbin here: http://jsbin.com/ocuta
function compare(x, y, context){
if($.inArray(x, context) > $.inArray(y, context)) return 1;
}
function dom_sort(selector, order_list) {
$items = $(selector);
var dirty = false;
for(var i = 0; i < ($items.length - 1); i++) {
if (compare($items.eq(i).attr('category'), $items.eq(i+1).attr('category'), order_list)) {
dirty = true;
$items.eq(i).before($items.eq(i+1).remove());
}
}
if (dirty) setTimeout(function(){ dom_sort(selector, order_list); }, 0);
};
dom_sort('#input div[category]', category_sort_order);
Note that the setTimeout might not be necessary, but it just feels safer. Your call.
You could probably clean up some performance by storing a reference to the parent and just getting children each time, instead of re-running the selector. I was going for simplicity though. You have to call the selector each time, because the order changes in a sort, and I'm not storing a reference to the parent anywhere.
It's seems fairly direct to use the sort method for this one:
var category_sort_order = ['any', 'product', 'download'];
// select your categories
$('#input > div')
// filter the selection down to wanted items
.filter(function(){
// get the categories index in the sort order list ("weight")
var w = $.inArray( $(this).attr('category'), category_sort_order );
// in the sort order list?
if ( w > -1 ) {
// this item should be sorted, we'll store it's sorting index, and keep it
$( this ).data( 'sortindex', w );
return true;
}
else {
// remove the item from the DOM and the selection
$( this ).remove();
return false;
}
})
// sort the remainder of the items
.sort(function(a, b){
// use the previously defined values to compare who goes first
return $( a ).data( 'sortindex' ) -
$( b ).data( 'sortindex' );
})
// reappend the selection into it's parent node to "apply" it
.appendTo( '#input' );
If you happen to be using an old version of jQuery (1.2) that doesn't have the sort method, you can add it with this:
jQuery.fn.sort = Array.prototype.sort;