I got myself stuck in a situation. I was coding a Wikipedia search tool for a personal practice project, but I've ran into a small error. When a user enters a word into the search bar, the input will be store into the data parameter of $.getJSON, then the response will return a array of title and description objects based on the word entered in the search bar. The $.getJSON function will display 5 sets of a title and it's description in a list format in the designated HTML. Simple enough, but the issue is the $.getJSON function will display the wording "undefined", then continue to display the required set of titles and descriptions. Below I have listed my JS coding for your viewing. Also, the full code can be viewed at my codepen.
Can anyone give me a heads up of what might be the issue. As $.getJSON is asynchronous, that might be the issue, but I can't quite put my finger on it. Thanks in advance!
$("#search-word").on("keydown", function(event) {
if(event.keyCode == 13) {
event.preventDefault();
var input = {search: $(this).val()};
getWikiInfo(input);
}
});//search end
function getWikiInfo(input) {
var wikipApi = "https://en.wikipedia.org/w/api.php?format=json&action=opensearch&callback=?";
var getWikipHtml = function(response) {
console.log(response);
var wikipHtml;
for(var i = 1; i < 6; i++) {
wikipHtml += '<div class="list"><h3>' + response[1][i] + '</h3><p>' + response[2][i] + '</p></div>';
}
$("#list-container").html(wikipHtml);
}
$.getJSON(wikipApi, input, getWikipHtml);
}//getWikiInfo end
You need to do minor change. Initialize wikipHtml to empty string and check if the response[1][i] is not undefined. Below is the updated code:
var wikipHtml = '';
for (var i = 1; i < 6; i++) {
if (response[1][i] !== undefined)
wikipHtml += '<div class="list"><h3>' + response[1][i] + '</h3><p>' + response[2][i] + '</p></div>';
}
This is happening because you are not initializing wikipHtml before appending to it, but I would strongly advise that you use proper DOM manipulation instead of building your HTML using string concatenation:
$("#search-word").on("keydown", function(event) {
if (event.keyCode == 13) {
event.preventDefault();
var input = {
search: $(this).val()
};
getWikiInfo(input);
}
}); //search end
function getWikiInfo(input) {
var wikipApi = "https://en.wikipedia.org/w/api.php?format=json&action=opensearch&callback=?";
var getWikipHtml = function(response) {
var content = [0, 1, 2, 3, 4, 5].map(function(i) {
return $('<div class="list">')
.append($('<h3>').text(response[1][i]))
.append($('<p>').text(response[2][i]));
});
$("#list-container").html(content);
}
$.getJSON(wikipApi, input, getWikipHtml);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id='search-word' type='text' />
<div id='list-container'></div>
Related
I have a small web application setup on google sheets which have almost 10k rows and 9 columns.
currently, I took all the data from Google sheets and putting it on an HTML Table and Then I have few inputs through which I filter the table using event listener.
As you could have guessed already it is taking too much of memory since it is on the client side and loading and filtering are slow.
Earlier I was having an interactive filter with an event listener on each key press I have changed it to "Enter" key since it was taking too much time for first two or three characters.
Script on index.HTML
<script>
//global variables
var rows = []; //rows
var currentOrder = 'ascending'; //sorting order
var inputFilter = document.getElementById('partNum'); //input field for ItemName
var inputFilterDes = document.getElementById('partDes'); //input field for description
var nameTable = document.getElementById('table'); //html table
//load function being used for pulling data from google sheet
function load() {
//calling get data function with array and filter array inside
google.script.run
.withSuccessHandler(function(response) {
//response function will be separted into column values
rows = response.map(function(element) {
//all the elements converted into columns
return {
itemCode: element[0],
itemName: element[1],
itemDescription: element[2],
inStock: element[3],
committed: element[4],
onOrder: element[5],
available: element[6],
warehouse: element[7]
};
});
//rows mapping finished
renderTableRows(rows);
//initial load finished here
//filter section starts
//Item name filter
inputFilter.addEventListener('keyup', function(evt) {
if (evt.keyCode === 13) {
// Cancel the default action, if needed
evt.preventDefault();
var filter = evt.target.value.toString().toLowerCase();
}
var filteredArray = rows.filter(function(row) {
return row.itemName.toString().toLowerCase().includes(filter);
});
renderTableRows(filteredArray);
});
//description filter
inputFilterDes.addEventListener('keyup', function(evt) {
if (evt.keyCode === 13) {
// Cancel the default action, if needed
evt.preventDefault();
var filterDes = evt.target.value.toString().toLowerCase();
}
var filteredArrayDes = rows.filter(function(row) {
return row.itemDescription.toString().toLowerCase().includes(filterDes);
});
renderTableRows(filteredArrayDes);
});
})
.getData("SAP"); //pull data from defined sheet
}
//retruing array values in HTML table and placing them in page
function renderTableRows(arr) {
nameTable.innerHTML = arr.map(function(row) {
return '<tr>' +
'<td>' + row.itemCode + '</td>' + '<td>' + row.itemName + '</td>' +
'<td>' + row.itemDescription + '</td>' + '<td>' + row.inStock + '</td>' +
'<td>' + row.committed + '</td>' + '<td>' + row.onOrder + '</td>' + '<td>' +
row.available + '</td>' + '<td>' + row.warehouse + '</td>' + '</tr>';
}).join('');
};
load();
</script>
My code.gs
function doGet(e) {
if (!e.parameter.page) {
// When no specific page requested, return "home page"
return HtmlService.createTemplateFromFile('index').evaluate().setTitle("My Web App");
}
// else, use page parameter to pick an html file from the script
return HtmlService.createTemplateFromFile(e.parameter['page']).evaluate();
}
function getData(sheetName) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
return sheet.getSheetValues(2, 1, sheet.getLastRow() - 1, sheet.getLastColumn());
}
function getScriptUrl() {
var url = ScriptApp.getService().getUrl();
return url;
}
I tried to move it on the server side using the following but failed
EDIT : Removed my Server side atempt code as i think it will create confusions.
I'm not a coder so please excuse me if it sounds silly or unorganized.
SO I am trying to increase the speed and for this, I want to Move scripts server-side however I am not fully confident it will help me or not so I am open to any other methods to improve the speed of application.
Apart from moving map() to async server call, you can optimize the client-side code by creating an ordering function that works over DOM. Currently, each time a keyup event is fired, you rerender the whole table (10K iterations each time if I understand the Spreadsheet size correctly).
First, access your table's children (assuming it is constructed with both <thead> and <tbody> elements: var collection = nameTable.children.item(1).children (returns HtmlCollection of all the rows).
Second, iterate over rows and hide ones that do not satisfy the filtering criteria with hidden property (or create and toggle a CSS class instead):
for(var i=0; i<collection.length; i++) {
var row = collection.item(i);
var cells = row.children;
var itemName = cells.item(1).textContent; //access item name (0-based);
var itemDesc = cells.item(2).textContent; //access item description (0-based);
var complies = itemName==='' && itemDesc===''; //any criteria here;
if( complies ) {
row.hidden = false;
}else {
row.hidden = true;
}
}
Third, move the renderTableRows() function to server async call as well, since you render your table rows with string concatenation (instead of createElement() on document) with htmlString.
Useful links
Document Object Model (DOM) reference;
Server-client communication in GAS reference;
Best practices for working with HtmlService;
Im trying to remove all the rows in jquery data tables when new data arrives in order to fill with new rows.
But the problem is despite calling clear function it simply add the result rows to previous rows.
How to clear the rows in jquery data tables . Following is the code
var table = $("#editable");
var tp = table.DataTable({
"paging": true,
"createdRow": function (row, data, index) {
$compile(row)($scope);
}
});
//more code here ajax call only from angular post
if (result != null) {
$scope.reviews = result;
$window.toastr["success"]("Loaded Successfully !", "Recent Reviews");
tp.clear().draw();
for (var i = 0; i < $scope.reviews.length; i++) {
var id = $scope.reviews[i].ID;
var checked = $scope.reviews[i].Enabled == "1" ? true : false;
tp.row.add([
$scope.reviews[i].ID,
$scope.reviews[i].AddedOn,
$scope.reviews[i].Company.Name,
$scope.reviews[i].Rate,
$scope.reviews[i].User.Name,
$scope.reviews[i].Description,
"<div class='switch'><div class='onoffswitch'><input ng-model='$scope.reviews[" + i + "].Enabled' ng-click='ChangeReviewPublishStatus(" + $scope.reviews[i].ID + ")' ng-checked='" + checked + "' class='onoffswitch-checkbox' id= 'stat" + id + "' type= 'checkbox'><label class='onoffswitch-label' for='stat" + id + "'><span class='onoffswitch-inner'></span><span class='onoffswitch-switch'></span></label></div></div>"
]).draw();
}
}
I would place that piece of code inside a $timeout. Both dataTables and angular wants to manipulate the DOM - angular wins the battle and by that dataTables never get a chance to finish its business. A $timeout will force the dataTables clear() and data population into the next digest, and ensure it is actually executed.
$timeout(function() {
tp.clear().draw();
for (var i = 0; i < $scope.reviews.length; i++) {
var id = $scope.reviews[i].ID;
var checked = $scope.reviews[i].Enabled == "1" ? true : false;
tp.row.add([
$scope.reviews[i].ID,
$scope.reviews[i].AddedOn,
//etc
]).draw();
}
});
Well, that is at least my theory - cannot replicate your scenario in full - let me know if it makes any difference.
I am trying to change the input value of a hidden form to update the score of a game in my database.
I have this form code on a php page that displays and plays the game.
<form id ="recordForm" method="POST" action="updatePHP.php">
<input type='hidden' name="record" id='record' value='' />
</form>
And am trying to change the value of the hidden input field with this javascript. This is in the separate javascript file that is controlling the game.
function postPHP(newRecord){
alert("POST TO PHP"); //test to make sure I am calling this function
alert (newRecord); //alerts the correct value
var elem = document.getElementById('record');
elem.value = 12;
// document.getElementById('record').value = newRecord;
// document.getElementById('recordForm').submit();
};
There are a lot of topics on this subject but I am just not able to figure out what I am doing wrong. Any suggestions?
you should try
elem.value = newRecord;
Your JS function should work like this, i tested, more less what you already have. I remove the alerts since you don't need them anymore and leave what you have commented. This means your JS function isn't the problem.
function postPHP(newRecord)
{
document.getElementById('record').value = newRecord;
document.getElementById('recordForm').submit();
};
Don't forget to sent the parameter when calling the JS function, i did it with a button
<button onClick="postPHP('14')">Change</button>
since your JS function is in a separate file don't forget to include it in the File where you call the function
<head>
<script type="text/javascript" src="PATH/exampleName.js"></script>
</head>
Replace the src of the above tag to your needs
And last but not least check your updatePHP.php with a call to the method print_r
print_r($_POST);
All that should make the trick
Thank you for all your suggestions! This was my first question ever, I will look at all of them and see if I can get it working.
This is where I am calling postPHP:
function checkScore(score, record) {
alert('Score= ' + score);
alert ('Record= '+ record);
if(score < record || record === 0){
alert ("NEW RECORD"); //this alert is displayed when needed
postPHP(score);
}
};
and checkScore was called when the user moved a target crate back to the beginning spot and the following statement was executed
if (this.hasWon()) {
var finalScore = this.getScore();
var record = this.getRecord();
checkScore(finalScore, record);
return ret; //moving not allowed
}
there are some access methods used there.
//access methods
Board.prototype.hasWon = function() {
return state === 1;
};
Board.prototype.getScore = function() {
return score;
};
Board.prototype.getWt = function(r, c) {
return b[r][c];
};
Board.prototype.getData = function() {
return {"bobR": bobR, "bobC": bobC, "bobDir": bobDir,
"tgtR": tgtR, "tgtC": tgtC,
"startC": startC, "n": n};
};
Board.prototype.getRecord = function(){
var s = "" + window.location;
var ampIdx = "" + s.indexOf("&");
ampIdx = parseInt(ampIdx);
ampIdx = ampIdx + 7;
var record = "" + s.substring(ampIdx);
//alert("Puzzle Record= " + record);
record = parseInt(record);
return record;
}
;
I do have the javascript included. I do call it once in the body of the HTML, for some reason it doesn't display the game correctly when included in the head.
Again, thank you for the help! I will let you know what I get to work!
This is what I got to work.
function postPHP(newRecord, seed) {
alert("POST TO PHP");
var inner = "<input type='hidden' name='record' id='record' value=" + newRecord + " >"+
"<input type='hidden' name='seed' id='seed' value=" + seed + " >";
document.getElementById('recordForm').innerHTML = inner;
document.getElementById('recordForm').submit();
};
Thanks again for all the help, I just don't know why the first method wasn't working. This is my first attempts at PHP and javascript.
I am currently trying to create a dynamic printable-document generator for my training department at work. I would like the entire project to remain in Javascript/browser-side scripting, as I'm trying to gain knowledge in Javascript exclusively. The UI is linked below (can't post images until I have 10 rep):
Hosted on my personal website - cgiv.webs.com/Test Platform/Training Plan.png
The issue I'm having is with regular expressions. I am fairly new to Javascript, but VERY new to regular expressions within Jscript. I'm currently using the following function to generate and identify three input texts per execution:
/*Variable Declarations*/
var i1 = 0;
var i2 = 0;
/* ------------------- */
function generateInput()
{
if (i1<15)
{
i1++;
var appendSpan = document.getElementById('appendSpan');
var appendStr = "<div class='row'><input id='text_topic" + i2.toString() + i1.toString() + "' class='text_topic' type='text'/>|<input id='text_instructor" + i2.toString() + i1.toString() + "' class='text_instructor' type='text'/>|<input id='text_date" + i2.toString() + i1.toString() + "' class='text_date' type='text'/></div>";
appendSpan.innerHTML += appendStr;
}
else
{
alert("Action Cancelled. Maximum fields reached.");
}
}
The i2 variable indicates the header number that the input fields fall under, where the i1 variable indicates the row that each cell falls into. I would like to place a regex identifier script within the following function to pull the values from each cell and append them underneath their respective target spans within the "newPage" variable:
function createPage()
{
var newPage = "<html><head><title></title>";
newPage += "<link rel='stylesheet' lang='text/css' href='output.css'>";
newPage += "</head><body>";
newPage += "<div class='head'>" + promptVal[0] + "</div><br/>";
newPage += "<span id='hcontent1'></div></span>";
var inputs = document.getElementsByTagName("input");
/* Uhhh.. Yeah. This is where I'm lost */
newPage += "</span>";
newPage += "</body></html>";
var j = window.open('')
j.document.write(newPage);
j.document.close();
}
Once I can get, for example, text_(topic, instructor, date)(11-13) all within the "hcontent1" span, I can format it out. I just want the data to be pulled from the text fields and placed into div tags on a separate page.
Thanks for your time, ahead of time!
I figured it out! After like three days of searching, this worked:
var regex1 = /1/g;
for (var i=0; inputs[i]; i++)
{
if (inputs[i].id.search(regex1) == 10)
{
alert("It worked");
}
else if (inputs[i].id.search(regex1) == 15)
{
alert("It worked again");
}
else if (inputs[i].id.search(regex1) == 9)
{
alert("You did it, man");
}
else
{
alert("Skip this one");
}
}
I have asked a question about how to avoiding to write the html in the js,then some people tell me using the javascript template,for example,the jquery/template pugin and ect.
It is a good idea when generate static html,for example:
<ul id="productList"></ul>
<script id="productTemplate" type="text/x-jquery-tmpl">
<li><a>${Name}</a> (${Price})</li>
</script>
<script type="text/javascript">
var products = [
{ Name: "xxx", Price: "xxx" },
{ Name: "yyy", Price: "xxx" },
{ Name: "zzz", Price: "xxx" }
];
// Render the template with the products data and insert
// the rendered HTML under the "productList" element
$( "#productTemplate" ).tmpl( products )
.appendTo( "#productList" );
</script>
However when I try to bind some event to the generated html,I meet some problem.
For example,I have a page which user can search some products by the price/name/location.
So I have three function:
searchByPrice(lowPrice,highPrice,productType,currentPage)
searchByName(name,productType,currentPage);
searchByLocation(location,currentpage);
ALl the above function have a realated method in the server side and they will retrun the products usint the xml format.
Since they will retrun so many items,so I have to paging them,the "currengPage" is used to tell the server side which part of results should be returned.
When the client get the result from the server side,now it is the js for display them int he div and create a Paging Bar if possible.
Before I know the template,I use this manner(which I hate most,try my best to avoid):
function searchByPrice(lowPrice,highPrice,productType,currentPage){
var url="WebService.asmx/searchByPrice?low="+lowPrice="&high="+highPrice+"&curPage="+currentPage;
//code to create the xmlHttp object
xmlhttp.open("GET",url,true);
xmlhttp.onreadystatechange=function(){
if (xmlhttp.readyState==4 && xmlhttp.status==200){
var i=0;
var Prohtml="";
var proList=parseProductList(xmlhttp.responseText);
for(i=0;i<prolist.length;i++){
Prohtml+="<li><a href='#'>"+prolist[i].name+"</a> ("+prolist[i].price"+)</li>";
}
//generate the paging bar:
var totleResult=getTotleResultNumber(xmlhttp.responseText);
if(totleResult>10){
var paghtml="<span>";
//need the paging
var pagNum=totleResult/10+1;
for(i=1;i<=pagenum;i++){
paghtml+="<a onclick='searchByPrice(lowPrice,highPrice,productType,currentPage+1)'>i</a>";
//here the synax is not right,since I am really not good at handle the single or doule '"' in this manner.
//also if in the searchByName function,the click function here should be replaced using the searchByName(...)
}
}
}
}
}
In the example,it is easy to use the template to generate the "Prohtml" since there is no event handling with them,but how about the "paghtml",the click function is different in differnt search type.
So,any good idea to hanld this?
Either:
Create DOM Elements instead of building HTML strings, using document.createElement or a small library if you're doing lots of this, which will allow you to attach events immediately in the usual fashion.
or
Give each element which needs to make use of event handlers a unique ID and build up a list of events to be attached once the HTML has been inserted into the document.
E.g.:
var eventHandlers = []
, eventCount = 0;
for (i = 1; i <= pagenum; i++) {
var id = "search" + eventCount++;
html += "<a id='" + id + "'>" + i + "</a>";
eventHandlers.push([id, 'click',
handler(searchByPrice, lowPrice, highPrice, productType, currentPage + i)])
}
// Later...
someElement.innerHTML = html;
registerEvents(eventHandlers);
Where registerEvents is:
function registerEvents(eventHandlers) {
for (var i = 0, l = eventHandlers.length; i < l; i++) {
var eventHandler = eventHandlers[i],
id = eventHandler[0],
eventName = eventHandler[1],
func = eventHandler[2];
// Where addEvent is your cross-browser event registration function
// of choice...
addEvent(document.getElementById(id), eventName, func);
}
}
And handler is just a quick way to close over all the arguments passed in:
/**
* Creates a fnction which calls the given function with any additional
* arguments passed in.
*/
function handler(func) {
var args = Array.prototype.slice.call(arguments, 1);
return function() {
func.apply(this, args);
}
}
I use something like this approach (but automatically adding unique ids when necessary) in the HTML generation portion of my DOMBuilder library, which offers a convenience method for generating HTML from content you've defined, inserting it into a given element with innerHTML and registering any event handlers which were present. Its syntax for defining content is independent of output mode, which allows you to switch between DOM and HTML output seamlessly in most cases.
First of all, you can simply use the $.get() or $.ajax() for your AJAX call.
Secondly, you can use .live() or .delegate() to bind events to elements that do not exist.
Thirdly, you can use the data attributes in the anchor elements as a way to pass in the arguments for the event handler, see .data().
So, to rewrite your function, you have may something like the following:
function searchByPrice(event) {
$this = $(this);
var lowPrice = $this.data('lowPrice'),
highPrice = $this.data('lowPrice'),
productType = $this.data('productType'),
currentPage = $this.data('currentPage');
var url = "WebService.asmx/searchByPrice?low=" + lowPrice = "&high=" + highPrice + "&curPage=" + currentPage;
$.get(url, function(data, textStatus, jqXHR) {
var i = 0;
var Prohtml = "";
var proList = parseProductList(data);
for (i = 0; i < prolist.length; i++) {
Prohtml += "<li><a href='#'>" + prolist[i].name + "</a> (" + prolist[i].price "+)</li>";
}
//generate the paging bar:
var totleResult = getTotleResultNumber(data);
if (totleResult > 10) {
var paghtml = "<span>";
//need the paging
var pagNum = totleResult / 10 + 1;
for (i = 1; i <= pagenum; i++) {
paghtml += '<a class="pagelink" ' +
'data-lowPrice="' + lowPrice + '" ' +
'data-highPrice="' + highPrice + '" ' +
'data-productType="' + productType + '" ' +
'data-currentPage="' + (currentpage + 1) + '">' + i + '</a>';
//here the synax is not right,since I am really not good at handle the single or doule '"' in this manner.
//also if in the searchByName function,the click function here should be replaced using the searchByName(...)
}
}
});
}
$(document).ready(function(){
$("a.pagelink").live('click', searchByPrice);
});