how do I best display matches from a large json object? - javascript

I am loading data from a json object into a table on my page. Then I allow the user to filter that data via an input and display only the matches. My method of doing this is surely not great but it does work.
Now I want to do the exact same thing with a list of airports and their codes. Problem is that the airport list is much longer and the page bogs down significantly when loading the table with data and when it searches for the user's input in the table.
Here's the information for the page that does work so you can se what Im doing.
What can I do differently to achive the same effect I have here when I have a much larger data set to search?
Page Displaying data: (type "american airlines" or "aa"as an example)
https://pnrbuilder.com/_popups/dataDecoder.php
json object containing airline information:
https://pnrbuilder.com/_java/airlineDecoder.js
Sript that loads data to the page and filters it based on user input:
https://pnrbuilder.com/_java/decodeData.js
Here's the most significant parts of my code:
// This function is called by a for loop on dom ready
// It basically prints data stored in a json object to a table on the page
function fillInfo(line) {
var table = document.getElementById('decodeTable');
var row = document.createElement('tr');
table.appendChild(row);
var col1 = document.createElement('td');
row.appendChild(col1);
var curCode = document.createTextNode(arlnInfo.d[line].IATA);
col1.appendChild(curCode);
var col2 = document.createElement('td');
row.appendChild(col2);
var curArln = document.createTextNode(arlnInfo.d[line].Airline);
col2.appendChild(curArln);
var col3 = document.createElement('td');
row.appendChild(col3);
var curPre = document.createTextNode(arlnInfo.d[line].Prefix);
col3.appendChild(curPre);
var col4 = document.createElement('td');
row.appendChild(col4);
var curIcao = document.createTextNode(arlnInfo.d[line].ICAO);
col4.appendChild(curIcao);
var col5 = document.createElement('td');
row.appendChild(col5);
var curCnty = document.createTextNode(arlnInfo.d[line].Country);
col5.appendChild(curCnty);
}
// This function checks user input against data in the table
// If a match is found whitin a row, the row containing the match is shown
// If a match is not found that row is hidden
function filterTable(input) {
var decodeTable = document.getElementById('decodeTable');
var inputLength = input.length;
// THis first part makes sure that all rows of the generated table are hidden when no input is present
if (inputLength == 0) {
for (var r = 1; r < decodeTable.rows.length; r++) {
decodeTable.rows[r].style.display = "none";
}
}
// This part checks just the airline codes "column" of the table when input is only one or two characters
else if (inputLength < 3) {
for (var r = 1; r < decodeTable.rows.length; r++) {
var celVal = $(decodeTable.rows[r].cells[0])
.text()
.slice(0, inputLength)
.toLowerCase();
if (celVal == input) {
decodeTable.rows[r].style.display = "";
} else {
decodeTable.rows[r].style.display = "none";
}
}
}
// This part checks several "columns" of the table when input is more than two characters
else if (inputLength > 2) {
for (var r = 1; r < decodeTable.rows.length; r++) {
var celVal = $(decodeTable.rows[r].cells[2])
.text()
.slice(0, inputLength)
.toLowerCase();
var celVal2 = $(decodeTable.rows[r].cells[1])
.text();
if (celVal == input || celVal2 == input) {
decodeTable.rows[r].style.display = "";
} else if (celVal2.replace(/<[^>]+>/g, "")
.toLowerCase()
.indexOf(input) >= 0) {
decodeTable.rows[r].style.display = "";
} else {
decodeTable.rows[r].style.display = "none";
}
}
}
}

The first little optimization you could apply is not to do the entire filter on every key up, wait until the user finished typing so delay calling it for half a second:
var timeOut = 0;
$("#deCode").keyup(function () {
// cancel looking, the user typed another character
clearTimeout(timeOut);
// set a timeout, when user doesn't type another key
// within half a second the filter will run
var input = $("#deCode").val().toLowerCase().trim();
timeOut=setTimeout(function(){
filterTable(input)
},500);
});
The next is comparing to your json data instead of jquery objects and converting your JSON data to lower case after creating the table so you don't have to check toLowerCase every time for every row:
function filterTable(input) {
var decodeTable = document.getElementById('decodeTable');
var inputLength = input.length;
if (inputLength ==0) {
for (var r = 1; r < decodeTable.rows.length; r++) {
decodeTable.rows[r].style.display = "none";
}
}
else if (inputLength <3) {
for (var r = 0; r < arlnInfo.d.length; r++) {
if (arlnInfo.d[r].IATA.indexOf(input)===0) {
decodeTable.rows[r+1].style.display = "";
}
else {
decodeTable.rows[r+1].style.display = "none";
}
}
}
else if (inputLength > 2) {
for (var r = 0; r < arlnInfo.d.length; r++) {
if (arlnInfo.d[r].Prefix.indexOf(input)===0) {
decodeTable.rows[r].style.display = "";
}
else if (arlnInfo.d[r].Airline.indexOf(input) >= 0) {
decodeTable.rows[r + 1].style.display = "";
}
else {
decodeTable.rows[r + 1].style.display = "none";
}
}
}
}
Problem is with your JSON data: "Prefix": 430 causes arlnInfo.d[r].Prefix.slice(0, inputLength) to throw an error because the data isn't a string but a number. If you have control over the JSON then you should convert these values to string ("Prefix":"430"), If you don't then convert it once and re create airlineDecoder.js using JSON.stringify(arlnInfo);
To convert your JSON you can copy and paste this in the chrome console (press F12) and run it (press enter). It'll log the converted JSON but you may need an IDE like netbeans to re format it:
var i = 0;
for(i=0;i<arlnInfo.d.length;i++){
arlnInfo.d[i].Prefix=arlnInfo.d[i].Prefix+"";
}
console.log("var arlnInfo = " + JSON.stringify(arlnInfo));
A last optimization you can apply is use DocumentFragment instead of directly adding every row to DOM, here we convert the JSON data to lower case so we don't have to do that for every search:
var decodeTable = document.getElementById('decodeTable');
function createTable() {
var df = document.createDocumentFragment();
for (var i = 0; i < arlnInfo.d.length; i++) {
fillInfo(i, df);
arlnInfo.d[i].IATA = arlnInfo.d[i].IATA.toLowerCase()
arlnInfo.d[i].Prefix = arlnInfo.d[i].Prefix.toLowerCase();
arlnInfo.d[i].Airline = arlnInfo.d[i].Airline.toLowerCase();
}
decodeTable.appendChild(df);
}
createTable();
....
function fillInfo(line,df) {
var row = document.createElement('tr');
df.appendChild(row);
....
row.style.display = "none";
}

Related

Google App Script returning "Attribute provided with no value"

I'm attempting to use the UrlFetchApp method to check a large sheet of (around 2000) rows where some of the links are no longer needed. They don't give a 404, so I'm searching the html of the link for the class error. My logic appears to be functioning more or less as I want it to, with rows containing links with the class error being passed into an array.
The issue I'm having is that the script is failing after a few minutes with the error Exception: Attribute provided with no value: url. Previously I was getting Address unavailable, but since wrapping UrlFetchApp in a try-catch I'm not seeing that anymore.
//If link is dead, delete
function deleteRows() {
//Check sheet for links where the car has been sold
function checkLinks(url){
try{
var html = UrlFetchApp.fetch(url).getContentText();
var searchstring = 'class="error"';
var index = html.search(searchstring);
return index > 0;
} catch(e) {
Logger.log(e);
}
}
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ss1 = ss.getSheetByName("Facebook Feed")
var rows = ss1.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
var toDelete = [];
for (var row = 1; row < values.length; row += 1) {
for(var column = 0; column < values[row].length; column += 1){
if (checkLinks(values[row][7])){
Logger.log(row);
toDelete.push(row);
}
}
}
Logger.log(toDelete);
for(var deleteRow = toDelete.length-1; deleteRow >= 0; deleteRow -= 1){
ss1.deleteRow(toDelete[deleteRow]+1);
}
SpreadsheetApp.flush();
};

How can I highlight duplicate rows in my sheet based on column value?

I am trying to highlight duplicate rows in my sheet by checking for the same email address entered under the 'Email Address' column.
I have some code (below) that does this - it looks for duplicate rows based on repeated values under 'Email Address' and highlights them red. However, once I revisit the sheet, manually remove the duplicate row and rerun the script, the same row is highlighted again. Why is this happening and what can I do to ensure that when I update the sheet, the (now) unique row is not highlighted again?
function findDupes() {
var CHECK_COLUMNS = [3];
var sourceSheet = SpreadsheetApp.getActiveSheet();
var numRows = sourceSheet.getLastRow();
var numCols = sourceSheet.getLastColumn();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var newSheet = ss.insertSheet("FindDupes");
for (var i = 0; i < CHECK_COLUMNS.length; i++) {
var sourceRange = sourceSheet.getRange(1,CHECK_COLUMNS[i],numRows);
var nextCol = newSheet.getLastColumn() + 1;
sourceRange.copyTo(newSheet.getRange(1,nextCol,numRows));
}
var dupes = false;
var data = newSheet.getDataRange().getValues();
for (i = 1; i < data.length - 1; i++) {
for (j = i+1; j < data.length; j++) {
if (data[i].join() == data[j].join()) {
dupes = true;
sourceSheet.getRange(i+1,1,1,numCols).setBackground("crimson");
sourceSheet.getRange(j+1,1,1,numCols).setBackground("crimson");
}
}
}
ss.deleteSheet(newSheet);
if (dupes) {
Browser.msgBox("Possible duplicate(s) found. Please check for repeat attendees.");
} else {
Browser.msgBox("No duplicates found.");
}
};
I want to be able to run the script again once I have manually removed the rows and have it reflect the updated nature of the sheet.
Try this:
Sorry, but there were so many things I couldn't understand why you were doing them that I find it easier just to show you how I'd do it.
function findAndHighlightDupesInColumn(col) {
var col=col||3;//I think you wanted to check column 3
var ss=SpreadsheetApp.getActive();
var sh=ss.getActiveSheet();
var rg=sh.getDataRange();
var vA=rg.getValues();//gets all data
var uA=[];//this is the array that hold all unique values
for(var i=1;i<vA.length;i++) {//assumes one header row
if(uA.indexOf(vA[i][col-1])==-1) {//if it's unique then put it in uA
uA.push(vA[i][col-1]);
}else{//if it's not unique then set background color
sh.getRange(i+1,1,1,sh.getLastColumn()).setBackground('crimson');
}
}
}
The following code will remove duplicates in column 3:
This method assumes that the first occurrence of any row is the row that you wish to keep. All other duplicate rows are deleted.
function removeColumnDupes(col) {
var col=col||3
var ss=SpreadsheetApp.getActive();
var sh=ss.getActiveSheet();
var rg=sh.getDataRange();
var vA=rg.getValues();
var uA=[];
var d=0;
for(var i=1;i<vA.length;i++) {
if(uA.indexOf(vA[i][col-1])==-1) {
uA.push(vA[i][col-1]);
}else{
sh.deleteRow(i+1-d++);
}
}
}
Try adding this before the for loops. It will turn all the rows into white, and then the rest of your code will turn the duplicates red:
sourceSheet.getRange(2,1,numRows,numCols).setBackground("white");
Also, you can add an onEdit() function to check for duplicates in real time, when you add or edit an email address:
function onEdit(e){
if (e.range.getColumn() == 3){
var sourceSheet = SpreadsheetApp.getActiveSheet();
var numRows = sourceSheet.getLastRow();
var numCols = sourceSheet.getLastColumn();
var data = sourceSheet.getRange("C2:C"+numRows).getValues();
var editedCell = e.value;
for (var i = 0; i < data.length;i++){
if (editedCell == data[i] && (i + 2) != e.range.getRow()){
sourceSheet.getRange(e.range.getRow(),1,1,numCols).setBackground("crimson");
sourceSheet.getRange(i+2,1,1,numCols).setBackground("crimson");
}
}
}
}

How to prevent additional table creation

I've been looking for workarounds to ensure that only one table is created. So far the only one i have come up with is to disable the button after it had been pressed. Here is my code:
function bikeData() {
// Select the Table
var tbl = document.getElementById('bikeInnerTable');
var th = document.getElementById('tableHead_B');
var headerText = ["ID", "Bike Status", "Bike Location", "Depot ID"];
// Set number of rows
var rows = 10;
// Set number of columns
var columns = headerText.length;
// create table header
for (var h = 0; h < columns; h++) {
var td = document.createElement("td");
td.innerText = headerText[h];
th.appendChild(td);
}
// create table data
for (var r = 0; r < rows; r++) {
var cellText = ["UNDEFINED", "UNDEFINED", "UNDEFINED", "UNDEFINED"];
// generate ID
x = getRandomNumber(1000, 1);
cellText[0] = x;
// generate Status
x = getStatus();
cellText[1] = x;
// generate Name
x = getLocation();
cellText[2] = x;
// generate depot ID
x = getRandomNumber(1000, 1);
cellText[3] = x;
var tr = document.getElementById("b_row" + r);
for (var c = 0; c < columns; c++)
{
var td = document.createElement("td");
td.innerText = cellText[c];
tr.appendChild(td);
}
}
}
If the button is pressed multiple times then the table is created multiple times. However how can I adapt the code to ensure that it if the table is already present within the div, then it doesn't continue in creating the table additional times.
You can set a flag and then only execute the code when applicable.
let firstTime = true;
function(){
...
if (firstTime) {
firstTime = false;
...
}
}
This is what Javascript variables are for. You can make a variable, then test that against a condition in the function. Let me show you what I mean:
window.timesRan = 0;
function bikeData() {
//Check if the variable is > 1
if (timesRan > 1) {
return false;
}
//code here
//then just add 1 to the variable every time
timesRan += 1;
}
All the best, and I hope my answer works for you :)

Gridview Rows Items Misalignment to Header

Please see image first
Misaligning of row items to header happens after keypress event in textbox for record searching takes place. It works using javascript using this code
var $KeyPressSearch = jQuery.noConflict();
function filter2(phrase, _id) {
var words = phrase.value.toLowerCase().split(" ");
var table = document.getElementById(_id);
var ele;
for (var r = 1; r < table.rows.length; r++) {
ele = table.rows[r].innerHTML.replace(/<[^>]+>/g, "");
var displayStyle = 'none';
for (var i = 0; i < words.length; i++) {
if (ele.toLowerCase().indexOf(words[i]) >= 0)
displayStyle = '';
else {
displayStyle = 'none';
break;
}
}
table.rows[r].style.display = displayStyle;
}
var lblTotalDSRdata = $KeyPressSearch("#grd tr").length;
}
this only happens to gridviews with jquery injected codes used to fixate the header. to other gridviews that do not use, all works fine.
So to fix the gridview header I apply the tutorial from this link http://gridviewscroll.aspcity.idv.tw/ (Basic)
[![enter image description here][3]][3]

Javascript to change innerHTML function not working

I'm building an interface that consists of 9 cells in table. When a person mouses over a cell, I want other cells to become visible, and change the text content of some of the cells. I can do that just fine if I create individual functions to change the content of each cell, but that's crazy.
I want a single function to change the text depending on the cells involved. I created a function that can take n arguments, and loops through making changes based on the arguments passed in to the function. It doesn't work.
Code for the function is below. If I call it, onMouseOver="changebox('div3')", the argument makes it to the function when I mouse over the cell. If I uncomment the document.write(cell) statement, in this instance, it prints div3 to the screen. So... why isn't it making any changes to the content of the div3 cell?
function changebox() {
for (var i = 0; i < arguments.length; i++) {
var cell = document.getElementById(arguments[i]).id;
var text = "";
if (cell == 'div3') {
text = "Reduced Travel";
} else if (cell == 'div4') {
text = "Reduced Cost";
}
//document.write(cell)
cell.innerHTML = text;
}
}
In your code cell is a string which holds the id of the object. Update the code as follows
function changebox() {
for (var i = 0; i < arguments.length; i++) {
var cell = document.getElementById(arguments[i]),
text = "";
if (cell.id == 'div3') {
text = "Reduced Travel";
} else if (cell.id == 'div4') {
text = "Reduced Cost";
}
//document.write(cell)
cell.innerHTML = text;
}
}
UPDATE :
You can reduce the code as #Tushar suggested.
No need of iterating over arguments(assuming there are only two elements, but can be modified for more elements).
function changebox() {
// As arguments is not real array, need to use call
// Check if div is present in the arguments array
var div3Index = [].indexOf.call(arguments, 'div3') > -1,
div4Index = [].indexOf.call(arguments, 'div4') > -1;
// If present then update the innerHTML of it accordingly
if (div3Index) {
document.getElementById('div3').innerHTML = 'Reduced Travel';
} else if (div4Index) {
document.getElementById('div4').innerHTML = 'Reduced Cost';
}
}
function changebox() {
var args = [].slice.call(arguments);
args.map(document.getElementById.bind(document)).forEach(setElement);
}
function setElement(ele) {
if (ele.id === 'div3') {
ele.innerHTML = "Reduced Travel";
} else if (ele.id === 'div4') {
ele.innerHTML = "Reduced Cost";
}
}
this make your function easy to be tested
As your assigning the cell variable the id of the element and changing the innerHTML of cell which is not valid .
var changeText = function() {
console.log("in change text");
for(var i= 0; i<arguments.length; i++) {
var elem = document.getElementById(arguments[i]);
var cell = document.getElementById(arguments[i]).id;
var text = "";
console.log(cell)
if (cell === "div-1") {
text = cell+" was selected!!";
} else if(cell === "div-3") {
text = cell+" was selected!!";
} else {
text = cell+" was selected";
}
elem.innerHTML = text;
}
}
This would properly change the text of div mouseovered!!

Categories