My recursive function isn't returning anything - javascript

I'm trying to select a the parent form element of an input. The form element isn't necessarily the direct parent node. Currently this outputs "undefined" to my log.
var anInputElement = document.querySelector(...);
var formElement = getFormElement(anInputElement);
console.log(formElement);
function getFormElement(elem) {
//if we've traversed as high as the `body` node then
//we aint finding the `form` node
if(elem.nodeName.toLowerCase() !== 'body') {
var parent = elem.parentNode;
if(parent.nodeName.toLowerCase() === 'form') {
return parent;
} else {
getFormElement(parent);
}
} else {
return false;
}
}
Why am I getting undefined in my console log?

not just
getFormElement(parent);
but
return getFormElement(parent);
and simplified, just for fun:
function getFormElement(elem) {
if(elem.nodeName.toLowerCase() !== 'body') {
var parent = elem.parentNode;
return parent.nodeName.toLowerCase() === 'form' ? parent : getFormElement(parent);
}
return false;
}

Related

How to get and pass the clicked element as THIS inside a addEventListener

So I'm building a small custom JS library like jQuery but I ran into an wall.
I have build a event delegation function which I will be using for a simple click event.
Within this prototype part I will run some logic(in this example a addClass) but it does not work with the this keyword. As I need to add for example a class to the the clicked element.
Constructor.prototype.on = function (eventName , elementSelector, callback) {
document.addEventListener(eventName, function(e) {
for (var target = e.target; target && target != this; target = target.parentNode) {
if (target.matches(elementSelector)) {
callback.call(target, e);
break;
}
}
}, false);
};
// X is the plugin
X('body').on('click','.someClass',function(){
X(this).addClass('clicked');// not going to work
});
You need to change your Constructor function so that it allows you to provide a DOM element, not just a selector, as a parameter. Then you can use X(this) to create an instance of your class that contains this element.
var Constructor = function(selector) {
if (!selector) return;
if (typeof selector != string) {
this.nodes = [selector];
} else if (selector === 'document') {
this.nodes = [document];
} else if (selector === 'window') {
this.nodes = [window];
} else {
this.nodes = document.querySelectorAll(selector);
}
this.length = this.nodes.length;
};

Get the text of an element using querySelectorAll

I am having some trouble getting the text of elements using querySelectorAll. I already tried using querySelector but it only gives me the value of the first element. What I want to achieve is to get the text of the clicked element. Here's my code:
function __(selector){
var self = {};
self.selector = selector;
if(typeof selector == 'undefined'){
self.element = [self.selector];
}else{
self.element = document.querySelectorAll(self.selector);
}
// creating a method (.on)
self.on = function(type, callback){
self.element.forEach(function(elements){
elements['on' + type] = callback;
});
}
self.text = function(elems){
[].slice.call(self.element).forEach(function(el,i){
return el.innerHTML;
});
}
return self;
}
And the HTML File:
<script src="selector.js"></script>
<script>
window.onload = function(){
__('p').on('click', function(){
alert(__(this).text());
});
}
</script>
<p>Hello</p>
<p>World</p>
The code above gives me an undefined value.
Your code actually throws an error: SyntaxError: '[object HTMLElement]' is not a valid selector. This is because at __(this) you’re passing the selected element itself to the function, not a selector. You could account for this by including this as a third case:
if(typeof selector == "undefined"){
self.element = [self.selector];
}
else if(typeof selector == "string"){
self.element = document.querySelectorAll(self.selector);
}
else if(selector instanceof HTMLElement){
self.element = [selector];
}
Now, the actual problem you’ve described occurs because you don’t return anything in the self.text function. The return is for the forEach function argument, which is ignored. Return the texts in the self.text function itself and use map instead of forEach. Something like this:
self.text = function(elems){
return [].slice.call(self.element).map(function(el,i){
return el.innerHTML;
});
}
This will return an array with the inner HTML texts for each element.
Code in action
function __(selector) {
var self = {};
self.selector = selector;
if (typeof selector == "undefined") {
self.element = [self.selector];
} else if (typeof selector == "string") {
self.element = document.querySelectorAll(self.selector);
} else if (selector instanceof HTMLElement) {
self.element = [selector];
}
// creating a method (.on)
self.on = function(type, callback) {
self.element.forEach(function(elements) {
elements['on' + type] = callback;
});
}
self.text = function(elems) {
return [].slice.call(self.element).map(function(el, i) {
return el.innerHTML;
});
}
return self;
}
console.log(__("p").text()); // Logs texts of all <p>s
__("p").on("click", function() {
console.log(__(this).text()); // Logs text of clicked <p>
});
<p>Hello</p>
<p>World</p>

Protractor: wait for an element and then perform action

I have written below code to check for locator type and based on whether element is visible or not, I am returning element. I am receiving error on call type(locatorType, value,text) method with appropriate values.
this.type = function(locatorType,value,text){
this.getElement(locatorType,value).sendKeys(text)
};
this.getElement = function(locatorType,value){
if(locatorType=='model'){
console.log(locatorType)
console.log(value)
return this.waiterFunc(element(by.model(value)));
}
else if(locatorType=='xPath'){
return this.waiterFunc(element(by.xPath(value)));
}
else if(locatorType=='buttonText'){
return this.waiterFunc(element(by.buttonText(value)));
}
};
this.waiterFunc = function(element){
console.log('In waiterfunc')
//console.log(element.getText())
browser.wait(function() {
return this.isVisible(element).then(function(){
return element;
})
})
};
this.isVisible = function(element){
return EC.visibilityOf(element);
};
Below is the error being received:
WebDriver is not able to find the element and perform the actions on it. Please suggest where I am wrong.
Separate the waiting function with the element return:
this.getElement = function(locatorType, value) {
var elm;
if (locatorType == 'model') {
elm = element(by.model(value));
this.waiterFunc(elm);
}
else if (locatorType == 'xPath') {
elm = element(by.xpath(value)); // also renamed xPath -> xpath
this.waiterFunc(elm);
}
else if (locatorType == 'buttonText') {
elm = element(by.buttonText(value));
this.waiterFunc(elm);
}
return elm;
};
In this case the waiterFunc would become simpler:
this.waiterFunc = function(element){
browser.wait(this.isVisible(element));
};

Finding closest element without jQuery

I am trying to find the closest element with a specific tag name without jquery. When I click on a <th> I want to get access to the <tbody> for that table. Suggestions? I read about offset but didn't really understand it too much. Should I just use:
Assume th is already set to clicked th element
th.offsetParent.getElementsByTagName('tbody')[0]
Very simple:
el.closest('tbody')
Supported on all browsers except IE.
UPDATE: Edge now support it as well.
No need for jQuery.
More over, replacing jQuery's $(this).closest('tbody') with $(this.closest('tbody')) will increase performance, significantly when the element is not found.
Polyfill for IE:
if (!Element.prototype.matches) Element.prototype.matches = Element.prototype.msMatchesSelector;
if (!Element.prototype.closest) Element.prototype.closest = function (selector) {
var el = this;
while (el) {
if (el.matches(selector)) {
return el;
}
el = el.parentElement;
}
};
Note that there's no return when the element was not found, effectively returning undefined when the closest element was not found.
For more details see:
https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
Little (very) late to the party, but nonetheless. This should do the trick:
function closest(el, selector) {
var matchesFn;
// find vendor prefix
['matches','webkitMatchesSelector','mozMatchesSelector','msMatchesSelector','oMatchesSelector'].some(function(fn) {
if (typeof document.body[fn] == 'function') {
matchesFn = fn;
return true;
}
return false;
})
var parent;
// traverse parents
while (el) {
parent = el.parentElement;
if (parent && parent[matchesFn](selector)) {
return parent;
}
el = parent;
}
return null;
}
Here's how you get the closest element by tag name without jQuery:
function getClosest(el, tag) {
// this is necessary since nodeName is always in upper case
tag = tag.toUpperCase();
do {
if (el.nodeName === tag) {
// tag name is found! let's return it. :)
return el;
}
} while (el = el.parentNode);
// not found :(
return null;
}
getClosest(th, 'tbody');
There exists a standardised function to do this: Element.closest.
Most browsers except IE11 support it (details by caniuse.com). The MDN docs also include a polyfill in case you have to target older browsers.
To find the closest tbody parent given a th you could do:
th.closest('tbody');
In case you want to write the function yourself - here is what I came up with:
function findClosestParent (startElement, fn) {
var parent = startElement.parentElement;
if (!parent) return undefined;
return fn(parent) ? parent : findClosestParent(parent, fn);
}
To find the closest parent by tag name you could use it like this:
findClosestParent(x, element => return element.tagName === "SECTION");
function closest(el, sel) {
if (el != null)
return el.matches(sel) ? el
: (el.querySelector(sel)
|| closest(el.parentNode, sel));
}
This solution uses some of the more recent features of the HTML 5 spec, and using this on older/incompatible browsers (read: Internet Explorer) will require a polyfill.
Element.prototype.matches = (Element.prototype.matches || Element.prototype.mozMatchesSelector
|| Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector
|| Element.prototype.webkitMatchesSelector || Element.prototype.webkitMatchesSelector);
Here's the simple function I am using:-
function closest(el, selector) {
var matches = el.webkitMatchesSelector ? 'webkitMatchesSelector' : (el.msMatchesSelector ? 'msMatchesSelector' : 'matches');
while (el.parentElement) {
if (el[matches](selector)) return el;
el = el.parentElement;
}
return null;
}
To extend #SalmanPK answer
it will allow to use node as selector, useful when you working with events like mouseover.
function closest(el, selector) {
if (typeof selector === 'string') {
matches = el.webkitMatchesSelector ? 'webkitMatchesSelector' : (el.msMatchesSelector ? 'msMatchesSelector' : 'matches');
while (el.parentElement) {
if (el[matches](selector)) {
return el
};
el = el.parentElement;
}
} else {
while (el.parentElement) {
if (el === selector) {
return el
};
el = el.parentElement;
}
}
return null;
}
Summary:
For finding a particular ancestor we can use:
Element.closest();
This function takes a CSS selector string as an argument. it then returns the closest ancestor of the current element (or the element itself) which matches the CSS selector which was passed in the arguments. If there is no ancestor it will return null.
Example:
const child = document.querySelector('.child');
// select the child
console.dir(child.closest('.parent').className);
// check if there is any ancestor called parent
<div class="parent">
<div></div>
<div>
<div></div>
<div class="child"></div>
</div>
</div>
Get closest DOM element up the tree that contains a class, ID, data attribute, or tag. Includes the element itself. Supported back to IE6.
var getClosest = function (elem, selector) {
var firstChar = selector.charAt(0);
// Get closest match
for ( ; elem && elem !== document; elem = elem.parentNode ) {
// If selector is a class
if ( firstChar === '.' ) {
if ( elem.classList.contains( selector.substr(1) ) ) {
return elem;
}
}
// If selector is an ID
if ( firstChar === '#' ) {
if ( elem.id === selector.substr(1) ) {
return elem;
}
}
// If selector is a data attribute
if ( firstChar === '[' ) {
if ( elem.hasAttribute( selector.substr(1, selector.length - 2) ) ) {
return elem;
}
}
// If selector is a tag
if ( elem.tagName.toLowerCase() === selector ) {
return elem;
}
}
return false;
};
var elem = document.querySelector('#some-element');
var closest = getClosest(elem, '.some-class');
var closestLink = getClosest(elem, 'a');
var closestExcludingElement = getClosest(elem.parentNode, '.some-class');
Find nearest Elements childNodes.
closest:function(el, selector,userMatchFn) {
var matchesFn;
// find vendor prefix
['matches','webkitMatchesSelector','mozMatchesSelector','msMatchesSelector','oMatchesSelector'].some(function(fn) {
if (typeof document.body[fn] == 'function') {
matchesFn = fn;
return true;
}
return false;
});
function findInChilds(el){
if(!el) return false;
if(el && el[matchesFn] && el[matchesFn](selector)
&& userMatchFn(el) ) return [el];
var resultAsArr=[];
if(el.childNodes && el.childNodes.length){
for(var i=0;i< el.childNodes.length;i++)
{
var child=el.childNodes[i];
var resultForChild=findInChilds(child);
if(resultForChild instanceof Array){
for(var j=0;j<resultForChild.length;j++)
{
resultAsArr.push(resultForChild[j]);
}
}
}
}
return resultAsArr.length?resultAsArr: false;
}
var parent;
if(!userMatchFn || arguments.length==2) userMatchFn=function(){return true;}
while (el) {
parent = el.parentElement;
result=findInChilds(parent);
if (result) return result;
el = parent;
}
return null;
}
Here.
function findNearest(el, tag) {
while( el && el.tagName && el.tagName !== tag.toUpperCase()) {
el = el.nextSibling;
} return el;
}
Only finds siblings further down the tree. Use previousSibling to go the other way
Or use variables to traverse both ways and return whichever is found first.
You get the general idea, but if you want to traverse through parentNodes or children if a sibling doesn't match you may as-well use jQuery. At that point it's easily worth it.
A little late to the party, but as I was passing by and just answer back a very similar question, I drop here my solution - we can say it's the JQuery closest() approach, but in plain good ol' JavaScript.
It doesn't need any pollyfills and it's older browsers, and IE (:-) ) friendly:
https://stackoverflow.com/a/48726873/2816279
I think The easiest code to catch with jquery closest:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script>
$(document).ready(function () {
$(".add").on("click", function () {
var v = $(this).closest(".division").find("input[name='roll']").val();
alert(v);
});
});
</script>
<?php
for ($i = 1; $i <= 5; $i++) {
echo'<div class = "division">'
. '<form method="POST" action="">'
. '<p><input type="number" name="roll" placeholder="Enter Roll"></p>'
. '<p><input type="button" class="add" name = "submit" value = "Click"></p>'
. '</form></div>';
}
?>
Thanks much.

Mustache js takes parent's object scope when none is found in the current one

According to the mustache RFC
A {{name}} tag in a basic template will try to find the name key in
the current context. If there is no name key, nothing will be
rendered.
I therefore expected this:
var template = '{{#anArray}}{{aString}}{{/anArray}}';
var json = {
"aString":"ABC",
"anArray": [1,{"aString":"DEF"}]
};
To give me once rendered:
"DEF"
However mustache.js looks for values in the parent's scope. Which gives me
"ABCDEF"
Do the context actually means including all the parents scopes ?
http://jsfiddle.net/ZG4zd/20/
Short answer: yes.
A bit longer answer. Context.prototype.lookup does a while loop, looking up a token in current context and it's parent contexts, while there is a parent context.
Relevant bit of code:
Context.prototype.lookup = function (name) {
var value = this._cache[name];
if (!value) {
if (name === ".") {
value = this.view;
} else {
var context = this;
//Iterate ancestor contexts
while (context) {
if (name.indexOf(".") > 0) {
var names = name.split("."), i = 0;
value = context.view;
while (value && i < names.length) {
value = value[names[i++]];
}
} else {
value = context.view[name];
}
if (value != null) {
break;
}
context = context.parent;
}
}
this._cache[name] = value;
}
if (typeof value === "function") {
value = value.call(this.view);
}
return value;
};

Categories