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';
}
Related
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'))
I want to enforce one-line if statements not having braces. For example:
// incorrect
if(foo) {
bar()
}
// correct
if(foo) bar()
But if I have else/else ifs, I still want to keep braces:
// incorrect
if(foo) bar()
else(baz) qux()
// correct
if(foo) {
bar()
} else(baz) {
qux()
}
I was able to create a plugin like this:
module.exports = {
meta: {
type: "layout",
fixable: "whitespace",
messages: {
noSingleLineIfsAsBlock: "Don't use braces with one line if statements",
useBlocksWithElseCases: "Use braces when there are else ifs and/or elses"
}
},
create(context) {
return {
IfStatement(node) {
if(node.alternate === null && node.parent.type !== "IfStatement") {
if(node.consequent.type === "BlockStatement" && node.consequent.body.length > 0) {
// assumes that blank lines are removed by other eslint rules, so at most three lines for a if block with one line inside
if(node.consequent.loc.end.line - node.consequent.loc.start.line + 1 <= 3) {
context.report({
node: node,
messageId: "noSingleLineIfsAsBlock",
fix(fixer) {
const sourceCode = context.getSourceCode();
const openingBrace = sourceCode.getFirstToken(node.consequent);
const closingBrace = sourceCode.getLastToken(node.consequent);
const firstValueToken = sourceCode.getFirstToken(node.consequent.body[0]);
const lastValueToken = sourceCode.getLastToken(node.consequent.body[0]);
return [
fixer.removeRange([openingBrace.range[0], firstValueToken.range[0]]),
fixer.removeRange([lastValueToken.range[1], closingBrace.range[1]])
];
}
})
}
}
} else if(node.alternate || node.parent.type === "IfStatement") {
if(node.consequent.type !== "BlockStatement") {
context.report({
node: node,
messageId: "useBlocksWithElseCases",
fix(fixer) {
// assumes that other eslint rules will fix brace styling
const sourceCode = context.getSourceCode();
const firstValueToken = sourceCode.getFirstToken(node.consequent);
const lastValueToken = sourceCode.getLastToken(node.consequent);
return [
fixer.insertTextBefore(firstValueToken, "{"),
fixer.insertTextAfter(lastValueToken, "}")
];
}
})
}
if(node.alternate && node.alternate.type !== "IfStatement" && node.alternate.type !== "BlockStatement") {
context.report({
node: node,
messageId: "useBlocksWithElseCases",
fix(fixer) {
// assumes that other eslint rules will fix brace styling
const sourceCode = context.getSourceCode();
const firstValueToken = sourceCode.getFirstToken(node.alternate);
const lastValueToken = sourceCode.getLastToken(node.alternate);
return [
fixer.insertTextBefore(firstValueToken, "{"),
fixer.insertTextAfter(lastValueToken, "}")
];
}
})
}
}
}
};
}
};
But I was wondering if there are already existing implementations I could use instead so I wouldn't have to store this in my project.
I browsed npm packages relating to eslint and searched for things like "eslint if statement formatting" but I couldn't find anything. I'm also pretty sure eslint doesn't have a built-in rule for this.
The most related issue I found was this, but it's asking how to avoid the style I'm trying to implement, and I'm not using prettier.
Are there any eslint plugins like this that can solve my problem?
I'm want to insert a switch/case statement inside a JavaScript class. In various cases, I'm trying to create a class that shows a specific step of a guided tour made with a library. So I asked myself if it is possible to insert this kind of statement inside a JavaScript class to execute depending on the PHP route name. Is it possible?
class TourUtils {
constructor(routeName) {
switch (routeName) {
case "host":
$(document).ready(function () {
document
.querySelector(".dashboard__tour")
.addEventListener("click", () => {
window.TOUR.start();
});
});
break;
case "Nuovo annuncio":
$(document).ready(function () {
window.TOUR.show("new-ad-page-intro-step");
});
break;
case "Modifica annuncio":
$(document).ready(function () {
window.TOUR.show("new-ad-page-nineth-step");
});
break;
case "Casa annuncio":
$(document).ready(function () {
if ($("#save").is(":visible"))
window.TOUR.show("mod-house-page-intro-step");
});
break;
case "Nuova casa":
$(document).ready(function () {
window.TOUR.show("mod-house-page-second-step");
});
break;
case "Modifica casa":
$(document).ready(function () {
window.TOUR.show("mod-price-page-intro-step");
});
break;
case "Calendario":
$(document).ready(function () {
if ($(".update-btn").is(":visible"))
window.TOUR.show("final-step");
else if (!$(".update-btn").is(":visible")) {
window.TOUR.show("mod-price-page-second-step");
}
});
break;
case "Aggiungi date":
$(document).ready(function () {
window.TOUR.show("add-date-page-intro-step");
});
break;
}
}
}
I don't see why not.
In general, before asking on a forum if some code works, there's a super easy thing you can do: try it. You can try it in a js file and run it with node or in your browser's console. Make a super minimal test and run it, you'll get your answer.
For the question "Is it possible to use switch case in a JS class constructor" here's the minimal test I would make
class A {
constructor(a) {
switch (a) {
case 1:
this.a = 'one';
break;
case 2:
this.a = 'two';
break;
default:
this.a = 'something else';
break;
}
}
}
let first = new A(1);
let second = new A(2);
let third = new A(93);
console.log({
test1: first.a,
test2: second.a,
test3: third.a
});
And make sure it shows
{
test1: 'one',
test2: 'two',
test3: 'something else'
}
You could simplify your code to the following. Maybe that helps finding the problem of your code.
class TourUtils {
constructor(routeName) {
var routes = {
"host": function () {
document
.querySelector(".dashboard__tour")
.addEventListener("click", () => {
window.TOUR.start();
});
},
"Nuovo annuncio": "new-ad-page-intro-step",
"Modifica annuncio": "new-ad-page-nineth-step",
"Casa annuncio": function () {
if ($("#save").is(":visible"))
window.TOUR.show("mod-house-page-intro-step");
},
"Nuova casa": "mod-house-page-second-step",
"Modifica casa": "mod-price-page-intro-step",
"Calendario": function () {
if ($(".update-btn").is(":visible")) window.TOUR.show("final-step");
else if (!$(".update-btn").is(":visible")) {
window.TOUR.show("mod-price-page-second-step");
}
},
"Aggiungi date": "add-date-page-intro-step"
};
$(document).ready(function () {
var route = routes[routeName];
if (typeof route === 'function') {
route();
} else {
window.TOUR.show(route);
}
}
}
}
function handleOrder(data) {
if (data.payment_type === 'VISA') {
handleVisaPayment()
}
if (data.payment_type === 'VISA') {
handleMastercardPayment()
}
if (data.payment_type === 'PayPal') {
handlePayPalPayment()
}
if (data.shipping === 'Express') {
handleExpressShipping()
}
if (data.shipping === 'Standard') {
handleStandardShipping()
}
}
Is there a better way to write this function especially by following best practices?
You can create an array data structure that will hold the value you want to compare and the function that you want to execute in that case. That will simplify your code and keep it manageable:
let allowedValues = [{
"value": "VISA",
"handler": handleVisaPayment
},
{
"value": "PayPal",
"handler": handlePayPalPayment
},
{
"value": "Express",
"handler": handleExpressShipping
},
{
"value": "Standard",
"handler": handleStandardShipping
}
]
function handleOrder(data) {
let matchedOrder = allowedValues.find(({
value
}) => value === data.payment_type);
if (matchedOrder) {
matchedOrder.handler();
}
}
You can even create a object mapping for those values and operations:
let allowedValues = {
"VISA": handleVisaPayment,
"PayPal": handlePayPalPayment,
"Express": handleExpressShipping,
"Standard": handleStandardShipping
}
function handleOrder(data) {
if (allowedValues[data.payment_type]) {
allowedValues[data.payment_type]();
}
}
First of all, you are executing the same IF statement twice in a row.
if (data.payment_type === 'VISA') {
handleVisaPayment
}
if (data.payment_type === 'VISA') {
handleMastercardPayment
}
I imagine it would be more logical if your second IF statement was like this:
if (data.payment_type === 'Mastercard') {
handleMastercardPayment
}
This looks like a simple copy-paste mistake, but I think it's worth pointing out.
Secondly, you might want to reorganize your code using a switch statement.
Ex:
switch (data.payment_type) {
case 'VISA':
handleVisaPayment
break;
case 'Mastercard':
handleMastercardPayment
break;
case 'PayPal':
handlePayPalPayment
break;
}
switch (data.shipping) {
case 'Express':
handleExpressShipping
break;
case 'Standard':
handleStandardShipping
break;
}
This should make your code easier to read/maintain/add new features in the future.
Again, I know it was specified to avoid using switch, but it does look like the most simple solution here.
I think this will give some building blocks to go further, as others have mentioned use mapping to resolve the functions you need.
const data = {
shipping: 'Standard',
payment_type: 'VISA',
}
const handleVisaPayment = () => console.log('handling visa payment')
const handleStandardShipping = () => console.log('handling standard shipping')
const orderHandlingMap = {
'Standard': handleStandardShipping,
'VISA': handleVisaPayment,
}
const handleOrder = data =>
Object.values(data)
.filter(element => orderHandlingMap[element])
.map(handlerName => orderHandlingMap[handlerName]())
handleOrder(data)
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]:"";