javascript invoke string as a function - javascript

I have made an i18n object in javascript like the following to manage the languages in my javascript files
i18n = {
currentCulture: 'pt_PT',
pt_PT : {
message_key : "text in portuguese"
},
en_US: {
message_key : "text in english",
},
/**
* translate
*/
__ : function(key,culture){
return this.culture.key;
},
/**
* returns the active user culture
*/
getUserCulture : function(){
return this.currentCulture;
},
/**
* sets the current culture
*/
setCulture : function(culture){
this.currentCulture = culture;
}
}
I need to return the correct message based on the key and culture params of the translate function.
The problem is that in the line return this.culture.key; javascript is trying to find a "culture" propriety in the i18n object.
How can i make it call, for example this.pt_PT.message_key?
Thanks for your help.
Thanks everyone who posted the solution. I can only accepted one anwser so i accept the first one.

Use bracket notation. Assuming culture is 'pt_PT' and key is 'message_key':
return this[culture][key];

Replace:
this.culture.key
with:
this[culture][key]

Javascript objects are associative arrays, and you can use array syntax to look up properties:
return this[culture][key];

Related

Ag Grid aggregation function with Valuegetter

In AgGrid, I am tyring to use aggregation functions and valuegetter on the same column. It seems that because of valuegetter, my aggreation functions are not working, I just get 0 or null value on aggregation. Could you please check my code for any possible solutions?
Thanks.
{ headerName: "Price", filter: "agNumberColumnFilter", valueGetter: priceValueGetter, allowedAggFuncs: ['avg', 'sum', 'min', 'max']};
function priceValueGetter(params) {
var value = '';
if (params.data) {
var EPrice = params.data.a;
var p1 = params.data.b;
if (EPrice && p1) {
value = (EPrice - p1).toFixed(2);
}
}
return value;
}
Your valueGetter is returning a string.
So the built-in aggregation functions, which expect numbers, are failing.
Try this valueGetter instead:
function priceValueGetter(params): number {
if (params.data && params.data.a && params.data.b) {
return params.data.a - params.data.b
}
return null;
}
This won't format the way that you want, but you can fix that by adding a valueFormatter.
One other piece of advice - given that you have a variable named 'EPrice', I'm assuming that you're dealing with money. You shouldn't depend on Javascript's native 'number' type for money, as it is essentially a 'float', and rounding errors will ensue. There are lots of articles about how to properly handle monetary values in Javascript - I personally use a Javascript port of Java's BigDecimal class, called 'big.js', but there are several other solutions.
Edit - also be careful of using if on a number - it evaluates to false if the number is zero. If that's what you want, fine, but if it isn't, adjust your logic accordingly.

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.

Cannot access object's properties - objects

I'm working on a discord bot of mine and I was making a "help" command. I first had a commands array and if they wanted help on a specific command I had to add more lines of code than I want to. I was wondering if I could do put an object inside of my object like this:
const commands1 = {
coinflip: {
usage: prefix + "coinflip",
description: `Flip a coin then guess what it landed on.`
}
diceroll: {
usage: prefix + "diceroll (number)",
description: `Roll a die and guess what it lands on`
}
};
Or would I have to do something else, because when I do
for(var name in commands1){
embed.addField("Command:", name)
}
this will list all the commands available. However I can't access the usage or description, I tried this by doing this:
.addField("Usage:", name.usage)
.addField("Description:", name.description)
(it says undefined) Am I accessing it wrong or can I not put objects in objects. Sorry, I'm relatively new to this :) Thanks.
I found out that name. is literal and it thinks I'm trying to access commands1.name when I wanted commands1.coinflip. So I fixed it by doing this
console.log(commands1.coinflip.usage)
You are using for...in loop, which iterates over the indexes of the array. But the real scenario is you have object. So, in this case i would suggest you this:
const commands1 = {
coinflip: {
usage: prefix + "coinflip",
description: `Flip a coin then guess what it landed on.`
}
diceroll: {
usage: prefix + "diceroll (number)",
description: `Roll a die and guess what it lands on`
}
};
const keys = Object.keys(commands1); // #Output : ["coinflip", "diceroll"]
for(let key of keys){
embed.addField("Command:", commands1[key].usage);
}
Don't worry about being new, we all started somewhere.
Your newbie questions are probably better than mine were!
const commands1 = {
coinflip: {
usage: prefix + "coinflip",
description: `Flip a coin then guess what it landed on.`
},
/* Added a missing , above */
diceroll: {
usage: prefix + "diceroll (number)",
description: `Roll a die and guess what it lands on`
}
};
for(var name in commands1){
embed.addField("Command:", name);
console.log(commands1[name].usage);
console.log(commands1[name].description); /* You can Use your index to directly access the object thru the parent object. */
}

React-Intl: Load new strings from api response

I have a component that needs strings from the backend. I currently request the .po file from the server, convert it to .json and return it to my React component. I then want to be able to display those strings whilst replacing the correct values in the string, i.e.
<FormattedMessage id={dynamicId} values={dynamicVals} />
dynamicId is pulled from a separate api call, as well as dynamicVals.
My problem is that these strings are not bundled like all of my other app strings, so react-intl is unaware of them. How can I add these strings to the library client-side/async? I've attempted using defineMessages and addLocaleData, but I either am doing something incorrectly, or am not using the right api methods. Does addLocaleData provide the means to adding strings to the library? Is this possible to do?
In summary:
How can I receive
{
notifications.friendships.nowfriends: "{name} is now your friend"
}
from the api and display it using:
<FormattedMessage id='notifications.friendships.nowfriends' values={{ name: 'StackOver Bro' }} />
Thanks for the help in advance.
In case anyone wants to know what I ended up doing...
Since I already had the strings and the variables to interpolate with, I bypassed the localization library and just used the following function (thanks to the answers on this question, especially #user2501097 and #Bryan Rayner)
/**
* see: https://stackoverflow.com/questions/29182244/convert-a-string-to-a-template-string
*
* Produces a function which uses template strings to do simple interpolation from objects.
*
* Usage:
* var makeMeKing = generateTemplateString('${name} is now the king of {country}!');
*
* console.log(makeMeKing({ name: 'Bryan', country: 'Scotland'}));
* // Logs 'Bryan is now the king of Scotland!'
*/
const generateTemplateString = (function () {
const cache = {}
function generateTemplate(template) {
let fn = cache[template]
if (!fn) {
// Replace ${expressions} (etc) with ${map.expressions}.
const sanitized = template
.replace(/\$?\{([\s]*[^;\s\{]+[\s]*)\}/g, (_, match) => {
return `\$\{map.${match.trim()}\}`
})
// Afterwards, replace anything that's not ${map.expressions}' (etc) with a blank string.
.replace(/(\$\{(?!map\.)[^}]+\})/g, '')
fn = Function('map', `return \`${sanitized}\``)
cache[template] = fn
}
return fn
}
return generateTemplate
}())
export default generateTemplateString

How can I enforce attribute types in a Backbone model?

I want to have a Backbone model with float attributes in it but without worrying too much about variable types.
I would like to encapsulate the value parsing right there in the model so I am thinking of overriding the set function:
var Place = Backbone.Model.extend({
set: function(attributes, options) {
if (!_.isEmpty(attributes.latitude)){
attributes.latitude == parseFloat(attributes.latitude);
}
if (!_.isEmpty(attributes.longitude)){
attributes.longitude == parseFloat(attributes.longitude);
}
Backbone.Model.prototype.set.call(this, attributes, options);
}
});
However this seems cumbersome, since I would have a similar logic in the validate method and potentially repeated across multiple models. I don't think the View should take care of these conversions.
So what is the best way of doing it?
Use a validation plugin for your model so that you can validate the input in a generic fashion.
There are several out there including one that I have written:
Backbone.Validator
Backbone.Validation
Then you don't worry about performing data validation anywhere else - your model does it and sends out and error message you can listen for and provide appropriate feedback.
Also, a lat/lng pair can, in rare circumstances, be an integer, such as Greenwich England: 0,0 or the north pole: 90,180. And since JavaScript only has "number" any valid input for parseFloat is also valid for parseInt.
But parseFloat will always return a float.
My solution was to replace Backbone.Model.prototype.set with a preprocessor proxy:
/**
* Intercept calls to Backbone.Model.set and preprocess attribute values.
*
* If the model has a <code>preprocess</code> property, that property will be
* used for mapping attribute names to preprocessor functions. This is useful
* for automatically converting strings to numbers, for instance.
*
* #param Backbone
* the global Backbone object.
*/
(function(Backbone) {
var originalSet = Backbone.Model.prototype.set;
_.extend(Backbone.Model.prototype, {
set: function(key, val, options) {
if(!this.preprocess) {
return originalSet.apply(this, arguments);
}
// If-else copied from Backbone source
if (typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
for(attr in this.preprocess) {
if(_.has(attrs, attr)) {
attrs[attr] = this.preprocess[attr](attrs[attr]);
}
}
return originalSet.call(this, attrs, options);
},
});
})(Backbone);
After this, models with a preprocess property will use it to map attribute names to preprocessor functions. For instance, preprocess: { age: parseInt } means that whenever the age attribute is set, the value will be passed through parseInt before actually setting it. Attributes with no corresponding preprocess entry will not be affected.
Example usage:
var Thing = Backbone.Model.extend({
preprocess: {
mass: parseInt,
created: function(s) { return new Date(s); },
},
});
var t = new Thing({
label: '42',
mass: '42',
created: '1971-02-03T12:13:14+02:00',
});
console.log(t.get('label')+3); // 423
console.log(t.get('mass')+3); // 45
console.log(t.get('created').toLocaleString('ja-JP', { weekday: 'short' })); // 水
Pros
The functionality is available in all models without needing to duplicate code
No need to send { validate: true } in every call to set
No need to duplicate preprocessing in validate, since this happens before validate is called (this might also be a con, se below)
Cons
Some duplication of Backbone code
Might break validation since preprocessing happens before validate is called. JavaScript parsing methods usually return invalid values instead of throwing exceptions, though (i.e. parseInt('foo') returns NaN), so you should be able to detect that instead.

Categories

Resources