Accessing dynamic properties of an Object in React - javascript

I’m checking whether there is a placeholder present in the “all” string within alertDetails object so my question is I need to access email,sms,fax property that appears dynamically based on user’s input (the code is a part of an alert box in which an user can choose to send alert via different delivery methods) so I saved all the possible delivery methods in an array and tried like below;
Const delmethod=[“email”,”sms”,”fax”]
for(let i=0;i<delmethod.length;i++)
{
Console.log(alertDetails &&
alertDetails.alertMessage &&
alertDetails.alertMessage[${lang}] &&
alertDetails.alertMessage[${lang}].all
? alertDetails.alertMessage[${lang}][‘${delmethod(i)}’].includes('placeholder')
: false;
}
P.S:the property “all” is fixed it’s just the email fax will be changing based on user’s input, to sum it up I want to return true if “placeholder” exists in any of the strings(all,email,fax,sms) the method I tried just prints the del method array, I’d appreciate any help thanks!

There are multiple issues with your code. You cannot just use ${lang}. You must surround your string with backticks (`) if you want to use template literals.
To access properties of an object you need a a key i.e. a string which you already have so in this case template literals are not required at all.
When you access an array by index you need to use [] not () so use delmethod[i] instead of delmethod(i). Additionally make sure an property exists on an JavaScript object.
const delmethod = ["email", "sms", "fax"];
const alertDetails = {
alertMessage: {
en: {
all: "emailsfsdfsdfsd",
fax: "placeholder",
sms: "sdkjföskjfsödkj"
},
},
};
const lang = "en";
for (let i = 0; i < delmethod.length; i++) {
if (
alertDetails &&
alertDetails.alertMessage &&
// use backticks ` when trying to use template literals
alertDetails.alertMessage[`${lang}`] &&
// there is actually no need for template literals here
alertDetails.alertMessage[lang].all &&
// you need to make sure "sms" or "fax" or "email" key actually exist on the object
alertDetails.alertMessage[lang][delmethod[i]] &&
alertDetails.alertMessage[lang][delmethod[i]].includes("placeholder")
) {
console.log(
`alertDetails.alertMessage[${lang}][${delmethod[i]}] does have a placeholder`
);
console.log(true);
} else {
console.log(
`alertDetails.alertMessage[${lang}] does NOT have property ${delmethod[i]} or does NOT have a placeholder`
);
console.log(false);
}
}

Related

Why is the value of a specific key for a doc getting 'undefined' [duplicate]

Is there something that I'm missing that would allow item to log as an object with a parameter, but when I try to access that parameter, it's undefined?
What I've tried so far:
console.log(item) => { title: "foo", content: "bar" } , that's fine
console.log(typeof item) => object
console.log(item.title) => "undefined"
I'll include some of the context just in case it's relevant to the problem.
var TextController = function(myCollection) {
this.myCollection = myCollection
}
TextController.prototype.list = function(req, res, next) {
this.myCollection.find({}).exec(function(err, doc) {
var set = new Set([])
doc.forEach(function(item) {
console.log(item) // Here item shows the parameter
console.log(item.title) // "undefined"
set.add(item.title)
})
res.json(set.get());
})
}
Based on suggestion I dropped debugger before this line to check what item actually is via the node repl debugger. This is what I found : http://hastebin.com/qatireweni.sm
From this I tried console.log(item._doc.title) and it works just fine.. So, this seems more like a mongoose question now than anything.
There are questions similar to this, but they seem to be related to 'this' accessing of objects or they're trying to get the object outside the scope of the function. In this case, I don't think I'm doing either of those, but inform me if I'm wrong. Thanks
Solution
You can call the toObject method in order to access the fields. For example:
var itemObject = item.toObject();
console.log(itemObject.title); // "foo"
Why
As you point out that the real fields are stored in the _doc field of the document.
But why console.log(item) => { title: "foo", content: "bar" }?
From the source code of mongoose(document.js), we can find that the toString method of Document call the toObject method. So console.log will show fields 'correctly'. The source code is shown below:
var inspect = require('util').inspect;
...
/**
* Helper for console.log
*
* #api public
*/
Document.prototype.inspect = function(options) {
var isPOJO = options &&
utils.getFunctionName(options.constructor) === 'Object';
var opts;
if (isPOJO) {
opts = options;
} else if (this.schema.options.toObject) {
opts = clone(this.schema.options.toObject);
} else {
opts = {};
}
opts.minimize = false;
opts.retainKeyOrder = true;
return this.toObject(opts);
};
/**
* Helper for console.log
*
* #api public
* #method toString
*/
Document.prototype.toString = function() {
return inspect(this.inspect());
};
Make sure that you have defined title in your schema:
var MyCollectionSchema = new mongoose.Schema({
_id: String,
title: String
});
Try performing a for in loop over item and see if you can access values.
for (var k in item) {
console.log(item[k]);
}
If it works, it would mean your keys have some non-printable characters or something like this.
From what you said in the comments, it looks like somehow item is an instance of a String primitive wrapper.
E.g.
var s = new String('test');
typeof s; //object
s instanceof String; //true
To verify this theory, try this:
eval('(' + item + ')').title;
It could also be that item is an object that has a toString method that displays what you see.
EDIT: To identify these issues quickly, you can use console.dir instead of console.log, since it display an interactive list of the object properties. You can also but a breakpoint and add a watch.
Use findOne() instead of find().
The find() method returns an array of values, even if you have only one possible result, you'll need to use item[0] to get it.
The findOne method returns one object or none, then you'll be able to access its properties with no issues.
Old question, but since I had a problem with this too, I'll answer it.
This probably happened because you're using find() instead of findOne(). So in the end, you're calling a method for an array of documents instead of a document, resulting in finding an array and not a single document. Using findOne() will let you get access the object normally.
A better way to tackle an issue like this is using doc.toObject() like this
doc.toObject({ getters: true })
other options include:
getters: apply all getters (path and virtual getters)
virtuals: apply virtual getters (can override getters option)
minimize: remove empty objects (defaults to true)
transform: a transform function to apply to the resulting document before returning
depopulate: depopulate any populated paths, replacing them with their original refs (defaults to false)
versionKey: whether to include the version key (defaults to true)
so for example you can say
Model.findOne().exec((err, doc) => {
if (!err) {
doc.toObject({ getters: true })
console.log('doc _id:', doc._id) // or title
}
})
and now it will work
You don't have whitespace or funny characters in ' title', do you? They can be defined if you've quoted identifiers into the object/map definition. For example:
var problem = {
' title': 'Foo',
'content': 'Bar'
};
That might cause console.log(item) to display similar to what you're expecting, but cause your undefined problem when you access the title property without it's preceding space.
I think using 'find' method returns an array of Documents.I tried this and I was able to print the title
for (var i = 0; i < doc.length; i++) {
console.log("iteration " + i);
console.log('ID:' + docs[i]._id);
console.log(docs[i].title);
}
If you only want to get the info without all mongoose benefits, save i.e., you can use .lean() in your query. It will get your info quicker and you'll can use it as an object directly.
https://mongoosejs.com/docs/api.html#query_Query-lean
As says in docs, this is the best to read-only scenarios.
Are you initializing your object?
function MyObject()
{
this.Title = "";
this.Content = "";
}
var myo1 = new MyObject();
If you do not initialize or have not set a title. You will get undefined.
When you make tue query, use .lean() E.g
const order = await Order.findId("84578437").lean()
find returns an array of object , so to access element use indexing, like
doc[0].title

Custom word translator in React

Update: scroll to see my solution, can it be improved?
So I have this issue, I am building a word translator thats translates english to 'doggo', I have built this in vanilla JS but would like to do it React.
My object comes from firebase like this
dictionary = [
0: {
name: "paws",
paws: ["stumps", "toes beans"]
}
1: {
name: "fur",
fur: ["floof"]
}
2: {
name: "what"
what: ["wut"]
}
]
I then convert it to this format for easier access:
dictionary = {
what : ["wut"],
paws : ["stumps", "toe beans"],
fur : ["floof"]
}
Then, I have two text-area inputs one of which takes input and I would like the other one to output the corresponding translation. Currently I am just logging it to the console.
This works fine to output the array of the corresponding word, next I have another variable which I call 'levelOfDerp' which is basically a number between 0 - 2 (set to 0 by default) which I can throw on the end of the console.log() as follows to correspond to the word within the array that gets output.
dictionary.map(item => {
console.log(item[evt.target.value][levelOfDerp]);
});
When I do this I get a "TypeError: Cannot read property '0' of undefined". I am trying to figure out how to get past this error and perform the translation in real-time as the user types.
Here is the code from the vanilla js which performs the translation on a click event and everything at once. Not what I am trying to achieve here but I added it for clarity.
function convertText(event) {
event.preventDefault();
let text = inputForm.value.toLowerCase().trim();
let array = text.split(/,?\s+/);
array.forEach(word => {
if (dictionary[word] === undefined) {
outputForm.innerHTML += `${word} `;
noTranslationArr.push(word);
} else {
let output = dictionary[word][levelOfDerp];
if (output === undefined) {
output = dictionary[word][1];
if (output === undefined) {
output = dictionary[word][0];
}
}
outputForm.innerHTML += `${output} `;
hashtagArr.push(output);
}
});
addData(noTranslationArr);
}
Also here is a link to the translator in vanilla js to get a better idea of the project https://darrencarlin.github.io/DoggoSpk/
Solution, but could be better..
I found a solution but I just feel this code is going against the reason to use react in the first place.. My main concern is that I am declaring variables to store strings inside of an array within the function (on every keystroke) which I haven't really done in React, I feel this is going against best practice?
translate = evt => {
// Converting the firebase object
const dict = this.state.dictionary;
let dictCopy = Object.assign(
{},
...dict.map(item => ({ [item["name"]]: item }))
);
let text = evt.target.value.toLowerCase().trim();
let textArr = text.split(/,?\s+/);
let translation = "";
textArr.forEach(word => {
if (dictCopy[word] === undefined) {
translation += `${word} `;
} else {
translation += dictCopy[word][word][this.state.derpLvl];
}
});
this.setState({ translation });
};
levelOfDerp is not defined, try to use 'levelOfDerp' as string with quotes.
let output = dictionary[word]['levelOfDerp' ];
The problem happens because setState() is asynchronous, so by the time it's executed your evt.target.value reference might not be there anymore. The solution is, as you stated, to store that reference into a variable.
Maybe consider writing another function that handles the object conversion and store it in a variable, because as is, you're doing the conversion everytime the user inputs something.

Function to conditionally assign variable member data to nested array in object?

I want to add data into an object, and my object contains nested data. Example data:
pageviewEvent {
client: "clientName",
page {
document_referrer: 'some http refer header data goes here',
window_height: 1920,
window_width: 1200
}
}
Some data is undefined or null and I do not want to add this undefined/null data into the object.
I made a function that works to add data to the object conditionally (if not undefined) but I can't figure out how to add data to nested objects in the function?
I could just make a bunch of if statements, but figure it's better to put the condition test into a function to reduce code size.
Example code with comment showing thinking of what I am trying but doesn't work:
//function to check if variable undefined or null, if not -> add to pageviewEvent arrayKey, variableName
function isUndefinedNull(arrayKey, variableName) {
var evalVariableName = eval(variableName);
if (evalVariableName !== undefined && evalVariableName !== null && evalVariableName !== "") {
console.log(arrayKey);
console.log(variableName);
pageviewEvent[arrayKey] = evalVariableName;
//array key could be nested, for instance pageview[page][title] or pageview.page.tile
}
}
//make event array
const pageviewEvent = { }
//add static data
pageviewEvent.client = 'neguse';
//if not null or undefined add data
isUndefinedNull('referrer.full', 'document.referrer');
//want to put data into object pageviewEvent.referrer.full or pageviewEvent[referrer][full]
Thanks for any help. I feel like this answer can help but I can't figure it out.
I recommend using the lodash function _.set(), documentation can be found here: https://lodash.com/docs/4.17.4#set
_.set( pageviewEvent, "referrer.full", "some-value" );
If you want to customise the behaviour of how nesting is handled when there's an undefined value, you can instead use _.setWith() - see https://lodash.com/docs/4.17.4#setWith

Angular2 bind ngModel from ngFor

So I'm rendering my textarea dynamically using ngFor however I'm not sure how I can pass the ngModel to bind it in my function.
<div *ngFor="let inputSearch of searchBoxCount; let i = index" [ngClass]="{'col-sm-3': swaggerParamLength=='3', 'col-sm-9': swaggerParamLength=='1'}">
<textarea name="{{inputSearch.name}}" id="{{inputSearch.name}}" rows="3" class="search-area-txt" attr.placeholder="Search Product {{inputSearch.name}}"
[(ngModel)]="inputSearch.name"></textarea>
</div>
textarea example:
textarea is render based on the length of the response I get from api call in my case searchBoxCount is basically searchBoxCount.length, so if it length is = 1 then it will only render 1 textarea if its 3 then it will show 3 textareas. The objs have different names (example: id/email/whatever), so ngModel is based on the obj name from the json object.
How do I bind inputSearch.name to my function getQueryString()
getQueryString() {
this.isLoading = true;
let idInputValue = inputSearch.name; //bind it here
return "?id=" + idInputValue
.split("\n") // Search values are separated by newline and put it in array collection.
.filter(function(str) {
return str !== ""
})
.join("&id=");
}
Search func where getQueryString() is called
searchProduct() {
let queryString1 = this.getQueryString();
this._searchService.getProduct(queryString1)
.subscribe(data => {
console.log(data);
});
}
I know how to do it if the ngModel is not coming from the ngFor, is there another way to get the value from the textarea without ngModel? maybe that's the only way or if I can still use ngModel.
Summary of current state
First, let me summarize where your data is. You have a list of one or more objects named searchBoxCount. Each of the elements in the list is an object which has a name property, so you could, for example, call let name = this.searchBoxCount[0].name; to get the name of the first object in the list.
In the HTML template you use ngFor to loop through all of the objects in the searchBoxCount list, and in each iteration you assign the object to a local (to the ngFor) variable named inputSearch. You then bind the input from the textarea created in each loop iteration to the name property for that iteration's inputSearch object.
How to get your data
The key here is that the inputSearch is the same Object as is stored in searchBoxCount at some particular index (index 0 for the first object, etc...). So when the ngModel is tied to inputSearch.name it is also bout to searchBoxCount[n].name. External to the ngFor, you would loop through the searchBoxCount list to get each name you need.
As a consequence
Based on the comments on the original post, it sounds like you can have one or
more names that you need to include in the query string output. That means for your getQueryString() to work, you have to loop through the list (or as in this case, let the list loop for us):
getQueryString() {
this.isLoading = true;
let result : string = "?id=";
this.searchBoxCount.forEach(
(inputSearch:any) => { //Not the same variable, but same objects as in the ngFor
result = result + inputSearch.name + "&id=";
});
result = result.slice(0, result.length - 4); //trim off the last &id=
return result;
}
Edit: Multiple different fields with different names
From the comments on this post, it now is clear each inputSearch has its own key to be used in the query string, that is stored in the name property. You need to preserve that name, which means you can't bind the ngModel to it. Otherwise the user will destroy the name by typing in their own text and there will be no way to get the correct key back. To that end, you need to store bind the ngModel to some other property of the inputSearch object. I am going to assume the object has a value property, so it looks like this:
{
name: "id",
value: "33\n44"
}
That is, each inputSearch has a name, and the value will have one or more values, separated by new line. You would then have to change the HTML template to this:
<div *ngFor="let inputSearch of searchBoxCount; let i = index"
[ngClass]="{'col-sm-3': swaggerParamLength=='3', 'col-sm-9':
swaggerParamLength=='1'}">
<textarea name="{{inputSearch.name}}"
id="{{inputSearch.name}}" rows="3" class="search-area-txt"
attr.placeholder="Search Product {{inputSearch.name}}"
[(ngModel)]="inputSearch.value"></textarea>
</div>
Notice that I changed the ngModel from inputSearch.name to inputSearch?.value (the ? allows for null if there is no value to begin with) inputSearch.value. The getQueryString() method then looks something like this:
getQueryString() {
let result:string = "?";
//for each of the input search terms...
this.searchBoxCount.forEach( (inputSearch:any) => {
// first reparse the input values to individual key value pairs
let inputValues:string = inputSearch.value.split("\n")
.filter(function(str) { return str !== "" })
.join("&" + inputSearch.name + "=");
// then add it to the overall query string for all searches
result = result +
inputSearch.name +
"=" +
inputValues +
"&"
});
// remove trailing '&'
result = result.slice(0, result.length - 1);
return result;
}
Note, using RxJs this is probably easier but I am testing vanilla javascript.
Using this, if the user entered two IDs (33 and 44), a single sku, and two emails, the result would be ?id=33&id=24&sku=abc123&email=name#compa.ny&email=an.other#compa.ny

Get only the Changed data from angular2 forms

Hie.I have a basic angular2 forms where i have nested objects.What i am trying to make is that i want to get only those fields which are dirty.I am facing problem to get the values of deeply nested objects...This is my demo http://plnkr.co/edit/gzT4mVWglHrFziRayHtK?p=preview ,here changes done in Contacts fields are coming in main Object...
{
"addressinfo": {
"Line1": "",
"Line2": ""
},
"firstname": "",
"lastname": "",
"Contacts": {
"Phone1": {
"Type": "",
"Number": ""
}
}
}
If I change Number Field and submit the form then I am getting the response object in this way...
{
"Contacts": {},
"Phone1": {
"Number": ""
}
}
Since Number field has been changed i want the output to be in this way
{
"Contacts": {
"Phone1": {
"Number": ""
}
},
}
Somebody help me to get only the changed data in the right object format...Thanks
Have you tried to step through your code in a debug session (by using the debugger statement for example)? When it is checking your Contacts control group it pushes this name as new object onto the ResultObject and starts executing recursivelyIterateProperties(jsonObject,ResultObject,activeProperty) again, with the following parameters:
jsonObject = {Phone1: ControlGroup},
ResultObject = {Contacts: {}},
activeProperty = 'Contacts'
The problem is that Phone1 is again a control group and therefore your === 'object' code is executed again, pushing a new Phone1 object directly onto the ResultObject. In other words, your code doesn't handle nested control groups very well.
I have taken the liberty to rewrite your code a bit into a working example: Plunker.
For clarity the iteration over the initial control groups has been split from the collection of the values. We first iterate over all controls:
iterateOverControls(controls, resultObject): any {
var resultObject = {};
// Iterate over controls and controlgroups
for ( var control in controls ) {
var result = {}
// Only look into dirty controls
if ( controls.hasOwnProperty(control) && !controls[control].pristine ) {
result[control] = this.retrieveValuesForControl(controls[control]);
// Once we have collected the changes, add them to the result object
for ( var key in result ) {
if ( result.hasOwnProperty(key) ) {
resultObject[key] = result[key];
}
}
}
}
return resultObject;
}
For each control that we encounter, we try to collect its values by calling retrieveValuesForControl. This method expects the control we are currently looking into as a parameter.
retrieveValuesForControl(control): any {
// Only check dirty objects
if ( control.pristine ) {
return;
}
var values;
if ( typeof(control.value) === 'object' && control.controls ) {
// The current control is a control group, so we need to look deeper
values = {};
for ( var item in control.controls ) {
// We retrieve values for this control again (recursively)
values[item] = this.retrieveValuesForControl(control.controls[item]);
}
} else if ( !control.pristine) {
// We have a control, so copy the value
values = control.value;
}
// Return our collected values (either a plain value or object of values)
return values;
}
In this method we first check if the control we are looking at is dirty. If it is pristine, we return directly. Next we check if we are looking at a control group or a control.
In case of a control group: we want to look into the control group again by calling iterateOverControls for each and every control that the control group contains. The results of these calls are collected, put into one object and returned when we are done.
In case of a control: we can collect the value an return it.
I feel like you are not able to find them properly.
console.log(this.myForm.controls.Contacts.controls.Phone1.controls);
This is the exact way where you can find Number and Type. I don't see any problem in your plunker.
You can also subscribe to
this.myForm.valueChanges.subscribe(...)
and collect changed values this way.

Categories