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.
Related
One can create shorthand for document.querySelector with
const $ = document.querySelector.bind(document);
const $$ = document.querySelectorAll.bind(document);
so now let a = $('a') and let a = document.querySelector('a') are equivalent.
Is there a way to create shorthand for the querySelector method itself?
I.e. to make let a = element.shortHand(args) and let a = element.querySelector(args) to be equivalent for any (unknown in advance) element.
Edit: Since people are telling that doing the above is a bad idea, there is another question: How to make$ $$ selectors like the one in the Chrome DevTools, which accept the root element as second parameter?
I.e. to make let a = $('a',element) and let a = element.querySelector('a') to be equivalent.
Here are some options:
Add Method to Element.prototype
Element.prototype.shortHand = Element.prototype.querySelector
This "monkey-patches" the Element class in the DOM itself and adds this function on all elements in DOM, which is just a copy of the querySelector function.
This is very discouraged. It's bad for performance and it is bad in case browsers decide to add more functions in the future that conflicts with your function. But if you're just playing around and not shipping this code it should be fine.
Mini jQuery
If you're looking to create your own mini jQuery, you can also do something like this:
class MiniJQuery {
constructor(el) {
this.el = el;
}
shortHand(...query) {
return this.el.querySelector(...query);
}
// ... put any other functions you want to use
}
const $ = (queryOrElement) => {
if (typeof queryOrElement === 'string') {
return document.querySelector(queryOrElement);
}
return new MiniJQuery(queryOrElement);
}
// Now you can:
const a = $(element).shortHand(args);
// which is equivalent to
const a = element.querySelector(args);
This is a much safer approach and not problematic. I don't think this adds much value as you can just type the slightly longer method name, but you could add more interesting methods on your class to make it worthwhile.
Proxy
Very similar to the approach above, but you can use a Proxy instead of the MinijQuery class to "forward" unknown methods to the element itself. This means that $(element) will have all the methods that element itself has.
Example:
const handler = {
get: function (target, prop, receiver) {
if (prop === "shortHand") {
return target.querySelector.bind(target);
}
const retVal = Reflect.get(...arguments);
// Bind methods to the element.
return typeof retVal === 'function'
? retVal.bind(target)
: retVal;
},
};
const $ = (queryOrElement) => {
if (typeof queryOrElement === 'string') {
return document.querySelector(queryOrElement);
}
// You can add all sorts of custom function handlers here.
return new Proxy(queryOrElement, handler);
}
$('div') // gets divs
$(element).shortHand(...)
// works the same as element.querySelector
// But the HTMLElement methods still work too:
$(element).querySelector
$(element).querySelectorAll
$(element).className
// ...
Read More Here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
I'm trying to learn javascript. As part of that effort, I am writing a basic minimax AI. I have the following methods:
Computer.prototype.expand = function(node) {
/* adds all state action pairs to the node.successors array */
};
Computer.prototype.getMove = function(boardAr) {
console.log("getMove");
var b2 = boardAr.slice();
var i;
var action;
this.root = new TNode(b2, this.mark);
this.root.AIPlayedLast = false;
this.expand(this.root);
this.root.successors.forEach(this.minVal);
action = maxNode(root.successors);
this.draw(action);
registerMove(action, this.mark);
};
Computer.prototype.minVal = function(node) {
if (node.isTerminal) {
return;
} else {
this.expand(node);
node.successors.forEach(maxVal);
node.utility = this.minNode(node.successors).utility;
}
};
When the getMove method is called the subsequent call to expand goes as expected. But, when expand is called from the minVal method I get: Uncaught TypeError: undefined is not a function. I'm utterly perplexed by this. Any help/suggestions would be greatly appreciated.
I think the reason is in this row:
this.root.successors.forEach(this.minVal);
You pass minVal as contextless reference, it will not be called in a context of your Computer instance (this)
Here is how you can improve it:
var self = this;
this.root.successors.forEach(function() {
self.minVal.apply(self,arguments);
})
The simplest and quickest solution is just to change
this.root.successors.forEach(this.minVal);
to
this.root.successors.forEach(this.minVal.bind(this))
This solves the problem in the same as the other answers, but in a way some might consider more compact.
Or, you can pass a "this" to the forEach function as the second argument, a somewhat under-utilized feature of forEach:
this.root.successors.forEach(this.minVal, this)
This feature is also available on other Array prototype methods that take functions, including map, filter, some, every (but not reduce and reduceRight).
ES6 arrow functions handle this differently, so you can do
this.root.successors(forEach(e => this.minVal(e)));
The forEach() method might be called for each of the successors. So, you pass the Computer::minVal method (this.minVal), but with the TNode(?) as this-pointer. Try:
var that = this;
this.root.successors.forEach(function(node) {
that.minVal(node));
});
I have just run into an unexpected problem whilst trying to create an array prototype for a function that emulates what is available in .net (linq) called SingleOrDefault().
Whilst the prototype function worked fine, I found that when iterating over a normal array using the following syntax for (var x in y)... my function name appeared as one of the properties and broke my logic.
As a result of some googling I have redefined the function (below).
My question is, is the code in the Current block the right way to extend the Array object without causing any unwanted side effects?
Previous
Array.prototype.singleOrDefault = function (predicate) {
var items = applyPredicate(this, predicate);
if (items.length > 1) {
throw new Error(errorOutput.multipleElements);
}
return (items) ? items[0] : null;
};
Current
Object.defineProperty(Array.prototype, 'singleOrDefault', {
value: function (predicate) {
var items = applyPredicate(this, predicate);
if (items.length > 1) {
throw new Error(errorOutput.multipleElements);
}
return (items) ? items[0] : null;
},
enumerable: false
});
Yes your 'current' code is correct, not as supported with older browsers as the previous code which is a downside if you need ie6/7 support (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty).
You could use the object.hasOwnProperty method with the 'previous' code to achieve the same effect when you are iterating:
for (var x in y) {
if (y.hasOwnProperty(y[x])) {
//would filter out SingleOrDefault in your examples above
}
}
I just want to share an experience with you all. So my problem was, that I came across the problem of binding javascript back-end objects to HTML front-end elements. Now, i have searched through google and read some stackoverflow articles about this problem, and many posts answer to this is to use jQuery.data(), however in my first attempts I did not succeed because there was something I did not know about jQuery's object creation method. My problem was that, I wanted to retrieve the stored data outside of the scope where i stored this, and jQuery always(i think) returns a new object reference when i write jQuery('selectorID'). So for example:
var ref1 = $('#myID');
var ref2 = $('#myID');
if(ref1 == ref2)
{
alert(true);
}
else
{
alert(false);
}
Will always alert false. However replacing the jQuery method with javascript's built-in getElementById() we will get the same reference! Hence the following code will always alert true!
var ref1 = document.getElementById("myID");
var ref2 = document.getElementById("myID");
if(ref1 == ref2)
{
alert(true);
}
else
{
alert(false);
}
The little morale of my story is that if you want to globally bind javascript objects to HTML elements and are thinking about using jQuery's data() method, store the data on the reference returned by javascript's built-in getElementById(). That way, whereever you retrieve the reference with getElementByID, you will always get the same reference and can get the data from it using jQuery's data() method.
My Questions:
Is my logic of thinking ok?
Is there a better way to globally bind javascript objects to HTML elements?
Whatever the reason behind the code you mention not working was, it was decidedly not the fact that jQuery gives you a new collection for every query. Given the HTML:
<div id="somediv"> foo bar </div>
the following Javascript works as expected:
var $ref1 = $('#somediv');
var $ref2 = $('#somediv');
console.log("$ref1:", $ref1);
console.log("$ref2:", $ref2);
// compare the collections / references
console.log("$ref1 == $ref2:", $ref1 == $ref2); // => false
console.log("$ref1 === $ref2", $ref1 === $ref2); // => false
// compare the referred DOM elements themselves
console.log("$ref1[0] == $ref2[0]:", $ref1[0] == $ref2[0]); // => true
console.log("$ref1[0] === $ref2[0]", $ref1[0] === $ref2[0]); // => true
$ref1.data('somedata', 'SOMEDATA');
console.log('$ref1->somedata:', $ref1.data('somedata')); // => SOMEDATA
console.log('$ref2->somedata:', $ref2.data('somedata')); // => SOMEDATA
The way I do it is something like this.
var ref1 = $('a');
var ref2 = $('div');
var equal = ref1.length === ref2.length;
if (equal) {
$.each(ref1, function(i) {
equal = equal && ref1[i] === ref2[i];
if (!equal) {
return false;
}
});
}
if (equal) {
alert(true);
} else {
alert(false);
}
ref1[0] === ref2[0] // Should return true
I think jQuery's instances are unique, so you can compare its matched items, which should be just one element when you reference an ID.
You could do something like this:
var ref1 = $('#myID')[0];
var ref2 = $('#myID')[0];
I dive into jQuery's source code, and find the constructor of jQuery. As follow:
// Define a local copy of jQuery
jQuery = function( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
return new jQuery.fn.init( selector, context, rootjQuery );
}
Whenever you use ref2 = $('#myID') to retrive a corresponding jQuery element, jQuery will create a new object to you. So the == will return false to you coz' the two element is completely different to js object engine.
Seems getElementById method is more fit your need. But I don't know how js engine perform its getElementById method.
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.