Ember - dynamically pass array of dependent keys to computed property - javascript

I would like to create a component that takes a list of property names and then dynamically builds a computed property which has each value from the list as a dependent key.
{{buttons-list-component
title="Status"
propertyNames="active, expired, pending"
toggleProperty="toggleProperty"
active=active
expired=expired
pending=pending
}}
For the above example, I would like to to take the string passed as propertyNames, split it into an array and then have the optionsObject computed property watch each value in the array as if it was passed explicitly.
propertiesArray: function() {
return this.get('propertyNames').split(", ");
}.property('propertyNames'),
When the property active is updated, the code below will not run, because the propertyNames has not been updated, and therefore propertiesArray has not changed.
optionsObject: function() {
console.log("foo") // Does not run.
}.property('propertiesArray.#each'),
Is there a way to build the computed property, so that it would work in the same way as the code below, but without explicitly passing the string value of each property that optionsObject is dependant on?
optionsObject: function() {
console.log("bar") // Does run.
}.property('active', 'expired', 'pending''),

Your propertyNames is being passed a constant string propertyNames="active, expired, pending" -- so to update propertyNames when active changes, pass a computed property to propertyNames which is calculated based on the three properties.
propertyString: function() {
return `${active} ${active} ${active}`
}.property('active', 'expired', 'pending'),
so now when propertyString updates, your propertyNames will update, and that will trigger propertiesArray.
One more thing to note here, you need to observe propertiesArray.[] not #each -- #each is used when observing a child property.
Also, you should use the new computed property format - https://guides.emberjs.com/v2.18.0/object-model/computed-properties/#toc-toggle explains the two points I mention well

Related

How can I use variables in an update operation in mongodb that involves an array or another type of embedded or nested document?

The document in the target collection ("myCollection") has a field called "japanese2". This is an array (or an object) that contains an object that contains a property called "japanese2a". The value of this property is initially set to 0 (although it may change later). I want to change this value to 100 with a script in node.js (I am using the Express framework). The database is Mongodb, the free cloud version called Atlas.
If I do this without using variables, it works well:
db
.collection("myCollection")
.updateOne({username:username}, {"$set":{"japanese2.0.japanese2a":100}});
However, if I try this using variables for both the field name, "japanese2", and the name of the element/property in the array/object, "japanese2a", it fails. I have tried the below and other variations but I couldn't find a solution. I have researched stackoverflow for an answer but couldn't find a solution.
There is only one element/property in the array/object to start with.
var field = req.body.fieldName; //want to set this for the field name="japanese2"
var task = req.body.question; //want to set this for the name of the element="japanese2a"
var myField = [task];
field = myField;
var fieldPos = field[0];
.
.
.
db
.collection("myCollection")
.updateOne({username:username}, {"$set":{[fieldPos]:100}});
The above creates a new field called "japanese2a":100" but it does not appear in the array under the field called "japanese2", which is what I want.
I don't get any error messages when I do the above in the console (probably mostly due to the fact that I haven't put in error statements/functions to catch errors), but the output is not correct.
Another way of updating that I found from here:
https://www.codementor.io/#prasadsaya/working-with-arrays-in-mongodb-16s303gkd3
that involves using something like this:
db.posts.updateOne(
{ _id : ObjectId("5ec55af811ac5e2e2aafb2b9"), "comments.user": "Database Rebel" },
{ $set: { "comments.$.text": NEW_CONTENT } }
)
doesn't work for me, as I don't know if the initial value of the element in the array will always be a zero or some other constant. And there is only one element in the array initially. I can only use the username for the match part in the updating. I can't make an expression that is a perfect match for some element in the array.
The update solution from here: MongoDB update data in nested field
db.users.update ({_id: '123'}, { '$set': {"friends.0.emails.0.email" : '2222'} });
works, and that is what I used successfully to update in the first updating attempt above, but I don't know how to incorporate variables into the updating operation, specifically a variable for the field name ("japanese2") that holds the array or the object, and the name of the first and only element/property in the array/object ("japanese2a").
EDITED: I asked for a solution for an "array" originally, but either a field that acts an array (that holds elements that act as objects) or an object (that holds other objects as properties) works in my case, so I edited the question body and title. Also, the accepted solution works with the field as an entity that holds an array, or as an entity that contains an object inside it.
In either case, if there is already a field with the appropriate name, an object is created (if it didn't already exist) as an object of that object called "field" or the array called "field", and the object's property is set as according to the variables in the script.
If the field doesn't exist, it's created (as an object), and this object contains another object that contains the property ("task" as the name and "100" as the value for the name-value pair). So the newly created "field" object contains an object as its property. The property of this object is a name-value pair of "japanese2a" and "100".
If the script is run again, with a different "task" name (eg. "japanese2b"), another property is created, with the name of "japanese2b" and the value of "100". It is created within that same object that is "inside" the "field" object, so the object field.0 (the object within the "field" object) ends up looking like this: {japanese2a: 100, japanese2b: 100}. And the object called "field" looks like this: {{japanese2a: 100, japanese2b: 100}}.
I think something like
var field = req.body.fieldName // japanese2
var task = req.body.question; // japanese2a
var updateObj = { $set : {} };
updateObj.$set[field + '.0.' + task] = 100
db
.collection("myCollection")
.updateOne({username:username}, updateObj);
Might work

javascript: Update property value in Array() with index as parameter

Currently I've a react function that removes from a Array called rents the current rent perfect. The issue is that I need to update the rent row value called status and set property from 1 to 4 the code below works. I don't seem to get how to get the Index of rent to be able to update it.
removeItem (itemIndex) {
this.state.rents.splice(itemIndex, 1) // removes the element
this.setState({rents: this.state.rents}) // sets again the array without the value to the rent prop
console.log(itemIndex) // itemIndex
}
currently I'm adding this to the code to debug but get this error
console.log(this.state.rents[itemIndex].unique_key)
Stack Trace
TypeError: Cannot read property 'unique_key' of undefined
I need to be able to update the rent property value called status from 1 to 4 and setState again
To elaborate the comments, starting first with the most important:
Like #devserkan said, you should never mutate your state (and props), otherwise you start to see some really weird hard-to-make-sense bugs. When manipulating state, always create a copy of it. You can read more here.
Now for your question:
this.setState is asynchronous, so to get your state's updated value you should use a callback function
const rents = [...this.state.rents]; // create a copy
rents.splice(itemIndex, 1);
this.setState({ rents }, () => {
console.log(this.state.rents); // this will be up-to-date
});
console.log(this.state.rents); // this won't
Personally, I like using the filter method to remove items from the state and want to give an alternative solution. As we tried to explain in the comments and #Thiago Loddi's answer, you shouldn't mutate your state like this.
For arrays, use methods like map, filter, slice, concat to create new ones according to the situation. You can also use spread syntax to shallow copy your array. Then set your state using this new one. For objects, you can use Object.assign or spread syntax again to create new ones.
A warning, spread syntax and Object.assign creates shallow copies. If you mutate a nested property of this newly created object, you will mutate the original one. Just keep in mind, for this situation you need a deep copy or you should change the object again without mutating it somehow.
Here is the alternative solution with filter.
removeItem = itemIndex => {
const newRents = this.state.rents.filter((_, index) => index !== itemIndex);
this.setState({ rents: newRents });
};
If you want to log this new state, you can use a callback to setState but personally, I like to log the state in the render method. So here is one more alternative :)
render() {
console.log( this.state.rents );
...
}

Adding a property to an Angular object

I am running an Angular app that pulls its data from a Web API service. The API returns the objects as JSON and the Angular service (via $http.get() ) returns them to the controller as an array of objects. Pretty normal stuff.
What I'd like to do is add a property to each of the returned objects called "Selected". This property is purely for GUI purposes (is the object selected or not in the UI) and doesn't need to be persisted in any way. I figured the easiest thing to do was loop through the returned array of objects and just add it. So my code looks like this:
function getData() {
myService.getData()
.then(function(data) {
$scope.myData = data.results;
// Add a "Selected" property to each object
$.each($scope.myData, function(item) {
item.Selected = false;
});
}
When it gets to the line that says, "item.Selected = false" it throw an error message, saying "Cannot assign to read-only property Selected".
It is unclear to me why "Selected" is read-only? I didn't know if maybe Angular does some funky object processing when it reads data? What am I doing wrong here? Or should I be approaching this a completely different way?
Note (I'd like to avoid having to make Selected a part of the original object, as it's not representative of anything related to that object).
to add property to an object use underscorejs,
"each _.each(list, iteratee, [context]) Alias: forEach
Iterates over a list of elements, yielding each in turn to an iteratee function. The iteratee is bound to the context object, if one is passed. Each invocation of iteratee is called with three arguments: (element, index, list). If list is a JavaScript object, iteratee's arguments will be (value, key, list). Returns the list for chaining."
"extend _.extend(destination, *sources)
Copy all of the properties in the source objects over to the destination object, and return the destination object. It's in-order, so the last source will override properties of the same name in previous arguments."
$scope.myData = data.results;
// Add a "Selected" property to each object
_.each($scope.myData, function(ent) {
_.extend(ent, {
Selected : false
});
});
Your debugger screenshot actually gives you a more useful error message than what you posted (emphasis mine):
Cannot assign to read only property 'Selected' of 0
This shows that instead of the object, you're getting a number as your item variable (0 in this case). Assigning properties to primitives in strict mode throws this "Cannot assign to read-only property" error. (Without strict mode, it just fails silently.)
As JcT pointed out in a comment, this is because $.each calls the function with 2 params passed, the index first, the value second. See the documentation of $.each:
callback
Type: Function( Integer indexInArray, Object value )
So even though you named your parameter item, it received the value of the current index instead. This means your code can be fixed by just adding this missing first parameter to your callback, like this:
function getData() {
myService.getData()
.then(function(data) {
$scope.myData = data.results;
// Add a "Selected" property to each object
$.each($scope.myData, function(index, item) { //index was added here!
item.Selected = false;
});
}

Ember data custom arrays push and delete

I was implementing an array for my ember data property
DS.JSONTransforms.array = {
serialize: function(value) {
return Em.isNone(value) ? [] : value ;
},
deserialize: function(value) {
return Em.isNone(value) ? [] : value ;
}
};
And I created this jsbin for test to add and remove items to the array http://jsbin.com/avENazE/4/edit
If I check the console
model.get('pages').push('hi');
console.log(model.get('pages'));
I can see that the new items are corectly add to the array, but are not displayed on the view.
Also the count property is not updated and this error shows on the console on save the model
Uncaught TypeError: You must pass a resolver function as the sole argument to the promise constructor
The make the view be aware of changes of the representing model data you need data binding to work properly. To get data binding to work properly you need to use the correct functions that are sensible to bindings, so in the case of operations done to an array you can't just use vanilla push but instead pushObject or the counterpart removeObject, the same applies for setting a new value to a property, while dot notation will work it will not update you bindings therefore .set() and .get() need to be used etc.
So that said, here your working jsbin.
Hope it helps.

Can I tell what property a generic getter/setter applies to in its body?

The context (this) is naturally the object that the property is being requested on, but there are no arguments passed to the getter function. I'd like to be able to get the name of the property being requested without using closures but it's looking like that's the only way to do it.
Object.defineProperty( someObj, "prop1", { get: genericGetter } );
Object.defineProperty( someObj, "prop2", { get: genericGetter } );
function genericGetter() {
// i want to figure out whether this is called on prop1 or prop2
}
Can I tell what property a generic getter/setter applies to in its
body?
That's not how getters work. A property of an object can either have a value or a get function. If the property has a value, then reading the property:
var x = obj.prop;
returns that value. However, if a property has a get function instead, then reading that property triggers that function. So, you use getters if the value of a certain property has to be computed dynamically, or if you want to perform certain operations whenever the property is read.
For instance, .innerHTML requires a getter, because its value is not stored statically, but computed on access:
var html = div.innerHTML;
Here, the browser has to serialize the DOM structure that is contained within the div element.
So, if you want a .get() function that retrieves various properties (Backbone.js has such a function), then you're not looking for getters.
The simplest implementation of what you want would be:
someObj.getProp = function ( name ) {
// perform required tasks
return this[ name ];
};

Categories