I'm building a JavaScript class and I would like to return a result at the end of a method chain without calling a specific method.
Example 1: myDate('01/01/2000').add(1, 'day') should return 01/02/2000.
Example 2: myDate('01/01/2000').add(1, 'day').format('MMMM D YYYY') should return January 2 2000.
I know that this is possible using a JS class because saw it working in DayJs, I just don't understand how: https://github.com/iamkun/dayjs/blob/dev/src/index.js#L77
For now, what I have looks like this:
class MyDate {
constructor(date) {
this.date = date;
}
add(count, unit) {
this.date = // function
return this; // to enable method chaining
}
format() {
this.date = // function
return this.date; // to return the desired result
}
get() {
return this.date;
}
}
// wrapper function to instantiate class on function call
const myDate = date => new MyDate(date);
With this setup, I get the following behavior:
Example 3: myDate('01/01/2000').add(1, 'day') returns { date: 01/02/2000} instead of 01/02/2000.
Example 4: myDate('01/01/2000').add(1, 'day').format('MMMM D YYYY') returns January 2 2000 as expected.
Example 5: myDate('01/01/2000').add(1, 'day').get() returns 01/02/2000 but there is that nagging get() that I would like to eliminate...
I googled the issue, "js detect end of method chain" and such, but I couldn't find any results that would explain how this works.
Thanks for your help!
Logically speaking add method on a class should return the object instance instead of a particular member variable.
What you have as of now is perfect and that's how any user would expect it to work.
Imagine a case where you had to add another member variable, say time to your class and you want to chain something like object.add(1, 'day').add(5, 'hour'). You can achieve this only if your add function returns an instance of your class.
It is also perfectly fine to have to attach a get() method at the end. Consider the famous moment.js package. Even they have a format() method to get the actual date object.
Hope this clarifies your confusion.
Looking back at it, what I wanted to do was a class that had the following features:
import myDate from "./myDate.js"
import { isMyDate } from "./myDate.js"
No need to use the new keyword to instantiate
Can pass an unlimited number of arguments
With function chaining
console.log(myDate(params)) outputs a custom string MyDate<date_in_ms)>
${myDate(params)} outputs a custom string date_in_ms
Here is a possible implementation of it:
// Custom console.log
// Deno: https://doc.deno.land/builtin/stable#Deno.inspect
const inspect = Symbol.for("Deno.customInspect")
// Node: https://nodejs.org/api/util.html#util_util_inspect_custom
// const inspect = Symbol.for('nodejs.util.inspect.custom');
class MyDate {
constructor(date = new Date(), options) {
// Set the class's properties
this._date = date;
this._options = options;
// Run initialization tasks
this.initialize();
}
initialize() {
// if Date, convert time to milliseconds
if (this._date instanceof Date) {
this._date = this._date.getTime();
}
}
// Output the result
format() {
return this._date;
}
// Chainable function
add(amount) {
this._date += amount;
return this;
}
// Output all the parameters past after the first one
options() {
return this._options;
}
// Custom console.log
[inspect]() {
return `MyDate<${this._date}>`;
}
// Custom String output
toString() {
return `${this._date}`;
}
}
// Automatically instantiate class
const myDate = (date, ...options) => {
return new MyDate(date, options);
}
// Stand-alone function
const isMyDate = date => date instanceof MyDate;
// Access the stand-alone funtion from the MyDate instance
myDate.isMyDate = date => isMyDate(date);
export default myDate;
export { isMyDate };
Link to Dev.to article: https://dev.to/rildev/modern-es6-class-48pb
Link to live proof-of-concept: https://replit.com/#RilDev/ModernES6Class
Related
I am trying to set an exported function as const in a material datepicker filter with params, but when I set params, in my component, the function is launched getting the result of the function (boolean) and not the definition of the function to set my datepicker filter.
Can someone help me with a solution or alternative?
Thanks anyway.
Exported function:
export const daysFunction = (d: Date, days: any): boolean => {
if (days) {
return true;
} else {
return false;
}
};
In my component:
daysFunction = daysFunction(null, days); // this launch my function
You can define the method in the component class as:
daysFunction = () => daysFunction(null, days); // if days is global or local
daysFunction = () => daysFunction(null, this.days); // if days is a class member
I'm not sure about the definition of cookieExists outside of the ieAlert class.
Is it ok that the variable cookieExists is outside of the class ieAlert?
Or should I define it as a property inside the class definition?
var cookieExists = document.cookie.indexOf('ie11_cookie') >= 0;
class ieAlert {
// Method for checking if IE11
static isIE() {
return window.navigator.userAgent.match(/(MSIE|Trident)/);
}
// Method for setting a cookie
static createCookie(name,value,days) {
if (days) {
var date = new Date();
date.setTime(date.getTime()+(days*24*60*60*1000));
var expires = "; expires="+date.toGMTString();
}
else var expires = "";
document.cookie = name+"="+value+expires+"; path=/";
}
}
if (!ieAlert.isIE() && !cookieExists) {
window.alert("Your browser is outdated!");
ieAlert.createCookie('myCookie', 'ie11_cookie', 1);
}
module.exports = ieAlert;
By following the advice I already gave, you could simply define cookieExists as a property of ieAlert. If you want the property access to re-evaluate the condition each time, then define it as a getter property:
const ieAlert = {
// Method for checking if IE11
isIE () {
return /MSIE|Trident/.test(window.navigator.userAgent);
},
get cookieExists () {
return document.cookie.includes('ie11_cookie');
},
// Method for setting a cookie
createCookie (name, value, days) {
const cookie = [`${name}=${value}`, 'path=/'];
if (days) {
const date = new Date();
date.setTime(date.getTime()+(days*24*60*60*1000));
cookie.splice(1, 0, `expires=${date.toGMTString()}`);
}
document.cookie = cookie.join('; ');
}
};
if (!ieAlert.isIE() && !ieAlert.cookieExists) {
window.alert("Your browser is outdated!");
// ieAlert.cookieExists === false
ieAlert.createCookie('myCookie', 'ie11_cookie', 1);
// ieAlert.cookieExists === true
}
module.exports = ieAlert;
I think this might need some more explanation regarding what you're looking to accomplish. Ran on its own, this code would execute without cookieExists being a class property.
However, since you're exporting it the class, I assume the question is pertaining to how this would operate as a module.
When this module is required and its code is loaded and evaluated, it will evaluate and execute the conditional expression. However, since the sole export is class ieAlert, and the evaluation of the conditional expression is not a part of that class at all, the result of evaluating the additional expression is what is known as a side effect of the module. See this stackoverflow question for more explanation.
This module would potentially affect the scope beyond defining the class ieAlert. In general, this wouldn't be recommended.
Maybe you could define a method of class ieAlert like so:
static findCookie() {
return document.cookie.indexOf('ie11_cookie') >= 0;
}
That way, you can have more control over when the evaluation occurs.
I currently have the following working code:
Function.prototype.GetLastCallerName = function () {
if (!this.arguments || !this.arguments.callee || !this.arguments.callee.caller) return null;
var result = /^function\s+([\w\$]+)\s*\(/.exec(this.arguments.callee.caller.toString());
this.LastCaller = result ? result[1] : 'Anonymous';
return this.LastCaller;
};
I picked up that code from another thread. As you can see, it extends the Function.prototype in order to add a method called GetLastCallerName, which picks the last calling function name and (1) sets it to LastCaller on Function.LastCaller and (2) returns it.
In order to make it work:
function MyFunction1() {
MyFunction1.GetLastCallerName();
console.log(MyFunction.LastCaller);
}
function MyFunction2() {
MyFunction1();
}
MyFunction2();
What I'd like to be able to do: Eliminate the need to use GetLastCallerName() every time and extend Function in order to perform that get every time any function is called.
I'm struggling to follow what you have tried so far with your example, but I think I get the idea of what you'd like to do. Why not leverage classes, and extend on them for your use case. Check out the following example...
class Base {
baseFn() {
console.log('from base');
}
}
class Thing extends Base {
fn1() {
this.baseFn();
}
}
let thingee = new Thing();
thingee.fn1();
So baseFn is now always called when fn1 is called.
JSFiddle Link - class demo
In some of your comments it looks like you are wanting to get the "last calling function's name." How about passing back the instance of the caller itself to the parent? This would surely give you even more flexibility because now you can sculpt your caller however you wish. Check out the following...
class Base {
baseFn(caller) {
console.log(caller.id); // 1
}
}
class Thing extends Base {
constructor(id) {
super();
this.id = id;
}
fn1() {
this.baseFn(this);
}
}
let thingee = new Thing('1');
thingee.fn1();
Now you can add whatever you'd like to your Thing instance, in this case, an object with an id of 1 which can be inspected when fn1 propagates up to baseFn
JSFiddle Link - caller demo
Having this function I need to create a new instance of it. Everything works fine in JavaScript but how to I convert it to TypeScript?
function Calendar(selector, events) {
this.el = document.querySelector(selector);
this.events = events;
this.current = moment().date(1);
this.draw();
var current = document.querySelector('.today');
if(current) {
var self = this;
window.setTimeout(function() {
self.openDay(current);
}, 500);
}
}
var calendar = new Calendar('#calendar', data);
var calendar = new Calendar('#calendar', data);
It is true that anything that works in JavaScript will work in TypeScript, but that just means that the TypeScript compiler will output your JavaScript more or less untouched, possibly spitting out a bunch of warnings on the way. If you just ignore the errors, things will still work.
But assuming you want to leverage the power of TypeScript, you should start changing things. Let's start.
First, you should install the typings from Moment.js in your project, probably by running npm install moment from your project folder.
Then, I usually like to turn on all the --strictXXX compiler flags (I think you can just use --strict) to get the maximum number of warnings to ignore and/or fix.
Okay, now: the ES6/TypeScript idiom for a constructible thing is to use a class. Here's a look at some modifications I made, with some inline comments:
import * as moment from 'moment';
class Calendar {
// a Calendar has an el property which is a possibly null DOM element:
el: Element | null;
// a Calendar has a current property which is a Moment:
current: moment.Moment;
// a Calendar has an events property which is an array of Event:
events: Event[];
// the constructor function is what gets called when you do new Calendar()
// note that I assume selector is a string and events is an array of Event
constructor(selector: string, events: Event[]) {
this.el = document.querySelector(selector);
this.events = events;
this.current = moment().date(1);
this.draw();
var current = document.querySelector('.today');
if (current) {
var self = this;
window.setTimeout(function() {
self.openDay(current);
}, 500);
}
}
draw() {
// needs an implementation
}
openDay(day: Element | null) {
// needs an implementation
}
}
declare let data: Event[]; // need to define data
var calendar = new Calendar('#calendar', data);
You need to implement the draw() and openDay() methods which are presumably part of the Calendar.prototype. I put stubs for them in there. You also need to define data, which is (I'm guessing) an array of events (if it's something else you need to change the type of events.
If you look at the compiled JavaScript output from the above, you'll see that it's more or less the same as what you had. But now, of course, TypeScript is happy to let you call new Calendar(...).
There are more changes you can make, of course. For example, you can use parameter properties and remove the this.events = events; line. Or you can use property initializers and move the this.current = ... out of the constructor function and into the property declaration. Et cetera.
But this should hopefully be enough to get you started. Good luck!
I have piece of code in js like this:
var obj = (function(){
var stateObj = {key:"privateValue"};
return {
getState: function() {
return stateObj.key;
},
publicFn : function(){
//do some operation with stateObj
if(getState() == "test") {
//. . . .
}
}
}
}());
and I tested the code like this:
//test case
sandbox.stub(obj.getState,"test")
assertItShouldGoInsideIfLoop(obj.publicFn())
However on the code review, my team lead said, this is wrong and he asked me to use Dependency injection for these cases.
I really don't know why the above approach is wrong or even why one should use DI.
If I'm interpreting this correctly, the issue here is that your test for the value of calling publicFn depends on the value of stateObj. The entire idea behind dependency injection is that you provide some means to supply these values in your tests in order to decouple the data from the behavior of the function under test.
var obj = (function(){
return {
setStateObj: function(stateObj) {
this.stateObj = stateObj;
},
getState: function() {
return this.stateObj.key;
},
publicFn : function(){
//do some operation with stateObj
if(getState() == "test") {
//. . . .
}
}
}
}());
Now we can use setStateObj to set the state as needed in our tests instead of stubbing the value, which can be dangerous:
obj.setStateObj({ key: 'test' })
assertItExecutesIfStatement(obj.publicFn())
obj.setStateObj({ key: 'blah' })
assertItDoesntExecuteIfStatement(obj.publicFn())
So why is this preferred to stubbing getState? Say we comment the contents of getState:
getState: function() {
// return this.stateObj.key;
}, // returns nothing, but you're stubbing it to return "test" anyway!
Clearly, this function will not work, but with your stub, it will! So you'll have passing tests despite the non-working code!
Here's another example that makes another case for dependency injection. Say I have the following function:
function getDayOfWeek() {
var date = new Date();
var dayNames = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
return dayNames[date.getDay()];
}
This getDayOfWeek function is dependent on date. This would be a nightmare to test since we have no control over date's value. If we rewrite this function with a way to supply a date value (i.e. inject the dependency), we can check test the function for any fixed date easily:
function getDayOfWeek(date) {
date = date || new Date();
var dayNames = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
return dayNames[date.getDay()];
}
assertEqual('Wednesday', getDayOfWeek(new Date(2015, 0, 1)));
assertEqual('Thursday', getDayOfWeek(new Date(2015, 0, 2)));
// and so on...