Call a class method from a another class in JavaScript? - 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!

Related

I can't get my data from my document with firebase

I'm creating a single page application with javascript by using the firebase firestore as database.
I've managed to get all my documents listed where I call for all of them. Each document has an href to the detail page. But on the detailpage, it looks like I don't have any data from my document.
I want to call the title from the document to show as an h1 but noting renders and I don't have any errors in my console..
Anyone who can help me?
My code:
This is to get all the documents (who are events)
// Get events
import firebase from 'firebase/app';
import 'firebase/firestore';
const Events = {
getAll: async () => {
// get firestore
const db = firebase.firestore();
// define query
const query = db.collection('events');
// query snapshot
const querySnapshot = await query.get();
// loop over all documents
return querySnapshot.docs.map((doc) => (
{
...doc.data(),
id: doc.id,
}
));
},
// get the data from a detailpage by the ID
getById: async (id) => {
const db = firebase.firestore();
const event = await (await (db.collection('events').doc(id).get())).data();
return event;
},
};
export default Events;
My component to render all documents as a list
import Component from '../lib/Component';
import Elements from '../lib/Elements';
import Router from '../Router';
import Events from '../lib/Events';
class EventsComponent extends Component {
constructor() {
super({
name: 'events',
model: {
events: [],
},
routerPath: '/events',
});
this.eventsLoaded = false;
}
// Get the events one by one, make them an href to their detail page and show the name of the
event
loadEvents() {
if (!this.eventsLoaded) {
Events.getAll().then((data) => {
this.model.events = data.map((event) => ({
href: `${Router.getRouter().link('/event')}/${event.id}`,
textContent: event.title,
}));
});
this.eventsLoaded = true;
}
}
render() {
const { events } = this.model;
// create home container
const eventsContainer = document.createElement('div');
// Load events
this.loadEvents();
// Check if there are any events
if (events.length === 0) {
eventsContainer.innerHTML = 'There are no events planned at the moment';
} else {
eventsContainer.appendChild(
Elements.createList({
items: this.model.events,
}),
);
}
return eventsContainer;
}
}
export default EventsComponent;
My detail page
// Event Component
import Component from '../lib/Component';
import Elements from '../lib/Elements';
import Events from '../lib/Events';
class EventComponent extends Component {
constructor() {
super({
name: 'event',
model: {
event: [],
},
routerPath: '/event/:id',
});
this.eventLoaded = false;
}
// Set the model loading to true when the id page is founded
loadEvent(id) {
if (!this.eventLoaded) {
this.eventLoaded = true;
Events.getById(id).then((data) => {
this.model.event = data;
});
}
}
render() {
const { event } = this.model;
// create event overview container
const eventContainer = document.createElement('div');
// Check for existing events and return the events.
// Create an h1 with the name of the event as title
if (!event) {
this.loadEvent(this.props.id);
} else {
eventContainer.appendChild(
Elements.createHeader({
textContent: event.title,
}),
);
console.log(this.model.event);
}
return eventContainer;
}
}
export default EventComponent;
You have model set as an array insted of an object in you detail page.
constructor() {
super({
name: 'event',
model: {
event: [],
},
routerPath: '/event/:id',
});
this.eventLoaded = false;
}
When you try to access your event you are trying to get .title from an array insted of an object.
Elements.createHeader({
textContent: event.title,
}),
I would map the event field by field { "field" : 0, "field2": ""} but you may find a way to make it less verbose

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>

unable to select all checkboxes in tree using angular2-tree on init

Objective : i have a button named "feed data" so when ever i click it the data will be loaded i mean the tree with checkboxes here my requirement is when ever i click it along with data all the check boxes have to be checked on init i tried using
this.treeComp.treeModel.doForAll((node: TreeNode) => node.setIsSelected(true));
but it is not working below is my code
click(tree: TreeModel) {
this.arrayData = [];
let result: any = {};
let rs = [];
console.log(tree.selectedLeafNodeIds);
Object.keys(tree.selectedLeafNodeIds).forEach(x => {
let node: TreeNode = tree.getNodeById(x);
// console.log(node);
if (node.isSelected) {
if (node.parent.data.name) //if the node has parent
{
rs.push(node.parent.data.name + '.' + node.data.name);
if (!result[node.parent.data.name]) //If the parent is not in the object
result[node.parent.data.name] = {} //create
result[node.parent.data.name][node.data.name] = true;
}
else {
if (!result[node.data.name]) //If the node is not in the object
result[node.data.name] = {} //create
rs.push(node.data.name);
}
}
})
this.arrayData = rs;
tree.selectedLeafNodeIds = {};
}
selectAllNodes() {
this.treeComp.treeModel.doForAll((node: TreeNode) => node.setIsSelected(true));
// firstNode.setIsSelected(true);
}
onTreeLoad(){
console.log('tree');
}
feedData() {
const results = Object.keys(this.data.info).map(k => ({
name: k,
children: this.data.info[k].properties
? Object.keys(this.data.info[k].properties).map(kk => ({ name: kk }))
: []
}));
this.nodes = results;
}
feedAnother() {
const results = Object.keys(this.dataa.info).map(k => ({
name: k,
children: this.dataa.info[k].properties
? Object.keys(this.dataa.info[k].properties).map(kk => ({ name: kk }))
: []
}));
this.nodes = results;
}
onActivate(event) {
this.selectedDataList.push(event.node.data);
console.log(this.selectedDataList)
}
onDeactivate(event) {
const index = this.selectedDataList.indexOf(event.node.data);
this.selectedDataList.splice(index, 1);
console.log(this.selectedDataList)
}
below is my stackblitz https://stackblitz.com/edit/angular-hrbppy
Use updatedata and initialized event to update the tree view to check all checkboxes.
app.component.html
<tree-root #tree *ngIf ="nodes" [nodes]="nodes" [options]="options" [focused]="true"
(initialized)="onTreeLoad()"
(updateData)="updateData()"
(select)="onActivate($event)"
(deselect)="onDeactivate($event)">
</tree-root>
It'll initiate tree-root component only if nodes variable is available,
then in the initialized and updateData event call selectAllNodes method to select all checkboxes.
app.component.ts
updateData() {
this.selectAllNodes();
}
onTreeLoad(){
this.selectAllNodes();
}
Refer to this slackblitz for working example.
just, in your function feed data call to your function this.selectAllNodes() enclosed in a setTimeout. You can see your forked stackblitz
setTimeout(()=>{
this.selectAllNodes()
})
NOTE: I see in your code you try to control in diferents ways the items selected. I simplified using a recursive function.
In this.treeComp.treeModel.selectedLeafNodeIds we have the items that are changed, so
getAllChecked()
{
const itemsChecked=this.getData(
this.treeComp.treeModel.selectedLeafNodeIds,null)
console.log(itemsChecked);
}
getData(nodesChanged,nodes) {
nodes=nodes||this.treeComp.treeModel.nodes
let data: any[] = []
nodes.forEach((node: any) => {
//in nodesChanged we has object like {1200002:true,123132321:false...}
if (nodesChanged[node.id]) //can be not changed, and then it's null because
//it's not in object or can be changed to false
data.push({id:node.id,name:node.name})
//or data.push(node.name); //if only need the "name"
if (node.children)
data=[...data,...this.getData(nodesChanged,node.children)]
}
);
return data
}
Updated I updated the function getData to include the "parent" of the node, but looking the code of #Raghul selvam, his function like me more than mine.
getData(nodesChanged,nodes,prefix) {
nodes=nodes||this.treeComp.treeModel.nodes
let data: any[] = []
nodes.forEach((node: any) => {
if (nodesChanged[node.id])
data.push(prefix?prefix+"."+node.name:node.name)
if (node.children)
data=[...data,...this.getData(nodesChanged,node.children,prefix?prefix+"."+node.name:node.name)]
}
);
return data
}
And call it as
this.getData(this.treeComp.treeModel.selectedLeafNodeIds,null,"")
You could add this in your onTreeLoad function. You could add a boolean flag(treeLoaded) for tracking if the tree has loaded or not.
onTreeLoad(tree){
this.selectAllNodes();
this.treeLoaded = true;
}

how to get the exact search result in angular 4

i have implemented the search module in my app. The search does not search for the exact phrase, but rather individually for each word in the phrase. For example, if you search for "Comprehensive Metabolic", you would only expect to see the CMP Panels, but the search actually returns every single panel that has either the word "Comprehensive" or "Metabolic", which is a much longer list.
any help can i get?
is there any pipe i can use to filter exact search?
here is my search component html
<input #searchInput type="text" (focus)="onFocus($event)" (blur)="onBlur($event)" (keyup)="onKeyUp($event)" placeholder="Search">
its Ts file
#Input() searchTerm: string = "";
#Output() onSearchInputUpdate = new EventEmitter();
#ViewChild("searchInput") searchInputField: ElementRef;
public searchFocus: boolean;
private searchTermTimeoutId;
private waitTime: number = 500; // half a second
onBlur(event) {
// setTimeout so clearSearch click event has time to be called first
setTimeout(() => {
if (event.srcElement.value.length === 0) {
this.searchFocus = false;
}
}, 100);
}
onKeyUp(event) {
if (this.searchTermTimeoutId) {
clearTimeout(this.searchTermTimeoutId);
}
this.searchTermTimeoutId = setTimeout(() => {
this.onSearchInputUpdate.emit(this.searchInputField.nativeElement.value);
}, this.waitTime);
}
i added this in my component where i am using it
here parent component's html
<app-search-list (onSearchInputUpdate)="onSearchFieldUpdate($event)">
</app-search-list>
<app-test-selection-category-list
(onCategorySelect)="updateTestPanelView($event)"></app-test-selection-
category-list>
its Ts File
onSearchFieldUpdate($event) {
this.searchField = $event;
this.updateTestPanelView(this.selectedCategoryId);
}
updateTestPanelView(categoryId: string) {
this.selectedCategoryId = categoryId;
switch (this.selectedCategoryId) {
case '-1':
this.fetchAllTests();
break;
case "0":
this.fetchFavoritesForCategories();
break;
default:
this.fetchTestsForCategory();
}
}
fetchAllTests() {
this.testOrderService.getAllTests(this.searchField).subscribe(response =>
{
const {panels, tests} = this.extractPanelsAndTests(response);
this.testSelectionSession = {
...this.testSelectionSession,
PanelsForAll: panels,
IndividualTestPanelsForAll: tests
};
this.store.dispatch(
new SetTestOrderTestSelectionSession(this.testSelectionSession)
);
})
}
fetchFavoritesForCategories() {
this.testOrderService
.getAllFavorites(this.searchField)
.subscribe(favorites => {
this.testSelectionSession = Object.assign(
{},
this.testSelectionSession,
{
FavoritesByCategory: _.groupBy(favorites, 'CategoryName')
}
);
this.store.dispatch(
new SetTestOrderTestSelectionSession(this.testSelectionSession)
);
});
}
fetchTestsForCategory() {
this.testOrderService
.getTestsByCategoryId(this.selectedCategoryId, this.searchField)
.subscribe(categoryResponse => {
const {panels, tests} = this.extractPanelsAndTests(categoryResponse);
this.testSelectionSession = Object.assign(
{},
this.testSelectionSession,
{
PanelsForCategory: panels.map(panel => {
panel.CategoryId = this.selectedCategoryId;
return panel;
}),
IndividualTestPanelsForCategory: tests.map(
test => {
test.CategoryId = this.selectedCategoryId;
return test;
}
)
}
);
this.store.dispatch(
new SetTestOrderTestSelectionSession(this.testSelectionSession)
);
});
}
i am getting every result which has either Comprehensive or metabolic.
like this
what can i do to get exact result
any help?
Thanks

Execute overrided static function from base class

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?

Categories