CanJS parseModels and data coercion - javascript

I think I may have an odd use case here. I've got a Code model with code, title, description attributes. Users are documenting work (healthcare), they enter the code, say 7, and 7 always means that something particular happened, say "The patient was cured." Whatever, doesn't matter. Point is, I don't want to bother saving the title and description in every model, but I want to be able to pull them for displaying.
So the API delivers an array of codes like [ 1, 13, "A4" ]. I'm trying to use both can.Model.parseModel and can.Map.define to coerce that array into Code models, but I'm having a hard time.
Why is parseModel, parseModels never called in this example? fiddle
Code = can.Model.extend({
parseModel: function(data) {
// return { code:data }
console.log('Never hit!');
},
parseModels: function() {
// ...
console.log('Never hit!');
}
},{
_title: can.compute(function() {
// return title from cached lookup
})
});
Model = can.Model.extend({
findAll: 'GET /Models'
},{
define: {
Codes: {
Type: Code.List
}
}
});
can.fixture('GET /Models', function() {
return [
{ Codes: [1,2,3] }, // I want to turn each number into an object
{ Codes: [4,5,6] },
{ Codes: [7,8,9] }
];
});
Model.findAll({});

.parseModels is only called during retrieval of CRUD service data.
To make your example work, you have to make a Model.parseModel convert each Code array to an an array of objects.
Alternately, you could change Model's define.Codes.Type to something like:
Codes: {
type: function(newVal){
if(newVal instanceof Code.List) {
return newVal
} else {
return new Code.List( newVal.map(function(num){ return {value: num}}) )
}
}
}

Related

Revert changes to array in Vue

I'm trying to have a component which can change some elements in it. In reality, a change will be like swapping the object in a given position. I did some POC and tried to do the reverting method to be able to leave it how it was before.
export default {
name: 'Landing',
data () {
return {
items: [
{
id: 1,
category: 'Something something'
},
{
id: 2,
category: 'Something something'
}
]
};
},
created() {
this.originals = this.items.slice(0);
},
methods: {
change() {
this.items[0].category = 'Changed';
},
revert() {
// ??
}
}
};
I've tried a couple of things especially after reading this: https://vuejs.org/2016/02/06/common-gotchas/#Why-isn%E2%80%99t-the-DOM-updating
while (this.snacks.length) {
this.items.pop();
}
this.originals.slice(0).forEach(o => this.items.push(o));
But it doesn't work. If I delete the pushing part, I get an empty list correctly but if I try somehow to push it back, it won't work.
What am I missing here?
If you give a working fiddle I can show you what happened.
Basically, because you are modifying the same array object. this.originals refers to the same array as this.items
Slice returns a shallow copy of the object. You should either take a deep copy or your revert should be the one initializing the object.
this.items.pop();
will remove the items from this.originals as well.

Manipulate ajax response

I have a ajax post method. I get an object from the backend
$.ajax({
type: "POST",
url: URL_one,
data: submitData
}).then(function (response) {
console.log("Ajax response", response);
});
and when i do a console.log(response); inside the post method, i see the following data.
>Object{Info:Array[200]}
>Info:Array[200]
>[0-99]
>0:Object
name:'Ashley'
on_pay: true
valid:"0"
>[100-199]
So each array has objects like one mentioned above with name, on_pay and valid. I want to do the following
Since all on_pay values are true in my case, i need to convert it to false. Also valid has string of 0. I need to put all values as blank instead of 0.
Is it possible to do ?? Can someone please shed some light on these.
Considering the JSON structure that you show, following should work to change the on_pay value:
response.Info.forEach(function(item){
item.on_pay = false;
});
If I'm understanding your question correctly, response is an array of items. You want to keep those items intact, but turn the on_pay property false and valid to an empty string.
You can use Array::map() to transform each item.
/*jslint node:true*/
"use strict";
// I am assuming your response looks something like this
var response = {
Info: [
{
name: "Ashley",
on_pay: true,
valid: "0"
},
{
name: "Jim",
on_pay: true,
valid: "0"
},
{
name: "John",
on_pay: true,
valid: "0"
}
]
};
// This will produce a new variable that will hold the transformed Info array
var fixedResponseInfo = response.Info.map(function (item) {
item.on_pay = false;
item.valid = "";
return item;
});
// This will edit the response.Info array in place
response.Info.forEach(function (item) {
item.on_pay = false;
item.valid = "";
});
console.log(fixedResponseInfo);
console.log(response);
This will keep your original response variable and produce a new variable fixedResponseInfo that contains the transformed array. If you don't care whether data in response is changed, you can use Array::forEach() to iterate instead.

Aggregating an object in AngularJS

So, I'm experimenting with AngularJS, and, as an exercise, figured I would make a simple application using the Steam API. I have made a simple Spring Boot Rest service, which provides a reverse proxy service for the Steam API, in such a way that certain calls can be forwarded. At this time there are two actions:
/user/ provides a list of steam id's.
/user/:id/games provides the output of the following api:
http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?key=MY_STEAM_KEY&steamid=STEAM_ID&format=json
which returns an answer in the following format:
{
"response":{
"game_count":3,
"games":[
{
"appid":70000,
"playtime_forever":0
},
{
"appid":550,
"playtime_forever":0
},
{
"appid":274900,
"playtime_forever":0
}
]
}
}
What I want to achieve is to extract the games array from this json object, and append it to the correct user. And I want to do this for all users. I have achieved something close to what I want using the $resource object, by defining the following factories:
angular.module("SAM.Resources", [])
.factory("UserList", ["$resource",
function ($resource) {
return $resource('/user');
}])
.factory("GamesList", ["$resource",
function ($resource) {
return $resource('/user/:id/games', {
id: '#id'
});
}
]);
And then in my controller use the following:
UserList.query(function(response){
$scope.users = response ? response : [];
for(index=0; index < $scope.users.length; ++index){
user = $scope.users[index];
$scope.users[index].games = GamesList.get({id:user.id});
}
});
This is close to what I want, however, it returns something of the format:
{
"id": "76561198119953061",
"name": "Yuri",
"games": {
"response": {
"game_count": 3,
"games": [
{
"appid": 70000,
"playtime_forever": 0
},
{
"appid": 550,
"playtime_forever": 0
},
{
"appid": 274900,
"playtime_forever": 0
}
]
}
}
}
And I don't want the games.response.games construction. I have tried to change it to:
$scope.users[index].games = GamesList.get({id:user.id}).response.games;
which fails, seems logical, as it is a promise, and doesn't immediately contain the response object.
I've also tried to use something like
GamesList.get({id:user.id}), function(response){
angular.extend(user, response);
});
Which does indeed append the response to the user object, only the user object is always the last value in the array by the time the promise resolves.
So basically my question comes down to: How can I extend my User object with the Games list?
You need to change your code around a bit:
UserList.query(function(response){
$scope.users = response ? response : [];
for(index=0; index < $scope.users.length; ++index){
user = $scope.users[index];
(function(index, id){
GamesList.get({id: id}, function(response){ // Get the games from the response
$scope.users[index].games = response.response.games;
}));
})(index, user.id)
}
});
In the for loop, user keeps changing value. By the time the first GameList.get has a value returned, your loop will be at the last user already.
Wrapping that in an IIFE separates those variables in a separate scope.
for(index=0; index < $scope.users.length; ++index){
user = $scope.users[index];
$scope.users[index].games = GamesList.get({id:user.id}, function(response){
angular.extend(user, response);
}));
}
When you do that, the user variable will change at every step. But the anonymous callback will be executed later. So only the last user is used.
You can fix that by using an anonymous function as a scope with forEach :
$scope.users.forEach(function(user) {
$scope.users[index].games = GamesList.get({id:user.id}, function(response){
angular.extend(user, response);
}));
});
If you want to avoid the user.games.response.games, you need to merge the objects in a different way.
$scope.users.forEach(function(user) {
$scope.users[index].games = GamesList.get({id:user.id}, function(response){
user.games = response.games;
user.games_count = response.games_count;
}));
});

MongoDB updated object with item remove not saving

I'm using Angular Fullstack for an web app.
I'm posting my data by $http.post() my object:
{ title: "Some title", tags: ["tag1", "tag2", "tag3"] }
When I edit my object and try to $http.put() for example:
{ title: "Some title", tags: ["tag1"] }
In console I get HTTP PUT 200 but when I refresh the page I still recive the object with all 3 tags.
This is how I save in the MongoDB:
exports.update = function(req, res) {
if (req.body._id) {
delete req.body._id;
}
Question.findByIdAsync(req.params.id)
.then(handleEntityNotFound(res))
.then(saveUpdates(req.body))
.then(responseWithResult(res))
.catch(handleError(res));
};
function saveUpdates(updates) {
return function(entity) {
var data = _.merge(entity.toJSON(), updates);
var updated = _.extend(entity, data);
return updated.saveAsync()
.spread(function(updated) {
return updated;
});
};
}
Can someone explain how to save the object with removed items?
What I'm doing wrong?
This is pretty bad practice to use things like _.merge or _.extend in client ( meaning your nodejs client to database and not browser ) code after retrieving from the database. Also notably _.merge is the problem here as it is not going to "take away" things, but rather "augment" what is already there with the information you have provided. Not what you want here, but there is also a better way.
You should simply using "atomic operators" like $set to do this instead:
Question.findByIdAndUpdateAsync(
req.params.id,
{ "$set": { "tags": req.body.tags } },
{ "new": true }
)
.then(function(result) {
// deal with returned result
});
You also really should be targeting your endpoints and not having a "generic" object write. So the obove would be specically targeted at "PUT" for related "tags" only and not touch other fields in the object.
If you really must throw a whole object at it and expect an update from all the content, then use a helper to fix the update statement correctly:
function dotNotate(obj,target,prefix) {
target = target || {},
prefix = prefix || "";
Object.keys(obj).forEach(function(key) {
if ( typeof(obj[key]) === "object" ) {
dotNotate(obj[key],target,prefix + key + ".");
} else {
return target[prefix + key] = obj[key];
}
});
return target;
}
var update = { "$set": dotNotate(req.body) };
Question.findByIdAndUpdateAsync(
req.params.id,
update,
{ "new": true }
)
.then(function(result) {
// deal with returned result
});
Which will correctly structure not matter what the object you throw at it.
Though in this case then probably just directly is good enough:
Question.findByIdAndUpdateAsync(
req.params.id,
{ "$set": req.body },
{ "new": true }
)
.then(function(result) {
// deal with returned result
});
There are other approaches with atomic operators that you could also fit into your logic for handling. But it is best considered that you do these per element, being at least root document properties and things like arrays treated separately as a child.
All the atomic operations interact with the document "in the database" and "as is at modification". Pulling data from the database, modifiying it, then saving back offers no such guarnatees that the data has not already been changed and that you just may be overwriting other changes already comitted.
I truth your "browser client" should have been aware that the "tags" array had the other two entries and then your "modify request" should simply be to $pull the entries to be removed from the array, like so:
Question.findByIdAndUpdateAsync(
req.params.id,
{ "$pull": { "tags": { "$in": ["tag2", "tag3"] } } },
{ "new": true }
)
.then(function(result) {
// deal with returned result
});
And then, "regardless" of the current state of the document on the server when modified, those changes would be the only ones made. So if something else modified at added "tag4", and the client had yet to get the noficiation of such a change before the modification was sent, then the return response would include that as well and everything would be in sync.
Learn the update modifiers of MongoDB, as they will serve you well.

Underscorejs filter on collection of object 2 levels deep

I have a an array of objects, that contains and array of objects, that contains an array of objects.
What I want to do is to filter on a property in the last array of objects.
Here is an example dataset
var sampleData=[
{
"name":"Cooking",
"shelve":[
{
"name":"Shelve 1",
"drawers":[
{
"Name":"Left"
},
{
"Name":"Middle"
}
]
},
{
"name":"Shelve 2",
"drawers":[
{
"Name":"Middle side"
},
{
"Name":"Left"
},
{
"Name":"Up"
}
]
}
]
},
{
"name":"DBs",
"shelves":[
{
"name":"Shelve 3",
"drawers":[
{
"Name":"asdfasdf"
},
{
"Name":"New Test 12"
}
]
}
]
}
];
Basically what I would like to do is to be able to get back the objects where drawer.name contains a substring. I would like for it to return the full object graph so if I searched for Middle I would expect to get back
var sampleData=[
{
"name":"Cooking",
"shelve":[
{
"name":"Shelve 1",
"drawers":[
{
"Name":"Middle"
}
]
},
{
"name":"Shelve 2",
"drawers":[
{
"Name":"Middle side"
}
]
}
]
}
];
I was hoping that using underscore would help and I initially just tried to nest filters but that did not work.
I tried the following and while found would evaluate to true when it should it would not filter as I hoped.
var secs = _.filter(sampleData, function(section) {
_.filter(section.shelve, function(shelve) {
_.filter(shelve.drawers, function(drawer) {
var found = drawer.Name.indexOf('Middle') !== -1;
if(found) {
var xa = 'found it!!';
}
return drawer.Name.indexOf('Middle') !== -1;
});
});
});
Here is a quick jsfiddler example. http://jsfiddle.net/cnalk/GtfNj/3/
Unfortunately, a basic array filter like _.filter doesn't provide a way to do two-step filtering (first, by values of an array; then by how many values passed), so you need to write your own filter function for each layer, such as:
function filter_shelf( shelf ) {
var filtered = _.omit( shelf, 'drawers' );
filtered.drawers = _.filter( shelf.drawers, filter_drawer );
return filtered.drawers.length && filtered;
}
Complete fiddle: http://jsfiddle.net/GtfNj/5/
Each step considers the property it needs to filter, filters it by the step "below" it, then returns a filtered copy of itself or false if the property ended up empty from the filter. The step "above" it then filters based on that result. And so on...
Ps. your sample data uses shelve or shelves inconsistently.

Categories

Resources