using $ instead of querySelector() - javascript

I want to implement something to make myself instead of writing something.querySelector() to write $, I've fixed the half of the problem which is when I want to make document.querySelector() instead, I can now use $
<div>
<button>hi</button>
</div>
let $ = (x) => {
return document.querySelector(x);
}
div = $("div");
But the thing that I want to do now is to select the button from inside the div, so now I can't do:
div.$("button");
I want to do it, I know a little bit about prototypes, but I don't know how to implement this thing, How?
[EDIT]: I've seen many people saying that It's a bad implementation, could you please tell why?

If you don't want to mess with prototypes, you can have $ accept a second argument, the parent to select from.
const $ = (x, parent = document) => {
return parent.querySelector(x);
}
const div = $("div");
const button = $('button', div);
console.log(button);
<div>
<button>hi</button>
</div>
Changing prototypes is bad practice, but it's doable:
const $ = (x, parent = document) => {
return parent.querySelector(x);
}
Element.prototype.$ = function(selector) {
return $(selector, this);
}
const div = $("div");
const button = div.$('button');
console.log(button);
<div>
<button>hi</button>
</div>

window.$ = HTMLElement.prototype.$ = function(selector) {
if (this === window) {
return document.querySelector(selector);
} else {
return this.querySelector(selector);
}
};
console.log($('div').$('button'));
<div>
<button>hi</button>
</div>
You can, I don't know if I would, put the method on the window and HTMLElement prototype to allow chaining like you asked about.

It's not recommended, but you can modify Element.prototype and Document.prototype.
Element.prototype.$ = Document.prototype.$ = function(x){
return this.querySelector(x);
}

Probably not the best approach but you could assign a property $ to the element that is a function that queries that element
let $ = (x) => {
let el = document.querySelector(x)
el.$ = (y) => el.querySelector(y)
return el;
}
div = $("div");
console.log(div.$('button'))
<div>
<button>hi</button>
</div>

It's very simple and you need only two lines of code to make it all work as expected.
Document.prototype.$ = Document.prototype.querySelector
Element.prototype.$ = Element.prototype.querySelector

Related

How to create shorthand for anyElement.querySelector method

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

How to optimize native JavaScript code when refactoring it from Jquery

I need to rewrite some Jquery into native JavaScript code but I am facing a problem that I am not so sure how to solve.
This is Jquery code I need to rewrite in native JS:
$('.class1').click(function () {
setTimeout(() => {
$('.class2').css('top', '252px');
$('.class3').css('bottom', '0px');
}, 200);
$('.class2').css('z-index', '-1');
$('.class1').css('z-index', '-1');
});
And this is what I have written in native JavaScrip:
if (document.querySelector('.class1')){
document.querySelector('.class1').addEventListener('click', function () {
setTimeout(() => {
if (document.querySelector('.class2')) {
document.querySelector('.class2').style.top = '252px';
}
if ( document.querySelector('.class3')) {
document.querySelector('.class3').style.bottom = '0px';
}
}, 200);
if (document.querySelector('.class2')) {
document.querySelector('.class2').style.zIndex = '-1';
}
if ( document.querySelector('.class1')) {
document.querySelector('.class1').style.zIndex = '-1';
}
})
}
I was hoping that people could explain to me how to solve two things:
Is there a more elegant way to check for an element on the current page if the code runs on the whole site?
Is there something else that I can replace those if statements inside the function?
In Jquery those statements are executed one by one but in my case I need to check for an element first and if it is there do something with it.
You can make the code more succinct by storing the result of querySelector within a variable. Also note that a class selector in jQuery can return multiple elements, so the native equivalent of it is querySelectorAll().
As such you will need to loop through all the elements in that collection and add the event handlers, or update their style, as necessary. Due to this loop you don't need to explicitly check for the existence of the elements, as the forEach() will simply not execute if the collection is empty.
With that said, try this:
let class1 = document.querySelectorAll('.class1');
let class2 = document.querySelectorAll('.class2');
let class3 = document.querySelectorAll('.class3');
class1.forEach(el => {
el.addEventListener('click', e => {
setTimeout(() => {
class2.forEach(el => el.style.top = '252px');
class3.forEach(el => el.style.top = '0px');
}, 200);
class1.forEach(el => el.style.zIndex = -1);
class2.forEach(el => el.style.zIndex = -1);
});
});

Hide/Show Element function is undefined?

Learning JS and was following a complex tutorial and keep getting errors for these specific functions. Stating that my "hide and show element" functions are undefined? Which i thought was odd since they are definitions kinda? Mostly confused because worked fine for the person in the video tutorial.
Any help understanding why is much appreciated!
'use strict';
hideElement = (element) => element.style.display = 'none';
showElement = (element) => element.style.display = 'block';
hideElement('connectWalletBtn');
showElement('userProfileBtn');
// define user button elements
const connectWalletBtn = document.getElementById('connectWalletBtn');
const userProfileBtn = document.getElementById('userProfileBtn');
<button id="connectWalletBtn">Connect Wallet</button>
<button id="userProfileBtn">Profile</button>
Firstly, you have never defined your variables for your functions. Do that:
const hideElement = (element) => element.style.display = 'none';
const showElement = (element) => element.style.display = 'block';
Second, your functions expect to get passed an element, but you are calling them with a string. Go with
hideElement(connectWalletBtn);
showElement(userProfileBtn);
instead.
The third problem is that you're using constant variables which you only declare after using them.
As an improvement, to toggle the display property of an element between none and initial, the DOM API has the hidden property/attribute (which sync). Use it.
Here's the corrected snippet:
'use strict';
const hideElement = element => element.hidden = true;
const showElement = element => element.hidden = false;
// make sure the DOM is fully parsed so elements
// are available for Javascript access
document.addEventListener('DOMContentLoaded', function() {
// find user button elements and store references in variables
const connectWalletBtn = document.getElementById('connectWalletBtn');
const userProfileBtn = document.getElementById('userProfileBtn');
hideElement(connectWalletBtn);
showElement(userProfileBtn);
})
<button id="connectWalletBtn">Connect Wallet</button>
<button id="userProfileBtn">Profile</button>
I think you forgot to grab the element from the dom.
hideElement = (element) => document.getElementById(element).style.display = 'none';
showElement = (element) => document.getElementById(element).style.display = 'block';

how do I override appendChild()?

appendChild = function(message) {
console.log("intercepted!");
}
using the code above does not seem to work.
Anyone knows?
What you might want to replace is Element.prototype.appendChild but it's probably a bad idea.
This example adds the text intercepted in the inserted element :
var f = Element.prototype.appendChild;
Element.prototype.appendChild = function(){f.apply(this, arguments);arguments[0].innerHTML="!Intercepted!"; };
document.body.appendChild(document.createElement("div"));
Demonstration
It is ill advised to overwrite native functions, but if you do it than make sure you return the appended element as well to prevent problems with code that uses the returned value of the native "appendChild" function:
window.callbackFunc = function(elem, args) {
// write some logic here
}
window.f = Element.prototype.appendChild;
Element.prototype.appendChild = function() {
window.callbackFunc.call(this, arguments);
return window.f.apply(this, arguments);
};

How can I refresh a stored and snapshotted jquery selector variable

I ran yesterday in a problem with a jquery-selector I assigned to a variable and it's driving me mad.
Here is a jsfiddle with testcase:
assign the .elem to my obj var
log both lengths to the console. Result => 4
Remove #3 from the DOM
log obj to the console => the removed #3 is still there and the length is still 4.
I figured out that jquery query is snapshotted? to the variable and can't?won't? be updated
log .elem to the console.. yep Result => 3 and the #3 is gone
Now I update .elem with a new width of 300
logging obj & obj.width gives me 300.. So the snapshot has been updated ? What's interesting is that 3 of the 4 divs have the new width, but the removed #3 doesn't...
Another test: Adding a li element to the domtree and logging obj and .elem.
.elem does have the new li and obj doesn't, because it's still the old snapshot
http://jsfiddle.net/CBDUK/1/
Is there no way to update this obj with the new content?
I don't want to make a new obj, because in my application there is a lot information saved in that object, I don't want to destroy...
Yeah, it's a snapshot. Furthermore, removing an element from the page DOM tree isn't magically going to vanish all references to the element.
You can refresh it like so:
var a = $(".elem");
a = $(a.selector);
Mini-plugin:
$.fn.refresh = function() {
return $(this.selector);
};
var a = $(".elem");
a = a.refresh();
This simple solution doesn't work with complex traversals though. You are going to have to make a parser for the .selector property to refresh the snapshot for those.
The format is like:
$("body").find("div").next(".sibling").prevAll().siblings().selector
//"body div.next(.sibling).prevAll().siblings()"
In-place mini-plugin:
$.fn.refresh = function() {
var elems = $(this.selector);
this.splice(0, this.length);
this.push.apply( this, elems );
return this;
};
var a = $(".elem");
a.refresh() //No assignment necessary
I also liked #Esailija solution, but seems that this.selector has some bugs with filter.
So I modified to my needs, maybe it will be useful to someone
This was for jQuery 1.7.2 didn`t test refresh on filtered snapshots on higher versions
$.fn.refresh = function() { // refresh seletor
var m = this.selector.match(/\.filter\([.\S+\d?(\,\s2)]*\)/); // catch filter string
var elems = null;
if (m != null) { // if no filter, then do the evarage workflow
var filter = m[0].match(/\([.\S+\d?(\,\s2)]*\)/)[0].replace(/[\(\)']+/g,'');
this.selector = this.selector.replace(m[0],''); // remove filter from selector
elems = $(this.selector).filter(filter); // enable filter for it
} else {
elems = $(this.selector);
}
this.splice(0, this.length);
this.push.apply( this, elems );
return this;
};
Code is not so beautiful, but it worked for my filtered selectors.
Clean and generic solution worked properly with jQuery 3.4.1:
My solution is to do the following:
Intercept the selector at the time of jQuery object initialization and in the same time maintain all other jQuery functionalities transparently all this using inheritance
Build refresh plugin that make use of the new "selector" property we added during initialization
Definition:
$ = (function (originalJQuery)
{
return (function ()
{
var newJQuery = originalJQuery.apply(this, arguments);
newJQuery.selector = arguments.length > 0 ? arguments[0] : null;
return newJQuery;
});
})($);
$.fn = $.prototype = jQuery.fn;
$.fn.refresh = function ()
{
if (this.selector != null && (typeof this.selector === 'string' || this.selector instanceof String))
{
var elems = $(this.selector);
this.splice(0, this.length);
this.push.apply(this, elems);
}
return this;
};
Usage:
var myAnchors = $('p > a');
//Manipulate your DOM and make changes to be captured by the refresh plugin....
myAnchors.refresh();
//Now, myAnchors variable will hold a fresh snapshot
Note:
As optimization, object selectors don't need refresh as they are pass by reference by nature so, in refresh plugin, we only refresh if the selector is a string selector not object selector for clarification, consider the following code:
// Define a plain object
var foo = { foo: "bar", hello: "world" };
// Pass it to the jQuery function
var $foo = $( foo );
// Test accessing property values
var test1 = $foo.prop( "foo" ); // bar
// Change the original object
foo.foo = "koko";
// Test updated property value
var test2 = $foo.prop( "foo" ); // koko
Jquery .selector is deprecated, it's better to remeber string with selector value to some variable at the moment when you assign
function someModule($selector, selectorText) {
var $moduleSelector = $selector;
var moduleSelectorText = selectorText;
var onSelectorRefresh = function() {
$moduleSelector = $(moduleSelectorText);
}
}
https://api.jquery.com/selector/
You can also return the JQuery selector in a function, and save this function into the variable. Your code will look a bit different but it works. Every time when you execute the function, your jquery selector will search the DOM again.
In this example I used an arrow function without brackets which will return whatever is next to arrow. In this case it will return the JQuery collection.
const $mySelector = () => $('.selector');
console.log($mySelector().last().text());
$('.parent').append('<li class="selector">4</li>')
console.log($mySelector().last().text()); //RETURNS 4 not 3
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul class="parent">
<li class="selector">1</li>
<li class="selector">2</li>
<li class="selector">3</li>
</ul>
If you use remove() it will remove only a part of the DOM but not all the children or related, instead if you use empty() on the element the problem is gone.
E.G.:
$('#parent .child).find('#foo').empty();
Maybe it can be useful to someone!

Categories

Resources