Using constants as indices for JavaScript associative arrays - javascript

I'm looking to create an associative array in JavaScript, but use constants defined as part of the class as indices.
The reason I want this is so that users of the class can use the constants (which define events) to trigger actions.
Some code to illustrate:
STATE_NORMAL = 0;
STATE_NEW_TASK_ADDED = 0;
this.curr_state = STATE_NEW_TASK_ADDED;
this.state_machine = {
/* Prototype:
STATE_NAME: {
EVENT_NAME: {
"next_state": new_state_name,
"action": func
}
}
*/
STATE_NEW_TASK_ADDED : { // I'd like this to be a constant
this.EVENT_NEW_TASK_ADDED_AJAX : {
"next_state": STATE_NEW_TASK_ADDED,
"action" : function() {console.log("new task added");},
}
}
}
// Public data members.
// These define the various events that can happen.
this.EVENT_NEW_TASK_ADDED_AJAX = 0;
this.EVENT_NEW_TASK_ADDED_AJAX = 1;
I'm having trouble getting this to work. I'm not too great with JavaScript, but it looks like no matter what I do, the array gets defined with strings and not constants. Is there a way to force the array to use the constants?

In ECMAScript 6 you can use computed values for object keys:
var CONSTANT_A = 0, CONSTANT_B = 1
var state_machine = {
[CONSTANT_A]: function () {
return 'a'
},
[CONSTANT_B]: function () {
return 'b'
}
};
console.log(state_machine)
This does not work in Internet Explorer 11 nor in Safari browsers:
https://kangax.github.io/compat-table/es6/#test-object_literal_extensions_computed_properties

See Kristian's answer re: ECMAScript 6/modern JavaScript, which has new syntax to make this possible.
The below is my original answer, from the pre-modern age.
The problem here, actually, is that you can't use a value for the key part when you're defining an object literally.
That is to say, this uses the constant values as expected:
var CONSTANT_A = 0, CONSTANT_B = 1;
var state_machine = {};
state_machine[CONSTANT_A] = "A";
state_machine[CONSTANT_B] = "B";
console.log(state_machine[0]); // => A
console.log(state_machine[1]); // => B
But this won't work as expected, instead using the string CONSTANT_A as key:
var CONSTANT_A = 0, CONSTANT_B = 1;
var state_machine = {
CONSTANT_A: "A",
CONSTANT_B: "B",
};
console.log(state_machine[0]); // => undefined
console.log(state_machine["CONSTANT_A"]); // => A
console.log(state_machine.CONSTANT_A); // => A
JavaScript has a shorthand to define object literals where you can omit the double-quotes around keys. Expressions can't be used, so CONSTANT_A won't be evaluated.

Let's say you have the following constants:
const COMPANIES = "companies";
const BRANCHES = "branches";
const QUEUES = "queues";
const LOGOUT = "logout";
If you declare the dictionary this way:
var itemsToState = {
COMPANIES: true,
BRANCHES: false,
QUEUES: false,
LOGOUT: false,
}
// You will get:
// { COMPANIES: true, BRANCHES: false, QUEUES: false, LOGOUT: false }
Note the keys are uppercase ^ because it is not using the constant's value.
If you want to use the constant's value as key, you need to do this:
var itemsToState = {
[COMPANIES]: true,
[BRANCHES]: false,
[QUEUES]: false,
[LOGOUT]: false,
}
// You will get:
// { companies: true, branches: false, queues: false, logout: false }
Note the keys are lowercase ^ because it is using the constant's value.

Related

How to get first properties of each object in an array of objects?

const combinations = [{rolledOnes: true, scoredOnes:false},
{rolledTwos: true, scoredTwos:false}];
I am fairly new to Javascript. So, my actual array is larger than this. I want to set rolledOnes and rolledTwos to false, without affecting scoredOnes and scoredTwos. Some sort of loop or nice method would be nice?
I tried an array of arrays and can get it to function the way i want, but it is not clear compared to objects.
We can using Array.forEach() combined with Object.keys() to do it
let combinations = [{rolledOnes: true, scoredOnes:false},
{rolledTwos: true, scoredTwos:false}];
combinations.forEach(e => {
let k = Object.keys(e)[0]
e[k] = false
})
console.log(combinations)
While object key order is deterministic in ES2015+, it's better not to rely on object key order in your logic.
A safer approach might be to store the targeted keys in an array, and then use that array as a filter while iterating over your objects. This approach also better describes your actual intent and will work even in cases where the object key orders are not what you showed in the question details. For example:
const combinations = [
{rolledOnes: true, scoredOnes: false},
{rolledTwos: true, scoredTwos: false},
];
const falseKeys = ['rolledOnes', 'rolledTwos'];
for (const obj of combinations) {
for (const key of falseKeys) {
if (key in obj) obj[key] = false;
}
}
console.log(combinations); // [ { rolledOnes: false, scoredOnes: false }, { rolledTwos: false, scoredTwos: false } ]
Use forEach() function to loop through object
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
And keys() to get property names
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
const combinations = [
{ rolledOnes: true, scoredOnes: false },
{ rolledTwos: true, scoredTwos: false }
];
combinations.forEach(combination => {
let property = Object.keys(combination)[0];
combination[property] = false;
return combination;
})
const combinations = [{
rolledOnes: true,
scoredOnes: false
},
{
rolledTwos: true,
scoredTwos: false
},
{
rolledThrees: true,
scoredThrees: false
},
];
combinations.forEach(comb => {
Object.keys(comb).map(key => {
if (key.startsWith('rolled')) {
comb[key] = false;
}
})
})
console.log(combinations);

Export a variable that is unique only first time it is referenced

Some context, I am trying to create a global variable that is set once using Math.random().toString(36).slice(2); or similar however whenever referenced again does not generate a new random string but returns the already generated random string.
However within parallel.conf.js and then in runner.js I have two different strings.
I have tried the logic of if localIdentifier is undefined generate a random string and if not return the already generated string by referencing where it has been inserted.
I have added the configuration I have setup relating to nightwatch + browserstack for context but don't think it should matter.
If unclear do let me know and I can provide more context.
parallel.conf.js
let localIdentifier;
let defineLocalIdentifier = (function(){
if (localIdentifier === 'undefined') {
localIdentifier = nightwatch_config.common_capabilities["browserstack.localIdentifier"];
return localIdentifier;
} else {
localIdentifier = Math.random().toString(36).slice(2);
return localIdentifier;
}
})();
nightwatch_config = {
common_capabilities: {
'browserstack.user': <user>,
'browserstack.key': <key>,
'browserstack.tunnel': true,
'browserstack.localIdentifier': defineLocalIdentifier,
'browserstack.local': true,
'name': 'Bstack-[Nightwatch] Parallel Test',
acceptInsecureCerts: true,
javascriptEnabled: true,
acceptSslCerts: true,
}
module.exports = nightwatch_config;
module.exports.bs = {
localIdentifier : defineLocalIdentifier
}
runner.js
const nightwatchConfig = require('./parallel.conf').bs.localIdentifier;
function BrowserStack () {
const browserStackArgs = {
key: apiKey,
'force': 'true',
'forceLocal': 'true',
'verbose': 'true',
'localIdentifier': nightwatchConfig
};

Generate vue components from a nested object

Is there any good way to use a nested object to generate vue components?
I have a deeply nested object that looks like this:
"api": {
"v1": {
"groups": {
"create": true,
"get": true,
"item": {
"get": true,
"destroy": false
}
}
}
}
I want to generate a form which has checkboxes for each of the values of the object.
I'm having trouble binding the values of the object to the v-models in a Vue checkbox
I've tried making a list of lookup keys like ["api.v1.groups.create", "api.v1.groups.get"]
then using a function like the following to get the entries:
getPerm (p) {
return p.split('.').reduce(
(xs, x) => (xs && xs[x]) ? xs[x] : null,
this.role.permissions)
}
However, this does not work because it gives me the boolean and not the reference.
Check out my:
Code sandbox to flatten dict and create a list from a nested entry
( I forked it from a basic Vue template so it still has some references to 'chart' in there)
Once you have flattened the dictionary you use them to generate vue components using v-for as you normally would!
Below is the processing I used to flatten the dictionary if you would like to check that here on site:
// Define out nested object
var nested = {
"api": {
"v1": {
"groups": {
"create": true,
"get": true,
"item": {
"get": true,
"destroy": false
}
}
}
}
}
// function for checking if we are at the bottom of the Object
const isObj = o => o?.constructor === Object;
// I had to use a class to store the output in the different recursive scopes
class NestedProcessor {
// Constructur starts the function and returns the dictionary as flat
constructor(leveled_dict) {
this.output = {}
this.process_dict_recursive(leveled_dict)
return this.ouput
}
process_dict_recursive(leveled_dict, keyList = [], depth = 0) {
if (isObj(leveled_dict)) { // check if we have hit the bottom
keyList.push('')
depth++
for (let key in leveled_dict) {
keyList[depth - 1] = key
this.process_dict_recursive(leveled_dict[key], keyList, depth) // call the function recursively at our new depth
}
}
else {
// Create our lookup keys
let path = ''
keyList.forEach((v) => {
path += v
path += '.'
})
path = path.slice(0, -1) // Remove the last '.'
this.output[path] = leveled_dict
}
}
}
console.log(new NestedProcessor(nested))
//{
// "output": {
// "api.v1.groups.create": true,
// "api.v1.groups.get": true,
// "api.v1.groups.item.get": true,
// "api.v1.groups.item.destroy": false
// }
//}
Notes:
I used a class, because I couldn't figure out how to handle variable scope within the recursion
I needed a way to check if we were at the bottom so grabbed a function from SO to check for that.

Assign methods to object by iterating over an array

In ES5, I know that it's possible to assign methods to an object using a forEach loop in the following way:
var myMethods = [
{
name: 'start',
src: someFn
},
{
name: 'stop',
src: someOtherFn
}
];
var myObject = {};
myMethods.forEach(function(method) {
myObject[method.name] = method.src;
});
In ES2015 (or ES6), is it possible to define these methods in tandem with creating the object? Here is an example of how I might expect this to work:
// example
const myObject = {
[...myMethods.map((method) => method.name)]: [...myMethods.map(method) => method.src)]
}
The end result would look like this:
const myObject = {
start: someFn,
stop: someOtherFn
}
If there is a way to iterate over these methods and assign them to myObject, I would happily restructure the myMethods array so that this is possible.
The end goal is to be able to assign each of these methods in an external module and not have to duplicate the definition.
Yes, you can use Object.assign and the spread operator in conjunction with computed property names to do
var myObject = Object.assign({}, ...myMethods.map(({name, src}) => ({[name]: src})));
First we map myMethods to an array of little one-property objects, whose key is given by the value of the name property and value by the src property. Then we use the spread operator ... to pass these to Object.assign as parameters. Object.assign then glues them all together for us.
Reduce should do the trick for you. Note that the optional second parameter is used to start with an empty object at the beginning.
var myMethods = [{
name: 'start',
src: function() {
console.log('started')
}
}, {
name: 'stop',
src: function() {
console.log('stopped')
}
}];
var myObject = myMethods.reduce((obj, method) => {
obj[method.name] = method.src;
return obj;
}, {})
console.log(myObject)
myObject.start()
myObject.stop()
Try assigning to myObject at same line of myMethods assignnemts
var myObject = {};
someFn = function(){console.log(this)};
someOtherFn = function(){console.log(this)};
var myObject = {};
someFn = function(){};
someOtherFn = function(){}
var myMethods = [
{
name: (myObject["start"] = "start"),
src: (myObject["start"] = someFn)
},
{
name: (myObject["stop"] = "stop"),
src: (myObject["stop"] = someOtherFn)
}
];

How can I get console.log to output the getter result instead of the string "[Getter/Setter]"?

In this code:
function Cls() {
this._id = 0;
Object.defineProperty(this, 'id', {
get: function() {
return this._id;
},
set: function(id) {
this._id = id;
},
enumerable: true
});
};
var obj = new Cls();
obj.id = 123;
console.log(obj);
console.log(obj.id);
I would like to get { _id: 123, id: 123 }
but instead I get { _id: 123, id: [Getter/Setter] }
Is there a way to have the getter value be used by the console.log function?
You can use console.log(Object.assign({}, obj));
Use console.log(JSON.stringify(obj));
Since Nodejs v11.5.0 you can set getters: true in the util.inspect options. See here for docs.
getters <boolean> | <string> If set to true, getters are inspected. If set to 'get', only getters without a corresponding setter are inspected. If set to 'set', only getters with a corresponding setter are inspected. This might cause side effects depending on the getter function. Default: false.
You can define an inspect method on your object, and export the properties you are interested in. See docs here: https://nodejs.org/api/util.html#util_custom_inspection_functions_on_objects
I guess it would look something like:
function Cls() {
this._id = 0;
Object.defineProperty(this, 'id', {
get: function() {
return this._id;
},
set: function(id) {
this._id = id;
},
enumerable: true
});
};
Cls.prototype.inspect = function(depth, options) {
return `{ 'id': ${this._id} }`
}
var obj = new Cls();
obj.id = 123;
console.log(obj);
console.log(obj.id);
I needed a pretty printed object without the getters and setters yet plain JSON produced garbage. For me as the JSON string was just too long after feeding JSON.stringify() a particularly big and nested object. I wanted it to look like and behave like a plain stringified object in the console. So I just parsed it again:
JSON.parse(JSON.stringify(largeObject))
There. If you have a simpler method, let me know.
On Node.js, I suggest using util.inspect.custom, which will allow you to pretty print getters as values, while keeping other properties output unchanged.
It will apply to your specific object only and won't mess the general console.log output.
The main benefit vs Object.assign is that it happens on your object, so you keep the regular generic console.log(object) syntax. You don't have to wrap it with console.log(Object.assign({}, object)).
Add the following method to your object:
[util.inspect.custom](depth, options) {
const getters = Object.keys(this);
/*
for getters set on prototype, use instead:
const prototype = Object.getPrototypeOf(this);
const getters = Object.keys(prototype);
*/
const properties = getters.map((getter) => [getter, this[getter]]);
const defined = properties.filter(([, value]) => value !== undefined);
const plain = Object.fromEntries(defined);
const object = Object.create(this, Object.getOwnPropertyDescriptors(plain));
// disable custom after the object has been processed once to avoid infinite looping
Object.defineProperty(object, util.inspect.custom, {});
return util.inspect(object, {
...options,
depth: options.depth === null ? null : options.depth - 1,
});
}
Here is a working example in your context:
const util = require('util');
function Cls() {
this._id = 0;
Object.defineProperty(this, 'id', {
get: function() {
return this._id;
},
set: function(id) {
this._id = id;
},
enumerable: true
});
this[util.inspect.custom] = function(depth, options) {
const getters = Object.keys(this);
/*
for getters set on prototype, use instead:
const prototype = Object.getPrototypeOf(this);
const getters = Object.keys(prototype);
*/
const properties = getters.map((getter) => [getter, this[getter]]);
const defined = properties.filter(([, value]) => value !== undefined);
const plain = Object.fromEntries(defined);
const object = Object.create(this, Object.getOwnPropertyDescriptors(plain));
// disable custom after the object has been processed once to avoid infinite looping
Object.defineProperty(object, util.inspect.custom, {});
return util.inspect(object, {
...options,
depth: options.depth === null ? null : options.depth - 1,
});
}
};
var obj = new Cls();
obj.id = 123;
console.log(obj);
console.log(obj.id);
Output:
Cls { _id: 123, id: 123 }
123
Use spread operator:
console.log({ ... obj });

Categories

Resources