Merge table rows based on row text and class - javascript

I have found this function here and it almost perfectly suits my needs. The function iterates through the first column of a table, and row merges similar values. Then, it iterates through the next column, and row merges those values as well, while taking into account the merges of the column before it. In this way, it perfectly suits what I need.
function MergeCommonRows2(table, firstOnly) {
var firstColumnBrakes = [];
for (var i = 1; i <= table.find('th').length; i++) {
var previous = null, cellToExtend = null, rowspan = 1;
table.find("td:nth-child(" + i + ")").each(function (index, el) {
if (previous == $(el).text() && $(el).text() !== "" && $.inArray(index, firstColumnBrakes) === -1) {
$(el).addClass('hidden');
cellToExtend.attr("rowspan", (rowspan = rowspan + 1));
} else {
if (firstOnly == 'first only') {
if (i === 1)
firstColumnBrakes.push(index);
} else {
if ($.inArray(index, firstColumnBrakes) === -1)
firstColumnBrakes.push(index);
}
rowspan = 1;
previous = $(el).text();
cellToExtend = $(el);
}
});
}
}
However, I wish to edit the function to consider classes as well. In other words, I wish to edit the function to first check the classes of the two rows being merged. And, even if they have the same value, if they have different classes, to NOT merge the rows. I have tried to edit the above function to do as much, but to no avail.
UPDATE
My attempts to edit the code were quite crude. Essentially, I put code before he if statement that adds a hidden class and said, to summarize, if previous.getClass = current.getClass, only then go onto the if statement that hides the class.
Here is the fiddle. I want to click the merge button and, since the "click me" cells have a different classes between them, I don't want them to merge.

Related

Handsontable - afterChangeEvent how to detect when a whole row is deleted or added

I have create a custom function that executes on the afterChange cell event as per the instructions in the following Handsontable link.
https://docs.handsontable.com/0.15.0-beta3/Hooks.html
And I can perform actions based on each cell that is changed fine. As per the following code.
var hot = new Handsontable(container, {
data: data(),
minSpareRows: 1,
rowHeaders: true,
colHeaders: ['some array of headers']
afterChange: function (changes, source) {
onChangeHandler(this, changes, source);
}
});
var onChangeHandler = function (hot_instance, changes, source) {
var changesLength;
var changePayloadtoServer = [];
if (changes != null) {
changesLength = changes.length;
} else {
changesLength = 0;
}
for (var i = 0; i < changesLength; i++) {
var row = changes[i][0],
col = changes[i][1],
oldVal = changes[i][2],
newVal = changes[i][3],
var trigger = false
// How to I determine that a whole row has been deleted/added here?
// trigger = some check that a whole row has been deleted/added.
if (trigger == true) {
// Perform action on row deletion
// i.e. CASCADE DELETE query on database
// Perform action on row addition
// i.e. INSERT INTO query on database
} else {
// Perform normal on cell change
// i.e. UPDATE query on database
}
}
}
However, I cant figure out how to determine whether a entire row has been deleted or added in the changes. Given that the change data provided in this afterChange event is in the follow format:
[
{changedRowNumber, chnagedColumnName, oldCellValue, newCellValue}
, {changedRowNumber, chnagedColumnName, oldCellValue, newCellValue}
, {changedRowNumber, chnagedColumnName, oldCellValue, newCellValue}
, {changedRowNumber, chnagedColumnName, oldCellValue, newCellValue}
, ... etc.
]
I have created an example fiddle. See link below.
https://jsfiddle.net/JoshAdams/go898kq6/
Does anyone know how I can detect when a whole row is added or deleted?
What you're looking for is two other events also defined in the documentation:
afterCreateRow
afterRemoveRow
As you can imagine, you will have to defiene this two events in your handsontable configuration and not in your afterChange event.
For example, see your JSFiddle edited :
afterRemoveRow: function (index, amount) {
console.log(amount + " row(s) removed starting line "+ index)
},
afterCreateRow: function (index, amount) {
console.log(amount + " row(s) added starting line "+ index)
},
EDIT : Based on your comment, I made an example for the case you want; knowing when every value from a row became empty after a change.
You can find a new simplified JSFiddle here containg the following afterChange event definition :
hot.addHook('afterChange', function(changes) {
if (!changes) {
return;
}
$.each(changes, function (index, element) {
var
rowEmpty = true,
row = element[0],
currentRowArray = hot.getData(row,0,row,hot.countCols());
for(var i=0; i<currentRowArray[0].length-1; i++) {
if(currentRowArray[0][i] != "") {
rowEmpty = false;
break;
}
}
if(rowEmpty) {
console.log("Row " + row + " is empty !");
}
});
});
Every time a change is made in a cell, the code within the event will check if there is at least one cell on the same row which contain a value (and finish the event if it sees one). If not, it prints in console that the row is empty. Hence the result you're looking for is the boolean rowEmpty.
Also, note two things :
The jQuery function $.each allows you to make it works when a user empty two (or more) rows at the same time.
The -1 in the condition of the for loop. If you want to compare the length of your array versus the number of columns, don't forget that the number of columns obviously don't start to count from 0. So if there is 6 columns, this will be column 0 to 5, hence the -1.

Google Script - Forms - Issues Deleting Page Breaks/Sections - "Invalid data updating form"

I am having an issue with the following code when trying to iterate through the items in a form and delete them to make way for new sections/questions. However, I sometimes get the following error "Invalid data updating form". I have worked around this multiple times now, but it keeps coming back up. My current workaround has been to set the section title to "", which made it available to delete. Previously, I didn't need to do this until today.
My question: What is the best way to iterate through the items in a form and delete them from a starting point and not encounter this error?
Reference:
f = the current active form
f_items = all of the items of the form in an array
function clearForm()
{
var clearQ = find(f_items, "Select Appointment Date/Time")+1;
var f_i_len = f.getItems().length-1;
var clear = clearQ;
while(clear <= f_i_len && clear >= clearQ)
{
var item = f.getItems()[clear];
Logger.log(item.getTitle() + " | " + item.getType());
Logger.getLog();
if(item.getType() == "PAGE_BREAK")
{ item.asPageBreakItem().setTitle(""); }
f.deleteItem(clear);
f_i_len = f.getItems().length-1;
clear++;
}
}
function find(src, name)
{
var s_len = src.length;
for(var iter = 0; iter < s_len; iter++)
{
var s = src[iter].getTitle();
if(s == name)
{ return iter; }
}
return -1;
}
The issue I had with this was that the PageBreakItem I was trying to delete was the destination for a conditional answer earlier in the form.
Below is my code where I needed to delete everything after a certain item, which linked to the sections I needed to delete, so I was able to iterate backwards with a while loop.
function getParentNameItem_(form, form_items){
//finds the item with conditional navigation to what you want to delete
var parent_name_item = find_(form_items, "Your Name");
parent_name_item = parent_name_item.asListItem();
//clears all choices which breaks the navigation dependency
//this frees up the items to be deleted
parent_name_item.setChoices([parent_name_item.createChoice("")]);
var size = form_items.length - 1;
//iterates from the end back to the last question I want to keep
while(form_items[size].getTitle() != "Your Name"){
//this can take either the item itself or the index as I've done
form.deleteItem(size);
size--;
}
/*I rebuild the choices for parent_name_item
later based on information from a spreadsheet
which I also use to determine the content of
the PageBreakItems I just deleted*/
return parent_name_item;
}
I found out the issue! It was the clear++ at the end of the loop. With the number of items in the going down with each iteration, the clear++ was causing it to skip over the page break items. Below is my finished code:
function clearForm()
{
var clearQ = find(f_items, "Select Appointment Date")+1;
var f_i_len = f.getItems().length-1;
var clear = clearQ;
while(clear <= f_i_len && clear >= clearQ)
{
var item = f.getItems()[clear];
if(item.getType() == "PAGE_BREAK")
{ item.asPageBreakItem().setTitle(""); }
f.deleteItem(clear); //}
f_i_len = f.getItems().length-1;
}
}

GridView Validation is not working properly in JavaScript

I want to validate on button click that at least one of the rows must be edited and updated in JavaScript.
So I wrote the below code for validation
function checkGridValidate() {
var StrPriError = "";
var grdCount = GrdProspective1.Rows.length;
for (var i = 0; i < grdCount; i++) {
if (GrdProspective1.Rows[0].Cells[5].Value == "" || GrdProspective1.Rows[0].Cells[7].Value == "") {
StrPriError += "Kindly edit atleast one row \n";
}
if (StrPriError != "") {
alert(StrPriError);
return false;
}
else {
return true;
}
}
}
What happening here is, when I update the first row and submit it is not giving any alert that's perfect, but when I update the second row it still asks me Kindly edit at least one row.
I don't know what's going wrong here.
Have a look the js fiddle for the same
Currently, the validation is limited to only check the top row for two reasons:
.Rows[0] will always inspect the top row, despite the for loop.
This should make use of i as it increments through the collection:
if (GrdProspective1.Rows[i].Cells[5].Value == "" ||
The last if..else, by returning in either case, will interrupt the loop. The return statements here have a similar effect to break statements, with regards to the loop.
So, unless you want the loop to be interrupted, they should be moved out the loop:
for (var i = 0; i < grdCount; i++) {
if (...) {
// ...
}
}
if (StrPriError != "") {
alert(StrPriError);
return false;
}
else {
return true;
}
Though, fixing those should reveal a different issue – the function is checking that every row has been edited rather than one-or-more.
If, for example, there are 5 rows and you fill in both fields in 2 of the rows, the remaining 3 rows will match the condition and append the error message.
Inverting the condition, so you're searching for a row that's filled in and remembering whether you have, should resolve this.
function checkGridValidate() {
// assume invalid until found otherwise
var anyEdited = false;
var grdCount = GrdProspective1.Rows.length;
for (var i = 0; i < grdCount; i++) {
var cells = GrdProspective1.Rows[i].Cells;
// verify that both fields were given a value
if (cells[5].Value !== "" && cells[7].Value !== "") {
anyEdited = true; // remember that you've found an edited row
break; // and, no need to keep looking for more
}
}
// alert only if no rows were filled out
if (!anyEdited) {
alert("Kindly edit at least one row.");
}
return anyEdited;
}

Javascript sort string or number

EDIT: Pete provided a really good solution that works when the fields contain numbers, however I need to be able to sort strings too - any ideas?
I'm trying to write a javascript sorting algorithm that will sort a table based on the column clicked - I know this is semi-reinventing the wheel but the design is too complex for me to try and insert some other plugin etc.
Some columns are text, some columns are numbers.
Clicking a column calls: sort(X,Y). X is the column number so we know which cells to compare for the sort. Y is the mode, i.e. ascending or descending.
The code for the sort function is:
function sort(field, mode) {
var tabrows = 0;
$(".data tr").each(function() { if($(this).hasClass("hdr")) { } else {tabrows++;} });
var swapped;
do {
swapped = false;
for (var i=0;i< tabrows; i++) {
var j = i + 3;
var k = i + 4;
var row1 = $(".data tr:nth-child("+j+")");
var row2 = $(".data tr:nth-child("+k+")");
var field1 = row1.find("td:eq("+field+")").text();
var field2 = row2.find("td:eq("+field+")").text();
if(shouldswap(field1, field2, mode)) {
swaprows(row1, row2);
swapped = true;
}
}
} while (swapped);
}
The shouldswap function is as follows:
function shouldswap(field1, field2,mode) {
if(field1 > field2) {
if(mode==1) {
return true;
} else {
return false;
}
}
return false;
}
Code for swaprows function:
function swaprows(row1, row2) {
row2.insertBefore(row1);
}
Can anyone see why this would cause the browser to freeze/lockup. I've been working on this for quite a while so I think a fresh pair of eyes may point out something silly! Any help is appreciated :)
The problem might be that you're calling the jQuery constructor a bunch of times and doing heavy operations on it (e.g. using .find() with complex selectors). Therefore, your function is just slow and that's probably the issue.
The good news is that JavaScript has a native implementation of QuickSort (a very fast sorting function) that will probably take care of your needs. When combined with a reduction in expensive calls, your code should end up being enormously more efficient. I'd change your code to look like this:
var sortByField = function(field, mode) {
var numExp = /^-?\d*\.?\d+$/;
var $rows = $(".data tr:not(.hdr)"), $table = $(".data");
$rows.each(function () {
this.fieldVal = $(this).find("td:eq("+field+")").text();
if(numExp.test(this.fieldVal)) { //if field is numeric, convert it to a number
this.fieldVal = +this.fieldVal;
}
}).sort(function (a, b) {
if (mode === 1) {
return (a.fieldVal > b.fieldVal) ? -1 : 1;
}
return (a.fieldVal < b.fieldVal) ? -1 : 1;
}).detach().each(function () {
$(this).appendTo($table);
});
};
This won't work well with multiple tables on one page (because it assumes everything is on the same table). So if you want to do that, you should pass in the table or table selector as a parameter. But that's an easy fix to make. You can see my solution in action here:
http://jsfiddle.net/r8wtK/ (updated)
It should be far more efficient than your code and should reduce "freezing" by quite a bit (ore even entirely).
UPDATE:
The OP noted that some fields may contain strings. Doing a string comparison on numbers is bad because it returns a lexicographical ordering (e.g. "10" < "2"). So I added a test to see if the data appear to be numeric before doing the sort.
Could it be that you're adding 3 and 4 to i in order to get your row indices? So when i gets to (tabrows-1), it appears that it will be trying to access rows with index of (tabrows+2) and (tabrows+3). If I understand your logic correctly, these are out of bounds, so row1, row2, field1 and field2 will be empty. Therefore, if you're in mode==1, I think this will make it so that your algorithm attempts to swap these two non-existent rows and keeps comparing for infinity. Does this make sense, or am I misunderstanding your logic?
If that's the case, I think you just need to change your for loop to:
for (var i=0;i< tabrows-4; i++) {
// your code
}
What is the purpose of adding 3 to j and 4 to k anyway? Do you have 3 rows of data at the top that you don't want to compare?

Why is this code so slow?

jsc.tools.road.correctType = function() {
for(row = jsc.data.selection.startX - 1; row <= jsc.data.selection.endX + 1; row++) {
for(col = jsc.data.selection.startY - 1; col <= jsc.data.selection.endY + 1; col++) {
if(jsc.data.cells[row-1][col].type != "road" && jsc.data.cells[row+1][col].type != "road" && jsc.data.cells[row][col].type == "road") {
jsc.ui.addClassToCell("horz", row, col);
}
else {
jsc.ui.removeClassFromCell("horz", row, col);
}
if(jsc.data.cells[row][col-1].type != "road" && jsc.data.cells[row][col+1].type != "road" && jsc.data.cells[row][col].type == "road") {
jsc.ui.addClassToCell("vert", row, col);
}
else {
jsc.ui.removeClassFromCell("vert", row, col);
}
}
}
};
// Elsewhere
jsc.ui.addClassToCell = function(class, x, y) {
$("#" + x + "-" + y).addClass(class);
};
jsc.ui.removeClassFromCell = function(class, x, y) {
$("#" + x + "-" + y).removeClass(class);
};
The code above runs very slowly. I can't figure out why. It's using jQuery 1.3.2. Any way to optimize it a bit?
EDIT: The code is part of a javascript game I am making as a personal project. It's basically a Simcity clone. This piece of code checks the neighbouring cells for each part of the road, and changes the class (and in turn the background image) to the correct one to make the road images line up right, e.g. horizontal, vertical and junction(no class) road images.
EDIT 2: A few more details to provide some context.
The jsc.data.cells is an 200 x 200 array. Each array element is an object with properties like so (default shown): {type: null, developed: false, powered: false, watered: false, hasTransport: false, wealth: 0, quality: 0} .
It's counterpart is in the UI, which is basically a giant table. (200 x 200 again). Each cell has a number of CSS classes added to it throughout the program to change the background image (e.g. .road to change it to road, .com.developed to make it a developed commercial zone). The table cells each have an id of the form #x-y which is what jsc.ui.addClassToCell, and jsc.ui.removeClassFromCell edit.
EDIT 3: Fixed the IDs starting with numbers. Trying out some of the optimizations now.
A short estimate using O() notation:
for(row) ... O(N)
for(col) ... O(N)
$().addClass/removeClass ... O(N^2)
the $() is even called twice within the nested for.
so you end up with O(N^4)
You can optimize this by caching the calculated classes in the as property of jsc.data.cells[row][col], e.g.
jsc.data.cells[row][col].horz = 1; // don't set class "horz" if not present
jsc.data.cells[row][col].vert = 1;
and use the cached data when you create the cells inside the HTML table, rather than calling $() for each cell.
Normally you can significantly optimize loops like these;
for( var x = 0; x < someMethod(); x++ ) {
//... do stuff
}
By exchanging them out with something like this
var x = someMethod();
while( x-- ) {
//...do stuff
}
Though it becomes slightly different semantically, it normally works quite well as long as you're not dependent upon order in your looping (order is opposite)
Even when you cannot change the order, you will also significantly improve your code by merely moving the someMethod call OUT of your actual loop, since in many JS implementations it will be called once for every iteration...
Depending on the size of your selection, you might be doing a whole lot of condition checks and DOM edits.
By commenting out the content of addClassToCell and removeClassFromCell and comparing run times you can find out whether the condition checking or the dom editing takes the most time and thus which one is the best candidate for optimising.
I can only give some tips, but don't know if they help much. Have no possibility to test your code.
1-st: declare variables in local function scope. I mean the row and col variables, which you declared as global (missing var statement). Access to global variables takes longer (AFAIK) than to local scope vars.
var row = jsc.data.selection.startX-1;
var col = jsc.data.selection.startY-1;
2-nd: cache references to common objects. Here, you can store reference for jsc.data and/ord jsc.data.selection and jsc.data.cells. IIRC, the access to an object property is linear.
jsc.tools.road.correctType = function() {
var data = jsc.data, selection = data.selection, cells = jsc.data.cells, ui.jsc.ui;
for(var row = selection.startX - 1, endX = selection.endX + 1, endY = selection.endY + 1; row <= endX; ++row) {
for(var col = selection.startY - 1; col <= endY; ++col) {
if(cells[row-1][col].type != "road" && cells[row+1][col].type != "road" && cells[row][col].type == "road") {
ui.addClassToCell("horz", row, col);
} else {
ui.removeClassFromCell("horz", row, col);
}
if(cells[row][col-1].type != "road" && cells[row][col+1].type != "road" && cells[row][col].type == "road") {
ui.addClassToCell("vert", row, col);
} else {
ui.removeClassFromCell("vert", row, col);
}
}
}
};
I also moved the declaration of endY variable to the outer loop, so it won't be computed with every access to inner loop.
-- edit
hope you know, that ID attribute values cannot start with a number, like you have, eg. #2-3
Use a memoizer or a local cache to store the jQuery objects you have already created. That will reduce the numer of calls of the $ function.
var cache = {}, selector;
for (/* … */) {
selector = "#" + x + "-" + y;
if (!cache[selector]) {
cache[selector] = $(selector);
}
// cache[selector] refers to the same object as $("#" + x + "-" + y)
}

Categories