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);
});
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;
I have a website with a list of json objects arranged something like this:
[
{
"a": true or false,
"b": "information",
"c": "information",
"d": "information",
"e": "information"
},
...
]
The idea of this code is to print out all the objects on a table and have a checkbox which filters out the false objects out when needed. The site is supposed to just have the the table with unfiltered object on there, but after I added the checkbox event listener the full table list disappeared. When I check the checkbox I get the filtered objects and it keeps adding more and more of the same filtered content on the bottom of the table if I keep re-clicking it.
What am I doing wrong here? Here is the code I have:
var stuff = document.getElementById("stuff-info");
var ourRequest = new XMLHttpRequest();
ourRequest.open('GET', 'url');
ourRequest.onload = function() {
var ourData = JSON.parse(ourRequest.responseText);
renderHTML(ourData);
};
ourRequest.send();
function renderHTML(data) {
var htmlString = "";
var filteredData = data.filter(function(element) {
return element.a
});
var checkbox = document.querySelector("input[name=hide]");
checkbox.addEventListener('change', function() {
if (this.checked) {
for (i = 0; i < filteredData.length; i++) {
htmlString += "<table><tr><td>" + filteredData[i].b + "</td><td>" + filteredData[i].c + "</td><td>" + filteredData[i].d + "</td><td>" + filteredData[i].e + "</td></tr>"
}
} else {
for (i = 0; i < data.length; i++) {
htmlString += "<table><tr><td>" + data[i].b + "</td><td>" + data[i].c + "</td><td>" + data[i].d + "</td><td>" + data[i].e + "</td></tr>"
}
}
stuff.insertAdjacentHTML('beforeend', htmlString);
});
}
Might be easier to filter with CSS selector:
#filter:checked ~ table .filter { display: none }
<input type=checkbox id=filter> Filter
<table border=1>
<tr class=filter><td>1</td><td>a</td></tr>
<tr><td>2</td><td>b</td></tr>
<tr class=filter><td>3</td><td>c</td></tr>
<tr><td>4</td><td>d</td></tr>
</table>
after I added the checkbox event listener the full table list disappeared.
All of your logic for deciding what to render is trapped inside your onchange event, so nothing will be drawn until a checkbox is changed.
When I check the checkbox I get the filtered objects and it keeps adding more and more of the same filtered.
All of your html strings are generated with += against the original htmlString variable trapped in the closure. So yeah, it will just keep adding more and more rows. You are also inserting the udated strings into the dom without removing the old table(s), so this will be exponential growth.
I think there is a great case here for higher order functions instead of for loops, you can use the map array method to transform each item in the array into a string, instead of manually iterating. This is cleaner and more maintainable.
Notice that now that the rendering logic is not mixed together with the event logic, it would be much easier to reuse the render function with some different data or different events. It's also somewhat trivial to add more transformations or filters.
const ourRequest = new XMLHttpRequest();
ourRequest.onload = function() {
const ourData = JSON.parse(ourRequest.responseText);
initialRender(ourData);
};
ourRequest.open('GET', 'url');
ourRequest.send();
function filterAll() { return true; }
function filterA() { return element.a; }
function toRowString(item) {
return `
<tr>
<td>${item.a}</td>
<td>${item.b}</td>
<td>${item.c}</td>
<td>${item.d}</td>
<td>${item.e}</td>
</tr>`;
}
function renderTable(predicate, parentElement, data){
const rows = data
.filter(predicate)
.map(toRowString);
parentElement.innerHTML = `<table>${rows}</table>`;
}
function initialRender(data) {
const stuff = document.getElementById("stuff-info");
const checkbox = document.querySelector("input[name=hide]");
renderTable(filterAll, stuff, data);
checkbox.addEventListener('change', function(event) {
renderTable(
event.target.checked ? filterA : filterAll,
stuff,
data
);
}
}
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>
I have this code to open a file in NW.JS.
function chooseFile(name, handleFile) {
var chooser = document.querySelector(name);
chooser.addEventListener("change", function(evt) {
for(var f of this.files){
console.log(f.name);
console.log(f.path);
handleFile(f.name, f.path);
}
}, false);
chooser.click();
}
chooseFile('#fileDialog', function(name, path){ ... /* do something with the file(s) */ });
I have concatenated that code into a string so that I can inject it into a div (when that div is created) from a button click that generates the above code with unique id’s thusly..
var letsDoThis = '$(".' + imgUploadClass + '").click(function(){ var leid = this.id; var theEditId = "divId" + leid.slice(5); function ' + imgUploadChoose + '(name, handleFile) { var chooser = document.querySelector(name);chooser.addEventListener("change", function(evt) { for(var f of this.files){console.log(f.name);console.log(f.path);handleFile(f.name, f.path);}}, false);chooser.click();}' + imgUploadChoose + '("#' + imgUploadzz +'", function(name, path){' + 'alert(path);' + ';});});';
When testing I can access the name and path inside the alert box! GREAT! The code I want to run where
'alert("Hello!");’
is needs to be the following (but instead of a hardcoded url I need to access “path” from the (uniquely named) chooseFile script)...
'$("#" +' + 'theEditId).css("background-image", "url(http://www.jqueryscript.net/images/Simplest-Responsive-jQuery-Image-Lightbox-Plugin-simple-lightbox.jpg)");'
I am pretty sure I am not escaping a comma or something, here is another piece of code I got to fire correctly but not being able to access “path”...
'$("#" +' + 'theEditId).css("background-color", "green");'
I'm trying to dynamically create elements and add an Event Listener to them. Here's some code:
var marks = {
list:[{
selected: "content",
url: "http://youtube.com/feed/subscriptions",
trait: "id"
},{
selected: "js-streams streams items",
url: "http://twitch.tv/directory/following",
trait: "class"
}]
};
var l = marks.list.length;
var web = "<webview src='https://youtube.com/feed/subscriptions' preload='preload.js' disablewebsecurity></webview>";
for(var i = 0; i < l; i++){
var webadd = "<webview id=web" + i + " src=" + marks.list[i].url + " preload='preload.js' disablewebsecurity></webview>";
$('#canvas1').append(webadd);
document.getElementById("web" + (i)).addEventListener("dom-ready", function() {
console.log("nice" + (i)); // <-- This line most likely needs changing
});
}
Right now, I'm able to get my elements and an event listener on each, but when it comes to console log, it prints out "nice2" twice. Is there a way to get that line to get var i's value as an integer instead of i itself? Ideally, I want it to be printing out nice0 and nice1. Any ideas? Thanks.
You better use a forEach loop. This way you can have a closure and keep you context. also reads better.
mark.list.forEach(function(item, i){
var webadd = "<webview id=web" + i + " src=" + item.url + " preload='preload.js' disablewebsecurity></webview>";
$('#canvas1').append(webadd);
document.getElementById("web" + i).addEventListener("dom-ready", function() {
console.log("nice" + i); // <--
});
})
You need an extra function that creates your event listener and binds the current i to it:
document.getElementById("web" + (i)).addEventListener("dom-ready", (function(n) {
return function() {
console.log("nice" + (n));
}
})(i));