I have some trouble in creating a selector in javascript.
This is my code:
function __(selector){
var self = {};
self.selector = selector;
if(typeof selector == 'object'){
self.element = self.selector;
}else{
self.element = document.querySelector(self.selector);
}
// make a .css method to an element
self.css = function(propval){
return Object.assign(self.element.style,propval);
}
return self;
}
And my html file
<script src="js/selector.js"></script>
<script>
window.onload = function(){
__('p').css({'color':'red'});
}
</script>
<p>Hello</p>
<p>World</p>
<p>John</p>
The code above will only apply the .css method in the first <p> element. It's because I only used querySelector. Because querySelector only selects the first element found. And querySelectorAll selects all elements found. But when I try to change my selector to querySelectorAll It doesnt work for me anymore.
Well, the reason is querySelectorAll() returns a NodeList of the selected elements and assigning CSS to a NodeList wouldn't make much of an effect
That said, essentially you need a way to handle the case of a single element and a many in the same way
From the top of my head, a simple solution could be to always use an arrays or the NodeList and forEach() over them since both implement this method, like so:
function __(selector){
var self = {};
self.selector = selector;
if(typeof selector == 'object'){
self.elements = [self.selector];
}else{
self.elements = document.querySelectorAll(self.selector);
}
// make a .css method to an element
self.css = function(propval){
self.elements.forEach(function(element){
Object.assign(element.style, propval);
});
}
return self;
}
I'm no expert here so this can probably be optimized, but with an array/list of objects you need to loop through each one
Updated with a polyfill so this one work on at least IE11/10/9
function __(selector){
var self = {};
self.selector = selector;
if(typeof selector == 'object'){
self.element = self.selector;
}else{
self.elements = document.querySelectorAll(self.selector);
}
// make a .css method to an element
self.css = function(propval){
if (self.elements) {
for (var i = 0; i < self.elements.length; i++) {
Object.assign(self.elements[i].style,propval);
}
return;
} else {
Object.assign(self.element.style,propval);
}
}
return self;
}
if (typeof Object.assign != 'function') {
Object.assign = function (target, varArgs) { // .length of function is 2
'use strict';
if (target == null) { // TypeError if undefined or null
throw new TypeError('Cannot convert undefined or null to object');
}
var to = Object(target);
for (var index = 1; index < arguments.length; index++) {
var nextSource = arguments[index];
if (nextSource != null) { // Skip over if undefined or null
for (var nextKey in nextSource) {
// Avoid bugs when hasOwnProperty is shadowed
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
};
}
<p>Hello</p>
<p>World</p>
<p>John</p>
<div>Albert</div>
<script>
window.onload = function(){
__('p').css({'color':'red'});
__(document.querySelector('div')).css({'color':'blue'});
}
</script>
Related
So as im building a small plugin I can across a small issue, when filtering the selectors I check if the selector is an Array, which in most cases works but when using a attribute(dataset in this case) it sees the selector as an array.
Is there a way to fix this?
var Constructor = function (selector) {
if (!selector) return;
if (selector === 'document') {
this.nodes = [document];
} else if (selector === 'window') {
this.nodes = [window];
} else if(typeof selector === 'string') {
this.nodes = document.querySelectorAll(selector);
}else{
this.nodes = Array.isArray ? selector : [selector];// it will return as an array
}
};
partofanplugin.find('[data-id="12"]')// will be seen as an array instead of a string.
You need to actually call the function, right now you are just checking if the function exists
Array.isArray(selector) ? selector : [selector];
instead of
Array.isArray ? selector : [selector];
I wrote a function for creating new elements. The code works correctly, but the function doesn't set that attribute for my element(my browser don't threw an error). I tried several methods but nothing worked.
function createElement(name, element, attribute, valueOfAttribute, text, indexOfChildNodes) {
name = document.createElement(element.toLowerCase());
var nodeText = document.createTextNode(text);
name.appendChild(nodeText);
var l = indexOfChildNodes;
document.childNodes[l].appendChild(name);
if(typeof attribute === 'array' && typeof valueOfAttribute === 'array'){
for(var i = 0, len = attribute.length; i<len; i++){
//name.setAttribute(attribute[i], valueOfAttribute[i]);
var attr = document.createAttribute(attribute[i]);
attr.value = valueOfAttribute[i];
name.setAttributeNode(attr);
}
} else {
return 'Check your "attribute" and "valueOfAttribute" arguments';
}
}
createElement('next', 'button', ['id'], ['next'], 'Next', 1);
It happens because typeof for arrays will never return "array" in JavaScript.
You may either use modern Array.isArray() method instead or try old-school trick with prototype:
if (Object.prototype.toString.call(attribute) === '[object Array]') { ... }
DEMO: http://jsfiddle.net/QXrwZ/
SIMPLIFIED EXAMPLE CODE:
var $ = function(selector, node) { // Selector engine
var selector = selector.trim(), node = node || document.body;
if (selector != null) {
return Array.prototype.slice.call(node.querySelectorAll(selector), 0); }
}
}
I want to use it like this...:
$("div").innerHTML='It works!';
...not like this...:
$("div")[0].innerHTML='It works only on the specified index!';
...or this:
for(i=0;i<$('div');i++) {
$("div")[i].innerHTML='It works great but it's ugly!';
}
This is as close as I got. I would like chaining to work and for it to be compatible with native methods:
if(!Array.prototype.innerHTML) {
Array.prototype.innerHTML = function(html) {
for (var i = 0; i < this.length; i++) {
this[i].innerHTML = html;
}
}
}
$("div").innerHTML('It works, but it ruins method chaining!');
I decided to build this engine to better learn JavaScript; It's working but I am hoping I can learn some more from the kind members of Stack Overflow. Any help would be much appreciated!
I want to use it like this...:
$("div").innerHTML='It works!';
...not like this...:
$("div")[0].innerHTML='It works only on the specified index!';
It sounds like you want to have assigning to innerHTML on your set of results assign to the innerHTML of all of the results.
To do that, you'll have to use a function, either directly or indirectly.
Directly:
var $ = function(selector, node) { // Selector engine
var selector = selector.trim(),
node = node || document.body,
rv;
if (selector != null) {
rv = Array.prototype.slice.call(node.querySelectorAll(selector), 0); }
rv.setInnerHTML = setInnerHTML;
}
return rv;
}
function setInnerHTML(html) {
var index;
for (index = 0; index < this.length; ++index) {
this[index].innerHTML = html;
}
}
// Usage
$("div").setInnerHTML("The new HTML");
There, we define a function, and we assign it to the array you're returning as a property. You can then call that function on the array. (You might want to use Object.defineProperty if it's available to set the setInnerHTML property, so you can make it non-enumerable.)
Indirectly (requires an ES5-enabled JavaScript engine):
var $ = function(selector, node) { // Selector engine
var selector = selector.trim(),
node = node || document.body,
rv;
if (selector != null) {
rv = Array.prototype.slice.call(node.querySelectorAll(selector), 0); }
Object.defineProperty(rv, "innerHTML", {
set: setInnerHTML
});
}
return rv;
}
function setInnerHTML(html) {
var index;
for (index = 0; index < this.length; ++index) {
this[index].innerHTML = html;
}
}
// Usage
$("div").innerHTML = "The new HTML";
There, we use Object.defineProperty to define a setter for the property.
In the comments below you say
I have a few prototypes that work when individually attached to the $ function. Example: $('div').makeClass('this'); They do not work when they are chained together. Example: $('div').makeClass('this').takeClass('that');
To make chaining work, you do return this; from each of the functions (so the end of makeClass would do return this;). That's because when you're chaining, such as obj.foo().bar(), you're calling bar on the return value of foo. So to make chaining work, you make sure foo returns this (the object on which foo was called).
This is what works; it's a slightly different syntax then I gave in my prior example, but the end result is the same. I had some great help from other Stack Exchange members, thanks again everyone.
var $ = function(selector, node) { // Selector engine
var selector = selector.trim(), node = node || document.body;
if (selector != null) {
return Array.prototype.slice.call(node.querySelectorAll(selector), 0); }
}
}
if(!Array.prototype.html) {
Array.prototype.html = function(html) {
for (var i = 0; i < this.length; i++) {
this[i].innerHTML = html;
}
return this; //<---- Silly me, my original code was missing this.
}
}
When I run it, everything (including chaining) works as desired:
$("div").html('hello world');
OUTPUT:
<div>hello world</div>
Cheers!
I am using custom javascript functions provided at this link (http://km0.la/js/mozXPath/) to implement particular XML functionality in FireFox.
Here is the code:
// mozXPath
// Code licensed under Creative Commons Attribution-ShareAlike License
// http://creativecommons.org/licenses/by-sa/2.5/
if( document.implementation.hasFeature("XPath", "3.0") ) {
if( typeof XMLDocument == "undefined" ) { XMLDocument = Document; }
XMLDocument.prototype.selectNodes = function(cXPathString, xNode) {
if( !xNode ) { xNode = this; }
var oNSResolver = this.createNSResolver(this.documentElement);
var aItems = this.evaluate(cXPathString, xNode, oNSResolver,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
var aResult = [];
for( var i = 0; i < aItems.snapshotLength; i++) {
aResult[i] = aItems.snapshotItem(i);
}
return aResult;
}
XMLDocument.prototype.selectSingleNode = function(cXPathString, xNode) {
if( !xNode ) { xNode = this; }
var xItems = this.selectNodes(cXPathString, xNode);
if( xItems.length > 0 ){ return xItems[0]; }
else{ return null; }
}
Element.prototype.selectNodes = function(cXPathString) {
if(this.ownerDocument.selectNodes) {
return this.ownerDocument.selectNodes(cXPathString, this);
}
else { throw "For XML Elements Only"; }
}
Element.prototype.selectSingleNode = function(cXPathString) {
if(this.ownerDocument.selectSingleNode) {
return this.ownerDocument.selectSingleNode(cXPathString, this);
}
else { throw "For XML Elements Only"; }
}
}
Assuming the XML object has been defined and loaded with XML content, here is an example of how one would access a an XML tag named "cd_rank":
var cd_rank_XMLObj = XMLObj.selectSingleNode("cd_rank");
What I want to do is add the property "nodeTypedValue" to the selectSingleNode() function, but I'm not sure how to do this. In the Element.prototype.selectSingleNode function, I tried adding:
this.prototype.nodeTypedValue = this.textContent;
However, it's giving me an error saying it's undefined. I even tried adding it outside of the function, just to dumb it down and get the concept, and it also says it's undefined:
var XMLObj.selectSingleNode.prototype.nodeTypedValue = XMLObj.textContent;
alert(XMLObj.selectSingleNode("cd_rank").nodeTypedValue);
Essentially what I'm trying to do, I suppose, is add a prototype property to a prototype function. But I need some help with this. How can i add "nodeTypedValue" such that I write "XMLObj.selectSingleNode(Path).nodeTypedValue"?
Okay, I think I figured out how to add it inside the function, probably due more to luck than logic:
Element.prototype.selectSingleNode = function(cXPathString){
if(this.ownerDocument.selectSingleNode) {
var result = this.ownerDocument.selectSingleNode(cXPathString, this);
if (result != null) {
result.nodeTypedValue = result.textContent;
}
return result;
}
else{throw "For XML Elements Only";}
}
i have been using jquery for a while now but only thing i know about jquery is probably a dozen of functions that get my job done. but i want to understand how jquery evolved from simpl plain javascript i.e how
$("#xyz").val();
is converted to
document.getElementById('xyz').value;
i have searched for my answer on the web but most of the writers are happy to show how you can hook on to different DOM elements with jquery, selector details etc. but nothing can be found about how actually the transition was made. can anyone refer me to some tutorial where i can get my required material?
thanks
jQuery is not a compiler. jQuery does not get compiled into javascript.
.val is a method of an object. The jQuery object.
Specifically it is
function (value) {
if (!arguments.length) {
var elem = this[0];
if (elem) {
if (jQuery.nodeName(elem, "option")) {
// attributes.value is undefined in Blackberry 4.7 but
// uses .value. See #6932
var val = elem.attributes.value;
return !val || val.specified ? elem.value : elem.text;
}
// We need to handle select boxes special
if (jQuery.nodeName(elem, "select")) {
var index = elem.selectedIndex,
values = [],
options = elem.options,
one = elem.type === "select-one";
// Nothing was selected
if (index < 0) {
return null;
}
// Loop through all the selected options
for (var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++) {
var option = options[i];
// Don't return options that are disabled or in a disabled optgroup
if (option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && (!option.parentNode.disabled || !jQuery.nodeName(option.parentNode, "optgroup"))) {
// Get the specific value for the option
value = jQuery(option).val();
// We don't need an array for one selects
if (one) {
return value;
}
// Multi-Selects return an array
values.push(value);
}
}
return values;
}
// Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
if (rradiocheck.test(elem.type) && !jQuery.support.checkOn) {
return elem.getAttribute("value") === null ? "on" : elem.value;
}
// Everything else, we just grab the value
return (elem.value || "").replace(rreturn, "");
}
return undefined;
}
var isFunction = jQuery.isFunction(value);
return this.each(function (i) {
var self = jQuery(this),
val = value;
if (this.nodeType !== 1) {
return;
}
if (isFunction) {
val = value.call(this, i, self.val());
}
// Treat null/undefined as ""; convert numbers to string
if (val == null) {
val = "";
} else if (typeof val === "number") {
val += "";
} else if (jQuery.isArray(val)) {
val = jQuery.map(val, function (value) {
return value == null ? "" : value + "";
});
}
if (jQuery.isArray(val) && rradiocheck.test(this.type)) {
this.checked = jQuery.inArray(self.val(), val) >= 0;
} else if (jQuery.nodeName(this, "select")) {
var values = jQuery.makeArray(val);
jQuery("option", this).each(function () {
this.selected = jQuery.inArray(jQuery(this).val(), values) >= 0;
});
if (!values.length) {
this.selectedIndex = -1;
}
} else {
this.value = val;
}
});
}
If we break the above wall down we can get
function (value) {
if (arguments.length === 0) {
return (this[0].value || "")
}
this.value = val;
return this;
}
Of course jQuery has a lot more code to deal with various edge cases and special things.
In essence jQuery takes a selector. finds the elements. Stores them internally then returns you an object.
This object has all kinds of methods that allow you to mutate the underlying dom objects stored internally. .val is one of them.
There are plenty of articles on how jQuery works (there are screencasts too).
jQuery, as you've noticed, is basically a bunch of methods operating on an array of elements. It is also intended to normalize browser differences under the hood.
Take the basic usage $("#xyz").val();
I can even tell you what jQuery is doing behind the scenes, but I don't think you really want to know. :)
var jQuery = function( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
return new jQuery.fn.init( selector, context );
},
// ...
jQuery.fn = jQuery.prototype = {
init: function( selector, context ) {
// ...
},
// ...
};
// Give the init function the jQuery prototype for later instantiation
jQuery.fn.init.prototype = jQuery.fn;
So basically $(selector) means newjQuery.fn.init(selector), it's just a shortcut for easier typing (and also to prevent the "bug" where fogetting new binds this to the global object, instead of the current instance).
Also, the so-called plug-ins added as jQuery.fn.ext are mapped to jQuery.fn.init.prototype as you can see in the last line, it's another shortcut. So when you call $(selector) everything that is added to jQuery.fn will also be on jQuery.fn.init.prototype and so the new instance will have those methods as $(selector).ext(...).
// as you use it today
jQuery.fn.plugin = function ( ... ) { ... }
$(selector).plugin( ... )
// as it would be without shortcuts
jQuery.fn.init.prototype.plugin = function ( ... ) { ... }
(new jQuery.fn.init(selector)).plugin( ... )