Javascript JQuery Recursion - javascript

I am writing a javascript recursion function but in always gave me 0 value. Below is my code snippet and a screenshot showing the markup:
getOdometerEnd: function(object){
var previous = jQuery(object).closest('tr').prevAll();
var odometerEnd = parseInt(previous.find('input[name=odometer_end\\[\\]]').val());
console.log(previous.find('input[name=destination\\[\\]]').val());
if(odometerEnd == 0){
this.getOdometerEnd(previous);
}
return odometerEnd;
},
updateOdometer: function(){
var self = this;
var distance = 0;
var odometerStart = 0;
var odometerEnd = 0;
jQuery('.app-table tr').each(function(index, object){
distance = jQuery(object).find('input[name=distance\\[\\]]').val();
if(typeof distance != 'undefined' && distance > 0){
if(index == 1){
odometerStart = jQuery(object).find('input[name=vehicle_odometer\\[\\]]').val();
}else {
odometerStart = self.getOdometerEnd(object);
}
odometerEnd = parseInt(odometerStart)+parseInt(self.userDistance(distance));
jQuery(object).find('input[name=odometer_start\\[\\]]').val(odometerStart);
jQuery(object).find('input[name=odometer_end\\[\\]]').val(odometerEnd);
}
});
},
I don't know where I have mistaken, but it gives me the correct value at second last recursion but at last it again returns 0.
Can you help me please.
I've attached an image showing my markup here.
P.S. Stack overflow didn't allowed me to post image. Alternatively you can view image here: http://www.yourcarlog.com/odometer-problem.jpg
Thanx in advance.

First, give the elements classes to make selection easier and quicker. Selection by name is cumbersome.
Second, you will find it much simpler and probably no less efficient to :
maintain a running odo_cumulative variable, thus avoiding the need to sniff up the table for a valid earlier value of odo_end.
set the odo_start and odo_end values unconditionally, thus allowing odo_cumulative to propagate through null rows.
(if you must) perform a second row-scan to zero out odo_start and odo_end in null rows.
Replace updateOdometer with the following version:
updateOdometer: function(){
var self = this;
var odo_cumulative;
var $rows = jQuery('.app-table tr').each(function(index, tr){
var $tr = jQuery(tr);
if(!odo_cumulative) {
odo_cumulative = parseInt($tr.find('input.vehicle_odometer').val());
}
var odo_start = odo_cumulative;
var odo_cumulative = odo_end = parseInt(odo_start) + parseInt(self.userDistance($tr.find('input.distance').val()));
$tr.find('input.odometer_start').val(odo_start);
$tr.find('input.odometer_end').val(odo_end);
});
//Now, if you absolutely must, :) DESPITE BEING TOTALLY ILLOGICAL AND AGAINST ALL MY ADVICE :), scan the table rows again to zero out the start/end values where distance ==0;
$rows.each(function(index, tr){
var $tr = jQuery(tr);
if(parseInt($tr.find('input.distance').val()) == 0){
$tr.find('input.odometer_start, input.odometer_end').val(0);
}
});
},
untested
Third, the getOdometerEnd method is no longer called so can be deleted unless needed elsewhere.
EDIT:
And if that works, then so should this, which is even simpler, avoiding the need to double scan:
updateOdometer: function(){
var self = this,
odo_cumulative;
var $rows = jQuery('.app-table tr').each(function(index, tr){
var $tr = jQuery(tr),
$inputs = $tr.find('input'),
distance = parseInt(self.userDistance($inputs.filter('.distance').val()));
if(!odo_cumulative) {
odo_cumulative = parseInt($inputs.filter('.vehicle_odometer').val());
}
$inputs.filter('.odometer_start').val((distance == 0) ? 0 : odo_cumulative);
odo_cumulative += distance;
$inputs.filter('.odometer_end').val((distance == 0) ? 0 : odo_cumulative);
});
},
untested

Related

Google Docs and xml reading

i've working code(below) to read xml in google docs. It works greate if xml looks like this(part i'm interested in):
<row orderID="4452813795" charID="96255569" stationID="60011752" volEntered="1" volRemaining="1" minVolume="1" orderState="0" typeID="11134" range="32767" accountKey="1002" duration="90" escrow="0.00" price="20000.00" bid="0" issued="2016-02-28 02:05:29"/>
What i want is volRemaining value and my code returns 1. But if xml looks like this:
<row orderID="4452813795" charID="96255569" stationID="60011752" volEntered="1" volRemaining="1" minVolume="1" orderState="0" typeID="11134" range="32767" accountKey="1002" duration="90" escrow="0.00" price="20000.00" bid="0" issued="2016-02-28 02:05:29"/>
<row orderID="4452814032" charID="96255569" stationID="60011752" volEntered="1" volRemaining="1" minVolume="1" orderState="0" typeID="11134" range="32767" accountKey="1002" duration="90" escrow="0.00" price="20000.00" bid="0" issued="2016-02-28 02:05:47"/>
Code still returns 1. What i need is to add these values from both rows so that code returns 2 in this case(there may be more rows like these and code need to check if orderState="0").
This is my code:
function getLevelByTypeFromRowset(rowset, id)
{
var rows = rowset.getChildren("row");
var level=null;
var level2=0;
for (var i=0;level==null && i<rows.length;i++)
{
var row=rows[i];
var typeIdAttr=row.getAttribute("typeID");
if (typeIdAttr && typeIdAttr.getValue()==id && row.getAttribute("orderState").getValue()==0)
{
level2=level2 + row.getAttribute("volRemaining").getValue();
if (i = rows.length){
level=level2;
}
}
}
return level;
}
function TradeVolume(id) {
//id=3389;
var idd="orders";
var url = "http://some.url.com";
var document = readXml(url);
var level = null;
var rowsets = document.getRootElement().getChild("result").getChildren("rowset");
for (var i=0;level==null && i<rowsets.length;i++)
{
var rowset=rowsets[i];
var typeIdAttr=rowset.getAttribute("name");
if (typeIdAttr && typeIdAttr.getValue()==idd)
{
level=getLevelByTypeFromRowset(rowset, id);
}
}
if (level==null){
level = 0
}
return parseFloat(level);
}
I've been trying to do it 3 hours and can't came up with any idea...
Edit:
Working code:
function getLevelByTypeFromRowset(rowset, id)
{
var rows = rowset.getChildren("row");
var level=0;
for (var i=0;i<rows.length;i++)
{
var row=rows[i];
var typeIdAttr=row.getAttribute("typeID");
if (typeIdAttr && typeIdAttr.getValue()==id && row.getAttribute("orderState").getValue()==0)
{
level=parseInt(level) + parseInt(row.getAttribute("volRemaining").getValue());
}
}
return level;
}
One problem is if (i = rows.length). You are making an assignment there, where you apparently meant to test for equality. The assignment causes the loop condition to be false, so the loop terminates early.
So you could change the if condition to if (i == rows.length). However, that test will never be true, since the loop condition includes && i<rows.length. Maybe you meant to say if (i == rows.length - 1), so that the body of the if would be executed on the last pass through the loop? But it would only happen if the last row satisfies
(typeIdAttr && typeIdAttr.getValue()==id && row.getAttribute("orderState").getValue()==0)
which I don't think is what you want.
Really what I expect you meant was to move the
if (i == rows.length - 1) {
level = level2;
}
outside of the block of the preceding if. But a simpler way of doing the same thing would be to remove this if block completely, and change
return level;
to
return level2;

Dynamic event attachment not working correctly [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 8 years ago.
var smartActionsId = ['smartActions1','smartActions5','smartActions10'];
for (var i in smartActionsId) {
console.log("smartActionsId ="+smartActionsId[i]);
$('#' + smartActionsId[i] + ' select').change(function () {
var value = $(this).val();
var disableValue;
var ruleIndex = smartActionsId[i].substr(11);
console.log("smartActionsId ="+smartActionsId[i]+" i ="+i);
if (value === '0') {
disableValue = true;
onRuleToggle(disableValue, ruleIndex)
}
else if (value === '1') {
disableValue = false;
onRuleToggle(disableValue, ruleIndex)
}
});
}
I'm creating change event dynamically for a multiple switch slider items using the above JavaScript code. But problem I'm facing is, when I click on any switch 'i' value gets replaced with the last value i.e. in smartActionsId I have 3 elements, which ever switch I change it effects for last switch (smartActions10).
Could you please help me resolving this issue?
Other answers here fixed your problem, but I think you can refactor your code a little and make it much more understandable.
First, I don't like IDs. in your scenario, you have multiple ids which needs to be treated the same. Why not use one mighty class?
Also, ruleIndex calculated from element's ID? smells rotten.
If it tells you something about the element, it should be in an attribute or a data-* attribute.
The first bit of code fixes the markup by adding ruleIndex as data attribute and adding a .smart-actionable class. (Maybe you can even move this part to the server-side, to provide yourself with easier markup for JS).
Now, this makes the event handling quite simple.
var smartActionsId = ['smartActions1','smartActions5','smartActions10'];
for (var i in smartActionsId) {
$('#' + smartActionsId[i])
.data('ruleIndex', smartActionsId[i].substr(11))
.addClass('smart-actionable');
}
$('.smart-actionable').on('change', 'select', function() {
var value = $(this).val();
var disableValue = (value === '0');
onRuleToggle(disableValue, $(this).data('ruleIndex'));
});
Hope it will help.
You don't want to attach event listeners inside a for loop because the variable that tracks the index is used by each loop cycle. If you do that, the i variable will always equal the length of the array minus 1. Use Array.prototype.forEach() instead to prevent that.
var smartActionsId = ['smartActions1','smartActions5','smartActions10'];
smartActionsId.forEach(function (identifier, index) {
console.log("smartActionsId ="+identifier);
$('#' + smartActionsId[index] + ' select').change(function () {
var value = $(this).val();
var disableValue;
var ruleIndex = smartActionsId[index].substr(11);
console.log("smartActionsId ="+smartActionsId[index]+" index ="+index);
if (value === '0') {
disableValue = true;
onRuleToggle(disableValue, ruleIndex)
}
else if (value === '1') {
disableValue = false;
onRuleToggle(disableValue, ruleIndex)
}
});
});
Please Note: IE8 and down does NOT support Array.prototype.forEach().
You cant use for...in in this case. Please try the code below:
var smartActionsId = ['smartActions1', 'smartActions5', 'smartActions10'];
for (var i = 0; i < smartActionsId.length; i++) {
console.log("smartActionsId =" + smartActionsId[i]);
$('#' + smartActionsId[i] + ' select').change(function() {
var value = $(this).val();
var disableValue;
var ruleIndex = smartActionsId[i].substr(11);
console.log("smartActionsId =" + smartActionsId[i] + " i =" + i);
if (value === '0') {
disableValue = true;
onRuleToggle(disableValue, ruleIndex)
} else if (value === '1') {
disableValue = false;
onRuleToggle(disableValue, ruleIndex)
}
});
}
I've always use names like smartActions_1. If you can use it, then in your .change function you can use
// if 'smartActionsId' is global variable
// and if you need to get position in 'smartActionsId' array
var numInArray = $.inArray( this.parentNode.id, smartActionsId );
// this - your select DOM object
var ruleIndex = parseInt( this.parentNode.id.split( "_" )[ 1 ] );
And remember that this in .change function its select which have no id and you must use this.parentNode or $( this ).parent() to get it's holder (I think its div or somethink like that).
#Jack in comments is right: select may not be a direct child. Then you can use this code:
var parent = $( this ).closest( "[id^=smartActions]" );
var numInArray = $.inArray( parent.attr( "id" ), smartActionsId );
var ruleIndex = parseInt( parent.attr( "id" ).split( "_" )[ 1 ] );

Saving an element's document model with JSON

I have written some code that allows one element on a page to be expanded to full screen and contracted back to its original size. This code works by saving the states of other elements on the page, altering their properties, and restoring them. This change has to survive a postback, so I'm attempting to use JSON and a hidden input element to preserve the state changes.
The element in question is nested within multiple IFRAMEs. Thus I have to save the document model in which the element resides. However, this causes the JSON conversion to choke. I need a way to resolve this problem that can be easily converted to JSON and back.
Here is the pertinent code:
// e.uniqueID : A unique identifer for the object.
// e.doc: The document model to which the element belongs (so we can find it later).
// e.style: The original style of the element.
function my_preserveElement(gn,elem)
{
if (elem == null) return;
if (elem.style == null) return;
if (elem.id == '') elem.id = PseudoGuid.GetNew();
var e = new Object();
e.uniqueID = elem.id;
e.doc = elem.document;
var cssString;
cssString = elem.style.cssText;
if( typeof(cssString) != 'string' ) { cssString = elem.getAttribute('style'); }
e.style = cssString;
me_aObjectStore[gn][me_aObjectStore[gn].length] = e;
}
function my_restoreElements(gn)
{
for (i = 0; i < me_aObjectStore[gn].length; i++)
{
var e = me_aObjectStore[gn][i];
var elem = e.doc.getElementById(e.uniqueID);
elem.style.cssText = e.style;
elem.setAttribute('style',e.style);
}
me_aObjectStore[gn] = null;
}
Discovered that since the code in question is running in the innermost frame, I need only walk up the frame tree, searching each level for each element by ID. The restore function becomes as follows, and there is no need to keep track of each element's location (just its unique ID).
function my_restoreElements(gn)
{
for (i = 0; i < my_aObjectStore[gn].length; i++)
{
var e = my_aObjectStore[gn][i];
// Find the element in this window or one of its parents.
// Because we need to check the top level frame and w.parent == w.self for that frame,
// we use the number of counts to keep the loop from being endless.
var w = window;
var elem = null;
var count = 0;
do {
elem = w.document.getElementById(e.uniqueID);
w = w.parent;
count++;
} while ((elem == null) && (count < 3))
if (elem != null) {
elem.style.cssText = e.style;
elem.setAttribute('style',e.style);
}
} //for
my_aObjectStore[gn] = null;
}
Note that I explicitly walk up only three levels. That's because in my specific case, the frames are that few levels deep. Other applications that can use this solution may need a deeper depth.

Advanced Checkboxes with Javascript

I have a form that is a table built with php to pull a bunch of information from the database. In these, i have a checkbox that, when clicked, pulls the value of the estimatedCost and throws it into a JavaScript function that calculates it and keeps a running total of all the objects checked.
What i'm trying to do is create a check all and uncheck all option that will still pass the needed variables into the other javascript functions. Let me demonstrate with some code:
This draws the checkbox next to the title.
foreach($replace as $key => $value)
{
$jScript = 'onclick=\'calcTotals("'.$replace[$key]['estimatedCost'].'","'.$replace[$key]['optionItem_id_replaceOrRepair'].'","'.$replace[$key]['service_title'].'","'.$replace[$key]['maintenanceitem_id'].'");\'';
$checkbox = sprintf('<input type="checkBox" name="ids[]" id="%s" value="%s" %s>', $replace[$key]['maintenanceitem_id'], $replace[$key]['maintenanceitem_id'], $jScript).' ';
$replace[$key]['title'] = $checkbox.$replace[$key]['title'];
$replace[$key]['estimatedCost'] = $replace[$key]['estimatedCost'];
}
This is the current check all and uncheck all links:
echo '<a href="#" onClick=\'setCheckboxes("budgeting", true); return false;\'>Check All</a> | ';
echo '<a href="#" onClick=\'setCheckboxes("budgeting", false); return false;\'>Uncheck All</a>';
Now the current functions i have in javascript:
function setBudgetCheckboxes(the_form, do_check) {
var elts = (typeof(document.forms[the_form].elements['ids[]']) != 'undefined')
? document.forms[the_form].elements['ids[]']
: (typeof(document.forms[the_form].elements['ids[]']) != 'undefined')
? document.forms[the_form].elements['ids[]']
: document.forms[the_form].elements['ids[]'];
var elts_cnt = (typeof(elts.length) != 'undefined')
? elts.length
: 0;
if (elts_cnt) {
for (var i = 0; i < elts_cnt; i++) {
elts[i].checked = do_check;
var name = document.getElementById(name);
} // end for
} else {
elts.checked = do_check;
} // end if... else
return true;
}
And the other, which handles the clicks one at a time:
function calcTotals(amount, type, service, name) {
if(amount[0] == '$') {
amount = amount.substr(1,amount.length);
}
var id = type+"_"+service+"_selected";
var grand_id = "Grand_selected";
var grand_service_id = "Grand_"+service+"_selected";
var type_id = type+"_selected";
var checked = document.getElementById(name).checked;
var multiplier = -1;
if(checked) {
multiplier = 1;
}
amount = amount * multiplier;
addBudgetValue(amount, id);
addBudgetValue(amount, grand_id);
addBudgetValue(amount, grand_id+"_h");
addBudgetValue(amount, type_id);
addBudgetValue(amount, grand_service_id);
addBudgetValue(amount, grand_service_id+"_h");
}
function addBudgetValue(amount, id) {
var current_value = document.getElementById(id).innerHTML;
var curtmp = 0;
if(current_value == "$0") {
current_value = amount;
}
else {
curtmp = parseFloat(current_value.substr(1,current_value.length));
current_value = (curtmp+parseFloat(amount));
}
var newVal = "$"+Number(current_value).toFixed(2);
if(newVal == "$0.00")
newVal = "$0";
document.getElementById(id).innerHTML = newVal;
}
So the question is this: How do you get the check all to check all the boxes, and pass the information into the calcTotals function so that the values are added correctly?
Here's a jsfiddle that does what (I think) you're looking for: http://jsfiddle.net/kz9gU/1/
I'm using a custom data-* attribute to store the estimated cost (spec compliant as per HTML5), so it's defined in the checkboxes' tags. Each checkbox then calls an updateTotal() function when checked/unchecked, but doesn't pass any arguments. updateTotal() then loops through the checkboxes, reading each amount from the data-cost attribute, and displays the sum.
The check/uncheck functions then don't need to calculate any values; they just check/uncheck everything and call updateTotal() afterwards.
Edit: Heh, just realized you're not using jQuery. I'm just starting to take jQuery for granted, even though I actually don't use jQuery myself. What you see in the fiddle is probably the longest piece of jQuery-based JS, I've actually written :)
At any rate, it's almost always a good idea to use some sort of library to smooth out browser differences, and jQuery is king of the heap right now.
But library or not, the code should give you an idea of how you could proceed.
I hope I understood you currectly, you want a check/uncheck all feature? http://jetlogs.org/jquery/jquery_select_all.html after that get the checkbox array with php and pass the information, in PHP do a loop to check the values of the array and execute your statements.

javascript not removing undefined objects from array

I've got an in page text search using JS, which is here:
$.fn.eoTextSearch = function(pat) {
var out = []
var textNodes = function(n) {
if (!window['Node']) {
window.Node = new Object();
Node.ELEMENT_NODE = 1;
Node.ATTRIBUTE_NODE = 2;
Node.TEXT_NODE = 3;
Node.CDATA_SECTION_NODE = 4;
Node.ENTITY_REFERENCE_NODE = 5;
Node.ENTITY_NODE = 6;
Node.PROCESSING_INSTRUCTION_NODE = 7;
Node.COMMENT_NODE = 8;
Node.DOCUMENT_NODE = 9;
Node.DOCUMENT_TYPE_NODE = 10;
Node.DOCUMENT_FRAGMENT_NODE = 11;
Node.NOTATION_NODE = 12;
}
if (n.nodeType == Node.TEXT_NODE) {
var t = typeof pat == 'string' ?
n.nodeValue.indexOf(pat) != -1 :
pat.test(n.nodeValue);
if (t) {
out.push(n.parentNode)
}
}
else {
$.each(n.childNodes, function(a, b) {
textNodes(b)
})
}
}
this.each(function() {
textNodes(this)
})
return out
};
And I've got the ability to hide columns and rows in a table. When I submit a search and get the highlighted results, there would be in this case, the array length of the text nodes found would be 6, but there would only be 3 highlighted on the page. When you output the array to the console you get this:
So you get the 3 tags which I was expecting, but you see that the array is actually consisting of a [span,undefined,span,undefined,undefined,span]. Thus giving me the length of 6.
<span>
<span>
<span>
[span, undefined, span, undefined, undefined, span]
I don't know why it's not stripping out all of the undefined text nodes when I do the check for them. Here's what I've got for the function.
performTextSearch = function(currentObj){
if($.trim(currentObj.val()).length > 0){
var n = $("body").eoTextSearch($.trim(currentObj.val())),
recordTitle = "matches",
arrayRecheck = new Array(),
genericElemArray = new Array()
if(n.length == 1){
recordTitle = "match"
}
//check to see if we need to do a recount on the array length.
//if it's more than 0, then they're doing a compare and we need to strip out all of the text nodes that don't have a visible parent.
if($(".rows:checked").length > 0){
$.each(n,function(i,currElem){
if($(currElem).length != 0 && typeof currElem != 'undefined'){
if($(currElem).closest("tr").is(":visible") || $(currElem).is(":visible")){
//remove the element from the array
console.log(currElem)
arrayRecheck[i] = currElem
}
}
})
}
if(arrayRecheck.length > 0){
genericElemArray.push(arrayRecheck)
console.log(arrayRecheck)
}
else{
genericElemArray.push(n)
}
genericElemArray = genericElemArray[0]
$("#recordCount").text(genericElemArray.length + " " +recordTitle)
$(".searchResults").show()
for(var i = 0; i < genericElemArray.length; ++i){
void($(genericElemArray[i]).addClass("yellowBkgd").addClass("highLighted"))
}
}
else{
$(".highLighted").css("background","none")
}
}
If you look at the code below "//check to see if we need to do a recount on the array length. ", you'll see where I'm stripping out the text nodes based off of the display and whether or not the object is defined. I'm checking the length instead of undefined because the typeof == undefined wasn't working at all for some reason. Apparently, things are still slipping by though.
Any idea why I'm still getting undefined objects in the array?
My apologies for such a big post!
Thanks in advance
I've modified your eoTextSearch() function to remove dependencies on global variables in exchange for closures:
$.fn.extend({
// helper function
// recurses into a DOM object and calls a custom function for every descendant
eachDescendant: function (callback) {
for (var i=0, j=this.length; i<j; i++) {
callback.call(this[i]);
$.fn.eachDescendant.call(this[i].childNodes, callback);
}
return this;
},
// your text search function, revised
eoTextSearch: function () {
var text = document.createTextNode("test").textContent
? "textContent" : "innerText";
// the "matches" function uses an out param instead of a return value
var matches = function (pat, outArray) {
var isRe = typeof pat.test == "function";
return function() {
if (this.nodeType != 3) return; // ...text nodes only
if (isRe && pat.test(this[text]) || this[text].indexOf(pat) > -1) {
outArray.push(this.parentNode);
}
}
};
// this is the function that will *actually* become eoTextSearch()
return function (stringOrPattern) {
var result = $(); // start with an empty jQuery object
this.eachDescendant( matches(stringOrPattern, result) );
return result;
}
}() // <- instant calling is important here
});
And then you can do something like this:
$("body").eoTextSearch("foo").filter(function () {
return $(this).closest("tr").is(":visible");
});
To remove unwanted elements from the search result. No "recounting the array length" necessary. Or you use each() directly and decide within what to do.
I cannot entirely get my head around your code, but the most likely issue is that you are removing items from the array, but not shrinking the array afterwards. Simply removing items will return you "undefined", and will not collapse the array.
I would suggest that you do one of the following:
Copy the array to a new array, but only copying those items that are not undefined
Only use those array items that are not undefined.
I hope this is something of a help.
Found the answer in another post.
Remove empty elements from an array in Javascript
Ended up using the answer's second option and it worked alright.

Categories

Resources