How to create a method for a function? - javascript

I wish I would create something that will help me to get the last item from an array like this
$$("div", list).getLastItem()
my function is:
const $$ = (selector, parent = document) => {
x = parent.querySelectorAll(selector);
x = Array.prototype.slice.call(x);
return x;
}
I was thinking of how to do it, but I'm unable to figure it out, I've tried to do it like this:
$$.prototype.getLastItem = ()=>{
return this.slice(-1)[0];
}
but it's giving an error:
Uncaught TypeError: Cannot set property 'getLastItem' of undefined
1-How to implement this?
2-Why my code is not working?

Return an instance of a class which has a getLastItem method:
class Dollar {
constructor(selector, parent = document) {
this.elements = [...parent.querySelectorAll(selector)];
}
getLastItem() {
return this.elements.slice(-1)[0];
}
}
const $$ = (...args) => new Dollar(...args);
console.log($$('.something').getLastItem());
<div class='something'>something</div>
<div class='something'>something else</div>
To access the nth element, reference the instance's .elements[n] property.
If you also want to be able to access the underlying elements without going through .elements, assign to numeric properties in the constructor:
class Dollar {
constructor(selector, parent = document) {
this.elements = [...parent.querySelectorAll(selector)];
Object.assign(this, this.elements);
}
getLastItem() {
return this.elements.slice(-1)[0];
}
}
const $$ = (...args) => new Dollar(...args);
const somethings = $$('.something');
console.log(somethings[0]);
console.log(somethings.getLastItem());
<div class='something'>something</div>
<div class='something'>something else</div>

getLastItem should be bound to the prototype of Array:
Array.prototype.getLastItem = function () { ... }

I'm not sure if you just want to get the last item or require creating a method for a function.
If you just want to get the last item, can't you just do:
const lastItem = $$('div').slice(-1)[0];
That seems to get the last div just fine...

You could achieve what you expected right from your current solution with a some changes
use function to access the this context
use New-Agnostic Constructor Pattern (as described in David Herman’s Effective JavaScript) to construct object without the need of using new
return the instance itself (return this) to use further defined method
function $$(selector, parent = document) {
if (!(this instanceof $$)) {
return new $$(selector, parent);
}
const x = parent.querySelectorAll(selector);
this.selected = Array.prototype.slice.call(x);
return this;
}
$$.prototype.getLastItem = function() {
return this.selected.slice(-1)[0];
}
console.log($$("div").getLastItem())
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>

You can simply move function getLastItem inside $$ and assign it to x as x.getLastItem, use return x.slice(-1)[0] and return x. It will work.
Try it below.
const $$ = (selector, parent = document) => {
let x = parent.querySelectorAll(selector);
x = Array.prototype.slice.call(x);
x.getLastItem = () => {
return x.slice(-1)[0];
}
return x;
}
console.log($$("div").getLastItem());
console.log($$("div")[0]);
<div id='test'></div>

Related

Get the names of the parameters of the wrapped function

I want to use the methods of the Minio class without specifying all their parameters, but substituting some of the parameters automatically. How do I do it...
I get all the class methods from the prototype of the Minio class and dynamically create wrappers for them in my class.
For each wrapper method, I get the parameter names from the original method of the Test class.
If there is one in the list of parameters that I want to omit when calling my wrapper method, then I add it to the list of arguments and call originalMethod.apply(this.minioClient, args).
Everything was fine until there were methods that were already wrapped.
I need to get the parameter list of the bucketExists method from outside the Minio class. Any idea how to get parameter names from such a wrapped method?
// minio/dist/main/helpers.js
exports function promisify(fn){
return function(){
const args = [...arguments];
fn.apply(this, args);
}
}
// minio/dist/main/minio.js
class Minio{
bucketExists(bucketName){
return bucketName;
}
methodThatNotWrappedByPromisifyAndWorksFine(bucketName){
return bucketName;
}
}
module.exports = Minio;
Minio.prototype.bucketExists = (0,helpers.promisify)(Minio.prototype.bucketExists)
I want to give an instance of my class with methods wrapped from the original class link the ability to work with only one bucket, that was passed to the my class constructor, without the ability to specify some other one after initialize.
My wrapper
const proxyHandler = () => {
return {
apply: (target, thisArg, argumentsList) => {
const funcParams = getMethodParamNames(target.source ? target.source.functionForWrap : target);
const bucketNameIndex = funcParams.indexOf("bucketName");
const regionNameIndex = funcParams.indexOf("region");
if (bucketNameIndex >= 0) {
argumentsList.splice(bucketNameIndex, 0, this.bucket.name);
}
if (regionNameIndex >= 0) {
argumentsList.splice(regionNameIndex, 0, this.bucket.region);
}
try {
return target.apply(this.minioClient, argumentsList);
} catch (error) {
logger.engine.error(`S3 '${this.bucket.name}' ${target} error: ${error.message}`, error.stack);
}
},
}
}
getMethods(this.minioClient).forEach(func => {
this[func] = new Proxy(this.minioClient[func], proxyHandler());
})
Solved the problem by overriding the method wrapper like this.
const MinioHelpers = require('minio/dist/main/helpers');
const origMinioPromisify = MinioHelpers.promisify;
MinioHelpers.promisify = (functionForWrap) => {
console.log("PATCHED!", functionForWrap);
var fn = origMinioPromisify(functionForWrap);
//from this i'll get all need information about wrapped function
fn.source = {
functionForWrap,
promisify: origMinioPromisify,
}
return fn
}
var Minio = require('minio');

Adding a class to a created element

The parameter of my function is a function. It should create an element but I should still be able to add attributes by using the parameter details.
E.g.:
const addElement = (details) => {
const element = document.createElement('div');
}
addElement(function() {
element.id = 'my-div'; // Not working since element is not defined
});
Well, I have tried to store the element in an object to be able to use it outside of that function.
let element = {};
const displayVideo = (type, details) => {
element = document.createElement(type);
element.width = 200;
element.height = 200;
element.classList.add('my-class'); // <--- THE PROBLEM!
if (details) {
details();
}
document.querySelector('#layer').insertBefore(element, document.querySelector('#el'));
};
displayVideo('VIDEO', function () {
element.controls = true;
});
My element can not be created because of element.classList.add('my-class'); and I don't even get an error message. If I remove that line, it works but I would still like to be able to add a class to that object. How can I do this?
Just pass element into the function. Since you're just editing properties on the object, this won't cause reference vs value errors.
const addElement = (details) => {
const element = document.createElement('div');
if (details) details(element);
return element;
}
const ele = addElement(function(element) {
element.id = 'my-div';
});
console.log(ele);
In this case details could be something like classname.
function element(type, classname) {
var element = document.createElement(type);
if (classname !== undefined) {
element.classList.add(classname);
}
return element;
};
element("div","my-class"); //<div class="my-class"></div>
Of course instead of classname you could use an array or an object and loop through in order to set multiple attributes.
Or you could store the return value of your function in a variable and then add all the attributes:
var myelement = element("div");
myelement.classList.add("my-new-class");
myelement //<div class=​"my-new-class">​</div>​

Adding a method to a function that returns an element

I'm playing around with a micro library for use within projects and I'm having difficulty adding a method to a function.
In this case I have shorthanded document.querySelector() which is all good and works as needed. However I have no real idea how to add a method to that which inherits the element from the selector function.
This is similar to how jquery works but a lot of code examples online are a fair few years old. I'm wanting a modern solution and to understand how this works going forward. I really hope someone can educate me and help to get this to work.
Cheers!
let S = (selector, container = document) => {
return container.querySelector(selector);
};
S.prototype = {
classAdd: (_class) => this.classList.add(_class)
};
S('div').classAdd('red');
https://codepen.io/matt3224/pen/NMqBLp?editors=0010
Arrow functions don't have a prototype property. To get it working use a normal function, that's not not modern. Arrow functions are not a replacement, but an addition. You could also use a class though - maybe that's what you mean with modern.
class S {
constructor(selector, container = document) {
this.selected = container.querySelector(selector);
}
classAdd(clazz) {
this.selected.classList.add(clazz);
}
}
new S('#foo').classAdd('bar');
console.log('#foo has bar as class: ', document.getElementById('foo').classList.contains('bar'));
<div id="foo">
foo
</div>
Let's take a closer look at your code, shall we?
First block:
let S = (selector, container = document) => {
return container.querySelector(selector);
};
This code declares a simple function and assigns function reference to variable S
The function itself returns HTML element as per querySelector documentation, which can not be use in a chain notation:
S('div').something(); // won't work
let ele = S('div'); // works
Second block:
S.prototype = {
classAdd: (_class) => this.classList.add(_class)
};
Because S is a simple function (NOT a Function-Object) it has no prototype element as a regular Object does. However, the code still works as it creates a new property named propotype on the S function.
To prove what has been stated above, I have created following code example:
let S = (selector, container = document) => {
return container.querySelector(selector);
};
let ele = S('div');
console.log(ele);
S.prototype = {
classAdd: (_ele, _class) => _ele.classList.add(_class)
};
S.prototype.classAdd(ele, 'red');
.red {
padding: 10rem;
background-color: coral;
}
<div>Text inside div</div>
If you want to use chain notation such as S('div').classAdd(..) your first block function must return a valid object to act upon
let S = (selector, container = document) => {
return {
ele : container.querySelector(selector),
classAdd: function(_class) { // regular function notation as it needs this
this.ele.classList.add(_class)
}
};
};
S('div').classAdd('red')
.red {
padding: 10rem;
background-color: coral;
}
<div>Inside Div</div>
you can define classAdd in the S and return an object containing the methods :
let S = (selector, container = document) => {
let element = container.querySelector(selector);
return {
classAdd: (_class) => {
element.classList.add(_class)
}
}
};
S('div').classAdd('hello')
<div>
hello
</div>

Javascript: Chaining on elements like jQuery

I'm trying to replicate jQuery's element manipulation to a certain extent. Now what I have found to be very useful is the .first() selector. I would like to be able to chain functions like this;
getElement(selector).first().hasClass(className);
Now, there are 2 issues with how far I've gotten (Do note that my code example is minimised, so please, no comments about error-handling.)
var getElement = function(selector, parent) {
ret = document.querySelectorAll(selector);
this.element = ret;
this.hasClass = function(className) {
className.replace('.', '');
if(this.multiple())
{
console.log('Cannot use hasClass function on multiple elements');
return false;
}
};
this.first = function() {
this.element = this.element[0];
return this;
};
return this;
};
My current problem
If I call my function;
var $test = getElement('.something');
//result: nodelist with .something divs
If I call for the first element within the result;
$test.first();
//Result: First div, perfect!
However, now if I call $test again, it will replace the elements property with the result of first(), meaning I have "lost" my old values. I don't want to lose them, I only want the first() functions for that specific functionality. Then I want $test to return all elements again.
Also, recalling first() will now end up undefined, since there is only 1 element left within this as it has deleted the old elements from within the object.
Another attempt
Now, I've also tried to turn it around a bit by returning the first-child instead of the entire class object;
this.first = function() {
return this.element[0];
};
However, I will
$test.first().hasClass(className);
//Returns ERROR, method hasClass undefined
this is because .hasClass exists on the original this, which doesn't get returned anymore since I'm now returning the element.
I have tried to get something out of jQuery's library, though that just confused me more...
I have googled this subject, but with all the 'chaining methods' solutions I'm finding, all of them seem to be overwriting the original values of the object, which is not what I want to happen. One other solution actually required me to re-initiate the object over and over again, which did not seem very efficient to me... Any help is appreciated. I'm assuming I'm going about this completely the wrong way.
-- If you can help me, please do explain why your solution works. I really feel like if I understand this, my understanding of javascript can expand a lot further. I just need to get past this structural(?) issue.
A method like first() should not modify this, it should create a new object and return that. You only use return this; in methods that modify an element rather than returning information derived from the element.
this.first = function() {
return new getElement(this.element[0]);
};
And note that you have to use new getElement to create an object, not just getElement.
This also requires a change to the constructor, so it can accept either a selector string or an element:
var getElement = function(selector, parent) {
var ret = typeof selector == "string" ? document.querySelectorAll(selector) : [selector];
...
}
You should also consider doing this in proper OO fashion, by putting the methods in a prototype, rather than defining them in every object.
var getElement = function(selector, parent) {
var ret = typeof selector == "string" ? document.querySelectorAll(selector) : [selector];
this.element = ret;
};
getElement.prototype.hasClass = function(className) {
className.replace('.', '');
if (this.multiple()) {
console.log('Cannot use hasClass function on multiple elements');
return false;
}
};
getElement.prototype.first = function() {
return new getElement(this.element[0])
};
this in your outer function refers to the window / global object.
Instead, return the ret variable itself.
In the inner functions (which become the object's methods), this acts the way you expect it to.
Here's an alternative solution, which allows chaining, even after you've called the first method:
var getElement = function(selector, parent) {
var ret = typeof selector == 'string' ? document.querySelectorAll(selector)
: selector;
ret.hasClass = function(className) {
if(!this.classList) {
console.log('Cannot use hasClass function on multiple elements');
return false;
} else {
return this.classList.contains(className);
}
};
ret.first = function() {
return new getElement(this[0]);
};
return ret;
};
console.log(getElement('p').length); //2
console.log(getElement('p').first().innerHTML); //abc
console.log(getElement('p').first().hasClass('test')); //true
console.log(getElement('p').first().hasClass('nope')); //fase
console.log(getElement('p').hasClass('test')); //false (multiple elements)
<p class="test">
abc
</p>
<p>
def
</p>
Here is how I would approach this:
Create a constructor, say Search, tasked to find the elements based on the input. Using a constructor is proper OO Programming and you also have the advantage of defining methods once in the prototype and they can be accessed by all instances.
Ensure that the context (this) is an array-like object, with numeric properties and a length, so that you can easily iterate over every matched element in the traditional way (using for loops, [].forEach etc).
Create a function, say getElement, that will use the constructor and return the result without having to use the new keyword all the time. Since the function returns an instance of our constructor, you can chain the methods you want as you would normally do.
The method first uses the constructor to create a new instance instead of modifying the original, since its role is to return the first element, not delete everything but the first element.
Each time you come up with a new method you want your object to have, you can simply add it to the prototype of the constructor.
Snippet:
;(function () {
function Search (value) {
var elements = [];
/* Check whether the value is a string or an HTML element. */
if (typeof value == "string") {
/* Save the selector to the context and use it to get the elements. */
this.selector = value;
elements = document.querySelectorAll(value);
}
else if (value instanceof Element) elements.push(value);
/* Give a length to the context. */
this.length = elements.length;
/* Iterate over every element and inject it to the context. */
for (var i = 0, l = this.length; i < l; i++) this[i] = elements[i];
}
/* The method that returns the first element in a Search instance. */
Object.defineProperty(Search.prototype, "first", {
value: function () {
return new Search(this[0]);
}
});
/* The global function that uses the Search constructor to fetch the elements. */
window.getElement = (value) => new Search(value);
/* Create a reference to the prototype of the constructor for easy access. */
window.getElement.fn = Search.prototype;
})();
/* Get all elements matching the class, the first one, and the first's plain form. */
console.log(getElement(".cls1"));
console.log(getElement(".cls1").first());
console.log(getElement(".cls1").first()[0]);
/* ----- CSS ----- */
.as-console-wrapper {
max-height: 100%!important;
}
<!----- HTML ----->
<div id = "a1" class = "cls1"></div>
<div id = "a2" class = "cls1"></div>
<div id = "a3" class = "cls1"></div>
Example:
In this example, I'm adding a new method called hasClass to the prototype of the constructor.
/* The method that returns whether the first element has a given class. */
Object.defineProperty(getElement.fn, "hasClass", {
value: function (value) {
return this[0].classList.contains(value);
}
});
/* Check whether the first element has the 'cls2' class. */
console.log(getElement(".cls1").first().hasClass("cls2"));
<!----- HTML ----->
<script src="//pastebin.com/raw/e0TM5aYC"></script>
<div id = "a1" class = "cls1 cls2"></div>
<div id = "a2" class = "cls1"></div>
<div id = "a3" class = "cls1"></div>
I think the easiest would be to return a new class that contains the nodes you have selected. That would be the easiest solution, as you don't really want to mutate any of your previous selectors.
I made a small example, using some ES6 that makes a few things easier to work with, which also has a $ to initiate the selections being made.
You would notice that first of all, any selection that is made, is just calling the native document.querySelectorAll but returns a new Node class. Both first and last methods also return those elements.
Lastly, hasClass should work on all elements in the current nodes selections, so it will iterate the current node, and check all classes in there, this one returns a simple bool, so you cannot continue with the method chaining there.
Any method you wish to chain, should either:
return this object (the current node)
return an element of the this object as a new node so any further manipulations can be done there
const $ = (function(global) {
class Node extends Array {
constructor( ...nodes ) {
super();
nodes.forEach( (node, key) => {
this[key] = node;
});
this.length = nodes.length;
}
first() {
return new Node( this[0] );
}
last() {
return new Node( this[this.length-1] );
}
hasClass( ...classes ) {
const set = classes.reduce( (current, cls) => {
current[cls] = true;
return current;
}, {} );
for (let el of this) {
for (let cls of el.classList) {
if (set[cls]) {
return true;
}
}
}
return false;
}
}
global.$ = function( selector ) {
return new Node( ...document.querySelectorAll( selector ) );
};
return global.$;
}(window));
let selector = $('.foo');
let first = selector.first(); // expect 1
console.log(first[0].innerHTML);
let last = selector.last();
console.log(last[0].innerHTML); // expect 4
console.log( first.hasClass('foo') ); // expect true
console.log( first.hasClass('bar') ); // expect false
console.log( selector.hasClass('foo') ); // expect true
console.log( selector.hasClass('bar') ); // expect true
<div class="foo">1</div>
<div class="foo">2</div>
<div class="foo bar">3</div>
<div class="foo">4</div>
You can update getElement so it returns back again when you send it an element.
var getElement = function(selector, parent) {
var ret = null
if (typeof selector === "string") {
ret = document.querySelectorAll(selector);
} else {
ret = selector
}
this.element = ret;
this.hasClass = function(className) {
className.replace('.', '');
if (this.multiple()) {
console.log('Cannot use hasClass function on multiple elements');
return false;
}
};
this.first = function() {
this.element = getElement(this.element[0]);
return this;
};
return this;
};
var test = getElement(".foo");
console.log(test.first())
console.log(test.first().hasClass)
<div class="foo">1</div>
<div class="foo">2</div>
<div class="foo">3</div>
<div class="foo">4</div>
You can use .querySelectorAll(), spread element and Array.prototype.find(), which returns the first match within an array or undefined
const getElement = (selector = "", {prop = "", value = "", first = false} = {}) => {
const el = [...document.querySelectorAll(selector)];
if (first) return el.find(({[prop]:match}) => match && match === value)
else return el;
};
let first = getElement("span", {prop: "className", value: "abc", first: true});
console.log(first);
let last = getElement("span");
console.log(all);
<span class="abc">123</span>
<span class="abc">456</span>

Rx.Observable.bindCallback with scope in rxjs

It seems in rxjs 4.x, Rx.Observable.fromCallback accept scope as the second parameter, but in 5.0, this method is changed to Rx.Observable.bindCallback and doesn't accept scope parameter. How to add scope parameter in bindCallback. For example in ES6.
class Test {
constructor(input) {
this.input = input;
}
callback(cb) {
return cb(this.input);
}
rx() {
// this works on rx 4.x
// var observable = Rx.Observable.fromCallback(this.callback, this)();
// this doesn't work, because this.callback function doesn't use original this, so cannot get this.input
var observable = Rx.Observable.bindCallback(this.callback)();
// Work around: Rx.Observable.bindCallback(this.callback)();
// var me = this;
// var observable = Rx.Observable.bindCallback((cb) => {me.callback(cb);})();
observable.subscribe(
input => console.log('get data => ' + input),
err => console.log('get error =>' + err),
() => console.log('complete')
);
}
}
new Test(100).rx();
There is an example at http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#static-method-bindCallback which shows how to do this.
Use bindCallback on object method
const boundMethod = Rx.Observable.bindCallback(someObject.methodWithCallback);
boundMethod.call(someObject) // make sure methodWithCallback has access to someObject
.subscribe(subscriber);
You can call it immediately without declaring a variable, and also pass args like this:
Rx.Observable.bindCallback(someObject.callback).call(someObject,<args>)
So to bind to this you can simply call
Rx.Observable.bindCallback(this.callback).call(this,<args>)
It works for me, when I add this to the constructor
constructor(input) {
this.input = input;
this.callback = this.callback.bind(this)
}

Categories