I tried to read sorted data from Cloud Firestore using OrderBy.
And Firestore returned data as Following Order:
AAA
BBB
aaa
bbb
Now, what I want is something like following:
AAA
aaa
BBB
bbb
I want this result only using OrderBy not by manual Sorting.
Is there any way to sort like this in Firestore?
Please provide me a solution for this.
Thanks in Advance.
Sorting and filtering in Cloud Firestore are case sensitive. There is no flag to make the sorting or filtering ignore the case.
The only way to achieve your use-case is to store the field twice.
Let's say your field that stores 'AAA' & 'aaa' is called myData. In your client code you'll need to store a second field called myData_insensitive where you store a case-insensitive copy of the data.
DocA:
-> myData = 'AAA'
-> myData_insensitive = 'AAA'
DocB:
-> myData = 'aaa'
-> myData_insensitive = 'AAA'
DocC:
-> myData = 'BBB'
-> myData_insensitive = 'BBB'
DocD:
-> myData = 'bbb'
-> myData_insensitive = 'BBB'
Now you can query and/or order by myData_insensitive, but display myData.
Two interesting thing about this area is:
With Unicode, removing case is more complex than just 'toLowerCase'
Different human languages will sort the same characters differently
Without creating separate indexes for each collation to solve (2), one implementation approach to deal with (1) is via case folding. If you want to only support modern browser versions, then the following gives you a JavaScript example:
caseFoldNormalize = function (s){
return s.normalize('NFKC').toLowerCase().toUpperCase().toLowerCase()
};
caseFoldDoc = function(doc, field_options) {
// Case fold desired document fields
if (field_options != null) {
for (var field in field_options) {
if (field_options.hasOwnProperty(field)) {
switch(field_options[field]) {
case 'case_fold':
if (doc.hasOwnProperty(field) && Object.prototype.toString.call(doc[field]) === "[object String]") {
doc[field.concat("_insensitive")] = caseFoldNormalize(doc[field])
}
break;
}
}
}
}
return doc;
}
var raw_document = {
name: "Los Angeles",
state: "CA",
country: "USA",
structure: 'Waſſerſchloß',
message: 'quıt quit' // Notice the different i's
};
var field_options = {
name: 'case_fold',
country: 'case_fold',
structure: 'case_fold',
message: 'case_fold'
}
var firestore_document = caseFoldDoc(raw_document, field_options);
db.collection("cities").doc("LA").set(firestore_document).then(function() {
console.log("Document successfully written!");
}).catch(function(error) {
console.error("Error writing document: ", error);
});
This will give you a document in Cloud Firestore with the following fields:
{
"name": "Los Angeles",
"state": "CA",
"country": "USA",
"structure": "Waſſerſchloß",
"message": "quıt quit",
"name_casefold": "los angeles",
"country_casefold": "usa",
"structure_casefold": "wasserschloss",
"message_casefold": "quit quit"
}
To handle older browser, you can see one solution in How do I make toLowerCase() and toUpperCase() consistent across browsers
You could also do it manually after you get your results:
docArray.sort((a, b) => {
if (a.myData.toLowerCase() < b.myData.toLowerCase()) {
return -1;
}
if (a.myData.toLowerCase() > b.myData.toLowerCase()) {
return 1;
}
return 0;
});
It's 2022. Firebase is awesome. Firestore is awesome. You don't need to stop using Firestore because of this limitation. Some things on the noSQL world are made on purpose just to speed up things.
What you can do in cases like this is just to create another property on the document, where you would parse the source value and lowercase it. Then you can use the parsed property to sort/order things.
Example:
interface ICompany {
dateAdded: Date
dateEdited: Date
description: string
id?: string
logo?: string
managers?: ICompanyManager
name: string
nameLowercase: string
website?: string
}
Here if you want to sort companies by name.
What you can do:
query(
companiesCollection,
orderBy('nameLowercase', 'asc'),
)
And when adding/editing:
const company = await addDoc(companiesCollection, {
name: data.name,
nameLowercase: data.name.toLowerCase(),
description: data.description,
website: data.website,
dateAdded: new Date(),
dateEdited: new Date(),
} as ICompany)
Voilà.
Related
I fot a bit of a complex issue on my hands that I would need your help.
I have multiple dropdown filters like location, category, subcategory e.t.c. My initial version is working where which ever combination is chosen, it will return the right result. My next challenge is to make the dropdowns with multi select. I am using firebase for this, trying to do a complex filtering on the client side.
I have list of objects like this that I get from the DB:
{
data1: {
status: true,
location: "New York",
category: "Food",
subcategory: "Pizza",
price: 24
}
data2: {
status: true,
location: "Chicago",
category: "Food",
subcategory: "Hot Dogs",
price: 5
}
data3: {
status: true,
location: "Miami",
category: "Food",
subcategory: "Taco",
price: 2
}
}
In order this to filter by multiple values I have the following:
filter() {
this.filteredData = _.filter(this.getListings, _.conforms(this.activeFilters)).filter(x =>
x.status === true);
}
Then, I have a method that sets the active filters based on a condition or show all from that property by removing that active filter, for example:
filterLocation() {
const location = this.filtersData.get('location').value;
this.locations = [];
for(let i = 0; i < location.length; i++) {
if (location[i].itemName === 'All') {
this.removeFilter('location');
}
if (location[i].itemName !== 'All') {
this.locations.push(location[i].itemName);
property = 'location';
this.activeFilters[property] = val => this.locations.some(x =>
val.includes(x));
this.filter();
}
}
}
And then, I have the same or similar method for category, subcategory e.t.c.
The thing I want to achieve now is for example I want to multi-select locations and categories or subcategories if needed but not to be limited to adding more properties in the future.
So, if I choose New York and Miami I want to get all items that contain those values but not to be limited if I want to filter by additional values like food, drink e.t.c.
This example works perfectly for filtering by single value, you can add up as many filters and combinations as you like, it will always return an array of objects matching what you selected from the dropdowns.
I am trying some stuff out, Ill update the question if I manage to solve it, in the meantime I appreciate all the help. Thanks.
I figured it out! I updated the filterLocation() method according to my solution. I am going to work on improving the filter now.
I've a collection of countries with country calling code in the country object. How can I find a country using calling code with a mobile number?
const countries = [
{
name: 'UAE',
callingCode: 971
},
{
name: 'USA',
callingCode: 1
},
{
name: 'UK',
callingCode: 44
}
];
const number = '971524500000'; // Input
How can I find country for the given mobile using regex in mongoose javascript;
[https://en.wikipedia.org/wiki/List_of_country_calling_codes][1]
Take a look at the link above on country calling codes, and specifically see the section "Tree List".
One solution would be to implement a schema in Mongo based on this tree in order to decode the country codes.
So, a table could be created to store Mongo documents containing a field "1x" "2x" "21x" etc (the Y axis in the Tree List table).
Each of these documents could contain an array of sub-documents from x=0 to x=9 (the x axis in the Tree List table). The sub-document can contain the country name/code you are looking for. You can use a direct index into the array in the Mongo document for an efficient lookup.
I think you'll find this to be a pretty efficient implementation and should cover all the bases.
If you can restructure your array to an object this would be the fastest
const countries =
{
971: 'UAE',
1: 'USA',
44: 'UK',
}
;
var code = 44;
console.log(countries[code]);
const countries = [
{
name: 'UAE',
callingCode: 971
},
{
name: 'USA',
callingCode: 1
},
{
name: 'UK',
callingCode: 44
}
];
var myFound =countries.filter(myFunc.bind(this,44));
function myFunc(code,element) {
if(element.callingCode == code){
return element;
}
}
console.log(myFound);
On MongoDB v 4.2 - you can use $let & $regexFind to do this :
db.collection.aggregate([
{
$match: {
$expr: {
$eq: [
{
$let: {
vars: {
value: {
$regexFind: {
input: "971524500000", // pass in as string
regex: { $toString: "$callingCode" }
}
}
},
in: "$$value.idx",
}
},
0
]
}
}
}
]);
Test : MongoDB-Playground
Explanation :
General Use-case :
In general regex searches - Will have an input which will be sub-string of actual string, Ex.:-
Actual string in DB : 'We can do it in mongo'
Input : mongo (/mongo/ - will work)
Your Use-case :
From above case as mongo exists in actual string/value of db field then you can get that document, But your case is different :
Actual string in DB : mongo
Input : 'We can do it in mongo'
This doesn't work that way, So using normal /We can do it in mongo/ isn't going help you here (Also doing few tricks with regex). So we need to make a trick to $regexFind operator. Unlike mongo documentation we need take 971524500000 into input field & regex as string value of callingCode field which is vice-versa to what's given in documentation.
So once we do that, We would get something like below :
{
"match" : "971", /** value of callingCode field */
"idx" : 0, /** Index of `971` in '971524500000' */
"captures" : []
},{
"match" : "1",
"idx" : 2,
"captures" : []
},
null /** 3rd doc no match */
As country code has to be at first of given number we need docs where "idx" : 0 - So we're using $let to get index of returned object & checking against 0 & eventually getting respective docs using $match.
Note :
There is something you need to look into, Just in case if you've docs like below :
{
"_id": ObjectId("5e8f67091aa1cc3d2158ade1"),
"name": "USA",
"callingCode": 1.0
},
{
"_id": ObjectId("5e8f67091aa1cc3d2158ade3"),
"name": "somecountry",
"callingCode": 197.0
}
& input is 1971524500000, then this query will bring both docs in result. This will be the case you need to check on. Anyhow I would suggest to try this query, rather than getting all documents for collection to the code & extract required o/p this might be better to do.
I'm looking for a way to give a user an option to define template literal. That template will be sent to backend and used with the data he provided.
Example scenario:
const dataSentToBackend = [
{
name: 'Adam',
country: 'Spain'
},{
name: 'Eve',
country: 'Germany'
}
]
User wants to generate two files that are like that: {name} from {country} (or anything else that let's him use tags how he like). So files in this example would be named as Adam from Spain and Eve from Germany.
The trick is that template needs to be preserved as a template so it can be used when data is actually being processed at the backend. So it can be used in a loop that names files one by one by the dataSentToBackend array.
So far I only found String.raw function (thanks to this page), but I didn't found it much helpful for me (or I didn't understood it).
Here's one way of doing it. I used the regular expression [a-z]+ for the property name which you could expand if you need to allow more characters (uppercase, underscores, numbers etc.)
const dataSentToBackend = [
{
name: 'Adam',
country: 'Spain'
}, {
name: 'Eve',
country: 'Germany'
}
]
const templateSentToBackend = "{name} from {country}";
serverSideReplace = (template, datas) =>
datas.map(data => template.replace(/\{([a-z]+)\}/g, (_, key) => data[key]));
console.log(serverSideReplace(templateSentToBackend, dataSentToBackend));
Assuming that your user is inputing is template string in an input field. You can simple save an escaped string in your database, and unescape the same before compiling it into template string.
And to actually use it back you can try something like this.
const templateString = "Hello ${this.name}!"; // replace it with unescaped template
const templateVars = {
name: "world"
}
const fillTemplate = function(templateString, templateVars){
return new Function("return `"+templateString +"`;").call(templateVars);
}
console.log(fillTemplate(templateString, templateVars));
But i would definitely say don't let user define templates and use it, unless you have a really solid sanitizing script.
I have documents that set up like this:
{ _id: 1, name: "A", timestamp: 1478115739, type: "report" }
{ _id: 2, name: "B", timestamp: 1478103721, type: "transmission" }
{ _id: 3, name: "C", timestamp: 1473114714, type: "report" }
I am trying to create a view that only returns the documents within a specific timestamp range. And I would love to be able to filter by type as well.
Here is my javascript call for the the data:
db.query('filters/timestamp_type', { startKey: 1378115739, endKey: 1478115740 })
.then(function(resp) {
//do stuff
})
I only know where to put the starting and ending timestamps. I'm having a hard time figuring out where I would say I only want the report's returned.
In addition, this is my map function for my filter, which is obviously not even close to being complete. I'm not sure how I even access the start and end key.
function (doc) {
if(type == "report" && startKey >= doc.timestamp && endKey <= doc.timestamp)
emit(doc._id, doc.name);
}
My question remains:
Where do I retrieve the start and end key's in my map function?
How can I add an addition type filter for only getting a specific type of report.
I know I might need to use a reduce function but it's going over my head. Here is the default reduce function but I'm not sure how it would work with the map function.
function (keys, values, rereduce) {
if (rereduce) {
return sum(values);
} else {
return values.length;
}
}
Thank you, any help or guidance would be appreciated.
Use a map function to get reports by a specific type-
function(doc) {
if(doc.type == "report") {
emit(doc.timestamp, doc);
}
}
when the view is queried, only documents with the type 'report' will be returned. If you need to support multiple types, you will have to create a new view for each type.
To query this view and specify the start & end timestamps, just add them to your query-
curl -XGET http://localhost:5984/<your-database>/_design/docs/_view/<your-view-name>?startkey="1478115739"&endkey="1478103721"
Reference
I'm currently looking into the Twitter-API - specifically the daily trends-API (http://search.twitter.com/trends/current.json).
Example return from Twitter
{
trends: {
2009-11-19 14:29:16: [
{
name: "#nottosayonfirstdate",
query: "#nottosayonfirstdate"
},
{
name: "New Moon",
query: ""New Moon""
},
{
name: "#justbecause",
query: "#justbecause"
}
]
}
}
I wonder how I can return the values within there without knowing the exact date at the time of the call, since it won't be possible to synchronise the client-time with the server-time.
Normally, I'd go for trends.something[1].name to get the name, but since the timestamp will change all the time, how can I get it when trends is no array?
you can use this:
for (var i in trends) {
alert (i); // "2009-11-19 14:29:16"
alert (trends[i][0].name); // "#nottosayonfirstdate"
}