javascript: dynamically create class instances - javascript

I am writing an app that has features that can be turned on and off via a config.json that looks something like this:
"appFeatures": {
"header": {
"create": true,
"title": "Here Comes the Sun"
},
"imageStrip": {
"create": false,
"imageDirectory": "../data/images",
"imageDimensions": [288, 162]
},
"areaChart": {
"create": true
},
"axes": {
"create": true
}
}
For each feature there is already a corresponding class of the same name that implements the feature. I'd like to use the name of the feature to create a new instance of the class. After fetching the config.json, I have code (within a Main.js class) that looks like:
this.features = Object.entries(this.config.appFeatures)
.filter((entry) => {
return entry[1].create === true;
});
this.features.forEach((feature) => { this.createFeatureInstances(feature[0]); });
And then I try to create instances, a la this.header = new Header():
createFeatureInstances(featureName) {
const className = `${featureName.replace(featureName[0], featureName[0].toUpperCase())}`;
this[`${featureName}`] = new Function(`
return class ${className} {}
`)();
This creates a new, empty Header class and, I suppose, it's instance. It is not the Header class that I have already written and want to create an instance for. How might I write the createFeatureInstances function so that I can create the instance of each class that corresponds to a feature?
EDIT Because new features may be added to this app in the future by others, I would like to minimize the times that I hard code which features are available to the app. With my current design, another developer can add another feature by writing a new feature class, importing that class into the Main.js class, and pop the config entries into the config .json without having to touch anything else in the code. For this reason, solutions like this one: Create an instance of a class in ES6 with a dynamic name? won't give me a solution because they rely on having a complete list of the classes that should already exist.

You need to have a name-class mapping somewhere. Factory function or somewhere else is your call. Your current solution lacks this, which is causing the problem:
...creates a new, empty Header class and, I suppose, it's instance. It is not the Header class that I have already written and want to create an instance for
Some explanation with a simple example
// Imagine you've defined Test
class Test {
callMe() {}
}
// And your consumer wants a className Test to be created
const className = "Test";
// This is a simple map of the name to class
const nameToClass = {
"Test": Test
}
// This creates a new class called Test, not your defined class
const AttemptedDynamicTest = new Function(`
return class ${className} {}
`)();
// The next two logs just prove the previous comment
console.log(Test === AttemptedDynamicTest); // false
console.log(typeof AttemptedDynamicTest.prototype.callMe); // undefined
// What you really need is to maintain a map, and just use it to dynamically
// create a new instance every time
console.log(typeof nameToClass[className].prototype.callMe); // function

You can use a string to initialize a cooresponding (valid and existing) class using the following snippet:
var dynamicClassInstance = new this[classNameString]();

Related

Js using reflection to create subclass in parentclass method

I got this idea about js extends system. It'll be much easier to understand what i mean by reading the following codes
in moduleA.js I have Frame class
class Frame{ // contains frame info
static Predefined_Paramters_Pattern = [1,2,3,4]
constructor(someParameters = {variable1=...,variable2=...,tag = 0}){
//do something
this.someParamtersPattern = someParamters
}
nextFrame(){
// for example
// when i call nextFrame it 'll construct 4 subclasses
// base on [this] constructor paramaters
// constructorParmatersPattern = {variable1=...,variable2=...,tag= 1 or 2 or 3 or 4}
// this code may looks meaningless
// but i just wanna make this a lot easier to be comprehended what i mean
// and avoid stucking into a complex business logic
Frame.Predefined_Paramters_Pattern.forEach(e=>{
// create sub class somehow
let type = this.type
this.someParamtersPattern.tag = e
let constructorParmatersPattern = this.someParamtersPattern
// if i use eval(`new ${type}(${constructorParmatersPattern })`)
// the browser will throw a error looks like 'SubFrame1 is not define'
})
}
}
export {Frame}
in moduleB.js I have SubFrame1 class
import {Frame} from './moduleA.js'
class SubFrame1 extends Frame{
constructor(somePramaters){
//do something
super(somePramaters)
//do something
this.type = 'SubFrame1'
}
}
export {SubFrame1}
Now what I want to achieve is when i call new SubFrame1().nextFrame(), it'll create several SubFrames automaticlly which are similar to the one I created. I can define some patterns for their constructor's paramaters. So no matter how many subclasses i have, as long as they extends Frame, then they all share the same patterns when they call .nextFrame().
But the thing is I do not wish to have a SubFrame1 class or SubFrame1,SubFrame2,...,SubFrame3 classes 's reference in my original class.
like this one
import {SubFrame1} from './subframe1.js'
import {SubFrame2} from './subframe2.js'
......
import {SubFrameN} from './subframen.js'
class Frame{
......
}
i'm pretty much a rookie to javascript but at school i was tought and told to write codes so I can reuse them and reduce redundant codes, any help or corrections would be great
So you want new SubFrame1().nextFrame() to construct a SubFrame1 instance, but new SubFrame2().nextFrame() to construct a SubFrame2 instance. Nothing easier than that, you don't even need a type property for that! JS prototype object (e.g. built with class syntax) already do contain a .constructor property holding the constructor function that you can call to create a new instance of the same (sub)class.
export class Frame{ // contains frame info
constructor(someParameters) {
// do something
this.someProperties = someParameters;
}
nextFrame() {
const type = this.constructor;
const instance = new type(this.someProperties);
return instance;
}
}
Now this assumes that the subclass constructors would be called with the same signature, and always expect the this.someProperties object as arguments for the cloning of the frame. To customise this, the subclass could of course modify this.someProperties, but as soon as the signatures become too different the subclass should better override the whole method:
class SpecialFrame extends Frame {
constructor(someParameters, someSpecials) {
super(someParameters);
this.specialProperties = someSpecials;
}
nextFrame() {
return new this.constructor(this.someProperties, this.specialProperties);
}
}
This can also be used for more complex initialisation than just copying a property value, or for hardcoding some specific arguments that nextFrame should always use. If nextFrame does more than just the cloning, split it into two methods - clone() that creates the copy and may be overridden in the subclasses, and nextFrame that calls this.clone() then modifies the copy to become the next frame instead of the same.
Edit : Bergi offered a much better solution!
____________________________________________________________________
I find out how i do this with es6 Reflect API in this link below
Create instance of "Class" Using "Reflection" in JavaScript
posted by soulshined
in moduleA.js
class Frame{ // contains frame info
static Predefined_Paramters_Pattern = [1,2,3,4]
constructor(someParameters = {variable1=...,variable2=...,tag = 0}){
//do something
this.someParamtersPattern = someParamters
}
nextFrame(){
Frame.Predefined_Paramters_Pattern.forEach(e=>{
// create sub class
let type = this.type
this.someParamtersPattern.tag = e
let constructorParmatersPattern = this.someParamtersPattern
Reflect.construct(this.type, constructorParmatersPattern )
})
}
}
export {Frame}
and subclass
import {Frame} from './moduleA.js'
class SubFrame1 extends Frame{
constructor(somePramaters){
//do something
super(somePramaters)
//do something
this.type = SubFrame1
}
}
export {SubFrame1}

Can I add custom methods to Google Apps Script classes?

I want to invoke custom methods on Google Apps Script classes such as Spreadsheet, Sheet, and DriveApp.
In https://stackoverflow.com/a/6117889, a minimal prototyping solution is used to add a method to the Javascript Date() class to get the week number. Is it possible to apply the same strategy to Google Apps Script classes?
As an example, I want to create a custom method for the Spreadsheet class that allows the spreadsheet to be moved to a particular folder in my google drive given the ID of that folder. Here is what I've tried:
Spreadsheet.prototype.moveToFolder = function(folderID) {
const file = DriveApp.getFileById(this.getId());
const destination = DriveApp.getFolderById(folderID);
file.moveTo(destination);
}
However, I get the error message "ReferenceError: Spreadsheet is not defined".
Is there another way to achieve what I want?
It is possible to add custom methods. But Spreadsheet class is not directly accessible. So, it is needed to first get a instance of Spreadsheet class using any of methods available:
const Spreadsheet = SpreadsheetApp.getActive();
Then use Object.getPrototypeOf() on the Spreadsheet instance to get it's prototype.
Object.getPrototypeOf(Spreadsheet).myMethod = function (){
console.info("myMethod was called!")
return true;
}
Any property defined on the prototype will then propogate through all Spreadsheet instances.
Update:
The prototype object returned by Object.getPrototypeOf(Spreadsheet) is Object. This can also be confirmed by logging Spreadsheet.constructor.name. This means that there is no special Spreadsheet prototype or constructor used to create the Spreadsheet instance. Therefore, Although you can add custom methods, They're added to all objects say, Range, DriveApp and any object created with var obj = {} or Object.create("Any object except null").
Given that Spreadsheet doesn't have a unique prototype but actually uses that of Object, as noted by TheMaster, you can simply add your method to the Object prototype.
Object.prototype.moveToFolder = function(folderID) {
const file = DriveApp.getFileById(this.getId());
const destination = DriveApp.getFolderById(folderID);
file.moveTo(destination);
}
Since this method will apply to all objects, you should ask yourself if it's really worth doing. See "Why is extending native objects a bad practice?"
Instead of modifying the native object, you could create a new class that "inherits" the native methods while also giving you the ability to override and add new methods.
function main() {
const ss = new Spreadsheet(SpreadsheetApp.getActive());
console.log(ss._native.getName()); // MySpreadsheet
console.log(ss.getName()); // The name is MySpreadsheet
ss.moveToFolder(FOLDER_ID);
}
class Spreadsheet {
constructor(native) {
this._native = native;
// Copy native's methods to this
Object.getOwnPropertyNames(this._native).forEach(property => {
this[property] = this._native[property];
});
// Override native methods
Object.defineProperties(this, {
'getName': {
value: function() {
return `The name is ${this._native.getName()}`;
}
}
});
}
moveToFolder(folderId) {
const file = DriveApp.getFileById(this.getId());
const destination = DriveApp.getFolderById(folderId);
file.moveTo(destination);
}
}

Making a custom group of defined chaining methods in js

The question is related to general js programming, but I'll use nightwatch.js as an example to elaborate my query.
NightWatch JS provides various chaining methods for its browser components, like: -
browser
.setValue('input[name='email']','example#mail.com')
.setValue('input[name='password']', '123456')
.click('#submitButton')
But if I'm writing method to select an option from dropdown, it requires multiple steps, and if there are multiple dropdowns in a form, it gets really confusing, like: -
browser
.click(`#country`)
.waitForElementVisible(`#india`)
.click(`#india`)
.click(`#state`)
.waitForElementVisible(`#delhi`)
.click(`#delhi`)
Is it possible to create a custom chaining method to group these already defined methods? For example something like:
/* custom method */
const dropdownSelector = (id, value) {
return this
.click(`${id}`).
.waitForElementVisible(`${value}`)
.click(`${value}`)
}
/* So it can be used as a chaining method */
browser
.dropdownSelector('country', 'india')
.dropdownSelector('state', 'delhi')
Or is there any other way I can solve my problem of increasing reusability and readability of my code?
I'm somewhat new to JS so couldn't tell you an ideal code solution, would have to admit I don't know what a proxy is in this context. But in the world of Nightwatch and test-automation i'd normally wrap multiple steps I plan on reusing into a page object. Create a new file in a pageObject folder and fill it with the method you want to reuse
So your test...
browser
.click(`#country`)
.waitForElementVisible(`#india`)
.click(`#india`)
.click(`#state`)
.waitForElementVisible(`#delhi`)
.click(`#delhi`)
becomes a page object method in another file called 'myObject' like...
selectLocation(browser, country, state, city) {
browser
.click(`#country`) <== assume this never changes?
.waitForElementVisible(country)
.click(country)
.click(state)
.waitForElementVisible(city)
.click(city);
}
and then each of your tests inherit the method and define those values themselves, however you chose to manage that...
const myObject = require ('<path to the new pageObject file>')
module.exports = {
'someTest': function (browser) {
const country = 'something'
const state = 'something'
const city = 'something'
myObject.selectLocation(browser);
You can also set your country / state / city as variables in a globals file and set them as same for everything but I don't know how granular you want to be.
Hope that made some sense :)
This is a great place to use a Proxy. Given some class:
function Apple ()
{
this.eat = function ()
{
console.log("I was eaten!");
return this;
}
this.nomnom = function ()
{
console.log("Nom nom!");
return this;
}
}
And a set of "extension methods":
const appleExtensions =
{
eatAndNomnom ()
{
this.eat().nomnom();
return this;
}
}
We can create function which returns a Proxy to select which properties are retrieved from the extension object and which are retrieved from the originating object:
function makeExtendedTarget(target, extensions)
{
return new Proxy(target,
{
get (obj, prop)
{
if (prop in extensions)
{
return extensions[prop];
}
return obj[prop];
}
});
}
And we can use it like so:
let apple = makeExtendedTarget(new Apple(), appleExtensions);
apple
.eatAndNomnom()
.eat();
// => "I was eaten!"
// "Nom nom!"
// "I was eaten!"
Of course, this requires you to call makeExtendedTarget whenever you want to create a new Apple. However, I would consider this a plus, as it makes it abundantly clear you are created an extended object, and to expect to be able to call methods not normally available on the class API.
Of course, whether or not you should be doing this is an entirely different discussion!

Call functions of a class instance from other file in Node

I have a bot.js class in which I made the function getUserInventory. I required bot.js in my main file app.js and created a new instance.
bot.js
class SteamBot {
constructor(logOnOptions){
[...]
this.logOn(logOnOptions);
}
logOn(logOnOptions){
[...]
}
getUserInventory(sid, gameid, contextid, onlyTradeable, callback){
[...]
}
}
module.exports = SteamBot;
app.js
const SteamBot = require('./bots/bot.js');
const bot = new SteamBot({
'logOnOptions'
});
Now I can call the function getUserInventory in my app.js file by just typing bot.getUserInventory(...).
But what if I want to call the function in another file? Do I have to just type the same code that I wrote in app.js? Or would that cause problems?
I am a beginner.
When you are calling new SteamBot(); you are creating a new instance of a SteamBot, it depends how you intend to use your SteamBot, do you need multiple steambots or just one ? If you only need one instance in your app,I would advise you to do something like that :
file /steambot.class.js
// this file contains your class
class SteamBot {
...
}
module.exports = SteamBot;
file /steambot.js
const SteamBotClass = require('./steambot.class');
let SteamBot;
module.exports = function(...args) {
SteamBot = SteamBot || new SteamBotClass(...args);
return SteamBot;
};
/app.js
const SteamBot = require('./steambot')({
'logOnOptions'
});
And if you want to use in a third file the same instance:
const SteamBot = require('./steambot')();
with this structure, in your app, you would be able to create a new class if you need it one day, with just the .class.js file, but if you need to keep one instance of SteamBot all along the app, just require steambot.js.
it gives you a singleton pattern based on file, a bit different from the answer from Grégory which I also like.
Hope this helps :)
What you are doing in app.js is instantiate a new object of the class SteamBot.
Then you are calling the method getUserInventory of that object.
Can you call the method of this object from another file?
Yes if you pass to that file the object you just created. If not you gotta instantiate a new object, which can be a mistake due to the fact that i'll be totally different from the first object you did created (an other implementation of the class that's going to have it's own attributes).
To pass through the object instantiation you have two things you can do :
Use static methods. A static method do not require an instantiation.
example:
SteamBot.getUserInventory(...)
class SteamBot {
static getUserInventory(...) {
...
}
}
Use singleton pattern, that will allow the creation of only one instance of a class.
example:
let instance = null;
class SteamBot {
constructor() {
if (instance) return instance;
instance = this;
return instance;
}
static getInstance() {
return instance || new SteamBot();
}
}
It doesn't mater how you name your bot variable. If you name it bot you will call bot.getUserInventory(...), if anotherBot then anotherBot.getUserInventory(...) if foo then foo.getUserInventory(...) and so on

Get file name from javascript ES6 class reference in nodejs

Is there a way to get the file name from a reference to a class? Please note this example is over simplified to illustrate what I'm trying to do (don't start suggesting logging libraries please!)
//Logger1.js
class Logger1 {
}
//MainProcess.js
class MainProcess {
startChildProcess(Logger) {
//This extension doesn't work, but looking to find something similar that does:
Logger.fileName = () => {
return __filename
}
let loggerFileName = Logger.fileName() //Returns "Main.js" not "Logger1.js", so no good...
childProcess.fork(processFileName, [loggerFileName] )
}
}
//In a child process spawned by main:
loggerPath = process.argv[2]
let Logger = require(loggerPath)[path.basename(loggerPath).replace(".js","")]
let logger = new Logger()
I can obviously add a property with a string value, or a method to return __filename the Logger1 class, but I'd rather avoid it. Is there a way to do this from inside the MainProcess class, keeping Logger1 and any other external code clean?
The reason I don't pass the instance of the logger, is the main process then creates child processes, and these child processes instantiate their own loggers. And there is no way to pass object references down to child processes as far as I'm aware.

Categories

Resources