I'm seeing something weird with some async Javascript accessing a class method.
I have some Javascript that does a fairly intensive search, which can take some time (about 1 minute), so I want to run it asynchronously.
Here's my MVCE:
<html>
<head>
<script
src="https://code.jquery.com/jquery-3.5.1.min.js"
integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="
crossorigin="anonymous"></script>
</head>
<body>
<div id="search-form">
<input id="search-terms" type="text" placeholder="enter search terms" />
<button id="search-button">Search</button>
</div>
</body>
<script type="text/javascript">
class Index {
constructor() {
this.internal_hash = {};
}
get_term_count(){
return Object.keys(this.internal_hash).length;
}
do_big_long_expensive_calculation(){
var i=0;
for(i=0; i<10000; i++){
this.internal_hash[i] = i+1;
}
return i;
}
search_text(text){
var results = this.do_big_long_expensive_calculation();
return results;
}
};
var search_index = new Index;
var search_results = null;
$(document).ready(function(){
async function searchIndex(){
let text = $('#search-terms').val();
console.log('searching index for text:'+text);
console.log('using index with terms:'+search_index.get_term_count());
search_results = search_index.search_text(text);
console.log('results:'+search_results.length);
return search_results;
}
$('#search-button').click(function(){
console.log('search button clicked')
var el = $(this);
el.prop("disabled", true);
el.text('Searching...');
searchIndex()
.then(function(){
console.log('search results found:'+(search_results.length));
el.text('Search');
el.prop("disabled", false);
})
.catch(reason => console.log(reason.message));
});
});
</script>
</html>
If I call:
search_index.search_text('sdfssdfsf')
from the browser's console, it returns the expected 10000.
However, if I click the search button, the console.log statement prints out undefined for the value returned by search_index.search_text.
Why is this? The only thing I can think of is there's some characteristic of async I'm not aware of. Does async Javascript not have access to the same memory as the normal synchronous execution model?
You cant get length of number, return number directly or convert to string and get length
<html>
<head>
<script
src="https://code.jquery.com/jquery-3.5.1.min.js"
integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="
crossorigin="anonymous"></script>
</head>
<body>
<div id="search-form">
<input id="search-terms" type="text" placeholder="enter search terms" />
<button id="search-button">Search</button>
</div>
</body>
<script type="text/javascript">
class Index {
constructor() {
this.internal_hash = {};
}
get_term_count(){
return Object.keys(this.internal_hash).length;
}
do_big_long_expensive_calculation(){
var i=0;
for(i=0; i<10000; i++){
this.internal_hash[i] = i+1;
}
return i;
}
search_text(text){
var results = this.do_big_long_expensive_calculation();
return results;
}
};
var search_index = new Index;
var search_results = null;
$(document).ready(function(){
async function searchIndex(){
let text = $('#search-terms').val();
console.log('searching index for text:'+text);
console.log('using index with terms:'+search_index.get_term_count());
search_results = search_index.search_text(text);
console.log('results:'+search_results);
return search_results;
}
$('#search-button').click(function(){
console.log('search button clicked')
var el = $(this);
el.prop("disabled", true);
el.text('Searching...');
searchIndex()
.then(function(){
console.log('search results found:'+(search_results));
el.text('Search');
el.prop("disabled", false);
})
.catch(reason => console.log(reason.message));
});
});
</script>
</html>
Related
I have the following test code and I want to update the div text and show one, two, three... but the result is only the last
Edit: Because any answer here it depends on "sleep", I don't actually use "sleep" on my code. I replaced the code with a for loop.
<!DOCTYPE html>
<html>
<body>
<button onclick="test()" id="myBtn">test</button><br />
<div id="demo"></div>
<script>
function dosomething() {
for(i=1; i<=500; i++) console.log(i);
}
function test() {
let $myBtn = document.getElementById('myBtn');
$myBtn.style.display = 'none';
document.getElementById("demo").innerHTML = 'one';
dosomething();
document.getElementById("demo").innerHTML = 'two';
dosomething();
document.getElementById("demo").innerHTML = 'three';
dosomething();
document.getElementById("demo").innerHTML = 'stop';
$myBtn.style.display = 'block';
}
</script>
</body>
</html>
In your example the system never has a chance to show you the one, two, three because it's tied up looping in your while and the page is not getting repainted.
You need to give it a break!
You can use the JS function setInterval for this - it will run a given function every time interval. But you also need to know when to stop it - and it's not absolutely accurate as there may be other things going on on your system so don't use it for e.g. implementing a stopwatch.
You can also use the JS function setTimeout which runs just the once, and then you run it again etc until you've output all the texts. Here's a small example:
<html>
<body>
<button onclick="test()" id="myBtn">test</button><br />
<div id="demo"></div>
<script>
let text = ['one', 'two', 'three', 'stop'];
let i = 0; //this indicates which text we are on
function test() {
document.getElementById("demo").innerHTML = text[i];
i++;
if ( i< text.length) {
setTimeout(test,1000);
}
}
</script>
</body>
</html>
You might be better off using the in-built setTimeout function, with some tweaks. Something like:
<html>
<body>
<button onclick="test()" id="myBtn">test</button><br />
<div id="demo"></div>
<script>
var contentArray = ['one', 'two', 'three', 'stop']; // <- declare the array of values you want here
function test() {
let $myBtn = document.getElementById('myBtn');
$myBtn.style.display = 'none';
for(let i = 0; i < contentArray.length; i++){ // <- using 'let' instead of 'var', so a new variable is created on each iteration
setTimeout(function() {
// console.log(i); // <- if you want to see the values, uncomment this line
document.getElementById("demo").innerHTML = contentArray[i];
}, 1000 * (i+1));
};
$myBtn.style.display = 'block';
}
</script>
</body>
</html>
With your self written sleep function, you stopped the Browser from passing the rendering Process until all your code had run.
You have to do something like this:
<!DOCTYPE html>
<html>
<body>
<button onclick="test()" id="myBtn">test</button><br />
<div id="demo"></div>
<script>
function sleep(sleepDurationMS) {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, sleepDurationMS);
});
}
function test() {
let $myBtn = document.getElementById('myBtn');
$myBtn.style.display = 'none';
document.getElementById("demo").innerHTML = 'one';
sleep(1000).then(() => {
document.getElementById("demo").innerHTML = 'two';
sleep(1000).then(() => {
document.getElementById("demo").innerHTML = 'three';
sleep(1000).then(() => {
document.getElementById("demo").innerHTML = 'stop';
$myBtn.style.display = 'block';
});
});
});
}
</script>
</body>
JavaScript is Asynchronous so you will Get 'stop' as output .
Here I am using setTimeout Function() .
You don't need sleep function and here.
<!DOCTYPE html>
<html>
<body>
<button onclick="test()" id="myBtn">test</button><br />
<div id="demo"></div>
<script>
function test() {
let $myBtn = document.getElementById('myBtn');
$myBtn.style.display = 'none';
setTimeout(function () {document.getElementById("demo").innerHTML = 'one';},1000);
setTimeout(function () {document.getElementById("demo").innerHTML = 'two';},2000);
setTimeout(function () {document.getElementById("demo").innerHTML = 'three';},3000);
setTimeout(function () {document.getElementById("demo").innerHTML = 'stop';},4000);
$myBtn.style.display = 'block';
}
</script>
</body>
</html>
Your sleep function is blocking the Event Loop and this line
$myBtn.style.display = 'none';
causes the text to be hidden.
If you want that sleep feature, you should write it in async manner using Promise with setTimeout like this
<!DOCTYPE html>
<html>
<body>
<button onclick="test()" id="myBtn">test</button><br />
<div id="demo"></div>
<script>
const sleep = async (sleepDurationMS) => new Promise((resolve) => setTimeout(() =>
resolve(), sleepDurationMS))
async function test() {
let $myBtn = document.getElementById('myBtn');
document.getElementById("demo").innerHTML = 'one';
await sleep(1000);
document.getElementById("demo").innerHTML = 'two';
await sleep(1000);
document.getElementById("demo").innerHTML = 'three';
await sleep(1000);
document.getElementById("demo").innerHTML = 'stop';
}
</script>
</body>
</html>
Hi this is working when I include the code inside of my html but when I shift it out into myScript.js I get no results, can anyone point out where I've gone wrong with this as I'd like to be able to access this function across several pages?
I've also got the side issue that if I enter something, then delete the input value my filtered array shows all the options, is it possible to set this to a "" value if the input box contains no value?
Thanks
function page_search(){
var pages = [
{name: "Test",url: "Test.html"},
{name: "Rest",url: "Rest.html"},
{name: "Best",url: "Best.html"},
];
let input = document.getElementById('searchbar').value
input=input.toLowerCase();
let x = document.getElementById('searchresults');
var results = [];
for(var i=0;i<pages.length;i++){
if(pages[i].name.toLowerCase().indexOf(input) > -1)
results.push("<a href='"+pages[i].url+"' target='_blank'>"+pages[i].name+"</a>")
}
if(results.length == 0)
$(x).html("No Results Found");
else
$(x).html(results.join("<br>"));
return results;
}
<html>
<head>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="myScript.js"></script>
</head>
<body>
<div class="searchbar">
<input type="text" id="searchbar" onkeyup="page_search()" placeholder="" value="Search.." maxlenght="25" autocomplete="off"/>
</div>
<div id="searchresults"></div>
</body>
</html>
I have added the code, it will definitely resolve your second issue of displaying "" when input box is empty. I am not able to replicate your first issue, can you please tell what is error you are getting ?
function page_search() {
var pages = [{
name: "Test",
url: "Test.html"
},
{
name: "Rest",
url: "Rest.html"
},
{
name: "Best",
url: "Best.html"
},
];
let input = $('#searchbar').val().toLowerCase()
let x = $('#searchresults');
var results = [];
if (input) {
for (var i = 0; i < pages.length; i++) {
if (pages[i].name.toLowerCase().indexOf(input) > -1) {
results.push(`${pages[i].name}`);
}
}
} else {
$(x).html("");
return;
}
if (results.length == 0)
$(x).html("No Results Found");
else
$(x).html(results.join("<br>"));
}
var ele = document.getElementById('searchbar');
ele.addEventListener('keyup', page_search);
<html>
<head>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
</head>
<body>
<div class="searchbarC">
<input type="text" id="searchbar" placeholder="Search.." maxlenght="25" />
</div>
<div id="searchresults"></div>
<script src="myScript.js" type="text/javascript"></script>
</body>
</html>
My question is: How can i use variable ime in Handlebars template(sadrzaj-template)?
My HTML code:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>2 kolokvijum</title>
</head>
<body>
Ime autora: <input id="imeAutora" type="text" value="..."><br><br>
<button id="btnAutor" type="submit" onClick="autorIme()">Prikazi</button><br><br>
<script src="handlebars-v4.0.11.js"></script>
<script id="sadrzaj-template" type="text/x-hanldebars-template">
{{#each knjige}} {{#equal autor ime}}
<h2>{{naslov}}</h2>
<img src="{{slika}}">
<h4>{{brojstrana}} strana</h4>
<h3>Autor: {{autor}}</h3>
<h3>cena: {{cena}}</h3>
{{/equal}} {{/each}}
</script>
<div id="sadrzaj"></div>
<script>
function autorIme() {
var ime = document.querySelector("#imeAutora");
console.log(ime.value);
var ourRequest = new XMLHttpRequest();
ourRequest.open('GET', 'json.json');
ourRequest.onload = function() {
if (ourRequest.status >= 200 && ourRequest.status < 400) {
var data = JSON.parse(ourRequest.responseText);
createHTML(data);
} else {
console.log("We connected to the server, but it returned an error.");
}
};
ourRequest.onerror = function() {
console.log("Connection error");
};
ourRequest.send();
function createHTML(knjigeData) {
var knjigeTemplate = document.querySelector("#sadrzaj-template").innerHTML;
var compiledTemplate = Handlebars.compile(knjigeTemplate);
var ourGeneratedHTML = compiledTemplate(knjigeData);
var knjigeContainer = document.querySelector("#sadrzaj");
knjigeContainer.innerHTML = ourGeneratedHTML;
};
};
</script>
<script>
Handlebars.registerHelper('equal', function(lvalue, rvalue, options) {
if (arguments.length < 3)
throw new Error("Handlebars Helper equal needs 2 parameters");
if (lvalue != rvalue) {
return options.inverse(this);
} else {
return options.fn(this);
}
});
</script>
</body>
</html>
You need to pass it as an argument (as a field in the argument object actually) to the compiled template (which is actually a function). In your case, it is compiledTemplate(). Since you're already passing knjigeData to it, just add your variable as a field to the data object which eventually becomes knjigeData.
var data = JSON.parse(ourRequest.responseText);
data.ime = ime.value;
createHTML(data);
Now you can use it like {{ime}} if you want the variable's direct value in the template. Or you can use it like how you've done, like {{#equal autor ime}}
Tinycc_client is not working. I am trying to generate short URL on the basis of given refer code. It keeps saying me not defined just like the screenshot. There is no error on console.
The output return [object Object]
script.js
arrjson = {
"4397242":"3627884"
}
var longLink = "";
var customParam = "";
function getCustomShortLink() {
var client = new tinycc_client({
api_root_url: "https://tinycc.com/tiny/api/3/",
username: 'mihir',
api_key: '*******-****-****-****-*******'
});
client.set_working_domain("lpts.2.vu");
for (x in arrjson) {
longLink += "http://app.nextbee.com/api/refer/"+arrjson[x];
customParam = x;
client.shorten(longLink, {"custom_hash": customParam}).then(function (result) {
document.getElementById("demo").innerHTML += result +"<br>";
});
}
}
getCustomShortLink()
html:
<div id="demo">
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="js/tinycc_client.js"></script>
<script src="js/script.js"></script>
It keeps saying that a variable is not defined.
I'm working on a tournament bracketing system, and I found a library called "JQuery bracket" which can help a lot. But there are some problems:
I was planning to retrieve team names (and possibly match scores) from a PostgreSQL database and put them on the brackets. However, the data must be in JSON, and the parser is in Javascript. I can't seem to figure out a workaround.
Original code:
<html>
<head>
<title>jQuery Bracket editor</title>
<script type="text/javascript" src="jquery.min.js"></script>
<script type="text/javascript" src="jquery.json-2.2.min.js"></script>
<script type="text/javascript" src="jquery.bracket.min.js"></script>
<link rel="stylesheet" type="text/css" href="jquery.bracket.min.css" />
<style type="text/css">
.empty {
background-color: #FCC;
}
.invalid {
background-color: #FC6;
}
</style>
<script type="text/javascript">
function newFields() {
return 'Bracket name [a-z0-9_] <input type="text" id="bracketId" class="empty" /><input type="submit" value="Create" disabled />'
}
function newBracket() {
$('#editor').empty().bracket({
save: function(data){
$('pre').text(jQuery.toJSON(data))
}
})
$('#fields').html(newFields())
}
function refreshSelect(pick) {
var select = $('#bracketSelect').empty()
$('<option value="">New bracket</option>').appendTo(select)
$.getJSON('rest.php?op=list', function(data) {
$.each(data, function(i, e) {
select.append('<option value="'+e+'">'+e+'</option>')
})
}).success(function() {
if (pick) {
select.find(':selected').removeAttr('seleceted')
select.find('option[value="'+pick+'"]').attr('selected','selected')
select.change()
}
})
}
function hash() {
var bracket = null
var parts = window.location.href.replace(/#!([a-z0-9_]+)$/gi, function(m, match) {
bracket = match
});
return bracket;
}
$(document).ready(newBracket)
$(document).ready(function() {
newBracket()
$('input#bracketId').live('keyup', function() {
var input = $(this)
var submit = $('input[value="Create"]')
if (input.val().length === 0) {
input.removeClass('invalid')
input.addClass('empty')
submit.attr('disabled', 'disabled')
}
else if (input.val().match(/[^0-9a-z_]+/)) {
input.addClass('invalid')
submit.attr('disabled', 'disabled')
}
else {
input.removeClass('empty invalid')
submit.removeAttr('disabled')
}
})
$('input[value="Create"]').live('click', function() {
$(this).attr('disabled', 'disabled')
var input = $('input#bracketId')
var bracketId = input.val()
if (bracketId.match(/[^0-9a-z_]+/))
return
var data = $('#editor').bracket('data')
var json = jQuery.toJSON(data)
$.getJSON('rest.php?op=set&id='+bracketId+'&data='+json)
.success(function() {
refreshSelect(bracketId)
})
})
refreshSelect(hash())
$('#bracketSelect').change(function() {
var value = $(this).val()
location.hash = '#!'+value
if (!value) {
newBracket()
return
}
$('#fields').empty()
$.getJSON('rest.php?op=get&id='+value, function(data) {
$('#editor').empty().bracket({
init: data,
save: function(data){
var json = jQuery.toJSON(data)
$('pre').text(jQuery.toJSON(data))
$.getJSON('rest.php?op=set&id='+value+'&data='+json)
}
})
}).error(function() { })
})
})
</script>
</head>
<body>
Pick bracket: <select id="bracketSelect"></select>
<div id="main">
<h1>jQuery Bracket editor</h1>
<div id="editor"></div>
<div style="clear: both;" id="fields"></div>
<pre></pre>
</div>
</body>
</html>
After the data is retrieved, upon display, you are going to want to add disabled to the html input element. For instance:
<input type="text" id="bracketId" class="empty" disabled>
This will render your text field uneditable.
If you are looking to do this as people are filling out their brackets, I would suggest you either add a <button> after each bracket or fire a jquery event with the mouseout() listener that adds the disabled attribute to your input fields.