Execute overrided static function from base class - javascript

I want to execute an overriden static method from the base class without being instantiated.
I want to use an MVC like pattern on an app I'm building and I've created a class named Model that connects to a database and gets the object, it has some static methods that I'm overriding such as the table name (tableName). The problem is that this method must be called from static methods.
From the base class all works like a charm, the problem is when I use other class that extends the base one.
Here's the code:
class Model {
static get tableName() {
return this.name;
}
static get primaryKey() {
return "id";
}
static get columns() {
return [];
}
static id(id) {
return new Promise((resolve, reject) => {
Model.get(Model.primaryKey, id)
.then(models => {
resolve(models[0]);
});
});
}
static get(columnName, value, compareSymbol) {
return new Promise((resolve, reject) => {
if (!compareSymbol) {
compareSymbol = "=";
}
let sql = `select * from ${this.tableName}`,
params = [];
if (typeof columnName !== "undefined") {
sql += ` where ${columnName} ${compareSymbol} ?`;
params = [columnName, value];
}
console.log(sql, params);
});
}
constructor(params) {
this.target = new.target
for (let name in params) {
if (Model.primaryKey == name) {
this[`#${name}`] = params[name];
} else {
this.set(name, params[name]);
}
}
}
set(name, value) {
if (name != this.target.primaryKey && this.target.columns.indexOf(name) > -1) {
this[`#${name}`] = value;
}
}
get(name) {
return this[`#${name}`];
}
executeSql(sql, variables) {
console.log(sql, variables);
}
update() {
let columns = this.target.columns.slice(),
values = [],
sql;
sql = `update ${this.target.tableName} set ${columns.join("=?, ")}=? where ${this.target.primaryKey} = ${this.get(this.target.primaryKey)}`;
for (let i = 0; i < columns.length; i++) {
values.push(this.get(columns[i]));
}
return this.executeSql(sql, values);
}
}
// from this line down is other different file
class Directory extends Model {
static get tableName() {
return "directories";
}
static get columns() {
return [
"name",
"path",
"recursive"
];
}
}
// shows "from Model" expected "from directories"
Directory.id(2);
// work as expected
let d1 = new Directory({
id: 1,
name: "name",
path: "path",
recursive: false
});
d1.update();
If called without being instantiated it returns "Model", is there any way to get the overriden value from the base class?

Related

Vue 3 changing the length of both arrays have same values intially

I am using vue 3 where is i am receiving an array of associate schedule from server. Now i am saving this schedule to 2 arrays. I am doing this because i need the original fetched data later after doings changes in associate list array which is my first array.
associateList
orignalList
The problem is when I am replacing the associate array after doing changes with original array .No nothing works infact original list contains same changes which i did on associate list array even i have not touched the original list anywhere in my code just saving the data from response on it. I just want the original res on original list array so i can replace associate list with original array when watch function detect changes in attendance list array.
<script lang="ts">
import { Options, Vue } from "vue-class-component";
import ApprovalService from "../../service/ApprovalService";
import Toaster from "../../helpers/Toaster";
import moment from "moment";
import { camelCase } from "lodash";
import {
ScheduleList,
AttendanceList,
ApprovedList,
} from "../hoursApproval/IHoursAppoval";
import VueCal from "vue-cal";
import "vue-cal/dist/vuecal.css";
import AssociatePinVerification from "../../components/AssociatePinVerification.vue";
#Options({
components: { VueCal, AssociatePinVerification },
watch: {
attendanceList() {
const oL = this.orignalList;
alert('orgi'+oL.length);
this.associateList = this.orignalList;
this.checkScheduleContainsLogedHrs();
},
},
})
export default class HoursApproval extends Vue {
private ApprovalTxn;
private scheduleID = "";
private toast;
private orignalList: ScheduleList[] = [];
private associateList: ScheduleList[] = [];
private approvedList: ScheduleList[] = [];
private attendanceList: AttendanceList[] = [];
private approveManually = {
hours: 0,
freezed: false,
shiftDate: "",
counterId: 0,
};
//DEFAULT METHOD OF TYPE SCRIPT
//CALLING WHENEVER COMPONENT LOADS
created() {
this.ApprovalTxn = new ApprovalService();
this.toast = new Toaster();
}
mounted() {
this.getSchedule();
}
getSchedule() {
this.ApprovalTxn.getAssociateShifts(this.searchDate).then((res) => {
const d = this.camelizeKeys(res);
const s = d.employeeList.scheduleList;
if (s != null)
{
this.orignalList = this.camelizeKeys(d.employeeList.scheduleList);
this.associateList = this.camelizeKeys(d.employeeList.scheduleList);
}
else
{
this.associateList = [];
this.orignalList = [];
}
this.scheduleID = d.employeeList.id;
this.weekStartingDate = d.postStartingDate;
this.weekEndingDate = d.postEndingDate;
this.weekNo = d.weekNo;
});
}
camelizeKeys = (obj) => {
if (Array.isArray(obj)) {
return obj.map((v) => this.camelizeKeys(v));
} else if (obj !== null && obj.constructor === Object) {
return Object.keys(obj).reduce(
(result, key) => ({
...result,
[camelCase(key)]: this.camelizeKeys(obj[key]),
}),
{}
);
}
return obj;
};
formatDate(value) {
if (value) {
return moment(String(value)).format("DD-MM-YYYY");
}
}
updateAssociateLogin() {
if (
this.loginDetails.loginTime == "" ||
this.loginDetails.logoutTime == "" ||
this.loginDetails.loginDate == ""
) {
this.toast.showWarning(
"Please set date login and logout timings for associate to proceed"
);
} else {
this.associateList = [];
this.ApprovalTxn.updateAssociateLogin(
this.loginDetails.loginTime,
this.loginDetails.attendenceID,
this.managerApproved,
this.loginDetails.logoutTime,
this.loginDetails.loginDate,
this.weekStartingDate,
this.weekEndingDate
).then((res) => {
this.toast.handleResponse(res);
alert(this.orignalList.length);
// this.associateList = this.orignalList;
const d = this.camelizeKeys(res);
//DOING THIS TO CHNAGE THE RE ACTIVITY OF VUE
//this.modifyTimings();
this.attendanceList = d.data;
//alert(this.orignalList.length);
//console.log(this.associateList);
});
this.loginHoursDialog = false;
}
}
}
</script>

Setting a variable equal to a return of function

I'm trying to set a variable equal to a return of a function but I don't understand how can i do.
In particular this is the code:
constructor() {
super();
this.manager = new BleManager()
this.state = {
info: "",
values: {}
}
this.deviceprefix = "FM_RAW";
this.devicesuffix_dx = "DX";
}
model_dx(model) {
return this.deviceprefix + model + this.devicesuffix_dx
}
if (device.name === "THERE i should use the return of model_dx") {
this.info(device.id)
this.manager.stopDeviceScan();
device.connect()
I should check device.name with the result of the model_dx function. How can I do?
Thank you
How about calling it? Create a instance of the object and call it:
// Assume the name is CustomObj
class CustomObj {
constructor() {
super();
this.manager = new BleManager()
this.state = {info: "", values: {}}
this.deviceprefix = "FM_RAW";
this.devicesuffix_dx = "DX";
}
model_dx(model) {
return this.deviceprefix + model + this.devicesuffix_dx
}
}
// I suppose this is outside of the object? Otherwise it would be out of scope anyways as you wrote your if in no function or whatsoever
CustomObj obj = new CustomObj(); //<-- Create instance
let alwaysdifferentParam = "model test";
if (device.name === obj.model_dx(alwaysdifferentParam )) { //<-- Call it
this.info(device.id)
this.manager.stopDeviceScan();
device.connect()
}
Try this:
if (device.name === this.model_dx('pass the desired value here')) {
this.info(device.id)
this.manager.stopDeviceScan();
device.connect()
}

Call a class method from a another class in JavaScript?

I am doing a task where I need to wire up a search field to a simple JS application that displays a few items and the user can search through and filter them.
There are three classes - App, ProductsPanel and Search. Both Search and ProductsPanel are being initialised inside the App class.
The ProductsPanel class holds an array with 10 products.
I want to call a method of ProductsPanel from inside Search that filters through the products. How can I do that?
I've tried using this.productsPanel = new productsPanel() inside the constructor of the first class, but that brings up a new instance which doesn't have the array of all of the products.
Here's the App class:
class App {
constructor() {
this.modules = {
search: {
type: Search,
instance: null
},
filter: {
type: Filter,
instance: null
},
productsPanel: {
type: ProductsPanel,
instance: null
},
shoppingCart: {
type: ShoppingCart,
instance: null
}
};
}
init() {
const placeholders = document.querySelectorAll("#root [data-module]");
for (let i = 0; i < placeholders.length; i++) {
const root = placeholders[i];
const id = root.dataset.module;
const module = this.modules[id];
if (module.instance) {
throw new Error(`module ${id} has already been started`);
}
module.instance = new module.type(root);
module.instance.init();
// console.info(`${id} is running...`);
}
}
}
app = new App();
app.init();
And here are the Search:
export default class Search {
constructor(root) {
this.input = root.querySelector("#search-input");
}
// addEventListener is an anonymous function that encapsulates code that sends paramaters to handleSearch() which actually handles the event
init() {
this.input.addEventListener("input", () => {
this.handleSearch();
});
}
handleSearch() {
const query = this.input.value;
app.modules.productsPanel.instance.performSearch(query);
}
}
And ProductsPanel classes:
export default class ProductsPanel {
constructor(root) {
this.view = new ProductsPanelView(root, this);
this.products = [];
}
init() {
this.products = new ProductsService().products;
this.products.forEach(x => this.view.addProduct(x));
}
performSearch(query) {
query = query.toLowerCase();
this.products.forEach(p => {
if (query === p.name) {
this.view.showProduct(p.id);
} else {
this.view.hideProduct(p.id);
}
});
}
addToCart(id) {
const product = this.products.filter(p => p.id === id)[0];
if (product) {
app.modules.shoppingCart.instance.addProduct(product);
}
}
}
I want to call ProductsPanel's performSearch method but on the instance created by the App class. I have no clue on how I can do that.
Try below custom event handler class
class CustomEventEmitter {
constructor() {
this.eventsObj = {};
}
emit(eName, data) {
const event = this.eventsObj[eName];
if( event ) {
event.forEach(fn => {
fn.call(null, data);
});
}
}
subscribe(eName, fn) {
if(!this.eventsObj[eName]) {
this.eventsObj[eName] = [];
}
this.eventsObj[eName].push(fn);
return () => {
this.eventsObj[eName] = this.events[eName].filter(eventFn => fn !== eventFn);
}
}
}
How to use?
create the object of CustomEventEmitter class
let eventEmitter = new CustomEventEmitter()
Subscribe an event
emitter.subscribe('event: do-action', data => {
console.log(data.message);
});
call the event
emitter.emit('event: do-action',{message: 'My Custom Event handling'});
Hope this helps!

ES6 Classes for Data Models

I'm trying to use ES6 Classes to construct data models (from a MySQL database) in an API that I'm building. I prefer not using an ORM/ODM library, as this will be a very basic, simple API. But, I'm struggling to get my head around how to define these models.
My data entities are (these are just some simplified examples):
CUSTOMER
Data Model
id
name
groupId
status (enum of: active, suspended, closed)
Private Methods
_getState(status) {
var state = (status == 'active' ? 'good' : 'bad');
return state;
}
Requests
I want to be able to do:
findById: Providing a single customer.id, return the data for that specific customer, i.e. SELECT * FROM customers WHERE id = ?
findByGroupId: Providing a group.id, return the data for all the customers (in an array of objects), belonging to that group, i.e. SELECT * FROM customers WHERE groupId = ?
Response Payloads
For each customer object, I want to return JSON like this:
findById(1);:
[{
"id" : 1,
"name" : "John Doe",
"groupId" : 2,
"status" : "active",
"state" : "good"
}]
findByGroupId(2);:
[{
"id" : 1,
"name" : "John Doe",
"groupId" : 2,
"status" : "active",
"state" : "good"
},
{
"id" : 4,
"name" : "Pete Smith",
"groupId" : 2,
"status" : "suspended",
"state" : "bad"
}]
GROUP
Data Model
id
title
Requests
I want to be able to do:
findById: Providing a single group.id, return the data for that specific group, i.e. SELECT * FROM groups WHERE id = ?
Response Payloads
For each group object, I want to return JSON like this:
findById(2);:
{
"id" : 2,
"title" : "This is Group 2",
"customers" : [{
"id" : 1,
"name" : "John Doe",
"groupId" : 2,
"status" : "active",
"state" : "good"
},
{
"id" : 4,
"name" : "Pete Smith",
"groupId" : 2,
"status" : "suspended",
"state" : "bad"
}]
}
Requirements:
Must use ES6 Classes
Each model in its own file (e.g. customer.js) to be exported
Questions:
My main questions are:
Where would I define the data structure, including fields that require data transformation, using the private methods (e.g. _getState())
Should the findById, findByGroupId, etc by defined within the scope of the class? Or, should these by separate methods (in the same file as the class), that would instantiate the object?
How should I deal with the case where one object is a child of the other, e.g. returning the Customer objects that belongs to a Group object as an array of objects in the Group's findById?
Where should the SQL queries that will connect to the DB be defined? In the getById, getByGroupId, etc?
UPDATE!!
This is what I came up with - (would be awesome if someone could review, and comment):
CUSTOMER Model
'use strict';
class Cust {
constructor (custData) {
this.id = custData.id;
this.name = custData.name;
this.groupId = custData.groupId;
this.status = custData.status;
this.state = this._getState(custData.status);
}
_getState(status) {
let state = (status == 'active' ? 'good' : 'bad');
return state;
}
}
exports.findById = ((id) => {
return new Promise ((resolve, reject) => {
let custData = `do the MySQL query here`;
let cust = new Cust (custData);
let Group = require(appDir + process.env.PATH_API + process.env.PATH_MODELS + 'group');
Group.findById(cust.groupId).then(
(group) => {
cust.group = group;
resolve (cust)
},
(err) => {
resolve (cust);
}
);
});
});
GROUP Model
'use strict';
class Group {
constructor (groupData) {
this.id = groupData.id;
this.title = groupData.title;
}
}
exports.findById = ((id) => {
return new Promise ((resolve, reject) => {
let groupData = `do the MySQL query here`;
if (id != 2){
reject('group - no go');
};
let group = new Group (groupData);
resolve (group);
});
});
CUSTOMER Controller (where the Customer model is instantiated)
'use strict';
var Cust = require(appDir + process.env.PATH_API + process.env.PATH_MODELS + 'cust');
class CustController {
constructor () {
}
getCust (req, res) {
Cust.findById(req.params.id).then(
(cust) => {
res(cust);
},
(err) => {
res(err);
}
)
}
}
module.exports = CustController;
This seems to be working well, and I've been able to use Class, Promise and let to make it more ES6 friendly.
So, I'd like to get some input on my approach. Also, am I using the export and required features correctly in this context?
Here is another approach,
Where would I define the data structure, including fields that require data transformation, using the private methods (e.g. _getState())
You should define those fields, relationship in your model class extending the top model. Example:
class Group extends Model {
attributes() {
return {
id: {
type: 'integer',
primary: true
},
title: {
type: 'string'
}
};
}
relationships() {
return {
'Customer': {
type: 'hasMany',
foreignKey: 'groupId'
}
};
}
}
Should the findById, findByGroupId, etc by defined within the scope of the class? Or, should these by separate methods (in the same file as the class), that would instantiate the object?
Instead of having many functions use findByAttribute(attr) in Model Example:
static findByAttribute(attr) {
return new Promise((resolve, reject) => {
var query = this._convertObjectToQueriesArray(attr);
query = query.join(" and ");
let records = `SELECT * from ${this.getResourceName()} where ${query}`;
var result = this.run(records);
// Note: Only support 'equals' and 'and' operator
if (!result) {
reject('Could not found records');
} else {
var data = [];
result.forEach(function(record) {
data.push(new this(record));
});
resolve(data);
}
});
}
/**
* Convert Object of key value to sql filters
*
* #param {Object} Ex: {id:1, name: "John"}
* #return {Array of String} ['id=1', 'name=John']
*/
static _convertObjectToQueriesArray(attrs) {
var queryArray = [];
for (var key in attrs) {
queryArray.push(key + " = " + attrs[key]);
}
return queryArray;
}
/**
* Returns table name or resource name.
*
* #return {String}
*/
static getResourceName() {
if (this.resourceName) return this.resourceName();
if (this.constructor.name == "Model") {
throw new Error("Model is not initialized");
}
return this.constructor.name.toLowerCase();
}
How should I deal with the case where one object is a child of the other, e.g. returning the Customer objects that belongs to a Group object as an array of objects in the Group's findById?
In case of relationships, you should have methods like findRelations, getRelatedRecords.
var customer1 = new Customer({ id: 1, groupId: 3});
customer1.getRelatedRecords('Group');
class Model {
...
getRelatedRecords(reln) {
var targetRelationship = this.relationships()[reln];
if (!targetRelationship) {
throw new Error("No relationship found.");
}
var primaryKey = this._getPrimaryKey();
var relatedObject = eval(reln);
var attr = {};
if (targetRelationship.type == "hasOne") {
console.log(this.values);
attr[relatedObject.prototype._getPrimaryKey()] = this.values[targetRelationship.foreignKey];
} else if (targetRelationship.type == "hasMany") {
attr[targetRelationship.foreignKey] = this.values[this._getPrimaryKey()];
}
relatedObject.findByAttribute(attr).then(function(records) {
// this.values[reln] = records;
});
}
...
}
Where should the SQL queries that will connect to the DB be defined? In the getById, getByGroupId, etc?
This one is tricky, but since you want your solution to be simple put the queries inside your find methods. Ideal scenario will be to have their own QueryBuilder Class.
Check the following full code the solution is not fully functional but you get the idea. I've also added engine variable in the model which you can use to enhance fetching mechanism. All other design ideas are upto your imagination :)
FULL CODE:
var config = {
engine: 'db' // Ex: rest, db
};
class Model {
constructor(values) {
this.values = values;
this.engine = config.engine;
}
toObj() {
var data = {};
for (var key in this.values) {
if (this.values[key] instanceof Model) {
data[key] = this.values[key].toObj();
} else if (this.values[key] instanceof Array) {
data[key] = this.values[key].map(x => x.toObj());
} else {
data[key] = this.values[key];
}
}
return data;
}
static findByAttribute(attr) {
return new Promise((resolve, reject) => {
var query = this._convertObjectToQueriesArray(attr);
query = query.join(" and ");
let records = `SELECT * from ${this.getResourceName()} where ${query}`;
var result = this.run(records);
// Note: Only support 'equals' and 'and' operator
if (!result) {
reject('Could not found records');
} else {
var data = [];
result.forEach(function(record) {
data.push(new this(record));
});
resolve(data);
}
});
}
getRelatedRecords(reln) {
var targetRelationship = this.relationships()[reln];
if (!targetRelationship) {
throw new Error("No relationship found.");
}
var primaryKey = this._getPrimaryKey();
var relatedObject = eval(reln);
var attr = {};
if (targetRelationship.type == "hasOne") {
console.log(this.values);
attr[relatedObject.prototype._getPrimaryKey()] = this.values[targetRelationship.foreignKey];
} else if (targetRelationship.type == "hasMany") {
attr[targetRelationship.foreignKey] = this.values[this._getPrimaryKey()];
}
relatedObject.findByAttribute(attr).then(function(records) {
// this.values[reln] = records;
});
}
/**
* Test function to show what queries are being ran.
*/
static run(query) {
console.log(query);
return [];
}
_getPrimaryKey() {
for (var key in this.attributes()) {
if (this.attributes()[key].primary) {
return key;
}
}
}
/**
* Convert Object of key value to sql filters
*
* #param {Object} Ex: {id:1, name: "John"}
* #return {Array of String} ['id=1', 'name=John']
*/
static _convertObjectToQueriesArray(attrs) {
var queryArray = [];
for (var key in attrs) {
queryArray.push(key + " = " + attrs[key]);
}
return queryArray;
}
/**
* Returns table name or resource name.
*
* #return {String}
*/
static getResourceName() {
if (this.resourceName) return this.resourceName();
if (this.constructor.name == "Model") {
throw new Error("Model is not initialized");
}
return this.constructor.name.toLowerCase();
}
}
class Customer extends Model {
attributes() {
return {
id: {
type: 'integer',
primary: true
},
name: {
type: 'string'
},
groupId: {
type: 'integer'
},
status: {
type: 'string'
},
state: {
type: 'string'
}
};
}
relationships() {
return {
'Group': {
type: 'hasOne',
foreignKey: 'groupId'
}
};
}
}
class Group extends Model {
attributes() {
return {
id: {
type: 'integer',
primary: true
},
title: {
type: 'string'
}
};
}
relationships() {
return {
'Customer': {
type: 'hasMany',
foreignKey: 'groupId'
}
};
}
}
var cust = new Customer({
id: 1,
groupId: 3
});
cust.getRelatedRecords('Group');
var group = new Group({
id: 3,
title: "Awesome Group"
});
group.getRelatedRecords('Customer');
var groupData = new Group({
"id": 2,
"title": "This is Group 2",
"customers": [new Customer({
"id": 1,
"name": "John Doe",
"groupId": 2,
"status": "active",
"state": "good"
}),
new Customer({
"id": 4,
"name": "Pete Smith",
"groupId": 2,
"status": "suspended",
"state": "bad"
})
]
});
console.log(groupData.toObj());

Is it possible to Proxy an extended class in javascript

I'm trying to Proxy an inheritance structure from within a node module and allow the client to instantiate a new Class A. Currently when trying to access class B's parent methods I get a.parentMethod is not a function
handler.js ->
module.exports = {
get(target, key, receiver) {
return target.getAttribute(key)
},
set(target, key, value, receiver) {
return target.setAttribute(key, value)
}
}
A.js ->
const handler = require('handler')
class B {
constructor(data) {
this.data = data
}
parentMethod() {
... do stuff
}
}
class A extends B {
constructor(data){
super(data)
}
}
module.exports = function(data) {
return new Proxy(new A(data), handler)
}
////
const A = require('A')
var a = new A
a.parentMethod()
Where am I going wrong with this structure? I'm new to Proxy!
Thanks
EDIT -
Further context:
I'm trying to keep sets of properties in sync based on a valueSchema I have defined. When I set Artwork.title I need Artwork['Artwork Title'] to be updated with the same value. Likewise when I retrieve Artwork.title I get the value of Artwork['Artwork Title']. Hopefully this helps a bit. I'm stuck at the above error so I can't be sure what I've written actually works yet! I'm trying to debug why the function can't be found first...
class Instance {
constructor(data) {
this._valueAttributes = {}
}
setAttribute(key, value) {
if (this._isValueAttribute(key)) {
return this._getSetValueAttribute(key, value)
}
throw Error('Cannot set invalid property '+key+' on instance.')
}
getAttribute(key) {
if (this._isValueAttribute(key)) {
return this._getSetValueAttribute(key)
}
}
_getSetValueAttribute(key, value) {
let schemaKey = this._getSchemaKey(key)
if (_.isFunction(schemaKey)) {
return alias(data)
}
if (value === undefined) {
return this._valueAttributes[schemaKey]
}
return this._valueAttributes[schemaKey] = value
}
_isValueAttribute(key) {
return _.keys(this._valueSchema).indexOf(key) === -1
}
}
class Artwork extends Instance {
constructor() {
this._valueSchema = {
medium: 'Artwork Medium',
title: 'Artwork Title'
}
}
}
///
a = new Artwork
a.title = 'thing'
a['Artwork Medium'] = 'medium';
I need
a.title == a['Artwork Title']
a['Artwork Medium'] == a.medium
It's very likely I've royally screwed it all up. I've assumed that I can access __valueSchema on the child from the parent. Is this not possible?

Categories