Reduce the number of switch - case with Object Literals - javascript

I'm studying javascript, and I've already seen that there's a way to reduce the number of switch - case with Object Literals. I'm trying to change this method, but I am not able to reduce the number of switches
static darkerColors(value: string, theme: ThemeMode) {
const test = theme === ThemeMode.LIGHT
switch (value) {
case Colors.BLUE: {
return test ? '#253F82' : '#BED1FF'
}
case Colors.CYAN: {
return test ? '#066262' : '#A2EAEA'
}
case Colors.PURPLE: {
return test ? '#4727B0' : '#D3C6FD'
}
case Colors.ORANGE: {
return test ? '#9C2100' : '#FF9377'
}
case Colors.YELLOW: {
return test ? '#6C5200' : '#F9E298'
}
default:
return test ? '#32363B' : '#C9CED4'
}
}

you can use an object to handle the configuration
like this
const config = {
light: {
blue: '#253F82',
default: '#32363B'
},
default: {
blue: '#BED1FF',
default: '#C9CED4'
}
}
function darkerColors(value, theme) {
const fallback = 'default'
const colors = config[theme] || config[fallback]
return colors[value] || colors[fallback]
}
console.log(darkerColors('blue', 'light'))
console.log(darkerColors('red', 'light'))
console.log(darkerColors('blue', 'dark'))
console.log(darkerColors('red', 'dark'))

Related

Using custom Angular Material Sort?

I want to create a dropdown (or mat-select) to use as a sorting mechanism instead of the Angular Material Sort Header. So, if I for example click on the 'username' inside the dropdown, I want the table to sort by the username (instead of clicking on the header).
How can I do it? Any documentation online on how to achieve this?
Thank you for any help.
As required, I attach some code:
ngOnInit(): void {
this.filteredOptions = this.myControl.valueChanges.pipe(
startWith(""),
map((value) => this._filter(value))
);
}
ngAfterViewInit() {
this.providersAdmin.sort = this.sort;
}
getAllAdmins() {
this.isLoading = true;
this.homeService.getAllAdmins().subscribe(
(response) => {
this.admins = response;
this.providersAdmin = new MatTableDataSource(this.admins);
this.isLoading = false;
},
(error) => {}
);
}
sortTableBy(event: any) {
const sortState: Sort = {
active: "username",
direction: "desc",
};
this.sort.active = sortState.active;
this.sort.direction = sortState.direction;
this.sort.sortChange.emit(sortState);
console.log(event);
}
The sortTableBy method is the one I found on here but nothing happens.
I added matSort on the mat-table and I added mat-sort-header on the header cell.
EDIT:
Hi, I managed to fix the problem by writing the following:
sortTableBy(event: any) {
const sortState: Sort = {
active: "username",
direction: "desc",
};
this.sort.active = sortState.active;
this.sort.direction = sortState.direction;
this.sort.sortChange.emit(sortState);
this.providersAdmin.sort = this.sort;
}
There is an example for you:
Exmaple
Your sort function has a wrong implementation, this is work for me:
sortData(fieldName: string) {
if (!fieldName) {
return;
}
const sortState: MatSortable = {
id: fieldName,
start: 'desc',
disableClear: true
};
this.sort.sort(sortState);
}
I am going to set up an example which you can adapt easily:
compare(a: number | string, b: number | string, isAsc: boolean) {
return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
}
sortData() {
let isAsc = this.sort.direction != "" ?
event.direction == SortDirection.asc :
true;
let data = this.dataSource.data.slice();
data.sort((a, b) => {
switch (this.myChosenSort) {
case 'healthCareCenterName':
return this.compare(a.healthCareCenterName, b.healthCareCenterName, isAsc);
case 'address':
return this.compare(a.address, b.address, isAsc);
case 'contact':
return this.compare(a.contact, b.contact, isAsc);
default:
return 0;
}
});
this.dataSource = new MatTableDataSource<ServiceProviderTable>(data);
}
To change the sort.direction you need to play around a little bit with the code, maybe directly from the dropdown and hardcoding the isAsc when calling the compare method, depending on the value of the this.myChosenSort.

Better way to write this switch/case in typescript?

I am developing a very simple weather app in Angular and I wanted to ask you if you think there are better ways to choose a certain image based on the "type" of weather codition.
enum WeatherCodition {
Thunderstorm = 0,
Drizzle,
Rain,
Snow,
Clear,
Clouds
}
export class Weather {
getIcon(condition: WeatherCodition): string {
var iconPath = "";
switch(condition){
case WeatherCodition.Thunderstorm:
iconPath = "thunderstorm.png";
break;
case WeatherCodition.Clouds:
iconPath = "clouds.png";
break;
case WeatherCodition.Drizzle:
iconPath = "drizzle.png";
break;
case WeatherCodition.Rain:
iconPath = "rain.png";
break;
case WeatherCodition.Snow:
iconPath = "snow.png";
break;
default:
iconPath = "clear.png"
}
return iconPath;
}
}
Please, consider using interface KeyValue<K, V> as array. My solution:
export enum WeatherCodition {
Thunderstorm = 0,
Drizzle,
Rain,
Snow,
Clear,
Clouds
}
import { KeyValue } from '#angular/common';
export class Weather {
public keyValueArray: KeyValue<WeatherCodition, string>[] =
[
{ key: WeatherCodition.Thunderstorm, value: "thunderstorm.png" },
{ key: WeatherCodition.Drizzle , value: "drizzle.png"},
{ key: WeatherCodition.Rain, value: "rain.png" },
{ key: WeatherCodition.Snow, value: "snow.png" },
{ key: WeatherCodition.Clear, value: "clear.png" },
{ key: WeatherCodition.Clouds, value: "clouds.png" },
];
getIcon(condition: WeatherCodition): string {
//check if 'condition' exists in array as key
return this.keyValueArray[condition] ?
this.keyValueArray[condition].value :
"clear.png";
}
}
Have a nice day!
Your approach is perfectly fine. You can also create a hashmap for constant time lookup as you are anyway harcoding the urls in your switch statement.
interface WeatherIcons {
Thunderstorm: string;
Clouds: string;
}
const icons: WeatherIcons = {
Thunderstorm: "Thunderstorm.jpg",
Clouds: "Clouds.jpg"
}
function getIcon(condition: WeatherCondition) {
return icons[condition] || "default.jpg";
}
You can create a object and access property based on key
let WeatherCodition = {
thunderstorm:"thunderstorm.png",
clouds:"clouds.png",
drizzle:"drizzle.png",
rain:"rain.png",
snow:"snow.png",
default:"clear.png"
}
function getIcon(condition) {
condition = condition || ""
condition = Object.keys(WeatherCodition).find(c=> c.toLowerCase() === condition.toLowerCase()) || 'default'
return WeatherCodition[condition]
}
console.log(getIcon(''))
console.log(getIcon('Clouds'))
console.log(getIcon())
console.log(getIcon('SnoW'))
Why not defined an array?
iconPaths:string[]=["thunderstorm.png","clouds.png","drizzle.png","rain.png","snow.png",
"clear.png"]
iconPath=this.iconPaths[condition];
//or
iconPath=(condition && condition<6)?this.iconPaths[condition]:"";

Javascript translation script improvement

I have read few articles about this topic and few SO questions like this one but nothing really fit to my situation.
So basically I'm creating simple one-page app almost without any javascript involved. Very only place where I need JS is this translation. Generally it's not a problem for me to create some script in JS to translate so I did like this:
I'm importing two files into my index.html:
<script src="js/text.js"></script>
<script src="js/translator.js"></script>
In text.js I have constant object containing texts for website to display:
// Global constant "text" accessible in translator.js file
const text = {
PL: {
aboutHeading: "Kilka słów o mnie"
},
ENG: {
aboutHeading: "Few words about me"
}
};
In translator.js I have object responsible for checking which language and filling divs / headings / whatever of text:
// global translator object
const translator = {
currentLanguage: '',
checkLanguage() {
switch (window.navigator.language) {
case 'pl':
case 'pl-PL': {
this.currentLanguage = 'pl';
break;
}
case 'en': {
this.currentLanguage = 'eng';
break;
}
default: {
this.currentLanguage = 'eng';
break;
}
}
//alert(this.currentLanguage);
//alert(window.navigator.language);
},
fillText(lang) {
if(lang === 'pl') {
document.getElementById('few-words-about-me-header').innerHTML = text.PL.aboutHeading;
alert('inserted pl');
}
if(lang === 'eng') {
document.getElementById('few-words-about-me-header').innerHTML = text.ENG.aboutHeading;
alert('inserted eng');
}
},
};
translator.checkLanguage();
translator.fillText(translator.currentLanguage);
document.getElementById('polish-flag').addEventListener('click', () => {
translator.fillText('pl');
});
document.getElementById('english-flag').addEventListener('click', () => {
translator.fillText('eng');
});
Generally everything is working as expected, the only thing I'm worried about is that I've got two global variables here and I'm not really sure what to do about it. I feel like I can do this translation in some more efficient, prettier way.
So questions are - if it's okey to have this two global variables and if I can achieve same as above code in some prettier way?
Simply encapsulate both in an IIFE in a single script so that nothing pollutes the global namespace.
// main.js
(() => {
const text = {
PL: {
aboutHeading: "Kilka słów o mnie"
},
ENG: {
aboutHeading: "Few words about me"
}
};
const translator = {
currentLanguage: '',
checkLanguage() {
switch (window.navigator.language) {
case 'pl':
case 'pl-PL':
{
this.currentLanguage = 'p';
break;
}
case 'en':
{
this.currentLanguage = 'eng';
break;
}
default:
{
this.currentLanguage = 'eng';
break;
}
}
//alert(this.currentLanguage);
//alert(window.navigator.language);
},
fillText(lang) {
if (lang === 'pl') {
document.getElementById('few-words-about-me-header').innerHTML = text.PL.aboutHeading;
alert('inserted pl');
}
if (lang === 'eng') {
document.getElementById('few-words-about-me-header').innerHTML = text.ENG.aboutHeading;
alert('inserted eng');
}
},
};
translator.checkLanguage();
translator.fillText(translator.currentLanguage);
document.getElementById('polish-flag').addEventListener('click', () => {
translator.fillText('pl');
});
document.getElementById('english-flag').addEventListener('click', () => {
translator.fillText('eng');
});
})();
If you have to create the text dynamically for some reason, you can avoid creating a global variable there by using <script type="application/json"> instead, which is parseable but not automatically added to the global namespace. For example:
<div></div>
<script type="application/json">{"foo": "foo", "bar": "bar"}</script>
<script>
(() => {
const text = JSON.parse(document.querySelector('script[type="application/json"]').textContent);
document.querySelector('div').textContent = text.foo + ' / ' + text.bar;
})();
</script>
You can also use object lookups to simplify the currentLanguage setting. switch statements are often too wordy and error-prone, compared to the alternative:
checkLanguage() {
const languages = {
pl: ['pl', 'pl-PL'],
en: ['eng'],
};
const navigatorLanguage = window.navigator.language;
const foundLanguageObj = Object.entries(languages)
.find(([setting, arr]) => arr.includes(navigatorLanguage));
this.currentLanguage = foundLanguageObj ? foundLanguageObj[0] : 'eng';
}

Javascript multiple decisions in switch case

I have difficulty with running a code which checks two criteria in switch case as this code prints 'unknown' since in javascript
['police',false]!=['police',false]
Is there any way to implement this code using switch-case rather than nested ifs?
var option='police';
var urgent=false;
switch([option,urgent])
{
case ['police',true]:
console.log('police,true');
call_police_urgent();
break;
case ['police',false]:
console.log('police,false');
call_police();
break;
case ['hospital',true]:
console.log('hospital,true');
call_hospital_urgent();
break;
case ['hospital',false]:
console.log('hospital,false');
call_hospital();
break;
case ['firestation',true]:
console.log('firestation,true');
call_firestation_urgent();
break;
case ['firestation',false]:
console.log('firestation,false');
call_firestation();
break;
default:
console.log('unknown');
}
Your code doesn't work because one array literal is never equal to another even if they look the same. There are many ways to solve that, but most of them boil down to converting arrays to something one can compare, e.g. strings:
let str = (...args) => JSON.stringify(args);
switch (str(option, urgent)) {
case str('police', false):
console.log('police,false');
break;
case str('hospital', true):
console.log('hospital,true');
break;
default:
console.log('unknown');
}
This works for your simple case, but not in general, because not everything can be stringified.
You can convert an array of options to string:
var option='police';
var urgent=false;
switch([option,urgent].join())
{
case 'police,true':
console.log('police,true');
break;
case 'police,false':
console.log('police,false');
break;
case 'hospital,true':
console.log('hospital,true');
break;
case 'hospital,false':
console.log('hospital,false');
break;
case 'firestation,true':
console.log('firestation,true');
break;
case 'firestation,false':
console.log('firestation,false');
break;
default:
console.log('unknown');
}
i don't know what you are trying to do, but your above code javascript engines and runtime environments with yell at you.
and secondly [] literal can and will never be equal to another [] literal
choose in between this two
var option='police';
var urgent=false;
function first_switch(option,urgent) {
switch(option) {
case "police":
if ( urgent )
console.log('police,true');
else
console.log('police,false');
break;
case "hospital":
if ( urgent )
console.log('hospital,true');
else
console.log('hospital,false');
break;
case "firestation":
if ( urgent )
console.log('firestation,true');
else
console.log('firestation,false');
break;
default:
console.log('unknown');
}
}
function second_switch(option,urgent) {
if ( urgent ) {
switch(option) {
case "police":
case "hospital":
case "firestation":
console.log(`${option}`, "true");
break;
default:
console.log('unknown');
}
return ;
}
switch(option) {
case "police":
case "hospital":
case "firestation":
console.log(`${option}`, "false");
break;
default:
console.log('unknown');
}
}
first_switch(option,urgent);
first_switch(option, true);
second_switch(option, urgent);
second_switch(option, true);
We can create the same funtionality without using switch cases. We could create a lookup table like:
var emergencyLookupTable = {
police: [{
case: true,
fn: call_police_urgent
},
{
case: false,
fn: call_police
}
],
hospital: [{
case: true,
fn: call_hospital_urgent
},
{
case: false,
fn: call_firestation_urgent
}
],
firestation: [{
case: true,
fn: call_firestation_urgent
},
{
case: false,
fn: call_firestation
}
]
}
And pass this object into emergency which is looking for the correct cases.
function emergency(lookup, option, urgent) {
if (lookup[option]) {
lookup[option]
.filter(function(obj) {
return obj.case === urgent
})
.forEach(function(obj) {
obj.fn()
})
} else {
console.log('unknown')
}
}
emergency(emergencyLookupTable, 'police', true)
Working Example
var emergencyLookupTable = {
police: [{
case: true,
fn: call_police_urgent
},
{
case: true,
fn: call_police_urgent2
},
{
case: false,
fn: call_police
}
],
hospital: [],
firestation: []
}
function emergency(lookup, option, urgent) {
if (lookup[option]) {
lookup[option]
.filter(function(obj) {
return obj.case === urgent
})
.forEach(function(obj) {
obj.fn()
})
} else {
console.log('unknown')
}
}
function call_police_urgent() {
console.log('call the police!')
}
function call_police_urgent2() {
console.log('call the police again!')
}
function call_police() {
console.log('call the police..')
}
emergency(emergencyLookupTable, 'police', true)
emergency(emergencyLookupTable, 'police', false)

prevent duplicate objects being added to state react redux

I have a question regarding preventing duplicates from being added to my redux store.
It should be straight forward but for some reason nothing I try is working.
export const eventReducer = (state = [], action) => {
switch(action.type) {
case "ADD_EVENT":
return [...state, action.event].filter(ev => {
if(ev.event_id !== action.event.event_id){
return ev;
}
});
default:
return state;
}
};
The action looks something like the below:
{
type: "ADD_EVENT",
event: { event_id: 1, name: "Chelsea v Arsenal" }
}
The issue is that on occasions the API I am working with is sending over identical messages through a websocket, which means that two identical events are getting added to my store.
I have taken many approaches but cannot figure out how to get this to work. I have tried many SO answers,
Why your code is failing?
Code:
return [...state, action.event].filter(ev => {
if(ev.event_id !== action.event.event_id){
return ev;
}
});
Because first you are adding the new element then filtering the same element, by this way it will never add the new value in the reducer state.
Solution:
Use #array.findIndex to check whether item already exist in array or not if not then only add the element otherwise return the same state.
Write it like this:
case "ADD_EVENT":
let index = state.findIndex(el => el.event_id == action.event.event_id);
if(index == -1)
return [...state, action.event];
return state;
You can use Array.prototype.find().
Example (Not tested)
const eventExists = (events, event) => {
return evets.find((e) => e.event_id === event.event_id);
}
export const eventReducer = (state = [], action) = > {
switch (action.type) {
case "ADD_EVENT":
if (eventExists(state, action.event)) {
return state;
} else {
return [...state, action.event];
}
default:
return state;
}
};
Update (#CodingIntrigue's comment)
You can also use Array.prototype.some() for a better approach
const eventExists = (events, event) => {
return evets.some((e) => e.event_id === event.event_id);
}
export const eventReducer = (state = [], action) = > {
switch (action.type) {
case "ADD_EVENT":
if (eventExists(state, action.event)) {
return state;
} else {
return [...state, action.event];
}
default:
return state;
}
};
Solution:
const eventReducer = ( state = [], action ) => {
switch (action.type) {
case 'ADD_EVENT':
return state.some(( { event_id } ) => event_id === action.event.event_id)
? state
: [...state, action.event];
default:
return state;
}
};
Test:
const state1 = eventReducer([], {
type: 'ADD_EVENT',
event: { event_id: 1, name: 'Chelsea v Arsenal' }
});
const state2 = eventReducer(state1, {
type: 'ADD_EVENT',
event: { event_id: 2, name: 'Chelsea v Manchester' }
});
const state3 = eventReducer(state2, {
type: 'ADD_EVENT',
event: { event_id: 1, name: 'Chelsea v Arsenal' }
});
console.log(state1, state2, state3);
You can something like this, for the logic part to ensure you don't get the same entry twice.
const x = filter.arrayOfData(item => item.event_id !== EVENT_FROM_SOCKET);
if (x.length === 0) {
// dispatch action here
} else {
// ignore and do nothing
}
You need to be careful when using Arrays in reducers. You are essentially adding more items to the list when you call:
[...state, action.event]
If you instead use a map then you can prevent duplicates
const events = { ...state.events }
events[action.event.event_id] = action.event.name]
{...state, events }
If duplicate exist in previous state then we should return same state else update the state
case "ADDPREVIEW":
let index = state.preview.findIndex(dup => dup.id == action.payload.id);
return {
...state,
preview: index == -1 ? [...state.preview,action.payload]:[...state.preview]
};

Categories