How to get the total count in observableArray - knockout-2.0

I have this list of datas:
var data = [
{"AMSnack": true, "PMSnack": false},
{"AMSnack": true, "PMSnack": false},
{"AMSnack": true, "PMSnack": false},
{"AMSnack": fale, "PMSnack": true},
{"AMSnack": true, "PMSnack": false}];
I want to get the total of AMSnack that is true and the PMSnack that is also true.
How can I get the totals inside of the observableArray?
here's the full code http://jsfiddle.net/comfreakph/gtPGz/5/

You need to define your GetCount as ko.computed (see doc) where you can use the ko.utils.arrayFilter (see doc) function to filter your array.
To get the count you just need to return the length of the filtered array:
self.GetCount = ko.computed(function(){
return ko.utils.arrayFilter(self.Data(), function(item) {
//return item.AMSnack || item.PMSnack; // AMSnack or PMSnack true
return item.AMSnack && item.PMSnack; // AMSnack and PMSnack true
}).length;
});
Demo JSFiddle.

Related

Mongoose get and set not working on Array Fields

I have a collection where I have to store array of userNames which needs to be encrypted(Don't ask why!).
const UserNameSchema = mongoose.Schema({
// Other Fields...
// ...
"userNames": {
type: Array,
set: encryptUserDetails,
get: decryptUserDetails
},
}, {
toObject: { getters: true, setters: true },
toJSON: { getters: true, setters: true },
runSettersOnQuery: true
});
But when I try to write any data into it, it is not calling the set method.
Here is how I am trying to write data in to the DB.
let addUserNameResult = await UserNameModel.update({// find condition omitted},
{
$addToSet: {
"userNames": "new_user_name"
}
}, {
upsert: true,
new: true
});
But this Query still stores the data in plain text without calling my set method.
Any help is appreciated. Thank You.
Edit: encryptUserDetails method added.
let encryptUserDetails = (plainText) => {
// Some encryption algorithm ... (No issues here, It works).
// . . .
return encryptedText;
}

Push into a List and Pop Conditionally in Mongoose and MongoDB

I am creating a list of scores for a user in mongoDB by adding a new score 1 at a time and sorting the list. I want to remove the lowest score when the list grows larger than 5 elements.
The reason for this is because I want to store the top 5 scores of the user.
What would be the best way to do this? Is there a way to make the whole thing an atomic operation?
My code is below. I'm using NodeJS with Mongoose and MongoDB.
const maxScoresToStore = 5
var scoreEntrySchema = new Schema({
score: Number,
when: { type: Date, default: Date.now }
})
var scoreSchema = new Schema({
_userid: { type: Schema.Types.ObjectId, ref: 'Users' },
username: {type: String, index:{unique: true}},
scores: [scoreEntrySchema]
})
const scoreModel = mongoose.model("Scores", scoreSchema)
exports.addUserScore = (uid, uname, score) => {
var query = {_userid:uid, username:uname},
update = { $push : {"scores" : {$each: [{"score": score}], $sort: {"score":-1}}} }, // sorts in descending order after pushing
options = { upsert: true, new: true, setDefaultsOnInsert: true };
scoreModel.findOneAndUpdate(query, update, options).then(
(result)=>{
if(result.scores.length > maxScoresToStore)
{
// ToDo:
// result.update({$pop: {"scores" : 1 }}) // pops the last element of the list
}
}
)
}
You can use $slice operator, And your query looks like:
let score = await scoreModel.findOneAndUpdate({ _userid: uid, username: uname },
{
$push: {
scores: {
$each: [{ score: score }],
$sort: { score: -1 },
$slice: maxScoresToStore
}
}
},
{
upsert: true,
new: true,
setDefaultsOnInsert: true,
multi: true
});
[DO VOTE TO THIS ANSWER, IF ITS HELPFUL TO YOU]
You can add slice option to your update option:
update = {
$push: {
scores: { $each: [{ score: score }], $sort: { score: -1 }, $slice: maxScoresToStore }
}
}
Here is the full method code written in async/await style:
exports.addUserScore = async (uid, uname, score) => {
const query = { _userid: uid, username: uname };
const update = {
$push: {
scores: {
$each: [{ score: score }],
$sort: { score: -1 },
$slice: maxScoresToStore
}
}
};
const options = {
upsert: true,
new: true,
setDefaultsOnInsert: true,
multi: true
};
try {
let score = await scoreModel.findOneAndUpdate(query, update, options);
if (!score) res.send(404).send("Score not found");
res.send("Everything is ok");
} catch (err) {
console.log(err);
res.status(500).send("Something went wrong");
}
};
I'm not certain If this would help, but it might work
scoreModel.update(
{ "scores.5": { "$exists": 1 } },
{ "$pop": { "scores": 1 } },
{ "multi": true }
)
As you are already sorting by descending, you can check if the array length is greater than 5 by using scores.5, If this returns true then you can pop the last element using $pop.
If $exists return false then it will skip the query. you can run this update after .then() and you won't have to use if condition.
But keep in mind $pop will only remove 1 element.

Sorting with Mongo

I am trying to sort my MongoDB data. I have a rentals Schema shown below which includes a "featured" item. IF the featured item is set to true (users can do this on their end) I want the featured item to be shown above the rest of the Rentals. In other words I need to sort the mongo data that has featured = true to the top.
How would I change my code below to do this?
Here is my rental Schema:
var rentalsSchema = new mongoose.Schema({
featured: {
type: Boolean,
default: false
},
Title: String,
});
Here is what I've tried so far (and is not working.)
router.get('/rentals', function(req, res) {
db.collection('rentals').find().sort({
featured: 1
}).toArray(function(err, items) {
});
});
Change .sort({ featured: 1 }) to .sort({ featured: -1 }). You want those with featured value of true to come before those with false false.
It looks like default sorting by boolean fields goes from false to true (maybe because of 0 and 1 numerical values, and default ascending order). To demonstrate this, open the Chrome Dev Tools console, enter [true, false, false, true].sort(), and the result is [false, false, true, true].

Mongoose .dot Notation equivalent for $and + $in + $or?

Am looking to '.find' all docs, '.where' the username is '.in' the to: array and either upVoted: true '.or' noVote: true, all sorted by rank descending
Here's an example doc structure:
to: [String],
voting: {
upVoted: Boolean,
noVote: Boolean,
downVoted: Boolean
},
rank: Number
This query is working, but how would this be written in Mongoose .dot notation:
Story.find({
$and: [
{ to: { $in: [ 'user1' ] } },
{ $or: [{ 'voting.upVote': true }, { 'voting.noVote': true }] }
]
}, function (err, stories) {
FYI am working on the correct syntax for sorting this
Your original query can be made simpler, because you don't need $and (it's implicit in MongoDB). Also, if you are looking for only one user from the ṫo array, then you also don't need the $in operator.
{
to: 'user1',
$or: [{ 'voting.upVote': true }, { 'voting.noVote': true }]
}
Using Mongoose query API:
var query = Story.find();
query.where('to', 'user1');
query.or({ 'voting.upVote': true }, { 'voting.noVote': true });
query.exec(function (err, doc) {
if (err) ...
});
Or if you're looking for more than one user then replace:
query.where('to', 'user1');
with:
query.where('to').in(['user1', 'user2']);

MongoDB/Mongoose aggregation or $or not working as expected?

Have some checkbox buttons in html that I would like to filter the results via the API on.click, over AJAX. There are 3 kinds of results: upVoted, noVote, downVoted. Subject to which of the checkboxes are checked, a combination of upVoted, noVote, and downVoted docs would appear to the user.
The ajax calls the URL of the API, and the data reaches the database call correctly after running through the following loop in the node.js API, which loops through the array of the strings ["upVoted", "noVote", "downVoted"] (or any combination thereof) and assigns the boolean value of true to the respective variable:
var upVoted = false;
var noVote = false;
var downVoted = false;
storyFiltering.forEach(storyFiltering);
function storyFiltering(element, index, array) {
if (element == 'upVoted') {upVoted = true}
else if (element == 'noVote') {noVote = true}
else if (element == 'downVoted') {downVoted = true}
}
console.log('upVoted: ' + upVoted + ', noVote: ' + noVote + ', downVoted: ' + downVoted);
These variables then match the Booleans of the voting in each of the documents.
Here's the database structure (with voting only one Boolean is ever true at once, the rest are false):
to: ["user1"],
voting: {
upVoted: true,
noVote: false,
downVoted: false
},
rank: 4
to: ["user2"],
voting: {
upVoted: true,
noVote: false,
downVoted: false
},
rank: 2
to: ["user1", "user2"],
voting: {
upVoted: true,
noVote: false,
downVoted: false
},
rank: 1
to: ["user1", "user2"],
voting: {
upVoted: false,
noVote: true,
downVoted: false
},
rank: 5
to: ["user1"],
voting: {
upVoted: false,
noVote: false,
downVoted: true
},
rank: 3
The result of the find (or aggregate) would be a callback of data filtered to match the toggled voting booleans..and then sorting them by descending 'rank'.
Toggling for all of the upVoted and noVote documents for user1 would return:
to: ["user1", "user2"],
voting: {
upVoted: true,
noVote: false,
downVoted: false
},
rank: 1
to: ["user1"],
voting: {
upVoted: true,
noVote: false,
downVoted: false
},
rank: 4
to: ["user1", "user2"],
voting: {
upVoted: false,
noVote: true,
downVoted: false
},
rank: 5
..whereas toggling for only the upVote documents for user1 would return:
to: ["user1", "user2"],
voting: {
upVoted: true,
noVote: false,
downVoted: false
},
rank: 1
to: ["user1"],
voting: {
upVoted: true,
noVote: false,
downVoted: false
},
rank: 4
..and in the same pattern, toggling for all of the upVoted + noVote + downVoted documents for User 1 would return all of the documents that include User 1, sorted by rank (1, 3, 4, 5)..while, constrastly, toggling for all of the downVoted documents for User 2 would return zero documents.
Here is your basic problem with your approach as I see it. What you are trying to do is have interaction with a user interface that "selects" certain items that you want to appear in your query results. That is all fine, but clearly you seem to be looking for "one query to rule them all" and this is where you come unstuck.
Taking your first example, your query for all "upvote" and "novote" documents comes out like this. Also stating away from aggregate right now, but the same applies as a query to a $match pipeline:
db.collection.find({
"to": { "$in": ["user1"] },
"$or": [
{ "voting.upVoted": true },
{ "voting.noVote": true }
]
})
That returns your expected results as the $or considers that "either" of those conditions could evaluate to true in order to satisfy the match. As explained before, all queries in MongoDB are implicitly an "and" query, so there is no need for the additional operator here.
In your next example, you really only need one of your criteria to be true, so we could remove the $or but I'll leave it here for the explanation later.
db.collection.find({
"to": { "$in": ["user1"] },
"$or": [
{ "voting.upVoted": true }
]
})
Again that matches your expected result as this returns both where the "user1" is present "and" that the "upVoted" value is true.
But now onto your interface interaction and how to handle that. Let us say that you have input coming from your interface that looks something like this:
{
"to": "user1",
"voting": {
"upVoted": true,
"downVoted": false,
"noVote": true
}
}
That confirms your basic selections in your user interface that were made for the query. In order to "process" this into your desired query then consider the following JavaScript code, to translate into whatever language you want to implement in. I'm just going to assume that structure as shown to be in a variable known as request:
var query = {
"to": { "$in": [] },
"$or": []
};
for ( k in request ) {
if ( k == "to" )
query.to["$in"].push( request.to );
if ( k == "voting" ) {
for ( v in request[k] ) {
if ( request[k][v] ) { // if that value is `true`
var obj = {};
obj[ k + "." + v] = true;
query["$or"].push( obj );
}
}
}
}
Running the code over that data the resulting query object now looks like:
{
"to" : {
"$in" : [
"user1"
]
},
"$or" : [
{
"voting.upVoted" : true
},
{
"voting.noVote" : true
}
]
}
Which is the same as the query we issued first, and changing your selections in request to look the way it would in the second example:
{
"to": "user1",
"voting": {
"upVoted": true,
"downVoted": false,
"noVote": false
}
}
The the resulting query object is:
{
"to" : {
"$in" : [
"user1"
]
},
"$or" : [
{
"voting.upVoted" : true
}
]
}
All that is left to do is issue the query which uses the object as it's argument:
db.collection.find( query )
So that is how you interact with data from your interface. You build dynamically, rather than try to throw all of the values you got in the response as parameters in the query.

Resources