I'm trying to figure out the vanilla equivalent of the following code:
$(document).attr('key', 'value');
So far I've looked into
document - it's not an element so you cannot call setAttribute on it
document.documentElement - returns the html tag. This is not the same "element" that jquery is targeting
$(document)[0] seems to return a shadow element in Chrome Inspector
$(document).attr('key', 'somethingUnique') doesn't exist in the Chrome Inspector
Is jQuery creating it's own shadow element mock of the document so it can treat it like a real element? What element is jQuery actually referencing when you do $(document)?
A jQuery results set is an array like object that in general holds DOMElement, but jQuery does not really care about what type the objects in the result set have. Neither the DOMElements nor any other element that is stored within the jQuery result set is somehow mocked/wrapped, they are directly stored in the result set. jQuery tries to figure out what it has to do to those objects by looking at their available functions.
When you call .attr, jQuery checks for each object in the set if it has the function getAttribute if this is the case it assumes that it also has a function setAttribute.
If it does not have a function getAttribute, then it will forward the function call to the .prop() function, and prop will internally just do:
elem[name] = value
So if you pass a plain object to jQuery, then it will just set its property.
var a = {
}
$(a).attr('test', 'hello world');
console.dir(a) // for `a` the property `test` is set
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
If you add a getAttribute and setAttribute function to that object then jQuery will call those:
var a = {
getAttribute() {
},
setAttribute() {
console.log('setAttribute was called')
}
}
$(a).attr('test', 'hello world');
console.dir(a);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
However, you should always assume, that the way and order how jQuery does these tests might change. Moreover, only rely on it if it is mentioned explicitly in the documentation.
I believe you're incorrect about $(document) not referring to document, thus, the answer is (quite simply):
document['key'] = 'value'
E.g. In Chrome:
> $(document)[0] === document
true
> $(document).attr('blarg', 'narf')
n.fn.init [document, context: document]
> document.blarg
"narf"
> document.foo = 'bar'
"bar"
> document.foo
"bar"
jQuery is just assigning the value to document directly.
$(document).attr('test', 'hello world');
console.log(document['test']); // prints "hello world"
I really thought jQuery would have wrapped DOM elements, since for some reason, I never write var x = $('#x') to reuse it later but recall $.
That´s why I wrote:
Yes it is wrapped
But after reading #t.niese answer here I tried
var x = $('#x')
var y = $('#y')
var ref = x[0]
x[0] = y[0] // hack jQuery Object reference to DOM element
setTimeout(() => x.html('1'), 1000) // actually writes in #y
setTimeout(() => x.html('2'), 2000) // actually writes in #y
setTimeout(() => { x.push(ref) }, 2500) // hack jQuery Object reference to DOM element
setTimeout(() => x.html('3'), 3000) // actually writes in both #x and #y
And understood I don't write var x = $('#x') not because it is a wrapped object, but exactly because it is not a wrapped object.
I thought the entry point of the API was $, but I may see the API like var api = $(), and the entry point as (el) => api.push(el) or (sel) => api.push(document.querySelector(sel))
I can $().push but I can not $().forEach nor shift nor unshift but yes delete an index, also
In the example
setTimeout(() => { x.map((item) => {console.log(item)}) }, 3500)
logs 0 and 1, not the elements. Tested using jQuery version 3.3.1
Related
I've been looking all over the web for how to do this. I am trying to make Jquerys .html() function in vanilla JavaScript. I want to recreate so I can understand it better. I've tried the following but nothing seems to work, I just don't understand what I am doing wrong.
let $ = function(ele) {
if (ele.charAt(0) == '.' || ele.charAt(0) == '#') {
let newEle = cut(ele,0);
if (ele.charAt(0) == '#')
get(newEle);
else
return document.getElementsByClassName(newEle);
} else
return document.getElementById(ele);
}
$.prototype.html = function(html) {
this.innerHTML = html;
}
$('test').html('hey');
$('.hey')[0].html('hey');
function cut(string,num) {
let a = string.slice(0,num);
let b = string.slice(num + 1,string.length);
return a + b;
}
It doesn't work, the console log reports this error:
Uncaught TypeError: $(...).html is not a function
Please help and thanks in advance.
The problem here is what you are returning from the $ function.
Think about this: document.getElementsByClassName and document.getElementById return dom elements and dom elements don't have a .html function. That is why you are getting the error.
What you need is to return is an object, a wrapper, with a .html function, and a closure over the dom elements that you want to modify.
Because you're returning an object from $, you're overriding the default behavior of new $; instead of resulting in the newly-created object, it results in the object you returned out of $.
Instead, you'd want to remember the results of those getElementsByClassName/getElementById calls in an array or Set you store on this (the newly-created object), and then use that array or Set within html (since you want to loop over all matching elements in the case where you're setting the new HTML).
Side note: Since you're using ES2015+ features anyway (let), you might want to use the simpler class syntax instead of a separate function declaration and assigning to $.prototype:
class $ {
constructor(ele) {
// ...
}
html(html) {
// ..
}
}
I'm trying to obtain some insight in the context of a jQuery object. I've read a ton of questions on SO, but they all want to fix a specific problem, and the answers thus fix the problem, and didn't really provide me with the knowledge I'm hoping for.
So I have two buttons, and I have a click event that listens for both of them like so:
$('#copyRI, #copyIR').click(function () {...}
Now when I try to establish which element was clicked like so:
$('#copyRI, #copyIR').click(function () {
var test = $(this);
var test2 = $('#copyIR');
var compare = (test === test2);
}
compare returns false when I click the button with id = #copyIR
when debugging I noticed that the context of test and test2 are different:
Now I'm not looking for a way to successfully establish which button was clicked, but I want to obtain some insight in the concept of the "context" in a jQuery object.
Thanks in advance!
When you call $(this) you create a new jQuery object, instantiating it with an HTML Element.
When you call $('#copyIR') you create a new jQuery object, instantiating it with a selector. This stores extra information in the object, including the selector itself.
Even if that wasn't the case, you would be creating two different objects and === (and == for that matter) test if two objects are the same object not if they are identical objects.
$(this) === $(this) would also return false.
If you want to test if the elements are the same, then you can compare the underlying DOM nodes (since those will be the same object)
var compare = (test[0] === test2[0]);
Alternatively, you can just test if the object you have in the first place matches the selector:
var compare = test.is('#copyIR');
You should rather use .is() method here.
Check the current matched set of elements against a selector, element, or jQuery object and return true if at least one of these elements matches the given arguments.
CODE:
$('#copyRI, #copyIR').click(function () {
var test = $(this);
var compare = test.is('#copyIR')
}
jQuery contexts (being an Object) are compared by reference, so test === test2 would obviously return false since each variable is pointing to a different jQuery context (the fact that both contexts internally contains a reference to same DOM object doesn't matter).
Try is() instead:
var compare = $(this).is('#copyIR');
See Documetnation
You could simply compare the id.
$('#copyRI, #copyIR').click(function () {
if (this.id === 'copyIR') {
// do something
}
}
The following works:
$ = document.form;
x = $.name.value;
This doesn't:
$ = document.getElementById;
x = $("id").value;
Any ideas on why this doesn't work or how to make it so?
The value of this depends on how you call the function.
When you call document.getElementById then getElementById gets this === document. When you copy getElementById to a different variable and then call it as $ then this === window (because window is the default variable).
This then causes it to look for the id in the window object instead of in the document object, and that fails horribly because windows aren't documents and don't have the same methods.
You need to maintain the document in the call. You can use a wrapper functions for this e.g.
function $ (id) { return document.getElementById(id); }
… but please don't use $. It is a horrible name. It has no meaning and it will confuse people who see it and think "Ah! I know jQuery!" or "Ah! I know Prototype" or etc etc.
The context object is different. When you get a reference of a function you're changing that context object:
var john = {
name : "john",
hello : function () { return "hello, I'm " + this.name }
}
var peter = { name : "peter" };
peter.hello = john.hello;
peter.hello() // "hello, I'm peter"
If you want a reference function bound to a specific context object, you have to use bind:
peter.hello = john.hello.bind(john);
peter.hello(); // "hello, I'm john"
So in your case it will be:
var $ = document.getElementById.bind(document);
Don't know what you want to achieve, but this can be made working like this
$ = document.getElementById;
x = $.call(document, "id").value;
because getElementById works only when it is a function of document because of the scope it needs.
But I would recommend #Quentin's answer.
getElementById is a method of the HTMLDocument prototype (of which document is an instance). So, calling the function in global context you will surely get an "Wrong this Error" or something.
You may use
var $ = document.getElementById.bind(document);
but
function $(id) { return document.getElementById(id); }
is also OK and maybe better to understand.
If you are trying to achieve something like that I would suggest using jQuery. Their $ notation is much more powerful than just getting an element by id.
Also, if you are using any platform that already uses the $ as a variable (ASP .Net sometimes uses this) you may have unpredictable result.
I have a function that I want to be able to allow passing in either a regular javascript DOM element object or a jQuery object. If its not yet a jQuery object I will then make it one.
Does anyone know of a very reliable way to detect this.
function functionName(elm){
//Detect if elm is not a jquery object in condition
if (elm) elm = $(elm);
}
A logical approach is to detect one of the properties of the DOM element object. The question is, which property would be the most reliable to detect?
I could also just make it a jquery object either way since jQuery doesn't have a problem with something like: $($imajQueryObjAlready); However, the purpose of this question is not to just solve the problem, but to find a good way to detect if its a DOM object vs. a jQuery object.
To test for a DOM element, you can check its nodeType property:
if( elm.nodeType ) {
// Was a DOM node
}
or you could check the jQuery property:
if( elm.jquery ) {
// Was a jQuery object
}
To test for a jQuery object, you can use the instanceof operator:
if(elm instanceof jQuery) {
...
}
or:
if(elm instanceof $) {
...
}
jQuery does it like this:
if ( selector.nodeType )
(jQuery 1.4.3, line 109)
The easiest way is to simply pass it into the jQuery function either way. If it's already a jQuery object, it will return it unchanged:
function(elem){
elem = $(elem);
...
}
From the jQuery source code, this is what's happening:
if (selector.selector !== undefined) {
this.selector = selector.selector;
this.context = selector.context;
}
return jQuery.makeArray( selector, this );
Where makeArray is merging the new (default) jQuery object with the passed in one.
elm instanceof jQuery is the most foolproof way, as testing elm.nodeType would mistake {nodeType:1} for a DOM element, and testing elm.jquery would mistake {jquery:$()} for a jQuery object, in addition to there being no guarantee future jQuery objects won't have a jquery property.
The classy way:
function is_jquery_object(x) {
return window.jQuery && x instanceof jQuery;
}
function is_dom_object(x) {
return window.HTMLElement && x instanceof HTMLElement;
}
Building on #karim79's answer, if you need to be sure it's either a DOM or jQuery object, use these tests. (The window test helps the function fail gracefully if the class is not defined, e.g. jQuery failed to load.)
I always wondered why jQuery returns true if I'm trying to find elements by id selector that doesnt exist in the DOM structure.
Like this:
<div id="one">one</div>
<script>
console.log( !!$('#one') ) // prints true
console.log( !!$('#two') ) // is also true! (empty jQuery object)
console.log( !!document.getElementById('two') ) // prints false
</script>
I know I can use !!$('#two').length since length === 0 if the object is empty, but it seems logical to me that a selector would return the element if found, otherwise null (like the native document.getElementById does).
F.ex, this logic can't be done in jQuery:
var div = $('#two') || $('<div id="two"></div>');
Wouldnt it be more logical if the ID selector returned null if not found?
anyone?
This behaviour was chosen because otherwise jQuery would regularly throw NullReference Exceptions
Almost all jQuery functions return a jQuery object as a wrapper around the Dom elements in question, so you can use dot notation.
$("#balloon").css({"color":"red"});
Now imagine $("#balloon") returned null. That means that $("#balloon").css({"color":"red"});
would throw an error, rather than silently doing nothing as you would expect.
Hence, you just gotta use .length or .size().
This is just how jQuery works.
$("#something")
Object 0=div#something length=1 jquery=1.2.6
$("#nothing")
Object length=0 jquery=1.2.6
You can come close to doing what you want by accessing the length the element, and combine with the ternary operator:
console.log(!!$('#notfound').length); // false
console.log(!!$('#exists').length); // true
var element= $('#notfound').length ? $('#notfound') : $('#exists');
console.log(element.attr('id')); // outputs 'exists'
As to the heart of the question:
Wouldnt it be more logical if the ID
selector returned null if not found?
No, not for the JQuery way of doing things - namely, to support chaining of JQuery statements:
$('#notfound').hide("slow", function(){
jQuery(this)
.addClass("done")
.find("span")
.addClass("done")
.end()
.show("slow", function(){
jQuery(this).removeClass("done");
});
});
Even though notfound doesn't exist this code will run without stopping script execution. If the initial selector returns null, you'll have to add in an if/then block to check for the null. If the addClass, find, end and show methods return null, you'll have to add an if/then block to check the return status of each. Chaining is an excellent way to handle program flow in a dynamically typed language like Javascript.
It returns true because to Javascript it is a defined object therefore not false, and jQuery will always give you a new object regardless of whether the element is found or not - however the array length will be zero, e.g.
$("span").length
If you have no <span>, this will be zero, but it could be 1 or more.
You can write your own plugin to avoid repeated if statements as a Jquery plugin, like I did for this one. It's fairly easy to do:
(function($)
{
/* Checks if a jQuery object exists in the DOM, by checking the length of its child elements. */
$.fn.elementExists = function()
{
/// <summary>
/// Checks if a jQuery object exists in the DOM, by checking the length of its child elements.
/// </summary>
/// <returns type="Boolean" />
return jQuery(this).length > 0;
};
})(jQuery);
Usage:
if ($("#someid").elementExists())
{
}
You could check the .length property of the jQuery object. Like this:
if($("#two").length > 0) { // exists...
} else { // doesn't exist
}
In short, you could think of the jQuery selector return value as a group containing 0..n elements, but never being null.
What you're probably really interested in is $("#two")[0], which will give you the first actual element returned by the selector.