What is the intention of the class `Model` in this code snippets? - javascript

I am reading a code base written by someone else. a Model class is defined in the following snippet, but I can not figure out why the writer defined this class. What is the intention to define this class other that using plain Object? What is the pattern here? Thank you very much!
const models = {}
export default class Model {
constructor() {
this._type = this.constructor.name
if (!models[this._type]) {
models[this._type] = this.constructor.prototype
}
}
}
const model = object => {
if (!object) {
return object
}
if (object._type && object._type != object.constructor.name) {
object.__proto__ = models[object._type]
}
(typeof object == "object") && Object.values(object).forEach(v => {
model(v)
})
return object
}
Its usage:
class xxxModel extends Model { ... }.
updated
and there are many occurrences of the following usage pattern:
const refreshData = function (...args) {
// do something to update UI.
const data = args[0]
Object.keys(data).forEach(key => model(this.data[key]))
}

Related

Lit-translate shows code or key instead of translation

I am trying to use lit-translate to translate my "Elmish" typescript website into different languages. I use webpack and dotnet.
Inside my index.ts I register the translate config:
registerTranslateConfig({
lookup: (key, config) => config.strings != null ? config.strings[key] : key,
empty: key => key,
loader: lang => {
return new Promise((resolve, reject) => {
resolve({"title": "Der Titel"});
})}
});
use("en");
(The loader is hardcoded because getting the localization file also didn't work, but that's not important for now).
Inside html I use get("title")or translate("title") to get the translation.
Instead of the translation, I either read [title] or
(part) => { partCache.set(part, cb); updatePart(part, cb); }
If I assign the result of translate() to a variable, I get the following result inside the Object:
TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
at Function.r (<anonymous>:1:83)
at Module../src/index.ts (http://localhost:8080/app.bundle.js:9800:10)
at __webpack_require__ (http://localhost:8080/app.bundle.js:12476:33)
at http://localhost:8080/app.bundle.js:13494:11
at http://localhost:8080/app.bundle.js:13497:12
I already tried disabling webpack strict mode.
The full class looks like the following:
export class App extends Application<Model, Message, {}> {
#property({type: Boolean})
hasLoadedStrings = false;
init(): [Model, Cmd] {
const initModel: Model = {
openPage: [{title: "Home"}, home]
}
return [initModel, Cmd.none]
}
constructor() {
super();
this.hasLoadedStrings = false;
}
shouldUpdate (changedProperties: Map<string | number | symbol, unknown>) {
return this.hasLoadedStrings && super.shouldUpdate(changedProperties);
}
async connectedCallback () {
super.connectedCallback();
await use("en");
this.hasLoadedStrings = true;
}
}
customElements.define('my-element', App);
I solved the problem by writing my own little translation library.
The lit-translate package contains errors if you use it as suggested in the documentation so feel free to use my solution:
translate.ts:
const de_config =
{
"Category": {
"Home": "Start",
},
"Home": {
"Welcome back,": "Willkommen zurück,"
};
export function translate(key: string) {
const keyParts = key.split(".");
const keyCategory = keyParts.shift();
const keyWord = keyParts.join('.');
let translationIndexes = typedKeys(de_config);
for(let translationIndex of translationIndexes)
{
if(translationIndex == keyCategory) {
let translationKeys = typedKeys(de_config[translationIndex]);
for(let translationKey of translationKeys) {
if(translationKey == keyWord) {
return de_config[translationIndex][translationKey];
}
}
}
}
return key;
}
function typedKeys<T>(o: T): (keyof T)[] {
return Object.keys(o) as (keyof T)[];
}
Access translations with:
import { translate } from './translate';
translate("Category.Home");
One could also store translation object in a different file, write a function to change language dynamically, etc...

code coverage doesn't reach void set function

Code Coverage doesn't reach some lines of codes even though I'm testing them. :(
Here is bind.ts decorator and [[NOT COVERED]] code coverage tag I created. Currently the set(value) is not covered by the test even though I'm covering it.
type Descriptor<T> = TypedPropertyDescriptor<T>;
export default function bind<T = Function>(
target: object,
key: string,
descriptor: Descriptor<T>
): Descriptor<T> {
...
set(value: T): void {
[[22-NOT COVERED]] if (process.env.NODE_ENV !== 'test') {
[[23-NOT COVERED]] throw new Error('Unable to set new value to decorated method');
[[24-NOT COVERED]] }
[[25-NOT COVERED]] Object.defineProperty(this, key, { ...descriptor, value });
},
};
}
bind.spec.ts
My strategy is to create new class Component and test its context on call
class MockClass extends React.PureComponent<Props, State> {
#bind
getProp(): string {
const { propName } = this.props;
return propName;
}
#bind
getState(): string {
const { stateName } = this.state;
return stateName;
}
#bind
setProp(value: string): void {
this.state = { stateName: value };
}
}
...
describe('bind', () => {
const mockState = {
stateName: 'stateName',
};
const mockProp = {
propName: 'propName',
};
const mockClass = new MockClass(mockProp, mockState);
...
it('should have called it once', () => {
expect(mockClass.getProp()).toBe(mockProp.propName);
});
it('should have called it in setState', () => {
expect(mockClass.setProp('newState')).toBe(undefined); <<<- This can cover 22-25??
});
The uncovered setter is code that would be exercised if you set a value of the class property. You don't have any test code that does this. You're only getting a property named setProp then calling it. The fact that the property has "set" in its name may be confusing matters.
Your test code would have to do something like this to test the setter of the decorator:
mockClass.props.otherPropName = 'blah';
mockClass.getProp = function() {
const { otherPropName } = this.props;
return otherPropName;
};
expect(mockClass.getProp()).toEqual('blah');

Using bracket notation depending on input data

I'm in the process of optimizing some code in my library, however, I have a bit of an issue regarding why bracket notation isn't working when trying to call an imported class.
Parameter type accepts a string that is camelCased, such as: myString.
The parameter data can be anything.
import { foo } from './example';
export const find = (type: string, data: any) => {
// This next line effectively deletes the end of the string starting
// from the first capital letter.
const f = type.replace(/[A-Z][a-z]+/, '');
try {
return [f][type](data);
} catch (e) {
return e;
}
};
this is what I expect it to look like if I was to visualize it using dot notation:
foo.fooBar(someRandomData)
This should call the static method fooBar(data) on the imported class foo,
however, I receive an error message:
TypeError: [f][type] is not a function
If I was to revert it back to my if..else if style, it works:
if (type.startsWith('foo')) return foo[type](data);
How can I do what is desired above without getting the defined error message?
Thank you for your help in advance!
EDIT: This is an example I modified from already existing code, therefore, I fixed a few typos.
EDIT #2: as per requested, the imported class foo looks like this:
export class foo{
static fooBar(data){
// Do stuff with data...
}
In the end you need some reference to the classes or object to get started with. Here is a working example of how you could do this type of functionality, but you have start with a map of your class instances so you can get to them:
class foo {
fooBar(data: any) { return { name: 'foo', data } };
}
class example {
exampleFood(data: any) { return { name: 'example', data } };
}
var lookup: { [classes: string]: any; } = { };
lookup['foo'] = new foo();
lookup['example'] = new example();
const find = (encodedType: string, data: any) => {
// This next line effectively deletes the end of the string starting
// from the first capital letter.
const f = encodedType.replace(/[A-Z][a-z]+/, '');
try {
return lookup[f][encodedType](data);
} catch (e) {
return e;
}
};
alert(JSON.stringify(find("fooBar", "Found you foo")));
alert(JSON.stringify(find("exampleFood", "Found you example")));
I would suggest you instead move over to using the nodeJS built-in EventEmitter.
You can do something like:
import * as EventEmitter from 'events';
import { foo } from './example';
import { bar } from './example2';
export const discordEventEmitter = new EventEmitter();
discordEventEmitter.on('fooBar', foo.fooBar);
discordEventEmitter.on('fooToo', foo.fooToo);
discordEventEmitter.on('barBell', bar.barBell);
Then, when you want to fire an event, you can simply:
discordEventEmitter.emit('fooBar', someData);
You can also simplify the event handler registration by writing:
const fooProps = Object.getOwnPropertyNames(foo) as (keyof typeof foo)[];
fooProps.filter(k => typeof foo[k] === 'function').forEach(funcName => {
discordEventEmitter.on(funcName, foo[funcName]);
});
const barProps = Object.getOwnPropertyNames(bar) as (keyof typeof bar)[];
fooProps.filter(k => typeof bar[k] === 'function').forEach(funcName => {
discordEventEmitter.on(funcName, bar[funcName]);
});

How to create Class dynamically in a proper way in Typescript?

I am trying to migrate my app to typescript. I have kind of a base class that is my base library object. My classes created dependent on the base class. Below is a glimpse of my problem.
Below code works but autocomplete doesn't work. Couldn't figure out what should be defined for the type of Model.
const map = new WeakMap();
function weakRef<T>(context: T): T {
// #ts-ignore
if (!map.has(context)) { map.set(context, {}); }
// #ts-ignore
return map.get(context);
}
function getModel(provider: Provider) {
return class Model {
getSomething(key: string) {
return weakRef(provider).configuration(key);
}
};
}
class Provider {
configuration: (key: string) => string;
constructor() {
weakRef(this).configuration = (key: string) => {
return key;
};
weakRef(this).Model = getModel(this);
}
get Model(): any {
return weakRef(this).Model;
}
set Model(model: any) {
weakRef(this).Model = model;
}
}
const provider = new Provider();
const obj = new (provider.Model)();
console.log(obj.getSomething('test')); // This works, but autocomplete doesn't
I don't want to pass provider to the constructor in base model.
Any help is appreciated, thanks.
You can get the return type of a function type with ReturnType<T>.
In this case, ReturnType<typeof getModel> should do the trick.
You can also fix the //#ts-ignore lines by constraining T to be an object type.
const map = new WeakMap();
function weakRef<T extends object>(context: T): T {
if (!map.has(context)) { map.set(context, {}); }
return map.get(context);
}
That said, I think this architecture is unnecessarily complicated. Why not just use this instead of a value tied to this?

Javascript es6 combine 2 static classes into an object

I am having this code:
class A {
static b() {
}
}
class B {
static c() {
}
}
I am trying to combine these 2 static classes into one:
const combined = { ...A, ...B };
However, the combined object results in an empty one while I am expecting an object containing all the static methods combined.
What am I doing wrong ?
You can get an array of the static methods of a class with Object.getOwnPropertyNames:
class A {
static staticMethod() {}
nonStaticMethod() {}
}
console.log(Object.getOwnPropertyNames(A));
There are a few properties we are not interested in, namely prototype, length and name. We can filter them out manually, e.g. like this:
class A {
static staticMethod() {}
nonStaticMethod() {}
}
console.log(
Object.getOwnPropertyNames(A)
.filter(prop => prop !=='prototype' && prop !== 'length' && prop !== 'name')
);
Good! Now we can create the combined object and add A's filtered methods to it:
class A {
static b() {
console.log('called A.b');
}
}
const classAMethods = Object.getOwnPropertyNames(A)
.filter(prop => prop !== 'prototype' &&
prop !== 'length' &&
prop !== 'name');
const combined = {};
combined.A = {};
classAMethods.forEach(method => {
combined.A[method] = () => A[method]();
});
console.log(combined);
combined.A.b();
If you'd rather like to be able to call combined.b(), you can do the following. Note that this way methods with the same name in multiple classes will clash. E.g. if you have both A.b and B.b defined, combined can only hold one of them.
class A {
static b() {
console.log('called A.b');
}
}
const classAMethods = Object.getOwnPropertyNames(A)
.filter(prop => prop !== 'prototype' &&
prop !== 'length' &&
prop !== 'name');
const combined = {};
classAMethods.forEach(method => {
combined[method] = () => A[method]();
});
console.log(combined);
combined.b();
To bring all together, we have the following. Note that I used ...args to add support for passing arguments when calling the class methods.
class A {
static b() {
console.log('called A.b');
}
}
class B {
static c(name1, name2) {
console.log('called B.c, hello', name1, 'and', name2);
return 'returned by B.c';
}
}
const getMethods = (cls) => Object.getOwnPropertyNames(cls)
.filter(prop => prop !== 'prototype' &&
prop !== 'length' &&
prop !== 'name');
const combined = {};
const addMethodsToCombined = (cls) => {
combined[cls.name] = {};
getMethods(cls).forEach(method => {
combined[cls.name][method] = (...args) => cls[method](...args);
});
};
addMethodsToCombined(A);
addMethodsToCombined(B);
console.log(combined);
combined.A.b();
console.log(combined.B.c('world', 'universe'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
When you add new static methods to class A or B, they will automatically be available through combined as well. If you create a new class C, you just need to call addMethodsToCombined(C).
You can set the methods at a new object
const combined = {A:A.b, B:B.c}
You could extend B from A, then extend another class from B.
If you're using static classes though, it might be better to just use object literals
class A {
static b() {
console.log('called A.b')
}
}
class B extends A {
static c() {
console.log('called B.c')
}
}
class Combined extends B {}
Combined.b()
Combined.c()

Categories