The code:
<body>
<div class="class0"> link </div>
<div class="class0"></div>
<div class="class0"></div>
</body>
<script>
proto={};
$ = function(selector){
var tags = document.querySelectorAll(selector);
tags.__proto__ = proto;
return tags;
}
proto.addClass = function(className){
for (var i = 0; i < this.length; i++){
this[i].classList.add(className);
}
return this;
}
proto.html = function(){
return this[0].innerHTML;
}
</script>
console.log( $('div').addClass('class1').html() ); this works
console.log( $('div').addClass('class1').html().addClass('class 2').html() ); but this not - need this to work
html(), on the one hand, should return this[0].innerHTML, on the other hand should return this.
I do not know how to combine it.
html should return this in case if it's a setter function (you don't have this yet):
$('div').addClass('class1').html('<span>HTML</span>').addClass('class 2');
In your case html is a getter function so it must return HTML string for the first element in collection. Hence there is no way you can combine both styles.
What you are trying to do is not possible, a function can return only one value.
If you have a look at the jQuery fluent API also, the you can't do what you are trying to do with the getter version of .html()
The getter & setter version can go something like
proto.html = function (html) {
if (arguments.length == 0) {
return this[0].innerHTML;
}
this[0].innerHTML = html;
return this;
}
proto.html = function(str){
if(str == undefined){
// getter mode
return this[0].innerHTML;
} else {
// setter mode
this[0].innrHTML = str;
return this;
}
}
Usage
$('div').html() // works as getter
$('div').html('some html').foo(); // works as setter
The task hasn't decided yet.
The comment of author of the question (team leader at EPAM systems):
"Need to redefine this's toString inside methods.
Now if you use the result as a string it will remain as string although you will
return this"
The decision exists
Related
I'm looking into a way to change a native JS function body, while making it not possible to see that it has been changed. Let's take an example with document.hasFocus():
document.hasFocus = ()=>true;
This method works well to spoof focus, but it can be easily detected that it was modified:
document.hasFocus.toString() // -> "()=>true"
Is there any way, in which I can modify such a function while making it impossible to see it has been tampered with?
You can overwrite toString method in Function prototype, and do something like that:
// https://stackoverflow.com/questions/1833588/javascript-clone-a-function
Function.prototype.clone = function() {
var that = this;
var temp = function temporary() {
return that.apply(this, arguments);
};
for (var key in this) {
if (this.hasOwnProperty(key)) {
temp[key] = this[key];
}
}
return temp;
};
Function.prototype.__oldToString = Function.prototype.toString.clone();
function __toStringHooked() {
if ((this.name == "")||(this.name == "hasFocus")) // on Firefox, hasFocus doesn't have any name
{
return eval+"" // this matches regexp
} else {
return this.__oldToString(); // we're returning default value
}
}
Function.prototype.toString = __toStringHooked
document.hasFocus = () => true
The code above is from Th3B0r3dD3v3l0p3r's GitHub repo, you can check it if you want: https://github.com/Th3B0r3dD3v3l0p3r/focus-spoofer/
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).
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!
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')
I am trying to get javascript chaining to work using variable substitution. Not able to get it work. Help appreciated.
var Class = function() {
this.one = function() {
alert('one');
return this;
}
this.two = function() {
alert('two');
return this;
}
if (this instanceof Class) {
return this.Class;
} else {
return new Class();
}
}
var test = new Class();
// this works
test.one().two();
var func = '.one().two()';
// want to make this work
test[func];
there is no function with the name '.one().two()'
Try this,
test['one']()['two']();
Edit:
I believe you are using this for learning purpose only and not on production site.
Highly not recommended. You might want to try an array instead:
var funcs = ['one','two'];
for(var i = 0; i < funcs.length; i++) {
test[funcs[i]]();
}
you can then wrap this into a little function:
function callChain(obj, funcs)
{
for(var i = 0; i < funcs.length; i++) {
obj[funcs[i]]();
}
return obj;
}
Edit: If your chain is stored as a string: .one().two(), you can use the split & string functions to generate the array dynamically.
Well, what you are asking for is far from best practice - so I will give you an unpopular answer - use eval.
If your input is general code as string, you don't really have any other option (specifically when your functions have parameters - .one(1 + 0.5).two(new Date())).
For example, to your Class, add:
this.excecute = function(commands){
eval('this' + commands);
};
And then:
test.excecute('.one().two(4 * 5)');
Working example: http://jsbin.com/ipazaz/1/edit
This emits the warning "eval is evil" (jslint, I think) - but I do not believe functions can be evil.
Even worse, what if you had the string 'one(); two(4 * 5);'?
You can make that work as well, using with:
this.excecute = function(commands){
with(this){
eval(commands);
}
};
This has an extra warning: "Don't use 'with'" - They really have something against us today, don't they?
Working example: http://jsbin.com/ipazaz/2/edit
Thank you all for prompt help. I ended up settling upon Ben Rowe suggestion.
var funcs = ['one','two'];
for(var i = 0; i < funcs.length; i++) {
test[funcs[i]]();
}
It fitted my requirement nicely. Appreciate all for the help. You all are wonderful.
You could add a method to the constructor:
this.chain = function chain(){
if (arguments.length && /\./.test(arguments[0])) {
return chain.apply(this,arguments[0].split('.'));
}
var methods = [].slice.call(arguments),
method = methods.shift();
if(this[method] instanceof Function){
this[method].call(this);
}
if (methods.length){
chain.apply(this,methods);
}
return this;
}
// now you could do something like:
test.chain('one.two.one.two.two');
Or extend Object.prototype
Object.prototype.chain = function chain(){
if (arguments.length && /\./.test(arguments[0])) {
return chain.apply(this,arguments[0].split('.'));
}
var methods = [].slice.call(arguments),
method = methods.shift();
if(this[method] && this[method] instanceof Function){
this[method].call(this);
}
if (methods.length){
chain.apply(this,methods);
}
return this;
};
// usage
({one:function(){console.log('I am one');},
two:function(){console.log('I am two');}})
.chain('one.two.one.one.two.two.two.one.two');
I think a simpler approach is to use javascript's array reduce function.
I needed this for some dynamic jquery stuff I was writing. Once you have your array of chain-able methods you could easily do the following.
var methods = ['next', 'child', 'parent'];
var element = methods.reduce(function(method){
return $(selector)[method]();
});
console.log(element) //works! as all method names in methods array are applied and returned each iteration.
For my case the accepted answer did not work for me it seems to only return the passed obj and not the obj plus it's chained methods.