Dynamically Access Model with View - javascript

I have the following script in my ASP.NET MVC Core View:
window["dataSet1"] = [];
#foreach (var item in Model.SelectedOptions)
{
foreach (var item2 in Model.MyChartData)
{
// ".mph" exists in 'MyChartData' model
// This works... but it's not dynamic
#:window["dataSet1"].push(['#item', #item2.mph);
// How can I get a dynamic variable determine
// what field to retrieve from the model?
// Example values for 'SelectedOptions' are:
// > "mph", "kph", "m/s", "knots"
// I'd like this to work...
#:window["dataSet1"].push(['#item', #item2.#item);
}
}
Instead of creating if else statements for each possible 'SelectedOptions' value (mph, kph, m/s, knots etc.), is it possible to simply use a variable to reference a specific object within my model?
The data I get back from my attempt is:
window["dataSet1"].push(['mph', MyProject.Models.MyChartData.mph]);
Rather than a value from my model, for example:
window["dataSet1"].push(['mph', 15.16451]);

You can solve it in c# adding a property using reflection to get the value or a simple case
Example:
#:window["dataSet1"].push(['#item', #item2.correctvalue( item );
In the item2 class:
public decimal correctvalue( propName ) {
return this.GetType().GetProperty(propName).GetValue(this, null);
}
Or more simple:
public decimal correctvalue( propName ) {
if (propName = "mph") {
return this.mph;
}
else ...
}
Keep in mind that you should validate the propName or the reflection will error. More info on using reflection to get the property value

The solution I created, whilst whacking it into a static class so it can be easily accessed in future.
public static object GetPropertyValue(this object myProperty, string propertyName)
{
return myProperty.GetType().GetProperties()
.Single(pi => pi.Name == propertyName)
.GetValue(myProperty, null);
}

Related

Class with grouped properties in deeper levels

I'm trying to build a complex class where I want to group properties, making the instantiated object have multiple layers, instead of every property being at root level.
So far, the only way I've found to do this is by making a class with the properties to group, and then in a "parent" class add a property of the class I built.
The problem here though is that two properties not sharing the same class can't communicate with each other.
There are ways around this, but I find them all very hacky and looking bad. One would be to create a hidden element, and store data in there that a property from another class can read.
Another would be to create static properties, but then, unless you do some major work with that property, you can only have one object created from the parent class, as it'll be the same no matter the instantiation of the class.
Very basic example:
class A {
constructor(prop1){
this.property = prop1;
}
}
class B {
constructor(prop2){
this.property = prop2;
}
}
class C {
constructor(prop1, prop2){
this.PropertyA = new A(prop1);
this.PropertyB = new B(prop2);
}
}
let obj = new C(1, 1);
console.log(obj.PropertyA.property);
In this example, the property from class A can't get a value from property in class B.
So, my question is, is there another way of building the class C to keep the levels of hierarchy in the object?
I use the class structure because I like how it looks. It looks far more readable to me than the prototype structure, and I'm not building an object directly, as I would like to instantiate more of them.
It feels like I have forgotten things I've looked at to try to do this, but I'm sure it'll come to me soon enough after I post this.
Sooo...
I worked a bit on a static-solution, and basically made a private static property to hold a unique id per instantiated object, with the key-value pairs I want to be able to share between the different classes. This should only expose the methods to either set or get those values. The only requirement is that all the classes needs to be constructed with the object ID, so they can get the right value.
I understand that people will roll their eyes at my infantile tries to break the actual points of classes and such, but it works for me anyway in this specific circumstance anyway.
I'm sure there a multitude of ways to update it to ensure it runs more smoothly, but I think it works for most cases at the moment.
The code made in example code:
"use strict";
class A {
#id
#testProp
constructor(id){
this.#id = id;
this.#testProp = 10;
}
get TestProp(){ return this.#testProp + C.getSharedProp(this.#id, "BValue")};
set TestProp(newValue) { this.#testProp = newValue; C.setSharedProp(this.#id, "AValue", this.#testProp) };
}
class B {
#id
#testProp
constructor(id){
this.#id = id;
this.#testProp = 10;
}
get TestProp(){ return this.#testProp + C.getSharedProp(this.#id, "AValue")};
set TestProp(newValue) { this.#testProp = newValue; C.setSharedProp(this.#id, "BValue", this.#testProp) };
}
class C {
#id
constructor(){
this.#id = Math.random().toString(36).substr(2, 9);
this.PropertyA = new A(this.#id);
this.PropertyB = new B(this.#id);
}
static #sharedProps = {};
static getSharedProp(charId, valueName) {
if(!charId){
throw "Must supply character ID";
}
if(!valueName){
throw "Must supply name of value to return";
}
if(!(charId in this.#sharedProps)){
throw "Character ID not found";
}
if(!(valueName in this.#sharedProps[charId])){
throw valueName + "-element not found";
}
return this.#sharedProps[charId][valueName];
}
static setSharedProp(charId, valueName, value) {
if(!charId){
throw "Must supply character ID";
}
if(!valueName){
throw "Must supply name of value";
}
if(!(charId in this.#sharedProps)){
this.#sharedProps[charId] = [];
}
if(!(valueName in this.#sharedProps[charId])){
this.#sharedProps[charId][valueName] = -1;
}
if(!value){
console.warn("Value not supplied of " + valueName + ". Not updating extant value");
}else{
this.#sharedProps[charId][valueName] = value;
}
}
}
let obj = new C();
obj.PropertyA.TestProp = 20;
obj.PropertyB.TestProp = 5;
console.log(obj.PropertyA.TestProp); //should be 25; 20 from its own class and 5 from foreign class-object
console.log(obj.PropertyB.TestProp); //should be 25; 5 from its own class and 20 from foreign class-object

How to serialize an object and cast it back to the same class as the original object

I'm new to JavaScript so bear with me if what I'm asking is not "how you do it in JavaScript". Advice on other approaches are welcome.
I have a class named State and I need need to serialize objects of that class using JSON.stringify(). The next step is to deserialize them back into an objects. However, my class uses setters and getters.
The problem that I'm facing is that after I deserialized those objects the setters and getters seem to be gone. I just cannot figure out how I can properly turn serialized objects back into objects of that class so that they behave exactly the same as objects that are created using new directly.
In another language I would cast those objects into State objects. I cannot find a JavaScript mechanism that seems to work that way.
The code looks as follows:
class State {
constructor(href) {
this.url = href;
}
set url(href) {
this._url = new URL(href);
this.demoParam = this._url.searchParams.get("demoParam");
}
get url() {
return this._url;
}
set demoParam(value) {
let param = parseInt(value, 10);
if(isNaN(param)) {
param = 2;
}
console.log("Setting 'demoParam' to value " + param);
this._demoParam = param;
}
get demoParam() {
return this._demoParam;
}
toJSON() {
let stateObject = {};
const prototypes = Object.getPrototypeOf(this);
for(const key of Object.getOwnPropertyNames(prototypes)) {
const descriptor = Object.getOwnPropertyDescriptor(prototypes, key);
if(descriptor && typeof descriptor.get === 'function') {
stateObject[key] = this[key];
}
}
return stateObject;
}
}
let originalState = new State(window.location.href);
let newState1 = JSON.parse(JSON.stringify(originalState));
newState1.demoParam = 12;
let newState2 = Object.create(State.prototype, Object.getOwnPropertyDescriptors(JSON.parse(JSON.stringify(originalState))));
newState2.demoParam = 13;
let newState3 = Object.assign(new State(window.location.href), JSON.parse(JSON.stringify(originalState)));
newState3.demoParam = 14;
let newState4 = Object.setPrototypeOf(JSON.parse(JSON.stringify(originalState)), State.prototype);
newState4.demoParam = 15;
I would expect that everytime I set the demoParam property of a newStateX object I'd see a console log message. However. I only see it twice, i.e. for every new State(window.location.href) statement.
I have used the answer of this question. However, it does not work as expected.
when you serialize an object you trigger the toString or the toJSON method of your class' instance and end up with just a "dumb" JSON representation of your enumerable attributes.
if you want to recreate an instance that behaves like it did prior to serialisation, you will need to set an extra key/value pair in your toJSON function like ___internalType: 'state' and then later use eg. a switch statement to recreate your specific class with the new MyClass(serializedData)and passing in your serialised instance. Within the constructor of your class, you set all the attributes you need and voilĂ , you have your "old" instance again
/edit: to clarify the reason why your console logs aren't showing up is because you are not recreating an instance of your class but just creating a new plain Object.
You can use Object.assign to copy plain object data into an "empty" new instance of the class along the lines of this code:
function cast(o) {
if (!o._cls) return o;
var _cls = eval(o._cls);
return Object.assign(new _cls(), o);
}
In JavaScript i personally like to avoid using classes for my data objects. TypeScript offers some better opportunities to solve this problem, one of these is TypedJSON:
https://github.com/JohnWeisz/TypedJSON

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

Access an unknown Firebase path

First of all will be easier if you check the Firebase realtime database image:
So with my code I create some "practicas" with an id (152648... in this case) and then, inside that object I create a list of "grupos" (groups). The problem comes here, to do this I use .push(), so Firebase creates a list inside that firebase main node, but the thing is that the 'key' it uses is random, so then, I want to access to the last step called "alumnos", but as I don't know the previous key I can't access there. I tried to use an ID to push the object but it adds the ID and then the key.
My code:
//don't take care about what is values[], grupoList[] and so on
//I just take values from a checkbox on the HTML and I send them to the 'grupo' value of the object 'practica'
addGroup(){
let y=0;
for(let i=0; i<this.values.length; i++){
if(this.values[i] == true){
this.grupoList[y] = this.profiles[i];
y++;
}
}
this.grupo.alumnos = this.grupoList;
this.practica.grupo = this.grupo;
this.practicaService.anyadirGrupos(this.practica);
this.navCtrl.setRoot(VerGruposPage, {'data': this.practica});
}
PracticaService:
//Here is where I work with firebase adding the 'grupo'
public anyadirGrupos(practica){
this.afDB.database.ref('practicas/' + practica.id + '/grupos/').push(practica.grupo);
}
//to access the node 'alumnos' (it doesn't work)
public getAlumnos(practica){
return this.afDB.list('practicas/' + practica.id +'/grupos/' + '../alumnos/')
}
Any idea to access to the last step without knowing the previous one?
You can have two different possible approaches:
1/ Write the "sub-grupos" without an extra key
Which means having a database structure like this:
- practicas
-idPracticas
-grupos
-alumnos
-0 ....
-1 ......
-anotherGroupName
-0 ....
-1 ......
To do that you should use set() instead of push()
2/ Keep your structure and loop over the different child nodes
db.ref('practicas/' + practica.id + '/grupos/').orderByKey().once('value').then(function(snapshot) {
console.log(snapshot.val());
snapshot.forEach(function(childSnapshot) {
console.log(childSnapshot.val());
console.log(childSnapshot.val().alumnos[0]);
console.log(childSnapshot.val().alumnos[1]);
});
});

Iterate over object properties

In my aurelia app, I need to display a list of elements. For performance reasons, these elements are stored as a javascript object instead of an array. That is what the object looks like :
var x = {
0 : {...},
3 : {...},
5 : {...},
}
Here is the part of the template where I display these elements :
<template>
<ul>
<li repeat.for="property of object | ObjectToArray">
${ property.text }
</li>
</ul>
</template>
As you can see, I'm currently using a value converter to be able to iterate over my object properties. The value converter simply converts the object to an array :
export class ObjectToArrayValueConverter {
toView(data : {[key : string] : any}) : any[] {
var array = [];
for (var key in data) {
array.push(data[key]);
}
return array;
}
}
This solution works very well as long as properties do not get removed or added to the object after the list has been rendered for the first time. The reason is that the value converter only gets called once.
In my case, however, I need my list to stay up to date whatever happens.
I know I could create a function that could be manually called every time the object is modified, but that would add some unwanted complexity in business logic.
Is there an aurelia functionality that coud help me achieve what I want ? I could not find any help in the docs. Thank you !
You can get the keys using Object.prototype.keys and call Array.prototype.map on it to get an array every time you want to list it out.
var obj={...}; //Object with many keys
var genArray = Object.keys(obj).map(function(key){return obj[key];})
//Using as a function
function getKeysAsArray(obj){
if(!obj){
return [];
}
return Object.keys(obj).map(function(key){return obj[key]});
}
There's an easier strategy, as shown in the docs:
export class KeysValueConverter {
toView(obj) {
return Reflect.ownKeys(obj);
}
}
Usage:
<p repeat.for="greeting of friends | keys">${greeting}, ${friends[greeting].name}!</p>

Categories