Weird behaviour of part of jQuery code - javascript

Ok, lets begin.
this code works fine in console, doing what is required from it.
$('#tasks-table input').parent().html($('#tasks-table input').siblings('textarea').val())
but in such a call it doesn't even execute. console is silent. all other function code is executed properly
updateTask.call(this, 131)
any ideas?
update
this
$('#tasks-table input').parent().html($('#tasks-table input').siblings('textarea').val())
is part of this
updateTask.call(this, 131)
and gets ignored in updateTask
this instruction works from console just fine, but does not when it's a part of updateTask
html is a 4-row php-generated table with a row typical to this
<tr>
<td><?php echo $task->id ?></td>
<td onclick='projects.editTask.call(this, <?php echo $task->id ?>)'><?php echo $task->text ?> </td>
<td><?php echo $task->status ?></td>
<td><?php echo $task->controls ?></td>
</tr>
editTask is a simple replace function(contents of td turned into textarea)
if(block || !($(this).attr('incomplete'))) return; //only one is allowed
block = true;
if(!$(this).children('textarea').length) {
$(this).html('<textarea style="width: 100%; height: 110px;">'+$(this).html()+'</textarea><br/><input type="button" value="Сохранить" onclick="projects.updateTask.call(this, '+parseInt(id)+')" />');
}
the goal of updateTask is to replace anything within td back to contents of textarea and send an update request back to server using $.post
contents of updateTask
var text = $(this).siblings('textarea').val();
//does not work only here, but works anywhere else
$('#tasks-table input').parent().text($('#tasks-table input').siblings('textarea').val())
$.post('/projects/ajax/', {
ajax: true,
command: 'update',
id: id,
text: text }
, function() {
block = false;
});
When I do it more complicated way, it works. more simple way like this one:
$(this).parent().html(text)
didn't work too

Edit:
Revisiting your question, it looks like the point I focused on may not be exactly the problem. I won't delete it because it is a valid issue with the code you posted. The issue with the textarea value not being queried or modified is most likely because you're adding the textarea after the DOM has loaded. You should be able to use a jquery.live() event on your table or the delegate event at the end of this post. Both are valid for any current or future DOM elements. These are a life saver when performing any DOM manipulation or ajax-based HTML updates. This would also explain why you're able to perform the operation in the console and not in the code.
Original answer:
I'm not sure if this will help fully, but I think what is preventing your code from executing is:
if(block || !($(this).attr('incomplete'))) return;
When your table cells don't have that attribute, you're actually executing:
if( block || !(undefined) ) return;
Which will always be true because !(undefined) is true. You most likely want to be forcing a boolean value:
if( block || !!(undefined) ) return;
This is fine if you are only expecting undefined and any text value. A better way to modify this condition is to explicitly check the value you're expected (and I'm assuming you expect to be "true"):
if(block || ($(this).attr('incomplete') === "true") ) return;
Suggestion:
One suggestion I would make is instead of inlining the onclick function, use HTML5 data-* attributes to store your data and perform the code in one place. This helps keep JavaScript in a single place and doesn't mix it throughout the HTML.
For example, you can generate a data-id attribute for each table cell and mark your table row editable with data-edit-cell="true"
<table>
<tr data-edit-cell="true">
<td data-id="1">cell one</td>
<td data-id="2">cell two</td>
<td data-id="3">cell three</td>
<td data-id="4">cell four</td>
</tr>
</table>​
Then, a second suggestion, instead of binding a click event to each cell , you can bind an event to the row using jquery.delegate(), which watches for bubbling events (click) of a child defined by a selector (td):
var block = false;
$('tr[data-edit-cell=true]').delegate('td', 'click', function() {
if (block || $(this).attr('incomplete') === "true" ) return; //only one is allowed
block = true;
console.log('clicked: ' + $(this).attr('data-id'));
});​
I've created a jsfiddle here so you can check this code out.

Related

Javascript function in PHP generated Table only works in Row 1

I have a PHP generated table that shows x number of rows based upon the number of product that is expected for that particular order. The idea is that a user can input an order number and the script will validate the input text against what is expected a return a Pass or Fail status.
This works for row 1, but after that it will not validate any of the other rows.
Reading about I am pretty sure it is due to duplicate id's, so i created a auto increment row field in the database to serve as the id. However it is beyond my skill to set the id as the row number and then validate against the order number.
Table Code:
if (sqlsrv_num_rows($getres) > 0) {
echo '<table cellpadding="0" cellspacing="0" class="db-table">';
echo '<tr><th>Row</th><th>Works Order</th><th>Scan</th></tr>';
echo 'T-Clip Scan: <br/>';
echo '<br/>';
while ($add_info = sqlsrv_fetch_array($getres)){
$row = ($add_info['row']);
$worksorder = ($add_info['id']);
print ("<tr> <td/> $row <td/> $worksorder
<td/>
<input id= 'worksorder' value='' />
<p id='TR'></p>
<script>
document.getElementById('worksorder').onblur = function() {myFunction()};
function myFunction() {
var worksorder, test;
worksorder = document.getElementById('worksorder').value;
test = (worksorder == '".$_SESSION["worksorder"]."') ? 'PASS':'FAIL';
document.getElementById('TR').innerHTML = test;
}
</script>
</tr>");
}
Screenshot of issue
Any help it getting it to work for all rows is appreciated,
Thanks
The problem is likely because you're using creating <input id='worksorder'> and using document.getElementById('worksorder') to reference it within a loop.
HTML IDs are supposed to be unique on the page, so getElementById will only ever return a single element, that being the first element in the code that has the named ID.
If you want to do this in a loop, then you need to make sure that the ID for each element you generate is unique. Typically this would be done by adding the row ID to the generated HTML ID.
eg <input id= 'worksorder_{$add_info['id']}'> or similar, and then reference that in the JS code.
Alternatively, use an HTML class instead of an ID, since class names do not need to be unique, and then write your JS code to use document.querySelector() to query by classname instead of getElemenetById(). If you do it that way, then the JS code doesn't need to be in the loop, as it will be run once and apply to all the matching elements in one go.
You don't have to use a separate script for every row. You can create a single script to hande all the rows.
Firstly, provide ids to the <tr> tag and the <input> tag to receive the values and add an onblur listener. You can use the autoincrementing column value for the ids that you get from database.
e.g.
echo "<tr> <td/> $row<td/> $worksorder <td/>
<input id='".$id."' value='' /><p id='TR_'".$id."' onblur='myFunction(this)'></p>";
Also, add the onblur listener and pass the object to myFunction().
Then add a single myFunction() at the end.
<script>
function myFunction(param) {
var worksorder, test, id;
worksorder = param.value;
id = param.id;
console.log("order " +worksorder + ' '+id);
test = (worksorder == '".$_SESSION["worksorder"]."') ? 'PASS':'FAIL';
document.getElementById('TR_'+id).innerHTML = test;
};
</script>
This will handle all the rows for you. I hope it helps you.
Working JS fiddle
You are setting all the input fields with the same id. Html cannot handle same ids. You should either use incremental ids or use classes instead of ids, and adapt the JS function to use parameters, so not to have a JS function in every row. One function is enough and you can call it with the row number as parameter

Javascript document.getElementById not printing to element - strange issue

This context of this issue is it is a set of measurements needed for review by the user. I'm reviewing some of my code and changing it to bootstrap's classes. I have a php function - reviewSlide(); that prints out the necessary html. From there it calls javascript script - reviewGetMeasurement(); function to grab the measurements the user inputs.
The event that triggers the javascript function is -
<button onclick="slideforms_step17(event);reviewGetMeasurement();" class="mdk_slidesforms_btn"><?= (!empty($button_text)) ? $button_text : 'Next' ?></button>
The function runs then moves into the below function reviewSlide() however if I do have the following code everything runs perfectly and the measurements are gathered and printed to the page.
function reviewSlide() {
<table>
<tr width="33%">
<table class="pktsq_measurement_table_my_account_table">
<tr>
<td colspan="2" class="pktsq_measurement_table_my_account_td pktsq_measurement_table_my_account_td_title">
Personal
</td>
</tr>
<tr>
<td class="pktsq_measurement_table_my_account_td">
Height
</td>
<td class="pktsq_measurement_table_my_account_td pktsq_measurement_table_my_account_td_measurement" id="userHeight"></td>
</tr>
</table>
</tr>
</table>
...
}
If I remove the above table nothing works.
I have tried
function reviewSlide() {
<p id="userHeight"></p>
<p id="userWeight"></p>
...
}
at the top of my function (which is at this stage above the table) and nothing comes up.
I've looked at other people's issues, some of the main problems were the page wasn't loading and you would have to wait for the page to load as the javascript would not see any html. But I believe I have a bit of a quirky problem here regarding the table code
Any help would be grateful. Thank you.
UPDATE and CLARIFICATION (same as comments):
I didn't write this so bear with me but yes reviewSlide is in php. To clarify I have a function stepxx() in a shortcodes.php file. Each step represents a slide and reviewSlide() also represents another slide, in this case the review slide with all the measurements. All this is written in html/php. In stepxx() there is a button tag that calls a javascript function slideforms_stepxx(event);. When each step is complete 1-xx, slidesforms_stepxx(event) is called to check the input.
This is where we come to the review slide. On the slide before the review slide we go into slideforms_step17 check the input and submit the values to the server via jquery. Then we enter the reviewGetMeasurement() function as seen above by <button onclick="slideforms_step17();reviewGetMeasurement();....>. ReviewGetmeasurement's task is to print out the values and this is where my problem lies.
Inside reviewGetMeasurements(), if I print out the value for a variable let's say the height via console.log(height), it'll work and I can see the value in the console. But once if I try to print the value our by doing
document.getElementById("userHeight").innerHTML = height + "cm";
I get null. But I know there is a value height and it's not null. HOWEVER if I put the <table> code from above it will work and the values are printed out. If I use the <p> tags with the right id it won't work.
You can't just blindly put HTML inside of a Javascript or PHP function. What's in those functions must be the appropriate type of code, not HTML.
If it's Javascript, then using Javascript you can create DOM elements and then insert them into the page.
If it's PHP running at page render time on the server, then you can use PHP's echo to insert content into the page.
If it's PHP running in an Ajax call, then you can also use echo to construct a response to the Ajax call.
Here's an example of inserting content into the page from a user event using a Javascript function:
function insertContent(html) {
var div = document.createElement("div");
div.innerHTML = html;
document.body.appendChild(div);
}
document.getElementById("run").addEventListener("click", function() {
insertContent(document.getElementById("newContent").value);
});
<input id="newContent" value="Some Text"> <button id="run">Insert</button>

simple jquery each loop making browser hang

so i'm calling a function in jquery that's looping over a table and determining whether to hide a row based on a hidden form element within each row.
when i run this script, toggling the rows either way, the browser hangs for at least 5 seconds even though there are fewer than 100 rows.
the js looks like this:
$('input.vv').each(function(index) {
var chk = $(this).val();
if (chk == "0") $(this).parents("tr").slideToggle(function() {
tableRows();
});
});
and a sample row from the html looks like this:
<tr class="sortable part item" id="row803">
<td class="col-check">Interior Fixed Dome Camera Surface Mounted<br />(Panasonic Part No. WV-CW484AS/29)
<input type="hidden" class="vv" value="50" id="v803" /></td>
<td class="col-equip cen" id="q803">70</td>
<td class="col-equip cen" id="s803">50</td>
<td class="col-equip cen"><div id="bom803|092311-001|15" />50</div></td>
<td class="col-equip cen" id="b803"><span class="shipped">20</span></td>
</tr>
the line of jquery.js that firebug refers to is 8449
return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed;
i'm stuck (can't link to the live site sorry). firebug may give me a way out of this but i'm unsure how to use it well enough. thoughts anyone? thanks!
$('input.vv') creates a loop which goes through all input elements, and checks whether they're a part of the vv class.
.parents("tr") loops through all parent nodes, and selects only the <tr> elements.
Then, you call .slideToggle, which creates an effect which requires a significant amount of computing power (at small intervals, CSS style adjustments through JQuery, CSS style parsing by browser). likely to be the main cause
Finally, you're calling tableRows();, which you haven't defined yet.
These operations, on "fewer than 100 rows" requires much computing power.
Try being a little more specific:
$('input.vv').each(function(index) {
if ($(this).value == "0") $(this).parent().parent().slideToggle(function() {
tableRows();
});
});

Optimizing jquery selector for Chrome

I have the following jquery code to loop over 525 (I know, alot!) checkboxes:
var elements = $("#profile-list table tr input[type=checkbox].email-checkout:not(:checked)");
$.each(elements, function(i) {
$(elements[i]).attr('checked', 'checked');
});
UPDATE The html looks like this:
<table>
<tr>
<th>Name</th>
<th>Title</th>
<th>E-mail</th>
<th>Telephone</th>
<th id="email_check"><img src="check_black.png"/></th>
</tr>
<?php foreach ($this->profiles as $profile): ?>
<tr>
<?php echo $this->presentProfile($profile, 'list') ?>
</tr>
<?php endforeach; ?>
This basically loops over all profiles in the database and creates a table row for each profile, where the last table data includes a checkbox, which one can select to send email to. If the user clicks the table header with the id of "email_check" then the javascript code should kick in, and that's where Chrome fails.
I attach the event with the following code:
$("#email_check img").live('click', function() {
//my code
}
When I run this code in Firefox (mac), it goes smoothly but when I run it in Chrome (mac) it takes forever and ends up giving me the window where chrome offers me the option of killing the window, so basically it never completes this loop.
I've been trying to optimize this selector as much as I can, and since jquery 1.3, I understand that they switched from left to right to right to left selector, which basically means that I should try to make my right most selector as specific as I can. Can it be any more specific than I currently have?
Or is it the loop that just takes so long? I have tried switching from $.each to just a regular for() without a positive result.
Any tips or ideas how I can fix this?
Ingiber
I really don't think this is a selector issue at all.
Your selector is a valid selector for querySelectorAll, which means it will be extremely fast.
I tested the exact selector in Chrome on Mac against a table with 250 rows, and the result was instantaneous.
I'd guess that there's something else going on.
Try removing the table tr part of the selector. It isn't adding anything.
Try this:
// console.time("test");
var elements = $("#profile-list input[type=checkbox].email-checkout").get();
var len = elements.length;
for (var i = 0; i < len; i += 1) {
elements[i].checked = true;
}
// console.timeEnd("test");
(So, first we select all check-boxes that are of the class "email-checkout" and are inside the #profile-list element. Then we just set their checked property to true. I assume, this is as fast as it can be.)
You could always give each check box a select/deselect event that will add/remove a class from the checkbox, then use the class as the selector.
You can use a .each() on the set to use the elements directly, like this:
$("#profile-list table tr input[type=checkbox].email-checkout").each(function() {
this.checked = true;
});
Also note the removal of :not(:checked) above...if you're going to check them all, that selector is more expensive that actually checking them anyway. More importantly is that this.checked = true; is tremendously cheaper than $(elements[i]).attr('checked', 'checked'); which happens every time.
Did you profile this? What is taking too long, getting the elements or looping over them? The only way to really speed up code is to profile and fix the slow parts.
FWIW, I would try
var elements = $("#profile-list").find("input[type=checkbox].email-checkout").get();
...
and see what happens.
Add an onlclick on the checkbox
$("#profile-list input[type=checkbox].email-checkout").click(function() {
var obj = $(this);
obj.hasClass("checked") ? obj.removeClass("checked") : obj.addClass("checked");
});
s = $("input[type=checkbox].email-checkout.checked");

visibility property not working as expected in javascript function

I have a table:
<table>
<tr>
<td colspan="2"><h2>Order Awards here:</h2></td>
</tr>
<tr>
<td class="aocBlack">Delivery:</td>
<td>
<select style="width: 200px;" id="deliveryMethod" name="deliveryMethod" size="1" onchange="showMailing()">
<option value="print">I will print it myself.</option>
<option value="mail">Please mail it to me.</option></select>
</td>
</tr>
<tr id="messageText" style="">
<td class="aocBlack" colspan="2">Message to appear on card:</td>
</tr>
<tr id="messageText2" style="">
<td colspan="2"><textarea id="certMessage" name="certMessage" rows="5" cols="10" style="width: 284px;"></textarea></td>
</tr>
</table>
When the select box called deliveryMethod is set to "print", the following two table rows (id messageText and messageText2) should be visible. When it's set to "mail", they should be hidden. I have some javascript that's worked before with no problem, but the id's I was targeting before were always divs. I don't know if table rows behave differently, but I'm getting some strange results. Here's the JS:
function showMailing(){
e = document.getElementById("deliveryMethod");
eVal = e.options[e.selectedIndex].value;
if (eVal == "mail"){
document.getElementById("messageText").style.display="none";
document.getElementById("messageText2").style.display="none";
}else{
document.getElementById("messageText").style.display="inline";
document.getElementById("messageText2").style.display="inline";
}
}
The results are somewhat strange, to my (admittedly javascript/css-rusty) eyes. For example, when the page initially loads, everything displays as it's supposed to: the dropdown's default value is "print", and so the two table rows in question display. When you change the dropdown to "mail", they both disappear. But when you change it back, the fields are all pushed over out of where they're supposed to be. These results are consistent across FF and Chrome (strangely it works correctly in IE) so I have to assume I'm doing something wrong, but I don't know what.
Here are some screenshots (note there are a few fields displayed in the screenshot that I've stripped out of the code shown here just for clarity.) Can anyone help me out here?
On initial load:
After changing from print to mail:
After changing back from mail to print:
The default display value for a table row is table-row(*). If you set it to inline instead you'll be asking the browser to draw table cells inside inline text instead of a row, which will confuse it and give unspecified results.
(*: except on IE<8, which don't support the table-related display values, instead setting them all to block and giving the elements themselves magic layout powers.)
The better way do show/hide, where you don't have to worry about what the default display value might be, is to define a class:
.hidden { display: none; }
and then toggle that class on and off the element.
document.getElementById('deliveryMethod').onchange= function() {
var cls= this.value==='mail'? 'hidden' : '';
document.getElementById('messageText').className= cls;
document.getElementById('messageText2').className= cls;
};
Assigning the handler from script allows you to drop the onchange inline attribute. You also don't need size="1" (that goes without saying for a single-select), or the style="".
The business with reading the select's value using this.options[this.selectedIndex].value you probably don't need any more, unless you're dealing with ancient browsers.
function showMailing(){
e = document.getElementById("deliveryMethod");
eVal = e.options[e.selectedIndex].value;
if (eVal == "mail"){
document.getElementById("messageText").style.display="none";
document.getElementById("messageText2").style.display="none";
}else{
document.getElementById("messageText").style.display="table-row";
document.getElementById("messageText2").style.display="table-row";
}
}
I'd recommend avoiding "eVal" as a variable name, because there's a native JS method called eval(). The capitalization you used is different ("eVal" not "eval"), so it probably won't break things. But it could easily be confusing to a human reader. Also, if you accidentally forget to capitalize the "V" and then need to use the eval() method, it could break your script.
So just in general, avoid using variable names that are similar to the names of existing methods. May I suggest "selectedOption" or "sel" or something?

Categories