Sorting by numbers as String - AngularJS - angularjs-orderby

I would like to sort a table by 'numbers'. Those 'numbers' represent version-numbers of applications or dependencies of those. They look like "2.11.7" or "6.41.6". Because of that, i can't convert them into numbers. But when handling them like a String, angulars orderBy sorts them not properly. To be more specific:
2.11.5
2.11.5
2.11.7
2.11.7
2.11.5
will end up in (DESC)
2.11.7
2.11.7
2.11.5
2.11.5
2.11.7
or (ASC)
2.11.7
2.11.5
2.11.7
2.11.7
2.11.5
Now i got stuck. Any idea how to solve that?

Sorting can be accomplished by plain javascript. Here is an example adapted from the accepted answer for sorting of versions
As far as AngularJs is concerned, the order by also allows for a custom comparator to be provided. Here they have an example with a custom comparator to sort the values. All it requires is to use a function similar to the one above (or your own logic) to provide for a custom comparator. The flag for sorting direction can be provided via the expression itself by passing true/false.
Here is a plunker edited from their own example to provide for your values. Feel free to play around. This example will run properly in the Plunker environment. The code is listed here for documentation
(function(angular) {
'use strict';
angular.module('orderByExample4', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.friends = [
{ name: 'John', favoriteLetter: '2.11.5' },
{ name: 'Mary', favoriteLetter: '2.11.5' },
{ name: 'Mike', favoriteLetter: '2.11.7' },
{ name: 'Adam', favoriteLetter: '2.11.7' },
{ name: 'Julie', favoriteLetter: '2.11.5' }
];
function pad(version) {
var paddingString = "0000000000";
return version
.split('.')
.map(function(majorMinorVersion) {
var index = paddingString.length - majorMinorVersion.length;
return paddingString.substr(0, index) + majorMinorVersion;
})
.join('.');
}
$scope.localeSensitiveComparator = function(v1, v2) {
// If we don't get strings, just compare by index
if (v1.type !== 'string' || v2.type !== 'string') {
return (v1.index < v2.index) ? -1 : 1;
}
var a = pad(v1.value);
var b = pad(v2.value);
return a.localeCompare(b);
};
}]);
})(window.angular);

Related

Meteor collections: upsert w/ array

Forms of this question have been asked a few times, but I've been unable to find a solution:
I have a schema like this (simplified):
StatusObject = new SimpleSchema({
statusArray: [statusSchema]
});
where statusSchema is
{
topicId:{
type: String,
optional: true
},
someInfo:{
type: Number,
optional: true,
decimal: true
},
otherInfo:{
type: Number,
optional: true
}
}
I am trying to upsert - with the following meteor method code:
var upsertResult = BasicInfo.update({
userId: this.userId,
statusArray: {
$elemMatch: { topicId : newStatus.topicId }
}
}, {
$set: {
"statusArray.$.topicId": newStatus.topicId,
"statusArray.$.someInfo": newStatus.someInfo,
"statusArray.$.otherInfo": newStatus.otherInfo
}
}, {
multi: true,
upsert: true
});
But I keep getting an error: statusArray must be an array
I thought by adding the $, I was making sure it is recognized as an array? What am I missing?
It seems (after your clarification comments), that you want to find a document with particular userId and modify its statusArray array using one of these scenarios:
Update existing object with particular topicId value;
Add a new object if the array doens't have one with particular topicId value.
Unfortunately, you can't make it work using just one DB query, so it should be like this:
// try to update record
const updateResult = BasicInfo.update({
userId: this.userId,
'statusArray.topicId': newStatus.topicId
}, {
$set: {
"statusArray.$": newStatus
}
});
if (!updateResult) {
// insert new record to array or create new document
BasicInfo.update({
userId: this.userId
}, {
$push: {
statusArray: newStatus
},
$setOnInsert: {
// other needed fields
}
}, {
upsert: true
});
}
Your code is treating StatusArray as an object,
Before you do the upsert, build the status array first, assuming that your current value is currentRecord
newStatusArray = currentRecord.statusArray
newStatusArray.push({
topicId: newStatus.topicId,
someInfo : newStatus.someInfo,
otherInfo: newStatus.otherInfo
})
and in the upsert, simply refer to it like this
$set: { statusArray: newStatusArray}

Meteor nested publications

I have two collections A and B in Meteor. For A I have a publication where I filter out a range of documents in A. Now I want to create a publications for B where I publish all documents in B that have a field B.length matching A.length.
I have not been able to find any example where this is shown but I feel it must be a standard use case. How can this be done in Meteor?
This is a common pattern for reywood:publish-composite
import { publishComposite } from 'meteor/reywood:publish-composite';
publishComposite('parentChild', {
const query = ... // your filter
find() {
return A.find(query, { sort: { score: -1 }, limit: 10 });
},
children: [
{
find(a) {
return B.find({length: a.length });
}
}
]
});
This is a quite different pattern than serverTransform as on the client you end up with two collections, A and B, as opposed to a synthetic single collection A that has some fields of of B. The latter is more like a SQL JOIN.
Use serverTransform
Meteor.publishTransformed('pub', function() {
const filter = {};
return A.find(filter)
.serverTransform({
'B': function(doc) {
return B.find({
length: doc.length
}); //this will feed directly into miniMongo as if it was a seperate publication
}
})
});

How to flowtype cover this code in a function with dereferenced object fields

I'm new to flow, any trying to cover some of my functions, however often I have these snippets where I extract fields form an object based on some condition. But I'm struggling to cover them with flow.
const _join = function ( that: Array<Object>, by: string, index: number) {
that.forEach((thatOBJ: {[string]: any}, i: number)=>{
let obj: {[string]: any} = {};
for (let field: string in thatOBJ) {
if (field !== by) {
obj[`${index.toString()}_${field}`] = thatOBJ[field]; // NOT COVERED
} else {
obj[field] = thatOBJ[field]; // NOT COVERED
}
that[i] = obj;
}
});
}
The array that in this code is a data array so can really be in any format of mongodb data.
Any ideas on what to add to make the two lines which are not covered by flow covered?
Thanks.
A few notes...
This function has a "side effect" since you're mutating that rather than using a transformation and returning a new object.
Array<Object> is an Array of any, bounded by {}. There are no other guarantees.
If you care about modeling this functionality and statically typing them, you need to use unions (or |) to enumerate all the value possibilities.
It's not currently possible to model computed map keys in flow.
This is how I'd re-write your join function:
// #flow
function createIndexObject<T>(obj: { [string]: T }, by: string, index: number): { [string]: T } {
return Object.keys(obj).reduce((newObj, key) => {
if (key !== by) {
newObj[`${index}_${key}`] = newObj[key]
} else {
newObj[key] = obj[key]
}
return newObj
}, {})
}
// NO ERROR
const test1: { [string]: string | number } = createIndexObject({ foo: '', bar: 3 }, 'foo', 1)
// ERROR
const test2: { [string]: string | boolean } = createIndexObject({ foo: '', bar: 3 }, 'foo', 1)

Meteor collection2 type Object

I'm trying to create a field modifiedBy with type: Object (to Meteor users).
I see you can setup blackbox: true for a Custom Object, but if I want to setup to a specific Object say a Group (collection) field modifiedBy is the logged in user, any pointers/help is greatly appreciated.
Thanks
As far as I see it, you have two options:
Store user-ids there with type: String
Denormalize it as you proposed
Denormalize it as you proposed
To denormalize it you can do something like this inside your schema:
...
modifiedBy: {
type: object
}
'modifiedBy._id': {
type: String,
autoValue: function () {
return Meteor.userId()
}
}
'modifiedBy.username': {
type: String,
autoValue: function () {
return Meteor.user().username
}
}
...
As you pointed out, you'd want to update these properties when they change:
server-side
Meteor.users.find().observe({
changed: function (newDoc) {
var updateThese = SomeCollection.find({'modifiedBy.username': {$eq: newDoc._id}})
updateThese.forEach () {
SomeCollection.update(updateThis._id, {$set: {name: newDoc.profile.name}})
}
}
})
Store user-ids there with type: String
I'd recommend storing user-ids. It's cleaner but it doesn't perform as well as the other solution. Here's how you could do that:
...
modifiedBy: {
type: String
}
...
You could also easily write a Custom Validator for this. Now retrieving the Users is a bit more complicated. You could use a transform function to get the user objects.
SomeCollection = new Mongo.Collection('SomeCollection', {
transform: function (doc) {
doc.modifiedBy = Meteor.users.findOne(doc.modifiedBy)
return doc
}
})
But there's a catch: "Transforms are not applied for the callbacks of observeChanges or to cursors returned from publish functions."
This means that to retrieve the doc reactively you'll have to write an abstraction:
getSome = (function getSomeClosure (query) {
var allDep = new Tacker.Dependency
var allChanged = allDep.changed.bind(allDep)
SomeCollection.find(query).observe({
added: allChanged,
changed: allChanged,
removed: allChanged
})
return function getSome () {
allDep.depend()
return SomeCollection.find(query).fetch()
}
})

In Collection.find, how to format .limit, .sort, fieldlist, and variable column names

In non Meteor Server-Side calls to mongodb it is possible make the following chained-option call to the database
collection.find( { myField: { $gte: myOffset } ).limit( myLimit ).sort( { mySortField : 1 } );
where myField, myOffset, myLimit and mySortField may be resolved from elsewhere at run-time.
This pattern is very useful to create such a run-time generated generic query.
Meteor seems to insist on the non-chained options pattern of
collection.find( { { myField: { $gte: myOffset } }, { limit: myLimit, sort: { mySortField : 1 }} );
and I am having problems 'building up' a working Find Query as required above from js objects as described
in previous questions 17362401 and 10959729
Would anyone like to help?
Edited to show usage of variable:
I do it this way. You send two hashes, where the first is the where clause, and all else are peer level keys.
var locations;
var myfield = 'gps';
search = {
sureties: {
$in: sureties
}
}
search[myfield] = {
$near: this.gps,
$maxDistance: kilometers
};
locations = Agents.find(search, {
fields: {
name: 1,
phone: 1
},
limit: limit,
sort: { field1 : 1 }
}).fetch();
The chained pattern is not possible in Meteor, neither server side nor on the client. But the params pattern is as universal, you should be able to create any query you need with those params.

Resources