Pattern matching in JavaScript? - javascript

I need to create a variable in JavaScript and assign it's value based on a condition. This works but feels a bit verbose:
const color = (() => {
switch (type) {
case "primary":
return CONSTANTS.colors.primary;
case "secondary":
return CONSTANTS.colors.secondary;
case "tertiary":
return CONSTANTS.colors.tertiary;
case "positive":
return CONSTANTS.colors.positive;
case "negative":
return CONSTANTS.colors.negative;
case "disabled":
return CONSTANTS.colors.disabled;
default:
throw new Error("A backgroundColor condition was missed");
}
})();
Is what I'm trying to do called "pattern matching"? Ive read that JavaScript doenst have this feature but Im not totally sure what it is.
Is there a more concise way of writing the code above? I could have lots of if statement but this feels messier and requires the variable to be let not const.
let color:
if (type === "primary") {
color = CONSTANTS.colors.primary;
} else if(type === "secondary") {
color = CONSTANTS.colors.secondary;
} else if(type === "tertiary") {
color = CONSTANTS.colors.tertiary;
} else if(type === "secondary") {
color = CONSTANTS.colors.secondary;
} else if(type === "positive") {
color = CONSTANTS.colors.positive;
} else if(type === "negative") {
color = CONSTANTS.colors.negative;
} else if(type === "disabled") {
color = CONSTANTS.colors.disabled;
}

The easiest solution for your problem is to check if the type is defined in the object CONSTANTS.colors. If you want to access a property by variable, you need to use the bracket annotation. Everything inside the brackets is evaluated as an expression (so type is a variable, 'type' the String value). Therefore, object.type returns the same value as object['type'].
let color = null;
if (typeof CONSTANTS.colors[type] !== 'undefined') {
color = CONSTANTS.colors[type];
} else {
throw new Error('A backgroundColor condition was missed');
}
console.log(color);
You can also first check if the key is defined in the object with Object.keys() and includes():
let color = null;
if (Object.keys(CONSTANTS.colors).includes(type)) {
color = CONSTANTS.colors[type];
} else {
throw new Error('A backgroundColor condition was missed');
}
console.log(color);
If you want to support IE11, you cannot use .includes(). Use .indexOf(type) !== -1 instead of .includes(type).

Pattern matching is generally referring to matching arguments passed to a function: testing to see if they match a specific "pattern". For example, a pattern match might allow you to write a function that takes an integer argument in "two different ways", one where the argument passed in is 0 and one when the argument passed is not 0 (the "otherwise" case). Switch statements are somewhat similar to this type of branching logic but aren't the same as a purely functional language like Haskell, and don't quite help with your goal here.
How about something like this instead?
const myColor = CONSTANTS["colors"][type];
if(typeof myColor !== 'undefined') {
color = myColor;
} else {
throw new Error("A backgroundColor condition was missed");
}

You are looking for property accessor:
color = CONSTANTS.colors[type];

An easy replacement for your code would be
const color = (() => {
const color = CONSTANTS.colors[type];
if (!color) {
throw new Error("A backgroundColor condition was missed");
}
return color;
}
})();
And no, that is not pattern matching.

I think it is wise to introduce an Enum that will hold the color values.
var ColorType = {
Primary: "primary",
Secondary: "secondary",
Tertiary: "tertiary,
...
};
Then you can use this enum in switch case and you will avoid the typos and referrence to string directly.
I think it will make the code less verbose and less prone to errors.

You can access a property of an object by using the property name as a string in square brackets.
(This example doesn't include the error catching you were using in your switch statement, but you can add that.)
const CONSTANTS = {
colors: {
primary: "blue",
secondary: "yellow"
}
}
function getColor(myPropName){
// Pass dynamic property names like this
return CONSTANTS.colors[myPropName];
}
console.log(getColor("secondary"));

Related

Check for certain value in every key of a Javascript object

I have the following line:
<button className={`actionBoxButton ${props.moves[0].moveName !== "FirstPassMove" && props.moves[0].moveName !== "PassMove" ? "actionBoxButtonGrey" : ''}`}
What it does is to check if the object "moves" has the value "FirstPassMove" for the key moveName. If so, I want it so switch to another style. This is working well.
But what I want to achieve is, to not only check the element 0 of the object, but all the objects and check if there is a moveName "FirstPassMove" in any element of the object.
It is written in React 16.12
Thanks!
You can simplify your jsx by defining a separate function for dynamic className like this:
const check = () => {
let className_ = "";
// if this.props.moves is an array, use of instead of in
for(let i in this.props.moves) {
if((this.props.moves[i].moveName !== "FirstPassMove") && (this.props.moves[i].moveName !== "PassMove")) {
className_ = "actionBoxButtonGrey";
}
}
return className_;
}
<button className={this.check()}>button</button>
props.moves.includes('FirstPassMove')
Assuming your props.moves is an array it will return true or false if this string is inside the props.moves array
If you want to add className in case at least one of your moves has FirstPassMove or PassMove name you should use Array.prototype.some()
const hasPass = moves.some(({ moveName }) => (
moveName === "FirstPassMove" ||
moveName === "PassMove"
));
I am guessing you want to check that all the moves satisfy the constraint, in which case, you can use Array.prototype.every to ensure every move satisfies the constraint. If you only need some moves to satisfy the constraint, you may use Array.prototype.some instead.
// For example
const props = {
moves: [
{ moveName: 'FirstPassMove' },
{ moveName: 'PassMove' },
{ moveName: 'PassMove' },
{ moveName: 'OtherPassMove' },
]
};
function isValidMove({ moveName }) {
return moveName !== "FirstPassMove"
&& moveName !== "PassMove";
}
function getActionBoxButtonClassName(hasColor) {
return `actionBoxButton ${hasColor ? "actionBoxButtonGrey" : ''}`;
}
console.log([
getActionBoxButtonClassName(props.moves.every(isValidMove)),
getActionBoxButtonClassName(props.moves.some(isValidMove))
]);

Variable in JSON Path - JavaScript

I already searched for similar issues but I didn't find anything that could help me yet.
I'm trying to reach a picture path (using JSON format) depending on the material type of the picked element. Actually, my code is built like this:
if (globalData.Material.Mat_type == "OSCILLOSCOPE") {
var picture = (globalData.Material.Oscilloscope.picture);
}
if (globalData.Material.Mat_type == "ALIMENTATION") {
var picture = (globalData.Material.Alim.picture);
}
But not optimized at all, so Im trying to make it this way :
var mat_type = (globalData.Material.Mat_type);
var picture = (globalData.Material[mat_type].picture);
But it doesn't work... Got some exception:
TypeError : globalData.Material[mat_type] is undefined.
I already tried a lot of things, have you got any idea? Thanks!
I outlined the issue with character case in the comment under the question, so presumably adjusting value of globalData.Material.Mat_type could do the trick:
var mat_type =
globalData.Material.Mat_type.charAt(0).toUpperCase() +
globalData.Material.Mat_type.substr(1).toLowerCase();
I can also see that this general rule may not be applicable in all cases. If it's not a typo, it won't work for the second case where Mat_type == "ALIMENTATION", because then you try to access Alim property of Material instead of Alimentation. In this case you could access property by prefix:
function pictureOf(material) {
if (!material || !String(material.Mat_type)) {
return null;
}
let mat_type = String(material.Mat_type).toUpperCase();
for (var propertyName in material) {
if (mat_type.startsWith(propertyName.toUpperCase())) {
return material[propertyName].picture || null;
}
}
return null;
}
console.log(pictureOf({
Mat_type: "OSCILLOSCOPE",
Oscilloscope: {
picture: "picture of oscilloscope"
}
}));
console.log(pictureOf({
Mat_type: "ALIMENTATION",
Alim: {
picture: "picture of alimentation"
}
}));
But this kind of approach can be error prone, if multiple properties share the same prefix. There's also a hidden issue with case-insensitive prefix matching in case you use some special unicode characters in property names. Lastly this method is not efficient, because it has to iterate over all properties of the object (worst case scenario). It can be replaced with much safer property mapping:
const matTypeMapping = {
"ALIMENTATION": "Alim"
};
function pictureOf(material) {
if (!material || !String(material.Mat_type)) {
return null;
}
let matType = String(material.Mat_type);
// find property mapping or apply general rule, if mapping not defined
let propertyName = matTypeMapping[matType] ||
matType.charAt(0).toUpperCase() + matType.substr(1).toLowerCase();
return material[propertyName].picture || null;
}
console.log(pictureOf({
Mat_type: "OSCILLOSCOPE",
Oscilloscope: {
picture: "picture of oscilloscope"
}
}));
console.log(pictureOf({
Mat_type: "ALIMENTATION",
Alim: {
picture: "picture of alimentation"
}
}));
NB: To avoid headaches, maybe you should prefer strict equality operator over loose equality operator.
Problem Solved
Peter Wolf was right ! It was a case-sensitive issue
I actually don't know how to promote his comment, sorry for this..
Anyway, thank you guys !
var mat_type = (globalData.Material.Mat_type);
if(mat_type!==undefined)
var picture = (globalData.Material[mat_type].picture)
Just do an existential check before accessing the value, for keys that may not be present.

Angular1/JavaScript - Improve algo condition

I have this condition which verifies the same property labelKey of an object projectType and return of different value according to the value of the property
checkProjectType () {
if (this.projectType.labelKey === 'project_type.rent') {
return 'geographical_area'
} else if (this.projectType.labelKey === 'project_type.buying') {
return 'geographical_area'
} else {
return 'address'
}
}
since there is too much resemblance in the condition how I refactored / optimized the condition with a simplified write using Lodash or ECMAScript 2015 for example ?
You can reduce this to less conditions as per your code.
checkProjectType () {
var labelKey = this.projectType.labelKey;
if (labelKey === 'project_type.rent' || labelKey === 'project_type.buying') {
return 'geographical_area';
}
return 'address';
}
Not sure what you want to do here with lodash
I also don't like if-else-if… chains, so prefer more readable variant.
function checkProjectType() {
const defaultType = 'address';
const key = this.projectType.labelKey;
let map = {
'project_type.rent': 'geographical_area',
'project_type.buying': 'geographical_area'
};
return map[key] || defaultType;
}
map can be defined somewhere else.
Setting an if do X else if do X else do Y is wrong to me, you can simplify that in a single line : if (this.projectType.labelKey === 'project_type.rent' || this.projectType.labelKey === 'project_type.buying') would be easier to read already.
One alternative way this could be written is using a switch statement:
switch (this.projectType.labelKey) {
case 'project_type.rent':
case 'project_type.buying':
return 'geographical_area';
default:
return 'address';
}
But one might argue it's a bit overkill in this case. Lodash or ECMAScript 2015 isn't going to do anything for you here.
You can check if the project type is included in an array of types, and use a ternary to select the response:
checkProjectType() {
return ['project_type.rent', 'project_type.buying'].includes(this.projectType) ? 'geographical_area' : 'address';
}
If the types that produce geographical_area, you can refactored them out of the method (and the object/class):
const geoTypes = ['project_type.rent', 'project_type.buying'];
checkProjectType() {
return geoTypes.includes(this.projectType) ? 'geographical_area' : 'address';
}

Jquery - Counting JSON objects

Im working on a chart system and i'm grabing my data from a ajax json reponse. Im just trying to count certain JSON objects and group them into categories.
For example im trying to loop on every JSON objects and seperate all the colors (Red, Blue, Yellow) into groups and within those groups I want to divide the results by month.I could then grab the results and put it into my charts.
Im currently getting the error:
Cannot set property '2016-10' of undefined
Heres an example of what im trying to accomplish. https://jsfiddle.net/awo5aaqb/5/
Here's the code that been causing me issues:
var dateCounts_Red = {};
var dateCounts_Blue = {};
var dateCounts_Yellow = {};
data.d.results.forEach(function(element) {
var date = element.created_date.slice(0, 7);
var yr = date.slice(0, 4);
var Color = element.colorvalue;
if (Color === "Red") {
if (!dateCounts_Red.hasOwnProperty(yr)) {
dateCounts_Red[yr] = {}
}
if (!dateCounts_Red[yr].hasOwnProperty(date)) {
dateCounts_Red[yr][date] = 0
}
dateCounts_Red[yr][date]++;
}else {dateCounts_Red[yr][date] = 0}
if (Color === "Blue") {
if (!dateCounts_Blue.hasOwnProperty(yr)) {
dateCounts_Blue[yr] = {}
}
if (!dateCounts_Blue[yr].hasOwnProperty(date)) {
dateCounts_Blue[yr][date] = 0
}
dateCounts_Blue[yr][date]++;
}else {dateCounts_Blue[yr][date] = 0}
if (Color === "Yellow") {
if (!dateCounts_Yellow.hasOwnProperty(yr)) {
dateCounts_Yellow[yr] = {}
}
if (!dateCounts_Yellow[yr].hasOwnProperty(date)) {
dateCounts_Yellow[yr][date] = 0
}
dateCounts_Yellow[yr][date]++;
}else {dateCounts_Yellow[yr][date] = 0}
});
Red_yr_2015_data = [dateCounts_Red['2015']['2015-01'],dateCounts_Red['2015']['2015-02'],dateCounts_Red['2015']['2015-03'],dateCounts_Red['2015']['2015-04'],dateCounts_Red['2015']['2015-05'],dateCounts_Red['2015']['2015-06'],dateCounts_Red['2015']['2015-07'],dateCounts_Red['2015']['2015-08'],dateCounts_Red['2015']['2015-09'],dateCounts_Red['2015']['2015-10'],dateCounts_Red['2015']['2015-11'],dateCounts_Red['2015']['2015-12']];
Blue_yr_2015_data = [dateCounts_Blue['2015']['2015-01'],dateCounts_Blue['2015']['2015-02'],dateCounts_Blue['2015']['2015-03'],dateCounts_Blue['2015']['2015-04'],dateCounts_Blue['2015']['2015-05'],dateCounts_Blue['2015']['2015-06'],dateCounts_Blue['2015']['2015-07'],dateCounts_Blue['2015']['2015-08'],dateCounts_Blue['2015']['2015-09'],dateCounts_Blue['2015']['2015-10'],dateCounts_Blue['2015']['2015-11'],dateCounts_Blue['2015']['2015-12']];
Yellow_yr_2015_data = [dateCounts_Yellow['2015']['2015-01'],dateCounts_Yellow['2015']['2015-02'],dateCounts_Yellow['2015']['2015-03'],dateCounts_Yellow['2015']['2015-04'],dateCounts_Yellow['2015']['2015-05'],dateCounts_Yellow['2015']['2015-06'],dateCounts_Yellow['2015']['2015-07'],dateCounts_Yellow['2015']['2015-08'],dateCounts_Yellow['2015']['2015-09'],dateCounts_Yellow['2015']['2015-10'],dateCounts_Yellow['2015']['2015-11'],dateCounts_Yellow['2015']['2015-12']];
Red_yr_2016_data = [dateCounts_Red['2016']['2016-01'],dateCounts_Red['2016']['2016-02'],dateCounts_Red['2016']['2016-03'],dateCounts_Red['2016']['2016-04'],dateCounts_Red['2016']['2016-05'],dateCounts_Red['2016']['2016-06'],dateCounts_Red['2016']['2016-07'],dateCounts_Red['2016']['2016-08'],dateCounts_Red['2016']['2016-09'],dateCounts_Red['2016']['2016-10'],dateCounts_Red['2016']['2016-11'],dateCounts_Red['2016']['2016-12']];
Blue_yr_2016_data = [dateCounts_Blue['2016']['2016-01'],dateCounts_Blue['2016']['2016-02'],dateCounts_Blue['2016']['2016-03'],dateCounts_Blue['2016']['2016-04'],dateCounts_Blue['2016']['2016-05'],dateCounts_Blue['2016']['2016-06'],dateCounts_Blue['2016']['2016-07'],dateCounts_Blue['2016']['2016-08'],dateCounts_Blue['2016']['2016-09'],dateCounts_Blue['2016']['2016-10'],dateCounts_Blue['2016']['2016-11'],dateCounts_Blue['2016']['2016-12']];
Yellow_yr_2016_data = [dateCounts_Yellow['2016']['2016-01'],dateCounts_Yellow['2016']['2016-02'],dateCounts_Yellow['2016']['2016-03'],dateCounts_Yellow['2016']['2016-04'],dateCounts_Yellow['2016']['2016-05'],dateCounts_Yellow['2016']['2016-06'],dateCounts_Yellow['2016']['2016-07'],dateCounts_Yellow['2016']['2016-08'],dateCounts_Yellow['2016']['2016-09'],dateCounts_Yellow['2016']['2016-10'],dateCounts_Yellow['2016']['2016-11'],dateCounts_Yellow['2016']['2016-12']];
I know the code is very clunky at the moment, but would anyone know of a better aproach to this?
It fails because if the color is not red, it goes to an else and tries to set the object, but the object was not created.
if (Color === "Red") {
if (!dateCounts_Red.hasOwnProperty(yr)) { <-- you create it here
dateCounts_Red[yr] = {}
}
if (!dateCounts_Red[yr].hasOwnProperty(date)) {
dateCounts_Red[yr][date] = 0
}
dateCounts_Red[yr][date]++;
}else {dateCounts_Red[yr][date] = 0} <-- it needed to be set here too...
So move the first hasOwnProperty check outside of the if since both the if and else need it.
Instead of re-inventing the wheel, try wrapping your head around the problem in this fashion:
Grouping objects by an attribute of the object is a common task. There are many libraries that help with this.
groupBy (documentation from underscore website)
Splits a collection into sets, grouped by the result of running each
value through iteratee. If iteratee is a string instead of a function,
groups by the property named by iteratee on each of the values.
_.groupBy([1.3, 2.1, 2.4], function(num){ return Math.floor(num); });
=> {1: [1.3], 2: [2.1, 2.4]}
_.groupBy(['one', 'two', 'three'], 'length');
=> {3: ["one", "two"], 5: ["three"]}
What is the most efficient method to groupby on a javascript array of objects?
if you want the easy way out
if (Color === "Red") {
if (!dateCounts_Red.hasOwnProperty(yr)) { <-- you create it here
dateCounts_Red[yr] = {}
}
if (!dateCounts_Red[yr].hasOwnProperty(date)) {
dateCounts_Red[yr][date] = 0
}
dateCounts_Red[yr][date]++;
}else {dateCounts_Red[yr]={date:0}} // set your object here, too

jslint giving expected a string and instead saw {a}

I have the following code fragment that appears to be correct, but jslint doesn't like it.
var VALID_TYPE = {
"stringType" : "string",
"arrayType" : "array",
"objectType" : "object"
},
DEFAULT_FIRST = 1, DEFAULT_LAST = 1, PRIMITIVE_TYPE = {
"stringType" : "string",
"arrayType" : "array",
"objectType" : "object",
"undefinedType" : "undefined",
"booleanType" : "boolean",
"numberType" : "number"
};
VALID_TYPE.toString = function () {
var types = [], currentType;
for (currentType in this) {
if (typeof this[currentType] === PRIMITIVE_TYPE.stringType) {
types.push(this[currentType]);
}
}
var outputString = types.join(', ');
return outputString;
};
The erroneous line is this, at the ".":
if (typeof this[currentType] === PRIMITIVE_TYPE.stringType) {
The exact text of the error is:
Expected a string and instead saw '.'.
toString() performs as expected. I can't see what I should change to avoid the error, except for placing the right side of the expression into another variable. The error is not yet described at jslinterrors.com.
As #SLaks stated in the comments, JSLint will warn when it encounters a comparison operator in which one of the operands is a typeof expression and the other operand is not a string literal.
Here's a cut down version of the code that performs this check:
function relation(s, eqeq) {
var x = infix(s, 100, function (left, that) {
// ...
if (are_similar(left, right) ||
((left.id === '(string)' || left.id === '(number)') &&
(right.id === '(string)' || right.id === '(number)'))) {
that.warn('weird_relation');
} else if (left.id === 'typeof') {
if (right.id !== '(string)') {
right.warn("expected_string_a", artifact(right));
} else if (right.string === 'undefined' || right.string === 'null') {
left.warn("unexpected_typeof_a", right.string);
}
} else if (right.id === 'typeof') {
if (left.id !== '(string)') {
left.warn("expected_string_a", artifact(left));
} else if (left.string === 'undefined' || left.string === 'null') {
right.warn("unexpected_typeof_a", left.string);
}
}
// ...
});
// ...
}
The only other time that specific warning is given is when JSLint encounters an unquoted JSON property:
{
a: 1
}
I'll get this up on http://jslinterrors.com as soon as I get a chance.
toString() performs as expected.
The code is perfectly valid, so yes it would do.
Remember that jsLint isn't looking for errors; it's looking for things it thinks are bad practice.
But those things aren't always definitively wrong in every case; often there is a legitimate use case for it, and if you've got one of those cases, then you'll still get the error, but just have to ignore it.
Lint errors should be considered as a guide rather than something to be strictly adhered to and causing build failures.
You may also want to consider using jsHint rather than jsLint. jsHint is based on jsLint, but tends to be a bit more pragmatic about what it complains about.
Hope that helps.

Categories

Resources