I need to run through a stack of HTML Elements. But all my attempts to write a recursive function didn't work.
In the snippet below I cannot return value from if statement and afterwards from a function itself.
I can console.log the information I need, it gets there, but trying to return it, it doesn't work.
I never had such an issue with returning some data that's why I decided to display it here so as to let a fresh-eye to revise the code.
function findElementByDataValue(target: EventTarget, data: {key: string, value: string}){
if (target.dataset[data.key] === data.value) {
return target;
};
if (target.children.length > 0) {
for (const child in target.children) {
const element = target.children[child];
// I tried to return "recursive" function here too. "Return" Abrupt execution (as it should)
if (element.children && typeof element === 'object') {
findElementByDataValue(element, data);
}
}
}
}
if you have any ideas or noticed an issue with my recursive function, I would appreciate any help.
Using the return keyword like you are doing.
Your second function calls the first function without returning the value returned by the first function:
Replace
if (element.children && typeof element === 'object') {
findElementByDataValue(element, data);
}
with:
if (element.children && typeof element === 'object') {
return findElementByDataValue(element, data);
}
In general, run your code in a debugger (popular web browsers provide debuggers) to see what is going on.
See some debuggers documentation:
https://developer.mozilla.org/en-US/docs/Tools/Debugger
https://developers.google.com/web/tools/chrome-devtools/javascript/
If you are new to JavaScript, I suggest looking into unit testing and test-driven development.
Writing tests (early) will help you think of what can go wrong with your code and write more robust functions. Jasmine is nice, this article suggests many other JavaScript unit testing options
Related
I'm working on a website, with jQuery but I'm trying to not use it anymore. In jQuery you can add an even listener on a element that wasn't on the website or wasn't created yet and no problem. I have elements that are only on the DOM when you're logged in, and I only have one JS file for the whole website.
Problem is, for example, when you're logged in you can't see the "log in" button, it's not even in the DOM, but it still have the event listener in the code, no error on the console, script runs well.
$("#logInButton").on("click", somefunction);
But, using document.querySelector("#logInButton").onclick = somefunction and being logged in already, it throws an error because document.querySelector("#logInButton") is null.
I can do like:
let logInButton = document.querySelector("#logInButton");
logInButton ? logInButton.onclick = somefunction : "";
And it works well, but I know it's not a good practice. Any workaround or improvement to that, not using jQuery?
JSFiddle if what happens. (See console)
And it works well, but I know it's not a good practice.
If having #logInButton on the page is optional, that's perfectly good practice — other than using onclick rather than addEventListener (but that's probably a matter of style). Naturally, you'd have this code in a script linked at the end of the document, just prior to the </body> tag (or trigger it via a DOMContentLoaded callback).
But if you want the equivalent of the jQuery, you need to think in jQuery's "set-based" mindset and use querySelectorAll:
// Not very efficient
document.querySelectorAll("#logInButton").forEach(function() {
// Set up the handler here using `this`
});
Except that jQuery optimizes queries using #id format to a getElementById call (which is dramatically faster) and then uses an if (like yours) to build the set with either one element or zero.
Perhaps in your quest to not use jQuery, you might give yourself a couple of helper functions to take its place, as the DOM API is quite verbose. If you like jQuery's set-based nature, you might even make them set-based:
function MyQuery(selector) {
if (!selector) {
this.data = [];
} else if (typeof selector === "string") {
// (jQuery takes it further than this, search in an unminified version for `rquickExpr`)
var id = /#([\w-]+)/.match(selector);
if (id) {
var e = document.getElementById(id[0]);
this.data = e ? [e] : [];
} else {
this.data = Array.from(document.querySelector(selector));
}
} else {
/* ...handle other things, such as DOM elements or arrays of them...? */
this.data = /*...*/;
}
}
MyQuery.prototype = {
constructor: MyQuery,
on: function(eventName, handler) {
this.data.forEach(function(element) {
element.addEventListener(eventName, handler);
});
return this;
}
// ...etc...
};
function qset(selector) {
return new MyQuery(selector);
}
Then
qset("#logInButton").on("click", /*...*/);
Of course, you might find yourself basically recreating jQuery. But if you keep it lean...
Side note: Using forEach on the return value of querySelectorAll requires an up-to-date browser, or that you polyfill it:
if (typeof NodeList !== "undefined" &&
NodeList.prototype &&
!NodeList.prototype.forEach) {
Object.defineProperty(NodeList.prototype, "forEach", {
value: Array.prototype.forEach
});
}
For truly obsolete browsers (like IE8), you'd have to polyfill Array.prototype.forEach first.
You can do it the same way jQuery does it, using event bubbling.
document.addEventListener('click', function (ev) {
if (ev.target.id === 'someIdHere') {
console.log('click');
}
});
I am making some SPA app. When app is running at some point i start one method. Some code in there give me boolean. I would like to stop method when is 'half executed' and that boolean is false. I need something like break for loop, but that might work with methods. It is possible ?
Thanks for answers.
if(el === 'work'){
if(!actions.authentication()){
// That code is in another method. I want break that with some code placed here.
}
}
It's kinda obvious and you almost answered yourself, but you can use return ...
if(el === "work") {
if(actions.authentication() == false) {
return
}
}
I'm using Protractor JS. And the site is written in Angular JS.
So I have a toggle switch. And I noticed the value in the toggle switch goes from true to false and
false to true when you switch it off or on.
I am trying create a condition when Protractor visits my page when it sees the toggle switch 'off' it will turn it 'on'. If the toggle switch is already 'on', it will first turn it 'off' then turn it 'on' again.
I came up with this code, but for some reason it is not working:
if( expect(element(By.id('toggle-switch')).element(By.css('[value="false"]')).isDisplayed()) ) {
element(By.id('toggle-switch')).click();
console.log('in the if')
}
else{
element(By.id('toggle-switch')).click();
browser.sleep(3000);
element(By.id('toggle-switch')).click();
console.log('in the else')
}
This code appears to work only for the if statement. For some reason it will never go to the else. Here is the error I'm receiving:
NoSuchElementError: No element found using locator: By.cssSelector("[value=\"false\"]")
So then I tried
.isPresent() instead of .isDisplayed()
I don't receive the above error anymore, but for some reason when using .isPresent() it always goes to the if statement and only runs that, and never the else statement. No errors displayed.
If there is a better way please let me know. This seems very limiting to not be able to create proper conditions in this framework.
Remember that isDisplayed() returns a promise, you can try with:
element(anyFinder).isDisplayed().then(function(result) {
if ( result ) {
//Whatever if it is true (displayed)
} else {
//Whatever if it is false (not displayed)
}
});
isDisplayed() did not work for me. The API may have been changed. isPresent() is my solution:
var logoutButton = element(by.css('[ng-click="log_out()"]'));
logoutButton.isPresent().then(function(result) {
if ( result ) {
logoutButton.click();
} else {
//do nothing
}
});
The problem is that isDisplayed(), as a lot of methods in WebDriverJS/Protractor, returns a promise which by definition is "truthy" which makes it difficult to debug problems like this.
Let's work through an example to get a better understanding.
Imagine, you have the following code, which may look okay at the first glance:
var elm = $("#myid");
if (elm.isDisplayed()) {
// do smth
} else {
// do smth else
}
Now, it has a serious problem. do smth else part will never be reached, since elm.isDisplayed() is not a boolean value - it is a promise. Even if the element is not displayed, you would still have // do smth part executed.
Instead, if you need to check the value of isDisplayed() to use inside a conditional expression, you have to resolve the promise with then() explicitly:
var elm = $("#myid");
elm.isDisplayed().then(function (isDisplayed) {
if (isDisplayed) {
// do smth
} else {
// do smth else
}
});
There is also a way to catch these kind of errors without even running the code - statically with ESLint and eslint-plugin-protractor plugin. There is a relevant rule that watches if certain Protractor methods are used inside if conditions directly.
Here is what it would output for the code above:
$ eslint test.js
test.js
2:1 warning Unexpected "isDisplayed()" inside if condition protractor/no-promise-in-if
Or try this solution implemented from the top of my head, Schedules a command to test if an element is present on the page. If any errors occur while evaluating the wait, they will be allowed to propagate.
function alwaysSwitchOn(element) {
browser.driver.isElementPresent(element).then(function(isPresent) {
if (isPresent) {
isPresent = true;
}
else {
browser.driver.wait(function () {
return browser.driver.isElementPresent(element);
}, 5000);
}
// to fail the test, then uncomment this line
//expect(isPresent).toBeTruthy();
}).then(function () {
if (element.getAttribute('value') === 'OFF') {
element.click();
}
else {
// turn it OFF
element.click();
// turn it back ON
element.click();
}
});
}
fn usage is to keep trying again and again for 5 seconds till it's true. if the element cannot be found within 5 sec then it'll result in an error code; No such an element is found.Note, If the condition is fulfilled before wait (5s) it'll quickly move to then(...).
If you're in 2021 or the following years
Forget about .then(). Do this instead:
it('test case', async () => {
if (await element(anyFinder).isDisplayed()) {
// Whatever if it is true (displayed)
} else {
// Whatever if it is false (not displayed)
}
});
Consider the following CoffeeScript:
$ ->
if localStorage["Flag1"] isnt "Done"
localStorage["Flag1"] = "Done" # Flagged on first page loading
$(".start").click ->
if localStorage["Flag2"] isnt "Done"
localStorage["Flag2"] = "Done" # Flagged on first click interaction
Which compiles into:
$(function() {
if (localStorage["Flag1"] !== "Done") {
localStorage["Flag1"] = "Done";
}
return $(".start").click(function() {
if (localStorage["Flag2"] !== "Done") {
return localStorage["Flag2"] = "Done";
}
});
});
There are two strange occurrence of "return" being planted into the rendered JavaScript. What do they do, and how will they affect the running of the script? Thanks!
They won't affect the running of your script. The first return will return $(".start") (since the jQuery click method returns an instance of jQuery) from the DOM ready event handler. Since it's a callback that runs at a certain point, you can't really do anything with that return value.
The second return will return "Done", after setting the localStorage property, but again, since it's returning from a callback (a click event handler this time) you won't be able to do anything with the returned value.
I believe CoffeeScript will return the value of the last expression in each function, which is why you see those return statements in the output. From the docs:
Even though functions will always return their final value, it's both
possible and encouraged to return early from a function body writing
out the explicit return (return value), when you know that you're
done.
I'm trying to write a function that will dump a recursive tree of window for all browsers. A problem that I immediately realized I was going to have, had to do with infinite objects (window.window.window.window). Just for laughs, I tried it anyways, and I got an error as I expected. Uncaught RangeError: Maximum call stack size exceeded (testing in Chrome)
So the first approach to check against objects that were going to cause this was simply:
if (variable != 'window' && variable != 'top' && variable != 'self' && variable != 'frames')
I'm thinking maybe that would have worked, and I simply missed a couple. It was a good theory, but I still get the maximum stack error. So I decided to type window in Chrome's console, and manually look for all of the [DOMWindow] types, to add to this list. While doing that, I noticed the Infinity: Infinity value, which brought me to my next approach:
if (typeof namespace[variable]['Infinity'] === 'undefined')
I still got the maximum stack error with that, so I did a bit of Google searching, and learned about isFinite, so now I have: (edit: actually I just realized isFinite isn't what I thought it was)
if (isFinite(tree[variable]))
The error finally went away, but the problem with this approach is that all objects in window are returning false for this, so the recursion fails. I realize that some of the approaches probably aren't even cross-browser compatible, but it would be nice if I could get it to at least work in one browser in the mean time.
So how can I check for objects that are going to cause an infinite loop?
Here's my code, just for anyone who might be interested:
(function () {
window.onload = function () {
window.onload = ''; // don't want to get our own code
console.log((function (namespace) {
tree = {};
for (var variable in namespace) {
/* gonna need these later
var variable_typeof = typeof namespace[variable],
variable_object_tostring = Object.prototype.toString(namespace[variable]);
*/
//if (variable != 'window' && variable != 'top' && variable != 'self' && variable != 'frames')
//if (typeof namespace[variable]['Infinity'] === 'undefined')
if (isFinite(tree[variable]))
tree[variable] = arguments.callee(namespace[variable]);
else tree[variable] = 'Infinity';
}
return tree;
})(window)); // Start from root
}
})();
Update:
Here is a working product of what I finally came up with, for anyone interested.
GGG is worthy of mention for his help.
function loop (namespace) {
if (namespace['__infinite_test']) return '[[recursion]]'; // It's infinite
namespace['__infinite_test'] = true; // Note that we've been through this object
var tree = {};
for (var variable in namespace) {
try { // For an issue in Chrome throwing an error
namespace[variable]['__tester'] = null;
delete namespace[variable]['__tester'];
}
catch (e) {
tree[variable] = namespace[variable];
continue;
}
if (namespace.propertyIsEnumerable(variable)) tree[variable] = loop(namespace[variable]);
else tree[variable] = namespace[variable];
}
return tree;
}
console.log(loop(window));
One way to prevent infinite recursion in your problem is to keep track of a list of all objects you have already visited and if you encounter an object you've already visited, you don't recurse into it.
When you encounter an object that is not in your list, you add it to your list and then recurse into it.