Maintain Array value with a recursive javascript function - javascript

I have a program that we use at my work, which outputs its data in to XML files (several of them). I am trying to develop an HTA (yes an HTA, i'm sorry) to read these files and process their data. Unfortunately there are a number of XML files and I only need to read a few specific ones, so I am trying to write a generic "XML to array" function.
I got it to read the XML file and now I want to process the file into a 2d Array. However, since I am using a recursive function I seem to lose data. Here is the function:
NodesToArray = function (xmlDOC){
//Must redeclare "i" with each recursion, or it won't work correctly. ie: for(VAR i = 0...
for(var i = 0; i < xmlDOC.length ; i++){
//Just because it has a child still do the check.
if(xmlDOC[i].childNodes.length > 1){
//Recursively run the function.
var ReturnArray = NodesToArray(xmlDOC[i].childNodes);
//alert(ReturnArray + " " );
if(ReturnArray) return ReturnArray;
}else{
//Check to see if the node has a child node, if not and a child node is called, it will error out and stop
if(xmlDOC[i].hasChildNodes() == true){
return xmlDOC[i].firstChild.nodeValue;
}
}
}
}
Where I return the first child value I put an alert and was able to see all the data I wanted. Of course when I set it up I found it wasn't keeping the data. I've done a ton of reading and have been pounding my head against my desk and still can't come up with anything.
I've googled, searched this site, and consulted many forums, and can't find anything that would work for me. I post here reluctantly as I am at a dead end. Thanks for any help and I will provide any additional information as I can.
Just a note, but I would like to be able to do this without any libraries (specifically jQuery). The HTA doesn't seem to support a lot of newer Javascript. I'm not a professional coder by any means and learn by doing everything from scratch.
Not sure how to set the solution, but I found it
function NodesToArray(xmlDOC, returnArray){
for(var i = 0; i < xmlDOC.length ; i++){
if(xmlDOC[i].childNodes.length > 1){
returnArray[returnArray.length] = NodesToArray(xmlDOC[i].childNodes, []);
}else{
if(xmlDOC[i].hasChildNodes() == true){
returnArray[returnArray.length] = (xmlDOC[i].firstChild.nodeValue);
}
}
}
return returnArray;
}
getArray = NodesToArray(getXML.getElementsByTagName(tagName)[0].childNodes,[]);
Thanks for the help!

The general way of retrieving data recursively in the same container is to write two function:
First one is the one that you call and which returns the array
Second one is called by first function and does the recursion. To be able to put the data in the same array that function has to take it as parameter.
Here is some pseudo code
getData(node) {
_2D_array = new array[][];
getData(node, _2D_array, 0);
return array;
}
getData(node, _2D_array, depth) {
if(node) { // end of recursion ?
_2D_array[depth].add(...); // populate from node
getData(node.next, _2D_array, depth++);
}
}

Your program exits when the first element is processed because the function returns. A function can only return once. You need to move the return statements outside the loop so that the loop completes.

Related

My Bools Aren't working?

I am trying to make a "Battleship" like game. Most of my board is supposed to be "missed" tiles, and only a few are supposed to be tiles with submarines on them for a "hit". The problem is that whenever I run my program, I cannot tell if it is ignoring my bool or if it isn't understanding what I coded, because everything I click is a "hit".
var cellClick=function(clicked)
{
var cell=clicked.target;
if(! cell.isEmpty) return;
if(cell.isSub=false)
{
cell.style.backgroundImage='url("missed.png")';
cell.style.backgroundSize='constrain';
}
else
{
cell.style.backgroundImage='url("hit.png")';
cell.style.backgroundSize='constrain';
cell.exploded=true;
}
cell.isEmpty=false;
console.log('click');
};
var waterCell=[[],[],[],[],[],[],[],[],[],[]];
for(var row=0;row<10;row++)
{
for(var column=0;column<10;column++)
{
waterCell[row][column]=document.createElement("div");
waterCell[row][column].style.width='10%';
waterCell[row][column].style.height='10%';
waterCell[row][column].style.position='absolute';
waterCell[row][column].style.left=(column*10)+'%';
waterCell[row][column].style.top=(row*10)+'%';
waterCell[row][column].style.backgroundImage='url("water_cell.jpg")';
waterCell[row][column].style.backgroundSize='contain';
gameBoard.appendChild(waterCell[row][column]);
waterCell[row][column].row=row;
waterCell[row][column].column=column;
waterCell[row][column].isEmpty=true;
waterCell[row][column].isSub=false;
waterCell[row][column].exploded=false;
}
}
//trying to make random subs
for(var i=0;i<5;i++)
{
row=Math.round(Math.random()*10);
column=Math.round(Math.random()*10);
waterCell[row][column].isSub=true;
}
gameBoard.addEventListener('click',cellClick,false);
Your code
if(cell.isSub=false) {
is assigning false to the property isSub of cell. The assignment's result is then tested by if, which is false. That's why the condition is never met and the else-branch is processed. Obviously, your intention was
if(cell.isSub==false) {
Maybe try swapping the branches of if statement and test for truthy value in cell.isSub instead making the code simpler:
if ( cell.isSub ) {
// it's a hit
} else {
// it's a miss
}
If you require to keep this order of branches testing for falsy values can be simplified as well by using negation operator:
if ( !cell.isSub ) {
// it's a miss
} else {
// it's a hit
}
One last tip: don't set custom properties of a DOM element as those perform poorly. Even worse, they are perfect soil for memory leakages when putting Javascript objects or functions. Thus, keep the two worlds - your Javascript and your HTML/DOM - as separated as possible and train yourself not to put additional data into Javascript-representations of objects managed in context of DOM. Use a separate description in pure Javascript (e.g. two-dimensional array) for tracking position of your subs and create a DOM representing that internal data set, only.

MS Word web add-in javascript search multiple strings at the same time

I have been able to run the Github project Word-Add-in-JS-Redact successfully. Now, I need to make a change in the code. Need help for that.
At present the code finds multiple occurrences of a single string in a Word Doc and highlights those. But, I need to search multiple strings (can be hard coded inside the js file as well) in the document with a single button click, and highlight those. For example, I want to find both strings 'this' and 'that' in the document at the same time, and highlight them.
The current code, which searches for a single string:
Word.run(function (context) {
//search string
var searchInput = "hello";
// Search the document.
var searchResults = context.document.body.search(searchInput, {matchWildCards: true});
// Load the search results and get the font property values.
context.load(searchResults, "");
// Synchronize the document state by executing the queued-up commands,
// and return a promise to indicate task completion.
return context.sync()
.then(function () {
var count = searchResults.items.length;
// // Queue a set of commands to change the font for each found item.
for (var i = 0; i < count; i++) {
searchResults.items[i].font.highlightColor = '#FFFF00'; //Yellow
}
return count;
})
.then(context.sync)
.then(reportWordsFound);
A couple of ways I have tried so far with no luck:
Ran context.document.body.search(searchInput, in a loop of the search strings, and tried to append the searchResults strings using + and push. This attempt gave an error saying, I cannot add multiple context results to a single object.
I tried to be creative with WildCards operators, but nothing was suitable for this. Many posts are talking about JS regex \(string1|string2/), but this seems to be invalid in Word context.
I could solve the problem at last, by creating a parent function, which calls the search function in a loop. My mistake was to create the loop inside the search function itself.
Here is the working code:
function searchStrings(){
searchString('hello world');
searchString('Goodbye');
}
RedactAddin.searchStrings = searchStrings;
function searchString(input) {
// Run a batch operation against the Word object model.
Word.run(function (context) {
//...... (same as the original) ...............

Check if any values in an array match an element in the dom and then sendkeys to that element in protractor

I am using jasmine/protractor to run test suites against multiple sites that my company manages. (The sites are order form/checkout sites) Currently, I have a separate test suite set up for each site that uses functions I've created and stored inside of a helper to fill out each form so that I can just call those functions in my specs, and run through the order process to place a test order, this currently works and calls to the helpers go through without issue.
Protractor: v5.1.2
Jasmine: v2.6
Node: v8.0.0
The trouble here is that each site tends to use any number of field identifiers for each field in a given form, and I have ended up writing a specific function for a specific form on a particular site. With each site using anywhere between 2 and 4 forms, the end result is hundreds of the same functions being repeated with only difference in the selector. So I am trying to refactor my code so that I can just have one function for each form used on every site.
Enter my issue: I am having trouble figuring out how to make my tests check the value of the elements loading on the page against a list of possible elements. basically what I need it to do is this:
Open page
check element(by.name('someName') against the list I have
once a match is found, use the match as the value for someName
sendKeys('someValue') to that value
repeat for all fields on the form
What I have:
My Spec:
This is the actual spec in my test file.
it('Checks for fieldName and then fills in the form', function(done) {
cQualify();
done();
});
cQualify function:
The function is stored in a helper called formFill.js.
cQualify = function() {
findElementByFieldName(cartData.fName).sendKeys('Jimmy');
findElementByFieldName(cartData.cGender).sendKeys('Male');
findElementByFieldName(cartData.cAge).sendKeys('34');
findElementByFieldName(cartData.cZip).sendKeys('33071');
//more fields here and a submit button
};
findElementByFieldName function:
This function is stored in a helper called arrayLoop.js and is my latest attempt to make this work. Originally this was more along the lines of:
browser.driver.findElement(by.name('someName')).sendKeys('nameToSend');
//repeated for each field on the form
Here is the function:
findElementByFieldName = function(fieldName) {
if (fieldName.constructor === Array) {
console.log('Array found, looping values for array: ' + fieldName);
for(var i=0; i < fieldName.length; i++) {
expect(element(by.name(fieldName[i])).isDisplayed()).toBe(true);
console.log('Checking if page element ' + fieldName[i] + ' exists');
}
//some code that checks if the current value of fieldName[i] is in the page DOM
//and if so; returns that and exits the loop
} else {
return browser.driver.findElement(by.name(fieldName));
}
}
List of possible elements:
The possible elements are stored inside of a helper called formData.js (Note: only those with multiple possible values are in an array; the others I am not having trouble with)
cartData = {
fName: ['cShipFname', 'zang_fname', 'fname'],
lName: ['cShipLname', 'zang_lname', 'lname'],
cZip: ['cShipZip', 'zang_zip', 'zip'],
cGender: 'zang_gender',
cAge: 'zang_age',
cProblem: 'zang_problem'
//lots of additional values here
};
Result:
When I run this as is, the test loops through all values contained within cartData.fName, considers them all displayed, and then fails when trying to sendKeys with:
Failed: Cannot read property 'sendKeys' of undefined
So here is where I am stuck. Not only do I need the loop to check if the value from the array is on the page, I need it to stop looping once a match is found and return that so that I can use it the way it's laid out in the cQualify() function. I've tried a few different things such as using isDisplayed() inside of an if, but it seems this can only be used with expect. I've also tried putting my spec inside of a function and then looping that function directly in the test- but this had similar results and would also defeat the purpose of formFill.js
Update:
I found another question on SO that handles something similar: here
The code from the accepted answer is:
var link = element.all(by.css('a')).reduce(function (result, elem, index) {
if(result) return result;
return elem.getText().then(function(text){
if(text === "mylink") return elem;
});
}).then(function(result){
if(!result) throw new Error("Element not found");
return result;
});
Though I am not able to figure out (so far) how I might adapt it to suit my needs.
Instead of making it more complex, you can simply construct a dynamic XPath expression with multiple or conditions. Look at below example code.
function getElement(nameList) {
if(nameList.constructor != Array){
nameList=[nameList]
}
var xpathExpression = ".//*["
nameList.forEach(function(name,index){
xpathExpression += "#name='"+name+"'";
if(index != nameList.length-1){
xpathExpression+=" or ";
} else {
xpathExpression+= "]";
}
});
return element(by.xpath(xpathExpression));
}
So if you want to find the element for fName: ['cShipFname', 'zang_fname', 'fname'], you can simply call getElement and it will return you the web element based on the matched XPath expression.The XPath expression for fname is,
.//*[#name='cShipFname' or #name='zang_fname' or #name='fname']
Though it's answered, I'm not really a fan of XPath. Therefore I'd like to provide an alternative approach using filter() and indexOf() or includes().
First with using indexOf()
function getElement(nameList) {
//keeping it an array for all calls (thumbs up for this thought of #SudharsanSevaraj)
if(nameList.constructor != Array){
nameList=[nameList]
}
//selecting all elements with a name-attribute
//then filter the one(s), that is/are present in nameList and use first
return $$('[name]').filter(function(elem, index){
return elem.getAttribute('name').then(function(elName){
//return the element, if its name-attribute is present in nameList
return nameList.indexOf('elName') !== -1
});
}).first();
};
And the same slightly different, using includes() this time.
function getElement(nameList) {
//keeping it an array for all calls (thumbs up for this thought of #SudharsanSevaraj)
if(nameList.constructor != Array){
nameList=[nameList]
}
//selecting all elements with a name-attribute,
//then filter the one(s), that is/are present in nameList and use first
return $$('[name]').filter(function(elem, index){
return elem.getAttribute('name').then(function(elName){
return nameList.includes(elName);
});
}).first();
};

Honestly hard to explain.. I use something like a=b, then a++, but b changes. Except, these are Arrays

Well my short and easy to explain explanation can be this. I have 2 arrays, FilterList and GamesReset. Whenever this function I have works and filters out some games with check boxes and a drop down menu, the function starts off with something like FilterList=GamesReset;. This functions seems to work fine until I filter out ages for the game. The function never touches GamesReset unless it's something like while(i<GamesReset.length){} or FilterList=GamesReset;. And the only tool I use when I filter games is FilterList.splice(i,1);. Now with that, GamesReset definitely, should never change as far as I know. I have it to reset FilterList, then depending on what needs to be filtered out, it will start removing those games from the FilterList. The problem I have, is that, GamesReset also becomes filtered. Which, does not make any sense at all. So like my title, it's just like saying b=0;, a=b;, a++;, and now b equals 1.
Now, I think that's the best/shortest way I can reveal this problem, without overdoing it with my bad habit of explaining things to people. I have a webpage currently available if anyone would like to see whats going on in action, because I wouldn't get what's going on with GamesReset either if I were you, here (url removed, read edit). To get the error working, just change the age to 10 without checking any boxes. The bottom paragraph is the GamesReset array (using <br> to separate each array), and it's the one that changes when I'm only changing FilterList in the JavaScript. The actual codes if you view the page source may be a little off compared to when I mentioned above, but it's pretty much 100% the same thing. I also wanted to have the codes available without a url and on this page, but I can't figure out how to do that with the html tags included.
Actually, here's the JavaScript function. I just figured out the 4 spaces thing when my question was rejected.
function SearchFilter() {
Games = GamesReset;
plat = document.getElementById('platformcheck').checked;
rpg = document.getElementById('rpgcheck').checked;
puzz = document.getElementById('puzzlecheck').checked;
hybo = document.getElementById('hybocollectcheck').checked;
ages = document.getElementById('agescheck').value;
if ((!plat) && (!rpg) && (!puzz) && (!hybo)) {
FilterList = Games;
} else {
FilterList = [];
i = 0;
while (i < Games.length) {
Set = '';
Set = Games[i];
Set = Set.split('</>');
StrFind = Set[0];
if (
(plat && (StrFind.search(',platform,') > -1)) || (rpg && (StrFind.search(',rpg,') > -1)) || (puzz && (StrFind.search(',puzzle,') > -1)) || (hybo && (StrFind.search(',hybocollect,') > -1))) {
FilterList.push(Games[i]);
}
i++;
}
// so by now, we should have the filtered array
}
//seperate filter for ages
i = 0;
while (i < FilterList.length) { //The problem should definitely start here
Set = '';
Set = FilterList[i];
Set = Set.split('</>');
StrFind = Set[1];
if ((Math.abs(StrFind)) > ages) {
FilterList.splice(i, 1);
} else {
i++;
}
}
GL.innerHTML = GamesReset.join('<br>');
}
As a reminder, the problem starts when the age filter is working. And the only thing it does is FilterList.splice(i,1);. But it ends up changing GamesReset. I changed this function a bit when I added Games=GamesReset;, but that was another test to try and make sure GamesReset doesn't get filtered like FilterList, but it still does.
EDIT: I removed my url since the answers definitely explained everything, so there's no need for it now.
Arrays are not copied when assigned, both variables will refer to the same data. Here is a post that goes into detail on this: Copying array by value in JavaScript
It makes perfect sense since variables are just references to objects in memory. One object can have several references. Consider this:
var a = { foo: 'bar' };
var b = a;
// b is now a reference to a and they both point to the same object
b.foo = 'doe';
alert( a.foo ); // alerts doe
The same goes for arrays. So when you do FilterList = GamesReset you are not copying the array - you are just assigning the same array to another variable. Any mutations or changes made to either reference will be reflected in all references.
To create a copy of an array you can use slice:
FilterList = GamesReset.slice();

Removing items from data bound array

How do I remove an items from a data bound array? My code follows.
for(var i = 0; i < listBox.selectedIndices.length; i++) {
var toRemove = listFiles.selectedIndices[i];
dataArray.splice(toRemove, 1);
}
Thanks in advance!
Edit Here is my swf. The Add Photos works except when you remove items.
http://www.3rdshooter.com/Content/Flash/PhotoUploader.html
Add 3 photos different.
Remove 2nd photo.
Add a different photo.
SWF adds the 2nd photo to the end.
Any ideas on why it would be doing this?
Edit 2 Here is my code
private function OnSelectFileRefList(e:Event):void
{
Alert.show('addstart:' + arrayQueue.length);
for each (var f:FileReference in fileRefList.fileList)
{
var lid:ListItemData = new ListItemData();
lid.fileRef = f;
arrayQueue[arrayQueue.length]=lid;
}
Alert.show('addcomplete:' + arrayQueue.length);
listFiles.executeBindings();
Alert.show(ListItemData(arrayQueue[arrayQueue.length-1]).fileRef.name);
PushStatus('Added ' + fileRefList.fileList.length.toString() + ' photo(s) to queue!');
fileRefList.fileList.length = 0;
buttonUpload.enabled = (arrayQueue.length > 0);
}
private function OnButtonRemoveClicked(e:Event):void
{
for(var i:Number = 0; i < listFiles.selectedIndices.length; i++) {
var toRemove:Number = listFiles.selectedIndices[i];
//Alert.show(toRemove.toString());
arrayQueue.splice(toRemove, 1);
}
listFiles.executeBindings();
Alert.show('removecomplete:' + arrayQueue.length);
PushStatus('Removed photos from queue.');
buttonRemove.enabled = (listFiles.selectedItems.length > 0);
buttonUpload.enabled = (arrayQueue.length > 0);
}
It would definitely be helpful to know two things:
Which version of ActionScript are you targeting?
Judging from the behavior of your application, the error isn't occurring when the user removes an item from the list of files to upload. Looks more like an issue with your logic when a user adds a new item to the list. Any chance you could post that code as well?
UPDATE:
Instead of: arrayQueue[arrayQueue.length]=lid
Try: arrayQueue.push(lid)
That will add a new item to the end of the array and push the item in to that spot.
UPDATE 2:
Ok, did a little more digging. Turns out that the fileList doesn't get cleared every time the dialog is opened (if you're not creating a new instance of the FileReferenceList each time the user selects new files). You need to call splice() on the fileList after you add each file to your Array.
Try something like this in your AddFile() method...
for(var j:int=0; j < fileRefList.fileList.length; j++)
{
arrayQueue.push(fileRefList.fileList[j]);
fileRefList.fileList.splice(j, 1);
}
That will keep the fileList up to date rather than holding on to previous selections.
I see one issue. The selected indices are no longer valid once you have spliced out the first element from the array. But that should only be a problem when removing multiple items at once.
I think we need to see more code about how you are handling the upload before we can figure out what is going on. It looks to me like you are holding a reference to the removed FileReference or something. The described problem is occurring when you upload a new file, not when you remove the selected one.
Do you mean to use listBox and listFiles to refer to the same thing?
I'm stepping out on a limb here, because I don't have a ton of experience with JavaScript, but I'd do this the same way that I'd do it in C, C++, or Java: By copying the remaining array elements down into their new locations.
Assuming that listFiles.selectedIndices is sorted (and its contents are valid indices for dataArray), the code would be something like the following:
(WARNING: untested code follows.)
// Don't bother copying any elements below the first selected element.
var writeIndex = listFiles.selectedIndices[0];
var readIndex = listFiles.selectedIndices[0] + 1;
var selectionIndex = 1;
while(writeIndex < (dataArray.length - listFiles.selectedIndices.length)) {
if (selectionIndex < listFiles.selectedIndices.length) {
// If the read pointer is currently at a selected element,
// then bump it up until it's past selected range.
while(selectionIndex < listFiles.selectedIndices.length &&
readIndex == listFiles.selectedIndices[selectionIndex]) {
selectionIndex++;
readIndex++;
}
}
dataArray[writeIndex++] = dataArray[readIndex++];
}
// Remove the tail of the dataArray
if (writeIndex < dataArray.length) {
dataArray.splice(writeIndex, dataArray.length - writeIndex);
}
EDIT 2009/04/04: Your Remove algorithm still suffers from the flaw that as you remove items in listFiles.selectedIndices, you break the correspondence between the indices in arrayQueue and those in listFiles.selectedIndices.
To see this, try adding 3 files, then doing "Select All" and then hit Remove. It will start by removing the 1st file in the list (index 0). Now what had been the 2nd and 3rd files in the list are at indices 0 and 1. The next value taken from listFiles.selectedIndices is 1 -- but now, what had been the 3rd file is at index 1. So the former File #3 gets spliced out of the array, leaving the former 2nd file un-removed and at index 0. (Using more files, you'll see that this implementation only removes every other file in the array.)
This is why my JavaScript code (above) uses a readIndex and a writeIndex to copy the entries in the array, skipping the readIndex over the indices that are to be deleted. This algorithm avoids the problem of losing correspondence between the array indices. (It does need to be coded carefully to guard against various edge conditions.) I tried some JavaScript code similar to what I wrote above; it worked for me.
I suspect that the problem in your original test case (removing the 2nd file, then adding another) is analogous. Since you've only shown part of your code, I can't tell whether the array indices and the data in listFiles.selectedIndices, arrayQueue, and fileRefList.fileList are always going to match up appropriately. (But I suspect that the problem is that they don't.)
BTW, even if you fix the problem with using splice() by adjusting the array index values appropriately, it's still an O(N2) algorithm in the general case. The array copy algorithm is O(N).
I'd really need to see the whole class to provide a difinitive answer, but I would write a method to handle removing multiple objects from the dataProvider and perhaps assigning a new array as the dataProvider for the list instead of toying with binding and using the same list for the duration. Like I said, this is probably inefficient, and would require a look at the context of the question, but that is what I would do 9unless you have a big need for binding in this circumstance)
/**
* Returns a new Array with the selected objects removed
*/
private function removeSelected(selectedItems:Array):Array
{
var returnArray:Array = []
for each(var object:Object in this.arrayQueue)
{
if( selectedItems.indexOf(object)==-1 )
returnArray.push( object )
}
return returnArray;
}
You might be interested in this blog entry about the fact that robust iterators are missing in the Java language.
The programming language, you mentioned Javascript, is not the issue, it's the concept of robust iterators that I wanted to point out (the paper actually is about C++ as the programming language).
The [research document]() about providing robust iterators for the ET++ C++ framework may still e helpful in solving your problem. I am sure the document can provide you with the necessary ideas how to approach your problem.

Categories