Match elements of properties in objects in JavaScript - javascript

I've been trying to compare all the series from my objects with the corresponding genre. I managed to display them but I'm practically writing the same code three times, while I'm sure there is a better way.
I'm trying to figure it out how to make the "type" argument match the right genre with the right series, so I don't have to call the same function three times. Maybe I shouldn't use objects into an Array, idk.
Please ask me if there is anything confusying.
'use strict';
//LIST OF SERIES
let listSeries =
[
{film: ['Breaking Bad', 'One of Us Is Lying'], genre: "Drama"},
{film: ['Servant', 'The Midnight Club'], genre: "Horror"},
{film: ['The Office US','Seinfeld'], genre: "Comedy"}
]
// SERIES CLASS
class Series
{
constructor(series, type)
{
this.series = series;
this.type = type;
}
}
//CLASS DISPLAY SERIES LIST WRTING HTML
class Display
{
tableBody = document.getElementById('tableBody');
add(libraryOfSeries)
{
let uiString = `<tr>
<td>${libraryOfSeries.series}</td>
<td class="table-dark">${libraryOfSeries.type}</td>
<button class="btn1 btn-primary" >Read!</button>
</tr>`;
tableBody.innerHTML += uiString;
}
}
// DISPLAY DRAMA SERIES AND CALLING THE ADD METHOD INTO THE CLASS DISPLAY
function displayDrama(series)
{
let dramaSeries = series.find(item => item.genre == "Drama");
for(let i of dramaSeries.film)
{
let currentSeries = new Series(i, dramaSeries.genre);
let display = new Display;
display.add(currentSeries)
}
}
displayDrama(allSeries);
// DISPLAY COMEDY SERIES AND CALLING THE ADD METHOD INTO THE CLASS DISPLAY
function displayComedy(series)
{
let comedySeries = series.find(item => item.genre == "Comedy");
for(let i of comedySeries.film)
{
let currentSeries = new Series(i, comedySeries.genre);
let display = new Display;
display.add(currentSeries)
}
}
displayComedy(allSeries);
// DISPLAY DRAMA SERIES AND CALLING THE ADD METHOD INTO THE CLASS DISPLAY
function displayHorror(series)
{
let horrorSeries = series.find(item => item.genre == "Horror");
for(let i of horrorSeries.film)
{
let currentSeries = new Series(i, horrorSeries.genre);
let display = new Display;
display.add(currentSeries)
}
}
displayHorror(allSeries);

Just use a function with two input variables, then whenever you are trying to call that function, you can decide what you are going to filter.
function displayGenre(series, genreToFilter)
{
let comedySeries = series.find(item => item.genre === genreToFilter);
for(let i of comedySeries.film)
{
let currentSeries = new Series(i, comedySeries.genre);
let display = new Display;
display.add(currentSeries)
}
}
// Calling the function to display drama:
displayGenre(allSeries, "Drama")
// Calling the function to display comedy:
displayGenre(allSeries, "Comedy")
// Calling the function to display horror:
displayGenre(allSeries, "Horror")
Then you can use type variable in the function as well.

Related

How to make a function change only the first array element

I'm quite new to JavaScript, and I'm stuck with an issue which I didn't manage to find an answer for.
I have an array, each item of which is tied to a card in markup.
When a button is pressed, a form pops up and allows to enter name and url for the new image, which is then unshifted to thebeginning of the array.
The last element of the array is popped, so there's always the same number of elements.
And the issue itself is:
When I add a single card, it works perfect: last card is popped and the new one is tucked in the beginning.
But when I use and submit the form again, the card that I have previously edited changes as well, although it should not.
I suspect that the mistake lies in the function that updates the cards, but I'm all out of ideas how to fix it.
Is there a way to fix that function?
const formAdd = document.querySelector('.popup__form-add')
const newElement = { name: '', link: '' };
const blank = { name: '', link: '' };
const placeName = document.querySelector('.popup__edit_type_place-name');
const placeImage = document.querySelector('.popup__edit_type_place-picture');
const elements = document.querySelector('.elements');
const cards = elements.querySelectorAll('.element');
const buttonAdd = document.querySelector('.profile__button-add');
const buttonAddClose = document.querySelector('.popup__add-button-close');
//content popup functions
function popupAddDisplay() {
popupAdd.classList.add('popup_opened');
}
function popupAddHide() {
popupAdd.classList.remove('popup_opened');
}
function contentUpdate() {
event.preventDefault();
newElement.name = placeName.value;
newElement.link = placeImage.value;
console.log(newElement);
initialCards.pop();
initialCards.unshift(blank);
blank.name = newElement.name;
blank.link = newElement.link;
console.log(initialCards);
cardsCheck();
popupAddHide();
};
//cards array, page content loads from here
const initialCards = [{
name: 'Архыз',
link: 'https://pictures.s3.yandex.net/frontend-developer/cards-compressed/arkhyz.jpg'
},
{
name: 'Челябинская область',
link: 'https://pictures.s3.yandex.net/frontend-developer/cards-compressed/chelyabinsk-oblast.jpg'
},
{
name: 'Иваново',
link: 'https://pictures.s3.yandex.net/frontend-developer/cards-compressed/ivanovo.jpg'
},
{
name: 'Камчатка',
link: 'https://pictures.s3.yandex.net/frontend-developer/cards-compressed/kamchatka.jpg'
},
{
name: 'Холмогорский район',
link: 'https://pictures.s3.yandex.net/frontend-developer/cards-compressed/kholmogorsky-rayon.jpg'
},
{
name: 'Байкал',
link: 'https://pictures.s3.yandex.net/frontend-developer/cards-compressed/baikal.jpg'
}
];
//compares info from the markup with the array, then pushes array info into markup
function cardsCheck() {
cards.forEach(function(item, index) {
const nameAlt = initialCards[index].name;
const imageSrcAlt = initialCards[index].link;
item.querySelector('.element__name').textContent = nameAlt;
item.querySelector('.element__image').src = imageSrcAlt;
if (nameAlt == '') {
item.style.display = 'none'
}
console.log(nameAlt);
});
}
document.onload = cardsCheck();
buttonAdd.addEventListener('click', () => {
popupAddDisplay()
});
buttonAddClose.addEventListener('click', popupAddHide);
formAdd.addEventListener('submit', contentUpdate);```
You always use the same blank object. So at the end you have the same object multiple times in the array. If you change an attribute of this object it is changed wherever it is in the list also.
To avoid this you need to create a new object before adding it to the array
function contentUpdate() {
event.preventDefault();
newElement.name = placeName.value;
newElement.link = placeImage.value;
console.log(newElement);
initialCards.pop();
let blank = { name: '', link: '' };
initialCards.unshift(blank);
blank.name = newElement.name;
blank.link = newElement.link;
console.log(initialCards);
cardsCheck();
popupAddHide();
};
Every time you create a new card, it seems like you override the global variables blank and newElement.
However, you don't have to. You could create a local variable in your contentUpdate function called newElement.
That way, each time updateContent is called, a new local variable is created.
You could therefore try the following code:
const formAdd = document.querySelector('.popup__form-add')
const placeName = document.querySelector('.popup__edit_type_place-name');
const placeImage = document.querySelector('.popup__edit_type_place-picture');
const elements = document.querySelector('.elements');
const cards = elements.querySelectorAll('.element');
const buttonAdd = document.querySelector('.profile__button-add');
const buttonAddClose = document.querySelector('.popup__add-button-close');
//content popup functions
function popupAddDisplay() {
popupAdd.classList.add('popup_opened');
}
function popupAddHide() {
popupAdd.classList.remove('popup_opened');
}
function contentUpdate() {
event.preventDefault();
const newElement = {}
newElement.name = placeName.value;
newElement.link = placeImage.value;
console.log(newElement);
initialCards.pop();
initialCards.unshift(newElement);
console.log(initialCards);
cardsCheck();
popupAddHide();
}

Can an instance of a type class object inherit a value of instance of a parent given in js?

I'm trying to make a program that allows you to calculate, how much a person on a students apartment, should pay for a invoice given.
At first I had created a Invoice and a Roomie classes and all worked just fine.
Continuing whit this, I had implemented the Apartment class, that has some property in it but a really important one for the logic of the program, the number of rooms.
The thing that I want to happen, is that you can create a instance of an Apartment (myHouse) and every time you add a Invoice gets the values of it.
class Apartment {
constructor(id, adress, numberOfRooms) {
this.id = id;
this.adress = adress;
this.numberOfRooms = numberOfRooms;
this.roomies = [];
this.invoices = [];
}
addRoomie(roomie) {
this.roomies.push(roomie);
}
addInvoice(invoice) {
this.invoices.push(invoice);
}
}
class Invoice {
constructor(total, type, begin, end) {
//Ask for this!
this.total = total;
this.type = type;
this.begin = begin;
this.end = end;
this.payed = false;
this.totalFractionated = this.totalPerPerson();
this.debtors = {};
}
totalPerPerson() {
const { begin, end, total, numberOfRooms } = this;
const difference = end.getTime() - begin.getTime();
const daysOfInterval = difference / (1000 * 60 * 60 * 24);
const totalPerDay = total / daysOfInterval;
return totalPerDay / 5; // This 5 I wanted to be numberOfRooms specify on the Aparment class
}
Of course that I can put the super method and everything, but I have to specify the values of the invoice (numberOfRooms) every time this way.
So, first thing, you shouldn't use inheritance here cause an Invoice isn't a specialized case of Apartment.
Said that, there are couple ways you can do this:
When you create an Invoice you pass the Apartment instance to the constructor so you can save it an refer it later as this.apartment.numberOfRooms
You can move the totalPerPerson method to the Apartment class and call it passing the index of the invoice you want as parameter, since seems like you're saving the invoices there
You can make a function that isn't part of any of those 2 classes and have it receive an instance of an Invoice and an instance of an Apartment as parameters
From experience, you might be missing some critical objects. Roomies have no relationship with an apartment whatsoever. You might be missing the Tenant class. Tenants occupy a room. Each tenant has a room instance, so you are also missing the room class. We can also make a room extend an apartment so that it can get its properties of the apartment which includes an address e.t.c. Each tenant in a room can have an invoice property if the monthly invoice will be splitted or each room can have an invoice property if the tenants decided together how to pay the rent every month. This will enable us have the following. Since pure JS does not have an abstract class, we will make an apartment a normal class.
class Apartment {
this.address = "1234 HackerWay, linux ln, 000000";
this.numberOfRooms = 20;
}
class Room extends Apartment {
constructor() {
super();
this.isOccupied = false;
this.tenants = [];
this.maximumTenantsAllowed = 2;
this._invoice = new Invoice();
}
addTenant(tenant) {
if(this.isFull) throw "Room at maximum capacity";
this.tenants.push(tenant);
}
get isFull() {
return this.tenants.length === this.maximumTenantsAllowed;
}
/// Returns the invoice object
get invoice() {
return this._invoice;
}
get numberOfTenants() {
return this.tenants.length;
}
}
class Tenant {
constructor(tenantName) {
this.name = tenantName;
this._invoice = new Invoice();
}
get Invoice() {
return this._invoice;
}
}
class Invoice {
generateInvoice(invoiceObject) {
if(invoiceObject instanceof Room) // write logic for Room
if(invoiceObject instanceof Tenant) // write logic for tenant
}
}
If there is a logic need to know if the apartment is full, create a RealEstateAgent class that keeps track of the apartment and modify the Room code as needed.
I didn't test the code. I only presented you a general idea.
I had earlier provided a OOP based answer that you can build on but it has limitations. Below is an algorithmic approach. This is just a framework that you can build on.
class Apartment {
constructor(name = "Apartment",address = "1234 HackerWay, linux ln, 000000") {
this.address = address;
this.name = name;
// This can also be a doubly linked list but for the sake of
// learning, we just use an array. A doubly linkedlist will allow
// us know which room is next to which within O(1) complexity.
// Keeping it in an array will cause at least O(n)
this.rooms = [
new Room(1),
new Room(2),
new Room(3),
new Room(4),
new Room(5),
];
}
// returns the first available room that is not filled
// to maximum capacity
get firstAvailableVacancy() {
return this.rooms.find((e) => !e.isFull);
}
// returns first empty room
get firstEmpty() {
return this.rooms.find((e) => e.isVacant);
}
findRoomByNumber(number) {
return this.rooms.find((e) => e.id === number);
}
get numberOfRooms() {
return this.rooms.length;
}
get hasVacancy() {
return Boolean(this.firstAvailableVacancy) || Boolean(this.firstEmpty);
}
get hasEmptyRoom() {
return Boolean(this.firstEmpty);
}
/// Or whatever algorithm for calculating cost
generateCostOfRoom(room) {
// algorithm to generate cost of room
}
// Adds tenant to room based on if they wish to have a
// co-tenant or not
addTenant(tenant) {
if (!this.hasVacancy) throw "No vacancy, check back again";
if (tenant.acceptsCoTenant) {
let vacantRoom = this.firstAvailableVacancy;
if (vacantRoom.hasTenant && vacantRoom.tenants[0].acceptsCoTenant) {
vacantRoom.addTenant(tenant);
this.generateCostOfRoom(vacantRoom);
tenant.room = vacantRoom;
return vacantRoom;
}
} else {
let vacantRoom = this.firstEmpty;
if (!vacantRoom)
throw "No vacancy, check back again or consider haviing a co-tenant";
vacantRoom.addTenant(tenant);
this.generateCostOfRoom(vacantRoom);
tenant.room = vacantRoom;
return vacantRoom;
}
}
}
class Room {
constructor(roomNumber) {
this.id = roomNumber;
this.tenants = [];
this.maximumTenantsAllowed = 2;
this._invoice = new Invoice();
this.cost = 0;
}
addTenant(tenant) {
if (this.isFull) throw "Room at maximum capacity";
this.tenants.push(tenant);
}
get isVacant() {
return this.tenants.length == 0;
}
get hasTenant() {
return this.tenants.length > 0;
}
get isFull() {
return this.tenants.length === this.maximumTenantsAllowed;
}
/// Returns the invoice object
get invoice() {
return this._invoice;
}
get numberOfTenants() {
return this.tenants.length;
}
}
class Tenant {
constructor(tenantName, acceptsCoTenant) {
this.name = tenantName;
this.acceptsCoTenant = acceptsCoTenant;
this.room = null;
this._invoice = new Invoice();
}
get Invoice() {
return this._invoice;
}
}
class Invoice {
generate(entity) {
if (entity instanceof Room) {
// Write logic. You can share the cost between tenants
}
if (entity instanceof Tenant) {
// Write logic. Divide tenant room cost by number of tenants in the room
}
}
}
let apartment = new Apartment();
apartment.addTenant(new Tenant("Michael", false));
apartment.addTenant(new Tenant("Joe", false));
apartment.addTenant(new Tenant("Mary", true));
apartment.addTenant(new Tenant("Turtle", true));
let room2 = apartment.findRoomByNumber(2);
console.log(room2.tenants);
if(room2.hasTenant) {
let tenant1 = room2.tenants[0];
console.log(tenant1.Invoice);
}

Creating x amount of new objects in Class Constructor with for loop and later modifying them in JavaScript

I am trying to create x amount of new Kitten() in the class constructor House and storing them in an object of objects this.kittens. But when I later try to modify this object of objects nothing happens or I get undefined. I am however able to modify another property/object of House, or more specifically the child class Playroom without any problems (this.exampleKitten) which was "created normally".
I do understand that the House constructor creates a new kittens object everytime it is called, however I would like for it to behave the same as exampleKitten, that is once it has been created I would like to be able to modify its contents. How can I achieve this? Is it bad practice to create and store x amount of new objects in a constructor the way I do?
I hope you understand my question! Thanks in advance!
class House {
constructor (numberOfKittens) {
this.numberOfKittens = numberOfKittens
this.kittens = function () {
const kitts = {}
for (let i = 0; i < this.numberOfKittens; i++) {
kitts[i] = new Kitten(`Kitten ${i}`)
}
return kitts
}
this.exampleKitten = {7: {name: "Jenny", favoriteToy: "dog"}}
}
}
class Playroom extends House {
constructor (numberOfKittens, kittens, exampleKitten) {
super(numberOfKittens, kittens, exampleKitten)
}
}
class Kitten {
constructor (name, favoriteToy) {
this.name = name
this.favoriteToy = favoriteToy
}
}
let p = new Playroom(15)
p.kittens()[0].name = "Does not work"
p.exampleKitten[7].name = "This does work"
I think you may be getting a little confused on how/when your kittens get generated vs how you interact with them.
The easier way to set this up is to create a function that will generate your kittens. Additionally, add another property that holds the kittens.
Given the below setup, once you 'new up' the Playroom, it will generate that amount of kittens and set your kittens property to the dataset.
You can then interact with the kittens by index by accessing your kittens property. ie Playroom.kittens[<index>].<property>
class House {
constructor(numberOfKittens) {
this.numberOfKittens = numberOfKittens
this.kittens = this.makeKittens(); // <-- Set a property for your kittens to live
this.exampleKitten = {
7: {
name: "Jenny",
favoriteToy: "dog"
}
}
}
makeKittens() { // Make a function that generates your kittens
const kitts = {};
for (let i = 0; i < this.numberOfKittens; i++) {
kitts[i] = new Kitten(`Kitten ${i}`);
}
return kitts;
}
}
class Playroom extends House {
constructor(numberOfKittens, kittens, exampleKitten) {
super(numberOfKittens, kittens, exampleKitten)
}
}
class Kitten {
constructor(name, favoriteToy) {
this.name = name
this.favoriteToy = favoriteToy
}
}
let p = new Playroom(15)
// p.makeKittens()[0].name = "Does not work"
// p.exampleKitten[7].name = "This does work"
console.log('Kitten Playroom', p);
console.log('First Kitten Name', p.kittens[0].name);
console.log('Updating First Kitten Name to "Frank"');
p.kittens[0].name = 'Frank';
console.log('First Kitten Name', p.kittens[0].name);
I would also suggest that updating any kittens by index as it's not readible and prone to error (index out of range, being off by one, etc...)
I would suggest adding a function similar to this:
updateKitten(idx, name) { // Or whatever you're wanting to update
const kitten = this.kittens[idx];
if (kitten) {
kitten.name = name;
return kitten;
}
throw new Error(`No kitten at index ${idx}`); // or return null or however you want to handle it
}
So the kittens() method is actually returning an object, but it is not assigning that object as the new value of kittens. What you have there is a factory, and you can use it to create separate instances if you want.
const house = new House()
const kittens1 = house.kittens()
const kittens2 = house.kittens()
kittens1[0] = oldKitten
kittens2[0] = newKitten
kittens1[0] // still oldKitten
If you want to manage only one instance at a time on the house object, I would rename your method addKittens() or something, and have the result assigned to this.kittens. Furthermore, I would use a native array instead of an object with indexes as keys. This will allow you to take advantage of all the powerful array methods js has to offer.
class House {
constructor() {
this.kittens = []
}
addKittens(numOfKittens) {
for (let i = 0; i < numOfKittens; i++) {
const newKitten = new Kitten(`Kitten ${i}`)
this.kittens.push(newKitten)
}
}
}

How can i create a cart(dictionary function) to add and remove items in javascript

I have created below code for item in javascript. What has been giving me issues is how to create a CART (array dictionary) which is to be a function on its own that will hold item name and price from the code below. This are what I am expected to do: Add item to cart and calculate all total of all item in the cart, remove item and do corresponding calculation(reduction in total price of cart) and show items from the cart.
function item(name) {
this.price = 0;
this.quantity = 0;
this.total = 0;
};
item.prototype = {
setItemPrice: function (itemPrice) {
this.price = itemPrice;
},
setQuantity: function (itemUnit) {
this.quantity = itemUnit;
},
itemTotal: function () {
return this.total += this.price * this.quantity;
}
}
bag = new item('BAG');
bag.setItemPrice(50);
bag.setQuantity(80);
bag.setQuantity(90);
bag.itemTotal();
I had a bit of time so I thought I'd tackle this.
Since the instances of item are objects it's best to just store those in an array. Arrays have a lot of useful methods that can manipulate the data to match your needs.
Let's create the cart.
// Cart is capitalised.
// It is a JS best-practice to capitalise your constructor function
// names to differentiate them from other functions
function Cart() {
// We only need an array here
this.items = [];
// But this is new.
// Returning the object allows you to chain instance methods
// together when the object has been instantiated
return this;
}
Cart.prototype = {
addItem: function (item) {
// Push a new item object into the cart array
this.items.push(item);
return this;
},
removeItem: function (name) {
// We pass in the name of an item
// and use filter` to filter/return all of the items *without*
// that name (thus removing the item we don't want)
this.items = this.items.filter(function (item) {
return item.name !== name;
});
},
showCart: function () {
// `showCart` simply returns the array
return this.items;
},
getCartTotal: function () {
// `reduce` is another useful function and allows us
// to use a function to return each item total
// and add it to the accumulated total
return this.items.reduce(function (total, item) {
return total + (item.quantity * item.price);
}, 0);
}
}
I made amendments to the Item constructor too, basically adding in return this to the methods so you can do this:
const bag = new Item('BAG').setItemPrice(50).setQuantity(80);
const scarf = new Item('SCARF').setItemPrice(10).setQuantity(20);
const bead = new Item('BEAD').setItemPrice(1).setQuantity(120);
const candle = new Item('CANDLE').setItemPrice(20).setQuantity(5);
You can the other code changes here.
Create the new cart.
const cart = new Cart();
Now we can add in the items
cart.addItem(bag).addItem(scarf).addItem(bead).addItem(candle);
Get the total:
cart.getCartTotal(); // 4420
Remove the scarf item:
cart.removeItem('SCARF');
Get the new cart total:
cart.getCartTotal(); // 4220
Show the cart:
cart.showCart();
OUTPUT
[
{"name":"BAG","price":50,"quantity":80,"total":0},
{"name":"BEAD","price":1,"quantity":120,"total":0},
{"name":"CANDLE","price":20,"quantity":5,"total":0}
]

Javascript: How can I select a functions internal properties via it's parameters

I have a function that creates a menu, it has some buttons assigned to it and accepts an args parameter. Part of the function is a method called setActiveButton. When I create a menu I would like to dictate which of the buttons is active by passing in an option args.
For example:
var createMenu = function (args) {
this.btnOne = new Button(); // construct a button
this.btnTwo = new Button();
this.btnTwo = new Button()
this.setActiveButton(args.desiredButton);
return this;
}
createMenu({ desiredButton: btnTwo });
How do I tell createMenu to use one of it's buttons via args? I can't pass in { desiredButton: this.btnTwo } - because at that point this.btnTwo is not defined.
I was thinking about passing in a string and then using conditional statements like this:
var createMenu = function (args) {
var buttonChoice;
this.btnOne = new Button();
this.btnTwo = new Button();
this.btnThree = new Button();
if (args.desiredButton === "button one") {
this.setActiveButton(this.btnOne);
}
if (args.desiredButton === "button two") {
this.setActiveButton(this.btnTwo);
}
if (args.desiredButton === "button three") {
this.setActiveButton(this.btnThree);
}
return this;
}
createMenu({ desiredButton: "button two" });
However, I feel that there should be a cleaner and more succinct way to do this.
What is your suggestion?
just pass the name of the button as a string, and access with brackets.
var createMenu = function (args) {
this.btnOne = new Button(); // construct a button
this.btnTwo = new Button();
this.btnTwo = new Button()
this.setActiveButton(this[args.desiredButton]); // access this[property]
return this;
}
createMenu({ desiredButton: 'btnTwo' }); // string name of property
It's a little unclear to me why you are returning an object of properties that have no values, but you could do it like this. In my example I set the properties equal to strings like 'button 1', 'button 2', etc:
// pass number of total buttons, index of active button
var createMenu = function (total, active) {
var obj = {};
for (var i = 0; i < total; i++) {
obj['btn'+(i+1)] = 'button '+(i+1);
}
setActiveButton(obj['btn'+active]);
return obj;
}
In your example you reference setActiveButton as a property of the function but it isn't defined, so I have referenced it as a separate function.

Categories