I'm trying to learn ES2015 and i am having issue with the small function that i have to parse some DOM element and to find all textnodes and delete them.
And I have this function in simple for loop statement.
function deleteTextNodes(element) {
if(!element) {
throw new Error ("Element doesn't exist")
}
for (let i = 0; i < element.childNodes.length; i ++) {
if (element.childNodes[i].nodeType == 3) {
element.removeChild(element.childNodes[i]);
i--;
} else if(element.childNodes[i].nodeType == 1) {
deleteTextNodes(element.childNodes[i])
}
}
And my try to rewrite function in for...on statement syntax below
function deleteTextNodes(element) {
if(!element) {
throw new Error ("Element doesn't exist")
}
for( elem of element.childNodes ) {
console.log(elem, elem.nodeType, );
if (elem.nodeType == 3) {
element.removeChild(elem);
} else if(elem.nodeType == 1) {
deleteTextNodes(elem)
}
}
return true
}
Probably, second function works fine except one - for...on jump over next one node after deletes textnode or something like this. I fix this issue in, first function by adding i--;
So, the question is how to fix this issue in second function?
Just an alternative suggestion, since the scope is playing around with ES6:
function deleteTextNodes(element) {
if(!element) {
throw new Error ("Element doesn't exist")
}
let coll = [element];
while(coll.length){
[{nodeType:nt}] = ([element,...coll] = coll);
if (nt == 3) {
element.remove();
} else if(nt == 1) {
coll.push(...element.childNodes);
}
}
}
This prevents the need for recursion by continuously adding the childnodes to the collection. [element,...coll] = coll assigns the first element to the element var and assigns the remaining collection to coll.
Not necessarily better, but it shows some nice elements of destructuring.
function deleteTextNodes(element) {
if (!element) {
throw new Error("Element doesn't exist")
}
let removeElement = [];
for (elem of element.childNodes) {
if (elem.nodeType == 3) {
removeElement.push(elem);
} else if (elem.nodeType == 1) {
deleteTextNodes(elem)
}
}
removeElement.forEach(elem =>elem.remove());
return true
}
Related
I'm trying to get automated tests back up and running and having the following issue when I run a test.
The objective of the function is if the selector comes from a section of a page object selector will be an array of objects starting from the outermost ancestor (section), and ending with the element Join their selectors in order.
Error message:
Error while running "getElements" command: ancestors.shift is not a function
Function:
function getSelector(selector) {
let ancestors = selector;
if (typeof ancestors !== "string") {
selector = "";
let oElement = ancestors.shift();
while (oElement) {
selector += " " + oElement.selector;
oElement = ancestors.shift();
}
}
return selector;
}
The code which calls the getSelector function below:
selector(selector) {
return featureHelper.getSelector(selector);
}
getElement(result) {
if (result.status === 0 && Array.isArray(result.value)) {
for (let i = 0; i < result.value.length; i++) {
if (typeof result.value[i] === "object") {
result.value[i].ELEMENT =
result.value[i][Object.keys(result.value[i])[0]];
}
}
return result;
} else {
return result;
}
}
};
I have been stuck on this issue for some time now. I am calling an API - get the results just fine. I am saving the values to an array. The problem which I am encountering is trying to get specific values from the array. I have a for in loop running which takes time, so when the if statement is ran the loop hasn't reached that value. If I use Postman, I see that the value exists, its just the loop doesn't execute in time. Here is my code:
var msg = {};
var embed = {};
var link = {};
var msgIn = [];
var rel = [];
return SkypeService.getEvent(msg).then(function (result) {
msg.eventsNext = result._links.next.href;
if (result && result.sender && result.sender.length > 0) {
if (result.sender) {
for (var item in result.sender) {
var event = result.sender[item].events;
for (var key in event) {
embed = event[key]._embedded;
msgIn.push(embed);
}
for (var key in event) {
link = event[key].link;
rel.push(link);
}
// console.log(Object.entries(msgIn))
if(rel['rel'] == 'message') {
console.log("message is there")
if(msgIn.message) {
console.log("links exist")
if(msgIn.message.direction == "Incoming") {
console.log("direction is there")
msg.participant = msgIn.message._links.participant.href;
msg.contMsg = msgIn.message._links.messaging.href;
msg.msgIn = msgIn.message._links.plainMessage.href;
break;
}
}
}
if(rel['rel'] == "messagingInvitation"){
console.log("invite there")
if(msgIn.messagingInvitation && msgIn.messagingInvitation.state !== "Failed") {
console.log("invite link")
if(msgIn.messagingInvitation.direction == "incoming") {
console.log("direction invite")
msg.msgInviteState = msgIn.messagingInvitation._links.state;
msg.acceptInvite = msgIn.messagingInvitation._links['accept'].href;
msg.msgIn = msgIn.messagingInvitation._links.message.href;
break;
}
}
}
if(rel['rel'] == 'messaging') {
console.log('messaging there')
if(msgIn.messaging) {
if(msgIn.messaging.state == "Disconnected") {
console.log("msgn Disconnected")
msg.addMsg = msgIn.messaging._links.addMessaging.href;
break;
}
}
}
}
}
}
console.log(msg)
})
Also, I've attached a screenshot of my local host printing the msgIn which shows that the keys exists.
When I test the code running sails lift, I can see that msgIn prints a couple of times each one increasing in length. This is what makes me think the for loop has not completed by the time the if statement runs.
Please help - I really need for this to be resolved. I need to capture the links so that I can use those in the next step.
Thanks.
I have resolved my issue by making changes to the code. Here is the new version:
return
SkypeService.getEvent(msg).then(function
(result) {
msg.eventsNext = result._links.next.href;
if (result.sender) {
for (var item in result.sender) {
var event = result.sender[item].events;
for (var key in event) {
embed = event[key]._embedded;
link = event[key].link;
};
if(link['rel'] == 'message') {
console.log("message is there")
if(embed.message) {
console.log("links exist")
if(embed.message.direction == "Incoming") {
console.log("direction is there")
msg.participant = embed.message._links.participant.href;
msg.contMsg = embed.message._links.messaging.href;
msg.msgIn = embed.message._links.plainMessage.href;
break;
}
}
};
if(link['rel'] == "messagingInvitation"){
console.log("invite there")
if(embed.messagingInvitation) {
console.log("invite link")
if(embed.messagingInvitation.direction == "incoming") {
console.log("direction invite")
msg.msgInviteState = embed.messagingInvitation._links.state;
msg.acceptInvite = embed.messagingInvitation._links['accept'].href;
msg.msgIn = embed.messagingInvitation._links.message.href;
break;
}
}
};
if(link['rel'] == 'messaging') {
console.log('messaging there')
if(embed.messaging) {
if(embed.messaging.state == "Disconnected") {
console.log("msgn Disconnected")
msg.addMsg = embed.messaging._links.addMessaging.href;
break;
}
}
};
console.log(msg)
};
};
});
I have removed the result validation and simplified the for (var key in event) to handle both operations in one. Also, I have removed the arrays which I was pushing the values into as I was not using that. That may have been the time consuming factor which was preventing me from getting the direction validated.
I'm tearing my hair out about a Syntax Error: Unexpected Identifier that I can't figure out. I know what the error means, but as far as I can tell there's nothing wrong.
I've posted the entirety of the script I'm using; what the code is meant to do is allow a user to step through a replay of a gomoku-like game one move at a time. The game data is stored in a csv file that has a row for every move and contains multiple games. Games are identified by an index value.
var replayArray = [],
rawData=[[]];
function importData(matchID,gI) {
var dataPromise = $.ajax({
url:"./data/" + matchID + ".csv",
dataType: 'text'
})
dataPromise.then(function(data) {
rawData = data;
rawData = String(rawData);
rawData = rawData.split(/\n/);
for (h = 0; h < rawData.length; h++){
rawData[h] = String(rawData[h]).split(",");
}
}).done(function(data){
dataToArray(gI,actionReplayKeydown);
})
}
function dataToArray(gI,cb) {
var f = 0;
var g = 0;
for (var i = 0; i < rawData.length; i++) {
var turnArray = [];
if (parseInt(eval(rawData[i][1])) === gI) {
turnArray[0] = colorToNumber(eval(rawData[i][5]));
turnArray[1] = parseInt(eval(rawData[i][6]));
replayArray[g] = turnArray;
g++;
} else {
doNothing();
}
}
cb(replayArray);
}
The dataToArray function is where the problem occurs, in the line
if (parseInt(eval(rawData[i][1])) === gI) {
I think dev tools has been indicating the problem occurs at rawData[i][1], but rawData is a two dimensional array and the indexing should work fine (the first column of rawData contains the game index, and I want all rows where the value of the game index equals the index of the queried game).
The rest of the code follows but is not afaik problematic.
function colorToNumber(inputColor) {
if (inputColor === "B" ) {
return 0
} else {
return 1
}
}
function actionReplay(inputArray) {
addStone(parseInt(inputArray[f][1]),parseInt(inputArray[f][0]));
f++;
$('#whiteLastMove').remove();
$('#blackLastMove').remove();
if ((f+1)===inputArray.length){
$(document).off('keyup').on('keyup',function(e){
if (e.keyCode === 32) {
clearBoard();
createTiles(M,N);
replayArray = [];
rawData="";
}
});
}
}
function actionReplayKeydown() {
$(document).off('keyup').on('keyup',function(e) {
if (e.keyCode === 13) {
actionReplay(replayArray);
evaluateWin(0);
evaluateWin(1);
} else if (e.keyCode === 32) {
clearBoard();
createTiles(M,N);
replayArray = [];
rawData="";
} else {
doNothing();
}
});
}
function playReplay(matchID,gI) {
openCurtain(doNothing);
importData(matchID,gI);
}
I'm sure I'm missing something obvious, but I'm just not figuring it out on my own.
The issue is that there is a js syntax error in the value of rawData[i][1]. If you use your debugger you can see the value and check whether it's valid js for eval to execute.
I am using Typeahead by twitter. I am running into this warning from Intellij. This is causing the "window.location.href" for each link to be the last item in my list of items.
How can I fix my code?
Below is my code:
AutoSuggest.prototype.config = function () {
var me = this;
var comp, options;
var gotoUrl = "/{0}/{1}";
var imgurl = '<img src="/icon/{0}.gif"/>';
var target;
for (var i = 0; i < me.targets.length; i++) {
target = me.targets[i];
if ($("#" + target.inputId).length != 0) {
options = {
source: function (query, process) { // where to get the data
process(me.results);
},
// set max results to display
items: 10,
matcher: function (item) { // how to make sure the result select is correct/matching
// we check the query against the ticker then the company name
comp = me.map[item];
var symbol = comp.s.toLowerCase();
return (this.query.trim().toLowerCase() == symbol.substring(0, 1) ||
comp.c.toLowerCase().indexOf(this.query.trim().toLowerCase()) != -1);
},
highlighter: function (item) { // how to show the data
comp = me.map[item];
if (typeof comp === 'undefined') {
return "<span>No Match Found.</span>";
}
if (comp.t == 0) {
imgurl = comp.v;
} else if (comp.t == -1) {
imgurl = me.format(imgurl, "empty");
} else {
imgurl = me.format(imgurl, comp.t);
}
return "\n<span id='compVenue'>" + imgurl + "</span>" +
"\n<span id='compSymbol'><b>" + comp.s + "</b></span>" +
"\n<span id='compName'>" + comp.c + "</span>";
},
sorter: function (items) { // sort our results
if (items.length == 0) {
items.push(Object());
}
return items;
},
// the problem starts here when i start using target inside the functions
updater: function (item) { // what to do when item is selected
comp = me.map[item];
if (typeof comp === 'undefined') {
return this.query;
}
window.location.href = me.format(gotoUrl, comp.s, target.destination);
return item;
}
};
$("#" + target.inputId).typeahead(options);
// lastly, set up the functions for the buttons
$("#" + target.buttonId).click(function () {
window.location.href = me.format(gotoUrl, $("#" + target.inputId).val(), target.destination);
});
}
}
};
With #cdhowie's help, some more code:
i will update the updater and also the href for the click()
updater: (function (inner_target) { // what to do when item is selected
return function (item) {
comp = me.map[item];
if (typeof comp === 'undefined') {
return this.query;
}
window.location.href = me.format(gotoUrl, comp.s, inner_target.destination);
return item;
}}(target))};
I liked the paragraph Closures Inside Loops from Javascript Garden
It explains three ways of doing it.
The wrong way of using a closure inside a loop
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
Solution 1 with anonymous wrapper
for(var i = 0; i < 10; i++) {
(function(e) {
setTimeout(function() {
console.log(e);
}, 1000);
})(i);
}
Solution 2 - returning a function from a closure
for(var i = 0; i < 10; i++) {
setTimeout((function(e) {
return function() {
console.log(e);
}
})(i), 1000)
}
Solution 3, my favorite, where I think I finally understood bind - yaay! bind FTW!
for(var i = 0; i < 10; i++) {
setTimeout(console.log.bind(console, i), 1000);
}
I highly recommend Javascript garden - it showed me this and many more Javascript quirks (and made me like JS even more).
p.s. if your brain didn't melt you haven't had enough Javascript that day.
You need to nest two functions here, creating a new closure that captures the value of the variable (instead of the variable itself) at the moment the closure is created. You can do this using arguments to an immediately-invoked outer function. Replace this expression:
function (item) { // what to do when item is selected
comp = me.map[item];
if (typeof comp === 'undefined') {
return this.query;
}
window.location.href = me.format(gotoUrl, comp.s, target.destination);
return item;
}
With this:
(function (inner_target) {
return function (item) { // what to do when item is selected
comp = me.map[item];
if (typeof comp === 'undefined') {
return this.query;
}
window.location.href = me.format(gotoUrl, comp.s, inner_target.destination);
return item;
}
}(target))
Note that we pass target into the outer function, which becomes the argument inner_target, effectively capturing the value of target at the moment the outer function is called. The outer function returns an inner function, which uses inner_target instead of target, and inner_target will not change.
(Note that you can rename inner_target to target and you will be okay -- the closest target will be used, which would be the function parameter. However, having two variables with the same name in such a tight scope could be very confusing and so I have named them differently in my example so that you can see what's going on.)
In ecmascript 6 we have new opportunities.
The let statement declares a block scope local variable, optionally initializing it to a value.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
Since the only scoping that JavaScript has is function scope, you can simply move the closure to an external function, outside of the scope you're in.
Just to clarify on #BogdanRuzhitskiy answer (as I couldn't figure out how to add the code in a comment), the idea with using let is to create a local variable inside the for block:
for(var i = 0; i < 10; i++) {
let captureI = i;
setTimeout(function() {
console.log(captureI);
}, 1000);
}
This will work in pretty much any modern browser except IE11.
So I have made this code, it works almost perfectly. The one thing is that setupListener method returns a error at the end, probably it finds a property I don't want to find. I've been struggling with this for a while, but I clearly don't have that much experience with js to solve it. I have setup some console.log methods for debugging. You use this function like this: setupListener(Id or Class or a Tag name as string, event to watch on as string, and an action);
eg. setupListener('.pun', 'click', function (e){ console.log(e); });
var getElement = function (onStr) {
if (onStr.indexOf('.') === 0) {
onStr = onStr.slice(1);
return document.getElementsByClassName(onStr);
}
if (onStr.indexOf('#') === 0) {
onStr = onStr.slice(1);
return document.getElementById(onStr);
}
if (onStr.indexOf('#') !== 0 && onStr.indexOf('.') !== 0) {
return document.onStr = document.getElementsByTagName(onStr);
}
};
var setupListener = function (elementStr, eventStr, action) {
var tempElement = getElement(elementStr);
// element a collection and has addEventListener method
if (tempElement.length > 1 && tempElement[1].addEventListener) {
for (var i = 1; tempElement.length >= i; i++) {
if (typeof(tempElement[i].addEventListener) !== "undefined")
console.log('1'); //debugging
tempElement[i].addEventListener(eventStr, action);
}
}
// IE < 9 Support
// element a collection and has NOT addEventListener method
else if (tempElement.length > 1 && !tempElement[1].addEventListener) {
for (var i = 1; tempElement.length >= i; i++) {
if (typeof(tempElement[i].addEventListener) !== "undefined")
console.log('2'); // debugging
tempElement[i].attachEvent(eventStr, action);
}
}
// element not a collection and HAS addEventListener method
else if (!tempElement.length > 1 && tempElement.addEventListener) {
console.log('3'); // debugging
tempElement.addEventListener(eventStr, action);
}
// element not a collection and has NOT addEventListener method
// IE < 9 support
else if (!tempElement.length > 1 && !tempElement.addEventListener) {
console.log('4'); // debugging
tempElement.attachEvent(eventStr, action);
}
else {
console.log('5'); // debugging
}
};
Your problem is in the part of the collection of elements. Note that you get the first element of the array by array[0] so you get the last element by array[array.length-1]. But what you are doing when you are iterating over the arrays is, starting at 1 and iterating to array.length. When you try to access an unavailable array-element (as array[array.length]) you will get undefined, and thats ecaxtly what your error says. So you have to change the bounds in the for-loops. I.e.
for (var i = 0; i < tempElement.length; i++)