Out of order execution - no Ajax involved - javascript

I'm experiencing some out-of-order execution in javascript, and it's not related to any ajax calls or such. The main bulk of code is a possibly slow DOM manipulation, followed by a method call. In every single case, the function call is being fired before the DOM manipulation finishes.
Here is my code:
$(this).parents('dd').siblings('dd').each(function(){
var filter_name = $(this).attr('data-filter-type');
if ($(this).hasClass('selected')) {
$(this).removeClass('selected', function(){
if ($(this).hasClass('date')) {
$('form[name="filter-form"] input[name="from"]').remove();
$('form[name="filter-form"] input[name="to"]').remove();
} else {
$('form[name="filter-form"] input[name="' + filter_name + '"]').remove();
}
console.log('removed');
});
}
});
var filter_type = $(this).parents('dd').attr('data-filter-type');
var filter_input = 'form[name="filter-form"] input[name="' + filter_type + '"]';
if ($(filter_input).length > 0) {
$(filter_input).val(filter_value);
} else {
$('form[name="filter-form"]').append('<input type="hidden" name="' + filter_type + '" value="true">');
}
doStuff($(this));
In my console, I am seeing the result of doStuff before I am seeing the debug.
Anybody have any ideas how to make the function call wait?

There is an overload of .removeClass that takes a function, but it's not a callback function (that operation is completed synchronously). I'd recommend removing the function argument to removeClass and placing the code immediately after that call:
$(this).parents('dd').siblings('dd').each(function(){
var filter_name = $(this).attr('data-filter-type');
if ($(this).hasClass('selected')) {
$(this).removeClass('selected');
if ($(this).hasClass('date')) {
$('form[name="filter-form"] input[name="from"]').remove();
$('form[name="filter-form"] input[name="to"]').remove();
} else {
$('form[name="filter-form"] input[name="' + filter_name + '"]').remove();
}
console.log('removed');
}
});
var filter_type = $(this).parents('dd').attr('data-filter-type');
var filter_input = 'form[name="filter-form"] input[name="' + filter_type + '"]';
if ($(filter_input).length > 0) {
$(filter_input).val(filter_value);
} else {
$('form[name="filter-form"]').append('<input type="hidden" name="' + filter_type + '" value="true">');
}
doStuff($(this));

I would wrap all your DOM manipulation into a function accepting callback as a parameter:
var manipulateDOM = function(callback){
...
if(typeof callback === "function")callback();
}
and call it like this:
manipulateDOM(function(){
doStuff(param);//I am not sure about the context so you need to figure out the value of param
})

Related

addEventListener does not work on "textarea" or "body"

While I'm writing a detect() function for objects, which the user would get a console.log message when they either mouseover or click an element. I got stuck at adding onclick and onmouseover for two elements (or more, but I can't see) : textarea, and body.
The element that works well is label. When I click it or mouseover it, I get a console.log message.
Here is my Code :
function $count(string){
count = count + 1;
if(count <= 9999){
if(count <= 999){
if(count <= 99){
if(count <= 9){
$log("0000" + String(count) + " : " + string);
}else{
$log("000" + String(count) + " : " + string);
}
}else{
$log("00" + String(count) + " : " + string);
}
}else{
$log("0" + String(count) + " : " + string);
}
}else{
$log(String(count) + " : " + string);
}
}
function $detect(obj, type, script){
if(obj.addEventListener){
obj.addEventListener(type, script, false);
}else{
if(window.attachEvent){
window.attachEvent('on' + type, script);
}else{
console.log("Neither window.addEventListener nor window.attachEvent is working.");
}
}
}
Object.prototype.listen = function(){
if(typeof(this) == "object"){
if(this.constructor == Array){
for(var i = 0; i < this.length; i++){
$detect(this[i], 'click', $count(this[i] + " EVENT : onclick"));
$detect(this[i], 'mouseover', $count(this[i] + " EVENT : onmouseover"));
}
}else{
if(isElement(this)){
$detect(this, 'click', $count(this + " EVENT : onclick"));
$detect(this, 'mouseover', $count(this + " EVENT : onmouseover"));
}else{
console.log("ERROR : this.constructor is not 'Object' or 'Array'.");
return null;
}
}
}else{
console.log("ERROR : typeof(this) is not 'Object'.");
return null;
}
return this;
};
When I perform this action :
document.getElementsByTagName("textarea")[0].listen();
I was willing to receive a console.log when I click it or move the mouse above it. However, when I click the textarea, it doesn't produce the console.log.
I guess the problem is at the detect() or listen().
However I did not want to use element.onclick = function(){}; because it will cover the original function, and I cannot add another onclick to it, so using addEventListener should be better.
Where, or which part, should I change my code?
The problem is at the listen() part :
//...
$detect(this, "click", $count("blahblahblah");
I need to wrap $count("blahblahblah") in function(){ }.
That's the problem. That's all.

Submit can't see return false;

I know that I must add return false; to function called in onsubmit (I added it to function and onsubmit="" in HTML). But it doesn't work... Page freshing after pressing Enter or clicking submit button.
JavaScript generated forms:
AnswersHTML += "<div><form onsubmit='cheking(this," + a + "," + b + ", this); return false;'><input class='put' type='text' size='40'><input type='submit' value='Проверить'></form>
and function
function checking(answer, nums, numq, what) {
var usr = answer;
if (isNaN(answer)) {
usr = answer.value;
if (answer.value.length == "") {
usr = "Вы ничего не ввели!"
}
else {
answer = answer.value.toLowerCase();
}
}
if (answer == correct[nums][numq]) {
$(what).parent().parent().append("<br><span class='right'>Ответ: " + usr + "<br>Правильно!</span>").slideDown();
$(what).parent().parent().find(".put").remove();
$("#board").animate({ backgroundColor: '#2ecc71'});
$("#board").animate({ backgroundColor: '#f1c40f'}, 1000);
if (isPhysics[nums][numq]) {
physics++;
$("#presult").text(physics);
}
else {
life++;
$("#lresult").text(life);
}
}
else {
$("#board").animate({ backgroundColor: '#e74c3c'});
$("#board").animate({ backgroundColor: '#f1c40f'}, 1000);
var desciptionToWrong = "<br>" + description[nums][numq];
if (description[nums][numq] == false) {
desciptionToWrong = ""
}
$(what).parent().parent().append("<br><span class='wrong'>Ответ: " + usr + "<br>Неправильно!" + desciptionToWrong + "</span>")
$(what).parent().parent().find(".put").remove();
$(what).remove(".pressenter");
}
return false;
}
Sorry for my possibly disgusting code
Here's the full page https://rawgithub.com/ruslankh/Kurchatovy/master/index.html
Problem in function because when I'm replaced function to just alert, it was fine
Its probably due to type. You have wrong spelling in function call that would cause an error and call goes to server.
Change
'cheking(this," + a + "," + b + ", this);
To
'checking(this," + a + "," + b + ", this);
Your inline onclick has a typo, you left one of the c's out of the function name. This error likely terminates the click handler before it gets as far as the return statement.
Try this,
AnswersHTML += "<div><form data-a='"+a+"' data-b='"+b+"' class='myform'><input class='put... "
SCRIPT
$(function(){
$(document).on('submit','form.myform',function(){
checking(this, $(this).data('a'), $(this).data('b'), this);
return false;
});
});

jquery click does not work

Anyone have an idea why my jQuery click won't work?
It's attached to a hyperlink.
jQuery(function ($) {
$(".delete").click(function(e) {
alert("Hello");
});
var socket = io.connect();
var $messageForm = $('#sendmessage');
var $messageTitle = $('#title');
var $messageBox = $('#message');
var $chat = $('#chat');
$messageForm.click(function (e) {
if ($.trim($("#title").val()).length === 0) {
alert('You must provide valid input');
$messageTitle.val('');
$messageBox.val('');
return false;
}
if ($.trim($("#message").val()).length === 0) {
alert('You must provide valid input');
$messageTitle.val('');
$messageBox.val('');
return false;
} else {
e.preventDefault();
socket.emit('send message',
'<b>' + $messageTitle.val() + '</b>' + ' - '
+ $messageBox.val() + ' ' + '[' +
'<a class="delete" href="#">Delete</a>' + ']');
$messageTitle.val('');
$messageBox.val('');
}
});
socket.on('new message', function (data) {
$chat.prepend(data + "<br/>");
});
});
Since the delete links are dynamically generated, you need to use event delegation:
$('#chat').on('click', '.delete', function(e) {
alert("Hello");
});
Hello try to modify your jquery initialization like this:
(function($){ }(jQuery)
If your script still doesn't fire the click event, check if $messageForm exists, using console.log($messageForm). You can modify var $messageForm in var messageForm from what I seen that variable does not need a scope so wide. I hope this could help you

Avoiding use of eval() to dynamically build event handlers

I'm struggling with managing dynamically built event handlers in javascript.
In several places, I build forms, or controls in which specific events (mainly mouseovers, mouse-outs, clicks) need to be handled.
The trick is that in a significant number of cases, the event handler itself needs to incorporate data that is either generated by, or is passed-into the function that is building the form or control.
As such, I've been using "eval()" to construct the events and incorporate the appropriate data, and this has worked somewhat well.
The problem is I keep seeing/hearing things like "You should never use eval()!" as well as a couple of increasingly ugly implementations where my dynamically-built event handler needs to dynamically build other event handlers and the nested evals are pretty obtuse (to put it mildly).
So I'm here, asking if someone can please show me the better way (native javascript only please, I'm not implementing any third-party libraries!).
Here's a crude example to illustrate what I'm talking about:
function CreateInput(controlName,type,activeStyle,dormantStyle,whenClicked)
{
var inp = document.createElement('input');
inp.id = controlName;
inp.type = type;
inp.style.cssText = dormantStyle;
eval("inp.onfocus = function() { this.style.cssText = '" + activeStyle + "'; }");
eval("inp.onblur = function() { this.style.cssText = '" + dormantStyle + "'; }");
eval("inp.onclick = function() { " + whenClicked + "; }");
return inp;
}
This function obviously would let me easily create lots of different INPUT tags and specify a number of unique attributes and event actions, with just a single function call for each. Again, this is an extremely simplified example, just to demonstrate what I'm talking about, in some cases with the project I'm on currently, the events can incorporate dozens of lines, they might even make dynamic ajax calls based on a passed parameter or other dynamically generated data. In more extreme cases I construct tables, whose individual rows/columns/cells may need to process events based on the dynamically generated contents of the handler, or the handler's handler.
Initially, I had built functions like the above as so:
function CreateInput(controlName,type,activeStyle,dormantStyle,whenClicked)
{
var inp = document.createElement('input');
inp.id = controlName;
inp.type = type;
inp.style.cssText = dormantStyle;
inp.onfocus = function() { this.style.cssText = activeStyle; };
inp.onblur = function() { this.style.cssText = dormantStyle; };
eval("inp.onclick = function() { " + whenClicked + "; }");
return inp;
}
...but I found that whatever the last assigned value had been for "activeStyle", and "dormantStyle" became the value used by all of the handlers thusly created (instead of each retaining its own unique set of styles, for example). That is what lead me to using eval() to "lock-in" the values of the variables when the function was created, but this has lead me into nightmares such as the following:
(This is a sample of one dynamically-built event-handler that I'm currently working on and which uses a nested eval() function):
eval("input.onkeyup = function() { " +
"InputParse(this,'ucwords'); " +
"var tId = '" + myName + This.nodeName + "SearchTable" + uidNo + "'; " +
"var table = document.getElementById(tId); " +
"if (this.value.length>2) { " +
"var val = (this.value.indexOf(',') >=0 ) ? this.value.substr(0,this.value.indexOf(',')) : this.value; " +
"var search = Global.LoadData('?fn=citySearch&limit=3&value=' + encodeURI(val)); " +
"if (table) { " +
"while (table.rows.length>0) { table.deleteRow(0); } " +
"table.style.display='block'; " +
"} else { " +
"table = document.createElement('table'); " +
"table.id = tId; " +
"ApplyStyleString('" + baseStyle + ";position=absolute;top=20px;left=0px;display=block;border=1px solid black;backgroundColor=rgba(224,224,224,0.90);zIndex=1000;',table); " +
"var div = document.getElementById('" + divName + "'); " +
"if (div) { div.appendChild(table); } " +
"} " +
"if (search.rowCount()>0) { " +
"for (var i=0; i<search.rowCount(); i++) { " +
"var tr = document.createElement('tr'); " +
"tr.id = 'SearchRow' + i + '" + uidNo + "'; " +
"tr.onmouseover = function() { ApplyStyleString('cursor=pointer;color=yellow;backgroundColor=rgba(40,40,40,0.90);',this); }; " +
"tr.onmouseout = function() { ApplyStyleString('cursor=default;color=black;backgroundColor=rgba(224,224,224,0.90);',this); }; " +
"eval(\"tr.onclick = function() { " +
"function set(id,value) { " +
"var o = document.getElementById(id); " +
"if (o && o.value) { o.value = value; } else { alert('Could not find ' + id); } " +
"} " +
"set('" + myName + This.nodeName + "CityId" + uidNo + "','\" + search.id(i)+ \"'); " +
"set('" + myName + This.nodeName + "ProvId" + uidNo + "','\" + search.provId(i)+ \"'); " +
"set('" + myName + This.nodeName + "CountryId" + uidNo + "','\" + search.countryId(i) + \"'); " +
"set('" + input.id + "','\" + search.name(i)+ \"'); " +
"}\"); " +
"var td = document.createElement('td'); " +
"var re = new RegExp('('+val+')', 'gi'); " +
"td.innerHTML = search.name(i).replace(re,'<span style=\"font-weight:bold;\">$1</span>') + ', ' + search.provinceName(i) + ', ' + search.countryName(i); " +
"tr.appendChild(td); " +
"table.appendChild(tr); " +
"} " +
"} else { " +
"var tr = document.createElement('tr'); " +
"var td = document.createElement('td'); " +
"td.innerHTML = 'No matches found...';" +
"tr.appendChild(td); " +
"table.appendChild(tr); " +
"} " +
"} else { " +
"if (table) table.style.display = 'none'; " +
"} " +
"} ");
Currently, I'm having problems getting the nested eval() to bind the ".onclick" event to the table-row, and, as you can see, figuring out the code is getting pretty hairy (debugging too, for all the known reasons)... So, I'd really appreciate it if someone could point me in the direction of being able to accomplish these same goals while avoiding the dreaded use of the "eval()" statement!
Thanks!
And this, among many other reasons, is why you should never use eval. (What if those values you're "baking" in contain quotes? Oops.) And more generally, try to figure out why the right way doesn't work instead of beating the wrong way into submission. :)
Also, it's not a good idea to assign to on* attributes; they don't scale particularly well. The new hotness is to use element.addEventListener, which allows multiple handlers for the same event. (For older IE, you need attachEvent. This kind of IE nonsense is the primary reason we started using libraries like jQuery in the first place.)
The code you pasted, which uses closures, should work just fine. The part you didn't include is that you must have been doing this in a loop.
JavaScript variables are function-scoped, not block-scoped, so when you do this:
var callbacks = [];
for (var i = 0; i < 10; i++) {
callbacks.push(function() { alert(i) });
}
for (var index in callbacks) {
callbacks[index]();
}
...you'll get 9 ten times. Each run of the loop creates a function that closes over the same variable i, and then on the next iteration, the value of i changes.
What you want is a factory function: either inline or independently.
for (var i = 0; i < 10; i++) {
(function(i) {
callbacks.push(function() { alert(i) });
})(i);
}
This creates a separate function and executes it immediately. The i inside the function is a different variable each time (because it's scoped to the function), so this effectively captures the value of the outer i and ignores any further changes to it.
You can break this out explicitly:
function make_function(i) {
return function() { alert(i) };
}
// ...
for (var i = 0; i < 10; i++) {
callbacks.push(make_function(i));
}
Exactly the same thing, but with the function defined independently rather than inline.
This has come up before, but it's a little tricky to spot what's causing the surprise.
Even your "right way" code still uses strings for the contents of functions or styles. I would pass that click behavior as a function, and I would use classes instead of embedding chunks of CSS in my JavaScript. (I doubt I'd add an ID to every single input, either.)
So I'd write something like this:
function create_input(id, type, active_class, onclick) {
var inp = document.createElement('input');
inp.id = id;
inp.type = type;
inp.addEventListener('focus', function() {
this.className = active_class;
});
inp.addEventListener('blur', function() {
this.className = '';
});
inp.addEventListener('click', onclick);
return inp;
}
// Called as:
var textbox = create_input('unique-id', 'text', 'focused', function() { alert("hi!") });
This has some problems still: it doesn't work in older IE, and it will remove any class names you try to add later. Which is why jQuery is popular:
function create_input(id, type, active_class, onclick) {
var inp = $('<input>', { id: id, type: type });
inp.on('focus', function() {
$(this).addClass(active_class);
});
inp.on('blur', function() {
$(this).removeClass(active_class);
});
inp.on('click', onclick);
return inp;
}
Of course, even most of this is unnecessary—you can just use the :focus CSS selector, and not bother with focus and blur events at all!
You don't need eval to "lock in" a value.
It's not clear from the posted code why you're seeing the values change after CreateInput returns. If CreateInput implemented a loop, then I would expect the last values assigned to activeStyle and dormantStyle to be used. But even calling CreateInput from a loop will not cause the misbehavior you describe, contrary to the commenter.
Anyway, the solution to this kind of stale data is to use a closure. JavaScript local variables are all bound to the function call scope, no matter if they're declared deep inside the function or in a loop. So you add a function call to force new variables to be created.
function CreateInput(controlName,type,activeStyle,dormantStyle,whenClicked)
{
while ( something ) {
activeStyle += "blah"; // modify local vars
function ( activeStyle, dormantStyle ) { // make copies of local vars
var inp = document.createElement('input');
inp.id = controlName;
inp.type = type;
inp.style.cssText = dormantStyle;
inp.onfocus = function() { this.style.cssText = activeStyle; };
inp.onblur = function() { this.style.cssText = dormantStyle; };
inp.onclick = whenClicked;
}( activeStyle, dormantStyle ); // specify values for copies
}
return inp;
}

How to avoid timeout while reading rows from a query (Phonegap + Javascript)

I am trying to insert around 58000 rows of a query inside a string. But after the row around 8000 I get a timeout error.
I've already tried to use SetTimeout funcions but it was of no use.
Check the code that I am working on:
function onQuerySuccess(tx, results) {
console.log("Entering onQuerySuccess");
if(results.rows) {
console.log("Rows: " + results.rows.length);
var len = results.rows.length;
if(len > 0) {
store_html(results, 0);
console.log("Finished Reading Rows: " + len);
saveNotes();
console.log("Finished Saving Notes");
} else {
//This should never happen
console.log("No rows.");
}
} else {
alert("No records match selection criteria.");
}
console.log("Leaving openView");
function store_html(results, rows_complete){
rows_complete=store_html_input(results, rows_complete);
console.log("Returning row:" + rows_complete);
if (rows_complete<results.rows.length)
{
setTimeout(store_html(results, rows_complete), 50);
}
}
function store_html_input(results, rows_complete){
for(var i = rows_complete; i < rows_complete+100; i++) {
gpsTextFile = gpsTextFile + results.rows.item(i).section + ' ' + results.rows.item(i).timestamp + ' ' + results.rows.item(i).latitude + ' ' +
results.rows.item(i).longitude + ' ' + results.rows.item(i).acx + ' ' + results.rows.item(i).acy + ' ' +
results.rows.item(i).acz + ' ' + results.rows.item(i).speed;
gpsTextFile = gpsTextFile + "\n\r";
}
return i;
}
So.. I get that "Javascript execution exceeded timeout".
Thank you for any of your help!
Best Regards.
You need to change your setTimeout() to NOT execute the function immediately. Change from this:
setTimeout(store_html(results, rows_complete), 50);
to this:
setTimeout(function() {store_html(results, rows_complete)}, 50);
As you had it before, it was immediately executing store_html(results, rows_complete) and passing the return value from that to `setTimeout() which was not delaying anything. This is a common mistake (2nd one of these problems I've answered today).

Categories