I'm having a lot of trouble figuring out how the recursive function is being done in this example. Problem is that he uses this technique farther down the book and I feel I need to have a grasp of what's he doing or I'm doomed. I've tried to console log the result of the function (part in quotes) to see how it changes down the line, but it doesn't give me a clear view of what's going on. Since returns often exit functions and loops, and the function is returning true at the end, I can't seem to track down how the function's suppose to work, let alone the recursive part
function talksAbout(node, string) {
if (node.nodeType == Node.ELEMENT_NODE) {
for (let child of node.childNodes) {
console.log(talksAbout(child,string));
if (talksAbout(child, string)) {
return true;
}
}
return false;
} else if (node.nodeType == Node.TEXT_NODE) {
return node.nodeValue.indexOf(string) > -1;
}
}
console.log(talksAbout(document.body, "book"));
// → true
From my best understanding of this, here's how it works, assuming the block of code is the talksAbout function.
if (node.nodeType == Node.ELEMENT_NODE) {
I'm guessing this is checking if the node it's looking at is an "element" that likely contains other nodes.
for (let child of node.childNodes) {
console.log(talksAbout(child,string));
if (talksAbout(child, string)) {
return true;
}
}
return false;
Here, the function seems to be iterating through the children of an element. Best I can explain it is, it's basically sending a "chain" of itself down the tree of an element, and if it finds something it's looking for, it returns true. If it doesn't find anything, it returns false.
} else if (node.nodeType == node.text_NODE) {
This is checking if the node it's looking at is a text node, which I'm assuming probably doesn't have any other children.
return node.nodeValue.indexOf(string) > -1;
}
Here, it's checking if the string in the "string" parameter exists inside the node. If it is, it returns true, and this "true" cascades down the line of recursive calls and eventually, the entire function returns true.
If it isn't, it just kinda... does nothing, letting the other calls in the "chain" check for other stuff.
EDIT:
It turns out, the function is massively overcomplicated. It isn't necessary to use a recursive function to search through all of the children: it's possible to just search through node.textContent and check for the string you're looking for. The recursive function is equivalent to this simplified function:
function talksAbout(node, string) {
return node.textContent.indexOf(string) > -1;
}
It also should be noted that if the <script> tag containing the function call is inside the node being searched, the function will always return true because it'll reach into the script tag and find the function's "string" parameter.
Example:
function talksAbout(node,string) {
return node.textContent.indexOf(string) > -1; // functionally equivalent to the recursive function
}
console.log("document.body talks about 'book': " + talksAbout(document.body, "book")); // This script tag is inside document.body, so it finds the "book" string here
console.log("wrapper talks about 'bar': " + talksAbout(document.getElementById("wrapper"), "bar")); // finds string in second layer
console.log("example talks about 'bar': " + talksAbout(document.getElementById("example"), "bar")); // the word "bar" comes before the element being searched
<div id="wrapper">foo
<div>bar
<div id="example">baz</div>
</div>
</div>
Related
This is not for use in my project, Only for learning purposes.
In jQuery,
When we call $('h1'). it simply returns all the h1 elements from the document. Again when we make some action on an element like $('h1').hide(), it simply hides all the elements(cool ah?)
I want to learn this similar functionality, for example:
function app(elm){
const x = (typeof elm !== 'object') ? document.querySelectorAll(elm) : elm
return {
hide : function(){
x.forEach( target =>{
target.style.display = 'none';
});
}
}
}
This is a simple code here. So, If I call it like app('h1').hide(); it will hide all the h1 elements from the document. But if I call it like app('h1') it returns the object what I return that's normal.
In here I need all h1 elements from the document like jQuery. I mean It should work like this,
$('h1') === app('h1') //JQuery is equal to myCFunction (problem)
$('h1').hide === app('h1').hide() //jQuery is equal to myCFunction (solved)
[NOTE] Here is an article that is similar to my question but it's not my question answer.
Article Link
You can return x instead of a custom object, but before returning inject the hide function into x object's prototype like x.prototype.hide = function(){/*...*/}.
I think $("h1") does not return selected elements. It stores the selected elements. Instead we can have new function(getElement) to get select elements.Hope this code helps.
var App = function() {
var x ;
this.app = function (elem) {
x = document.querySelectorAll(elem);
return this;
}
this.hide = function(){
x.forEach(target => {
target.style.display = 'none';
});
return;
}
this.getElement = function(){
return x;
}
}
var $ = new App();
$.app("h1").hide();
console.log($.app("h1").getElement());
I've got a mostly working solution, but you still have to fix one small but annoying problem (see caveat 3). It's mostly done so I'll put it here anyway.
I think this is what you are looking for:
function app(selector) {
const retArr = document.querySelectorAll(selector); // The array to return
// Add proxies for all prototype methods of all elements
for (let e of retArr) {
let methods = getProtoMethods(e);
for (let mKey in methods) {
// Skip if the proxy method already exists in retArr
if (retArr[mKey] !== undefined) continue;
// Otherwise set proxy method
Object.defineProperty(retArr, mKey, {
value: function(...args) {
// Loop through all elements in selection
retArr.forEach(el => {
// Call method if it exists
if (el[mKey] !== undefined) el[mKey](...args);
});
}
});
}
}
return retArr;
// Gets all prototype methods for one object
function getProtoMethods(obj) {
let methods = {};
// Loop through all prototype properties of obj and add all functions
for (let pKey of Object.getOwnPropertyNames(Object.getPrototypeOf(obj))) {
// Skip properties that aren't functions and constructor
if (pKey !== "constructor" && typeof obj[pKey] === "function") {
methods[pKey] = obj[pKey];
}
}
return methods;
}
}
The idea is to put all the selected objects in an array, then define additional methods on the array. It should have all the method names of the selected objects, but those methods are actually proxies of those original methods. When one of these proxy methods is called, it calls the original method on all (see caveat 1) the selected objects in the array. But otherwise the returned object can just be used as a normal array (or more accurately, NodeList in this case).
However it's worth mentioning that there are several caveats with this particular implementation.
The list of proxy methods created is the union of the methods of all selected objects, not intersection. Suppose you selected two elements - A and B. A has method doA() and B has method doB(). Then the array returned by app() will have both doA() and doB() proxy methods. However when you call doA() for example, only A.doA() will be called because obviously B does not have a doA() method.
If the selected objects do not have the same definition for the same method name, the proxy method will use their individual definitions. This is usually desired behaviour in polymorphism but still it's something to bear in mind.
This implementation does not traverse the prototype chain, which is actually a major problem. It only looks at the prototypes of the selected elements, but not the prototypes of prototypes. Therefore this implementation does not work well with any inheritance. I did try to get this to work by making getProtoMethods() recursive, and it does work with normal JS objects, but doing that with DOM elements throws weird errors (TypeError: Illegal Invocation) (see here). If you can somehow fix this problem then this would be a fully working solution.
This is the problematic recursive code:
// Recursively gets all nested prototype methods for one object
function getProtoMethods(obj) {
let methods = {};
// Loop through all prototype properties of obj and add all functions
for (let pKey of Object.getOwnPropertyNames(Object.getPrototypeOf(obj))) {
// Skip properties that aren't functions and constructor
// obj[pKey] throws error when obj is already a prototype object
if (pKey !== "constructor" && typeof obj[pKey] === "function") {
methods[pKey] = obj[pKey];
}
}
// If obj's prototype has its own prototype then recurse.
if (Object.getPrototypeOf(Object.getPrototypeOf(obj)) == null) {
return methods;
} else {
return {...methods, ...getProtoMethods(Object.getPrototypeOf(obj))};
}
}
Sorry I cannot solve your problem 100%, but hopefully this at least somewhat helpful.
I have a 'select' element in a UI component from which I need to retrieve the selected option (if any). As a beginner in both JavaScript and protractor, I am having trouble figuring out how to accomplish this without a bunch of nested promises:
I have two locators -- one for the selector's current selection and one for all the options:
selector = element(by.model("something.someId"));
this.selectorOptions = element.all(by.repeater("repeat in someOptions | orderBy:'name'"));
getSelectedOption = function () {
return this.selector.getText().then( function (selectionText) {
return this.selectorOptions.filter(function (option) {
option.getText().then(function (optionText) {
if(optionText === selectionText) {
option.getAttribute("value").then(function (value) {
// Some logic here which uses the value to return an pojo representing the selection
})
}
})
})
})
};
The above is just awful and I am sure this can be done better. I have looked at a lot of examples, but I haven't found one that involves dealing with nested promises which need to take parameters and then do something conditional based on the value, so I am having difficultly applying them to my situation, mostly because I don't really feel comfortable with asynchronous programming yet. How can I take the mess above and refactor it into something that isn't a nested callback hell?
Maybe playing a little bit with promises, protractor, arguments and bind you could get it quite cleaner.
Then you are using the protractor filter method which needs a boolean to be returned, in order to filter your values. But, from the way you used it, maybe
you were looking for each():
http://www.protractortest.org/#/api?view=ElementArrayFinder.prototype.each
I didn't have any chance to test the following code, so it may most probably not work :D
selector = element(by.model("something.someId"));
this.selectorOptions = element.all(by.repeater("repeat in someOptions | orderBy:'name'"));
getSelectedOption = function () {
return this.selector.getText().then(firstText.bind(this));
};
function firstText(text) {
return this.selectorOptions.filter(filterSelector.bind(this, text));
}
function filterSelector(text, option) {
return option.getText().then(optionText.bind(this, text, option));
}
function optionText(text, option, optionText) {
if(optionText === text) {
return option.getAttribute("value").then(someLogic);
}
}
function someLogic(value) {
console.log(value);
// value should be your value
// Some logic here which uses the value to return an pojo representing the selection
// return true or false, filter is still waiting for a boolean...
}
Another version just using arguments without function parameters. Specially follow the arguments which got printed, to see if the order is correct:
selector = element(by.model("something.someId"));
this.selectorOptions = element.all(by.repeater("repeat in someOptions | orderBy:'name'"));
getSelectedOption = function () {
return this.selector.getText().then(firstText.bind(this));
};
function firstText() {
console.log(arguments);
// arguments[0] should be your selectionText
return this.selectorOptions.filter(filterSelector.bind(this, arguments[0]));
}
function filterSelector() {
console.log(arguments);
// arguments[0] should be your previous selectionText
// arguments[1] should be your option
return arguments[1].getText().then(optionText.bind(this, arguments[0], arguments[1]));
}
function optionText() {
console.log(arguments);
// arguments[0] should be your optionText
// arguments[1] should be your selectionText
// arguments[2] should be your option
if(arguments[0] === arguments[1]) {
return arguments[2].getAttribute("value").then(someLogic);
}
}
function someLogic(value) {
console.log(value);
// value should be your value
// Some logic here which uses the value to return an pojo representing the selection
// return true or false, filter is still waiting for a boolean...
}
I was simply practicing a little bit of JavaScript. My goal was to create a function that can call another function with the .invoke() until .revoke() is called, which then nullifies the function.
Later on, I've added .porcupine() which was, in theory, supposed to take the firstly invoked function (in this case, alert()) and then reapply it to the original "temp". The issue is, though, after being revoked temp becomes unknown, therefore it can not call anything anymore. Is there something very obvious to this that I'm missing out or will the solution have to be fairly messy?
var denullifier;
function revocable(unary) {
if (denullifier === null)
denullifier = unary;
return {
invoke: function(x) {
return unary(x);
},
revoke: function() {
var nullifier = unary;
unary = null;
return nullifier.apply(this, arguments);
},
porcupine: function() {
unary = denullifier;
return unary.apply(denullifier, arguments);
}
};
};
console.log('----------');
temp = revocable(alert);
temp.invoke(7); ///alerts 7
temp.revoke();
temp.porcupine(); //exception
temp.invoke(7); //doesn't get here
I don't quite understand what you're doing, but there are a few problems with your code.
if (denullifier === null)
denullifier = unary;
denullifier is not null here, it's undefined - so the condition isn't met.
return nullifier.apply(this, arguments);
You can't call alert this way, the first param must be null or window.
return unary.apply(denullifier, arguments);
The same.
This is your problem:
var denullifier;
function revocable(unary) {
if (denullifier === null)
denullifier = unary;
denullifier is undefined when declared without a value. However, you are checking for type-strict equality with null, which will be false, so denullifier is never set and porcupine is not able to restore the unary function.
I'd suggest:
Use == instead of === to get equality with undefined
Even better, use typeof denullifier != "function"
Or, (although I don't know your design) you should not make denullifier a global, static variable that will be shared amongst revocable instances, but instead make it instance-specific by putting the declaration inside the function body.
I have a javascript method that takes a single parameter. That parameter can be a path (in which case the function will use ajax to resolve the variable, a function (which will be called to resolve the variable, or valid markup (which will be used directly).
I'm looking for suggestions for distinguishing between a string that represents a path and a string that represents markup.
Note: I'm not too concerned if the markup is invalid
Well if you are stuck with the function you got, than you would have to use a regular expression to dtermine what you got. Problem with that, it can be error prone.
You are better off passing in an object and letting the object tell you what you have based on what properties it has.
function myMethod( obj ) {
if (obj.path) {
console.log("I have a path:", obj.path);
} else {
console.log("I have a path:", obj.html);
}
}
myMethod({"path":"/foo/bar/"});
myMethod({"html":"<p>/foo/bar/</p>"});
You can use something like this:
function validate(param) {
if (typeof(param) == 'function') {
console.log('It is a function!')
} else if (typeof(param) == 'string') {
if (param.indexOf('http') == 0) {
console.log('It is a path!')
} else {
console.log('It is a markup!')
}
}
}
It's far from perfect (especially in Path department) but it's a starting point.
Demo: http://jsfiddle.net/4369k/
I am trying to create a recursive function that will loop through a multidimensional object and test whether the key exists in a separate object. If the key does not exist I want to break the loop and return false, if all the keys exist I want to return true.
The problem I am having is that the function always seems to be returning true. Here is the code I am using:
var properties = {'global': {'structure' : {'body': {}}}};
var testExists = {'global': {'structure': {'test': 'value'}}};
if( ! this.exists(properties, testExists)) {
console.log("DOESNT EXIST");
}
exists: function(destination, source) {
var exists = true;
check:
for (var property in source) {
if(destination[property]) {
arguments.callee(destination[property], source[property]);
}
else
{
exists = false;
break check;
}
}
console.log(exists);
return exists;
},
When I view the console to see the value of 'exists' I see two line the first false the second is true, so there must be an error with the recursion I am creating
Your problem seems to be that you don't use the result of the recursively called function.
Also, you shouldn't use arguments.callee, but a function name, and potentially check for the parameters to be objects before enumerating their properties. And you might want to check also for properties of destination that are not in the enumerated source.
Try this:
function equal(destination, source) {
if (Object(destination)!==destination || Object(source)!==source)
// at least one of them is a primitive value
return destination == source; // maybe use strict equality === ?
for (var prop in source)
if (!(prop in destination) || !equal(source[prop], destination[prop]))
return false;
return true;
}
You're making it more complicated than it needs to be:
function exists(destination, source) {
for (var property in source) {
if(destination.hasOwnProperty(property)) {
if (!exists(destination[property], source[property])) {
return false;
}
} else {
return false;
}
}
return true;
}
Note that .hasOwnProperty means that this will only compare direct properties of the objects and not those inherited from prototypes. I assumed that this was what you were looking for.
Also note: it actually uses the result of the recursive calls, it recurses properly, it uses .hasOwnProperty instead of just checking falsiness, and it doesn't use intermediate variables to store the result (which wouldn't work in a recursion the way you were using them, anyway).
One more thing: This will only go "one way," i.e. any properties in the destination that are not in the source will not be checked. To check both ways, you have to call it twice or extend it to loop over both.