I don't want to use jQuery and I want to create a function that is similar to $ in jQuery.
Using code that is not a function is not suitable for what I want.
For example, we can use a code like this in jQuery:
$('.myClass').specialFunction();
Its equivalent in pure JavaScript is something like this:
document.querySelectorAll('.myClass').forEach(el=>{el.specialFunction()});
But this increases the size of the code. I am looking for a function that can execute something like this:
myfunc('.myClass').specialFunction();
If there is one element in myClass I can do this:
var myFunc=function(slct){
return document.querySelector(slct)
}
but if there are multiple elements in this class, what must we do?
It is not possible that return multiple elements from myFunc function.
and if using return document.querySelectorAll(slct) i can't call some JavaScript function such as addEventListener on returned object.
How to create such a myfunc function?
You might end up creating a new Library, but in short :
function $query(selector) {
if (typeof selector === 'object' && selector !== null) {
return selector;
} else {
let choice = selector[0]; // # or . etc
if (!['*', '.', '#'].includes(choice)) {
choice = '';
}
let selectorName = selector.toString().replace(choice, '');
switch (choice) {
case '#':
return document.getElementById(selectorName);
case '.':
return document.getElementsByClassName(selectorName);
case '*':
return document.getElementsByName(selectorName);
case '':
return document.getElementsByTagName(selector);
default:
return document.querySelector(selector);
}
}
}
// ### USAGE
let appDiv = $query('#app');
appDiv.innerHTML = `<h1>using ID</h1>`;
// TAG NAME
appDiv = $query('p');
appDiv[0].innerHTML = `<h1>using TagName</h1>`;
// CLASS NAME
appDiv = $query('.app');
appDiv[0].innerHTML = `<h1>using ClassName</h1>`;
// BY NAME
appDiv = $query('*app');
appDiv[0].innerHTML = `<h1>using Name</h1>`;
Try here at Stackblitz
You can add more features to it, depending on the need.
You can use prototype
You can also use this Build in Simple function:
document.querySelector(YOUR_Query);
Related
Background:
I have a function that I call like this:
hide_modules('string1','string2');
The function is something like:
function hide_modules(param1,param2) {
MM.getModules()
.withClass(param1)
.exceptWithClass(param2)
.enumerate(function(module) {
module.hide(
// some other code
);
});
}
Most of the time I call the function with values as shown above.
Sometimes I do not want 'string1' to have a value and I'd like the my function to not use that first selector, effectively like this:
MM.getModules()
// .withClass(param1)
.exceptWithClass(param2)
.enumerate(function(module) {
module.hide(
// some other code
);
});
I've tried just calling it with an empty string, 0, false as param1 but the end result class selection is not what I want.
Sometimes I also call it with param2 empty and not wanting to have the param2 related selector used either.
So the question is:
Without writing a big if-then-else statement, is there some fancy way I can make those selectors non-functional (the equivalent of commenting it out like above) when the param1 and/or param2 values are not specified?
The supporting code that my function calls is provided for me in a 3rd party library that I can't change. I include some of the relevant parts here as it may help with the answer:
var withClass = function (className) {
return modulesByClass(className, true);
};
var modulesByClass = function (className, include) {
var searchClasses = className;
if (typeof className === "string") {
searchClasses = className.split(" ");
}
var newModules = modules.filter(function (module) {
var classes = module.data.classes.toLowerCase().split(" ");
for (var c in searchClasses) {
var searchClass = searchClasses[c];
if (classes.indexOf(searchClass.toLowerCase()) !== -1) {
return include;
}
}
return !include;
});
Since js doesn't supports function overloading, the only way is to validate your parameters inside your method. Check for truthy and ternary operator will do the trick
var modules = MM.getModules();
modules = param1 ? modules.withClass(param1) : modules;
modules = param2 ? modules.exceptWithClass(param2) : modules;
modules.enumerate(function(module) {
module.hide(
// some other code
);
});
to skip first parameter
hide_modules(null,'string2');
to skip second parameter
hide_modules('string1');
I'm learning about JavaScript functions and I've written three, which are basically identical:
function filterAll() {
location.hash = "/"
}
function filterCompleted() {
location.hash = "/completed"
}
function filterActive() {
location.hash = "/active"
}
Rather than having three functions, is it possible to combine them and call the paramaters that I need at that time through one function name? This is how I see it in my head, but I can't seem to work it out:
function filters(all, completed, active) {
all = location.hash = "/";
completed = location.hash = "/completed";
active = location.hash = "/active";
}
filters(all);
Using an object literal as a simple map lookup you could do this->
const actions = {
all: '/',
completed: '/completed',
active: '/active'
}
function filter(action) {
location.hash = actions[action];
}
//use like
filter('all');
filter('completed');
filter('active');
If you don't want to pass a string, another idea is using the map as an enum, to do this we could do these changes->
function filter(action) {
location.hash = action;
}
//use like
filter(actions.all);
filter(actions.completed);
filter(actions.active);
You could use lots of consts like #Rounin mentions, but I'm not a fan of making more variables, even if they are scoped.
I'm not sure what you're trying to do, can you add some notes.
If it's to pass three variables then your last code sample is fine.
IF its to pass one variable that has three data points, then use an object.
var filters = {all:"/", completed: "/completed", active: "active"};
then you can retrieve the values with (for example) alert(filters.all);
You can try this:
function filters(val) {
switch (val) {
case 'all':
location.hash = "/";
break;
case 'completed ':
location.hash = "/completed";
break;
case 'active':
location.hash = "/active";
break;
default:
break;
}
}
You can combine the three functions below into a single function which takes a single parameter.
Step One
First, set up three const variables:
const all = '/';
const completed = '/completed';
const active = '/active';
Step Two
Next, declare your function:
function myFilter(filterType) {
location.hash = filterType;
}
Step Three
Now you can invoke one function using different values:
myFilter(all);
myFilter(completed);
myFilter(active);
It looks like this is what you are looking for.
function filters(filterType) {
if (filterType == 'all') location.hash = "/";
if (filterType == 'completed') location.hash = "/completed";
if (filterType == 'active') location.hash = "/active";
}
Although I would not recommend using your functions like this, this is how you would use the function:
filters('all'); // Set all filters
filters('completed'); // Set completed filters
filters('active'); // Set active filters
First You will need to define those variables as constant, just to used as parameters(action).
You may use that code Below
function filters ( action ) {
return action==="all" ? location.hash = "/" : action==="completed" ? location.hash = "/completed": action==="active" ? location.hash = "/active" : "No Match Found";
}
To test the function i provided a simple example:
let tod = "all";
let com = "completed";
let act = "active";
//Here you will see all the information you need. You can run it in your browser.
[filters(tod),filters(com),filters(act)].forEach;
Consider the function:
tagName => document.createElement(tagName).constructor
which returns the constructor function of HTML elements with a particular tag name.
I would like to reverse this process and get the tag name (or names?) of an HTML element provided its constructor. I would like to do this for some unit tests that I'm writing, but there are probably other use cases.
A few examples:
tagNameOf(HTMLDivElement) // should be "div"
tagNameOf(HTMLIFrameElement) // should be "iframe"
tagNameOf(HTMLHtmlElement) // should be "html"
tagNameOf(HTMLTableRowElement) // should be "tr"
Unfortunately, I have no idea where to start from, except maybe using a static mapping.
Is there a simple method to achieve this?
I suppose a dirty-looking solution would be to check the constructor's toString():
const makeConstructor = tagName => document.createElement(tagName).constructor;
const constructorToTagName = constr => constr.toString().match(/HTML(\w+)Element/)[1].toLowerCase();
console.log(constructorToTagName(makeConstructor('div')));
console.log(constructorToTagName(makeConstructor('span')));
I extracted this solution of one of my projects:
const tagNameOf = (function () {
// This lookuptable is key to the solution, it must somehow be kept up to date
const elementNameLookupTable = {
'UList': ['ul'],
'TableCaption': ['caption'],
'TableCell': ['th', 'td'],
'TableCol': ['col', 'colgroup'],
'TableRow': ['tr'],
'TableSection': ['thead', 'tbody', 'tfoot'],
'Quote': ['q'],
'Paragraph': ['p'],
'OList': ['ol'],
'Mod': ['ins', 'del'],
'Media': ['video', 'audio'],
'Image': ['img'],
'Heading': ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
'Directory': ['dir'],
'DList': ['dl'],
'Anchor': ['a']
};
return function (HTMLElementConstructor) {
let match; let tagName = [];
if (typeof HTMLElementConstructor === 'function') {
match = /HTML(\w+)Element/.exec(HTMLElementConstructor.name);
if (match) {
tagName = elementNameLookupTable[match[1]] || [match[1].toLowerCase()];
}
}
return tagName;
};
}());
// Test:
console.log(tagNameOf(HTMLAnchorElement));
console.log(tagNameOf(HTMLMediaElement));
console.log(tagNameOf(HTMLHeadingElement));
It has to be noted that the function returns an array, since there are more than one possible corresponding tag names.
I'm trying to reinvent the wheel, sort of.. Just messing around trying to remake some jquery functions.. I've come this far
var ye = function (ele) {
if (ele[0] == "#")
{
return document.getElementById(ele.slice(1));
}
else if (ele[0] == ".")
{
// returns an array, use index
return document.getElementsByClassName(ele.slice(1));
}
else
{
// also returns an array
return document.getElementsByTagName(ele);
}
}
but how can I use this element as a parameter in a function in the 'ye' prototype. For example, if I wanted to make fontsize how could I get the dom element like here:
ye.prototype.fontSize = function (ele)
{
ele.style.fontSize = "30px";
}
Just to add a bit to make the title relevant.. forEach inserts three arguments into the callback function, just like I want ye to insert ele into the fontSize function.
Just messing around trying to remake some jquery functions...
...but how can I use this element as a parameter in a function in the 'ye' prototype..
Here is a very crude and simple way to start...
Create a function with a property called elems which is an array and will store the selected DOM elements.
Like this:
var oye = function() { this.elems = []; };
On its prototype, you can create your custom functions which you want to expose. e.g. the function fontSize (as in your question), iterate over the elems array property that we created earlier changing the font size of each DOM element stored in. this points to the instance which is calling this function which we will ensure to be of type oye later on. To enable chaining, we simply return itself via this.
Like this:
oye.prototype.fontSize = function(size) {
this.elems.forEach(function(elem) {
elem.style.fontSize = size;
});
return this;
};
Now create the selector function called ye. This serves the purpose of selecting the DOM elements, storing them in the elems array property of a new instance of oye class, and return the instance. We call the slice of the array prototype to convert the nodeList to an array.
Like this:
var ye = function(elem) {
var newOye = new oye;
newOye.elems = [].slice.call(document.querySelectorAll(elem));
return newOye;
};
Now start using it in your code. Just like jQuery, you can use ye to select and then call your custom functions.
Like this:
ye("#elem1").fontSize('30px');
Just like jQuery, you can also chain multiple custom functions as shown in the complete working example below:
ye("P").fontSize('24px').dim(0.4);
Next step: Remember this is just a very crude example. You can now proceed to club the step 1 and 2 into a single call using the init pattern returning the new object from the selector function itseld. Learn more about Javascript and best practices.
Here is a sample working demo:
var oye = function() { this.elems = []; };
oye.prototype.fontSize = function(size) {
this.elems.forEach(function(elem) {
elem.style.fontSize = size;
});
return this;
};
oye.prototype.dim = function(value) {
return this.elems.forEach(function(elem) {
elem.style.opacity = value;
});
return this;
};
var ye = function(elem) {
var newOye = new oye;
newOye.elems = [].slice.call(document.querySelectorAll(elem));
return newOye;
};
ye("#elem1").fontSize('30px');
ye(".elem2").fontSize('20px');
ye("P").fontSize('24px').dim(0.4);
<div>This is normal text.</div>
<div id="elem1">size changed via id.</div>
<div class="elem2">size changed via class.</div>
<div class="elem2">size changed via class.</div>
<p>size changed and dimmed via tag name</p>
<p>size changed and dimmed via tag name</p>
Regarding your question, I may think you're new to JavaScript, or not familiar with its basic concepts. I'm not sure reinventing the wheel is a good thing in such conditions.
Since you've cited jQuery, you can have a look at its source code to understand how it works under the hood:
https://github.com/jquery/jquery/blob/99e8ff1baa7ae341e94bb89c3e84570c7c3ad9ea/src/core.js#L17-L23
https://github.com/jquery/jquery/blob/99e8ff1baa7ae341e94bb89c3e84570c7c3ad9ea/src/core.js#L38-L81
https://github.com/jquery/jquery/blob/99e8ff1baa7ae341e94bb89c3e84570c7c3ad9ea/src/core/init.js#L19-L114
Having that said, I would have done something like this:
var ye = function ( ele ) {
return new ye.prototype.init(ele);
};
ye.prototype.init = function( ele ) {
this._elements = [].slice.call(document.querySelectorAll(ele));
return this;
};
ye.prototype.forEach = function( fn ) {
this._elements.forEach(fn);
return this;
};
ye.prototype.fontSize = function( fontSizeValue ) {
this.forEach(function (ele) {
ele.style.fontSize = fontSizeValue;
});
return this;
};
The associated usage is as follow:
var myCollection = ye('.someClassName');
myCollection.forEach(function ( item, index ) {
console.log(item.style.fontSize);
});
myCollection.fontSize('45px');
myCollection.forEach(function ( item, index ) {
console.log(item.style.fontSize);
});
Use ye function calling before setting style, something like:
ye.prototype.fontSize = function(ele) {
ye(ele).style.fontSize = '30px';
}
returned object should be richer, like that:
var baseObject = {
// Will be used for the element:
element: null,
width: function(){ return this.element.getwidth(); /* or anything similar*/ }
// ... Further methods
}
and then in your ye function:
var ye = function (ele) {
var yeElem = clone(baseObject); // See comment below!!
if (ele[0] == "#") { yeElem.element = document.getElementById(ele.slice(1)); }
else if (ele[0] == "."){ /*...*/ }
else { /*...*/ }
return yeElem;
}
This way the new element has built in methods.
As for the clone() method used, it doesn't exist but you have to use some clone method.
I recommend Loadsh's _.cloneDeep() (here).
Honestly, I am trying to understand JavaScript prototypes and I'm not making much progress. I am not exactly sure how to explain what I am trying to do, except to say that in part my end goal is to learn how to traverse the DOM similar to jQuery and to add custom methods to manipulate particular elements being accessed.
EDIT : The code below has been updated to reflect concepts I have learned from the answers received so far, and to show where those fall short of what I am looking to accomplish.
function A(id) {
"use strict";
this.elem = document.getElementById(id);
}
A.prototype.insert = function (text) {
"use strict";
this.elem.innerHTML = text;
};
var $A = function (id) {
"use strict";
return new A(id);
};
var $B = function (id) {
"use strict";
return document.getElementById(id);
};
function init() {
"use strict";
$A('para1').insert('text goes here'); //this works
$A('para1').innerHTML = 'text goes here'; //this does not work
console.log($A('para1')); //returns the object A from which $A was constructed
console.log($B('para1')); //returns the dom element... this is what I want
/*I want to have $A('para1').insert(''); work and $A('para1').innerHTML = '';
work the same way that $B('para1').innerHTML = ''; works and still be able
to add additional properties and methods down the road that will be able
act directly on the DOM element that is contained as $A(id) while also
being able to use the properties and methods already available within
JavaScript*/
}
window.onload = init;
Where possible please add an explanation of why your code works and why you believe it is the best possible method for accomplishing this.
Note: The whole purpose of my inquiry is to learn this on my own... please do not suggest using jQuery, it defeats the purpose.
var $ = function(id) {
return new My_jquery(id);
}
function My_jquery(id) {
this.elem = document.getElementById(id);
}
My_jquery.prototype = {
insert : function(text) { this.elem.innerHtml = text; return this;}
}
$('para1').insert('hello world').insert('chaining works too');
add any method u want to operate on elem in My_jquery.prototype
You can use a scheme like the following:
function $(id) {
return new DOMNode(id);
}
function DOMNode(id) {
this.element = document.getElementById(id);
}
DOMNode.prototype.insert = function(value) {
if (value) {
// If value is a string, assume its markup
if (typeof value == 'string') {
this.element.innerHTML = value;
// Otherwise assume it's an object
} else {
// If it's a DOM object
if (typeof value.nodeName == 'string') {
this.element.appendChild(value);
// If it's a DOMNode object
} else if (this.constructor == DOMNode) {
this.element.appendChild(value.element);
}
}
} // If all fails, do nothing
}
$('id').insert('foo bar');
Some play stuff:
<div id="d0">d0</div>
<div id="d1">d1</div>
<div id="d2">d2</div>
<script>
// insert (replace content with) string, may or may not be HTML
$('d0').insert('<b>foo bar</b>');
// insert DOMNode object
$('d0').insert($('d1'));
// Insert DOM element
$('d0').insert(document.getElementById('d2'));
</script>
You may find it useful to study how MyLibrary works, it has some very good practices and patterns.
Try this.
var getDOM= function(id) {
this.element= document.getElementById(id);
}
getDOM.prototype.insert= function(content) {
this.element.innerHTML= content;
}
var $= function(id) {
return new getDOM(id);
};
$('id').insert('Hello World!'); // can now insert 'Hello World!' into document.getElementById('id')