Firebase 3.0 sort by value - sorting

I am using firebase 3.0, I see examples for the firebase 2.x versions, however they seem to not work in 3.0. I have a simple structure that I want to have returned to me sorted by value.
{
"Regions" : {
"All" : 0,
"Eastern & Southern Africa" : 1,
"Global" : 6,
"Himalayas" : 2,
"Mekong" : 3,
"Mesoamerica" : 5,
"West Africa" : 4
}
}
The code I am using returns the json, however it is not sorted by value, it is sorted alphabetically.
var config = {
apiKey: "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
authDomain: "servir-activity-database.firebaseapp.com",
databaseURL: "https://servir-activity-database.firebaseio.com",
storageBucket: "",
};
firebase.initializeApp(config);
var theRegions;
firebase.database().ref('Regions/').orderByChild('value').on('value', function (regions) {
theRegions = regions.val();
loadRegions(theRegions);
});
function loadRegions(which)
{
$.each(which, function (i, value) {
$('#ddlRegions').append($('<option>').text(i).attr('value', value));
});
}
I realize that I can do the sort on the client side, however this seems like a simple thing that can be returned sorted and I am probably missing something really simple.

This is a really long winded answer and is way more than is needed to answer the question, but should probably be considered:
One of the life lessons Firebase teaches us is that disassociating key names from the data is usually a good idea. I have (several times) coded myself into a corner because I used what I thought was a static key name, only to find it needed to be changed later. So let me expound on that briefly:
For example, say you have a users node
Frank_Jones: Madagascar
Leroy_Jenkins: UBRS
and suppose Frank decides that he no longer wants to be using the name 'Frank', but instead wants to be called 'Puf'
In that case every single node in your entire database would then have to be updated to refer back to the newly called Puf_Jones node. Ugh.
To avoid this issue, let Firebase generate your node names with childByAutoId, and let your values be children. This is a 'randomly' created node name that is guaranteed to be discreet.
A better Firebase Structure for your data would be
Regions
-Ykjoas99joksjk
region_name: "Himalayas"
rank: 2
-Jlioksjnfjp987
region_name: "Eastern & Southern Africa"
rank: 1
-J989j99ajskmds
region_name: "West Africa"
rank: 4
The -Ykjoas99joksjk etc node names are created by Firebase.
As you can see we now have a name and rank for each region. Let's say you wanted to add average summer temperature:
-Ykjoas99joksjk
region_name: "Himalayas"
rank: 2
temp: 77F
This allows your Firebase structure to be elastic depending on your needs.
(some Swift code to follow but you'll get the idea)
To get the region names in order
let ref = myRootRef.childByAppendingPath("Regions")
ref.queryOrderedByChild("region_name").observeEventType(
.ChildAdded, withBlock : { snapshot in
print(snapshot)
})
To get the two highest ranks
ref.queryOrderedByChild("rank").queryLimitedToLast(2).observeEventType(
.ChildAdded, withBlock : { snapshot in
print(snapshot)
})
and the Swift answer to your question with your existing Firebase Structure:
regionsRef.queryOrderedByValue().observeEventType(.ChildAdded, withBlock: {
snapshot in
print(snapshot)
})
Here's my lol translation that needs to be fixed...
firebase.database().ref('Regions').orderByValue.on('value', function (snapshot) {
loadRegions(snapshot.val());
});

Thank you #Jay, your answer was just long winded enough! I had to change the code part from swift to javascript but i followed the autoid for the key method as you described. Here is the js i used to process the return data.
firebase.database().ref('Regions').orderByChild("rank").on('child_added', function (regions) {
loadRegions(regions.exportVal());
});
firebase.database().ref('Regions').orderByChild("rank").on('child_removed', function (regions) {
removeRegion(regions.exportVal());
});
function removeRegion(which)
{
$("#ddlRegions option[value="+which.rank+"]").remove();
}
function loadRegions(which)
{
var count = 0;
var RegionValue;
$.each(which, function (i, value) {
if (count % 2 == 0) {
RegionValue = value;
}
else {
$('#ddlRegions').append($('<option>').text(value).attr('value', RegionValue));
}
count++;
});
}

Related

How to query relational data in ascending order in strapi?

I have this query that works
async find(ctx) {
let { _start, _limit } = ctx.request.query;
console.log(ctx.request.query)
_limit ? 0 : (_limit = 10);
const entities = await strapi.services["course-series"].find({});
return entities.map((entity) => {
// Do I sort them here or in the url query (and how)
entity.courses = entity.courses.slice(_start, _limit);
return sanitizeEntity(entity, { model: strapi.models["course-series"] });
});
}
The idea is that I can load 10 courses from each series at first and then get the next 10...
I just realized that the first 10 I am getting are not the recent ones.
As I commented // Do I sort them here or in the url query (and how)
What version of Strapi do you use?
What does this line do strapi.services["course-series"].find({})? How did you build this find method in the service? What does it do? Does it accept params?
Personally I'd do something like that (assuming you're working with Strapi version > 4:
const entities = await strapi.entityService.findMany('api::course-series.course-series', {
fields: [/* list the course-series fields you want to populate */],
populate: {
courses: {
fields: [/* list the course fields you want to populate */],
sort: 'createdAt:desc', // You can use id, publishedAt or updatedAt here, depends on your sorting prefrences
offset: _start,
limit: _limit // I must admit I haven't tested `offset` and `limit` on the populated related field
}
}
})
// ...the rest of your code, if needed
Read more about Entity Service API here.
Doing it the way you did it, you will always first retrieve the full list of courses for each course-series, and then run costly operations like mapping (the lesser of 2 evils) and above all sorting.

Filter empty nodes on GitHub GraphQL API

I'm trying to use the V4 API of GITHUB to get a list of my assigned issues along with its labels and references.
After some time I got the query you can see below, which works exactly how I want.
There is however, a problem: it includes a lot of empty nodes I am not interested at. For example, if I want to get all the CrossReferencedEvent that are issues I will get a lot of empty nodes on the timeline edges array because the other events: LabeledEvent, ReferencedEvent, AssignedEvent etc.
How can I filter out those so I only get the events I am interested at?
Is this a limitation of graphql? Am I forced to remove the useless nodes locally?
This is the query that I have currently
{
search(query: "assignee:danielo515", type: ISSUE, last: 100) {
edges {
node {
... on Issue {
number
title
state
timeline(first: 10) {
edges {
node {
... on CrossReferencedEvent {
source{
... on Issue {
title
number
}
}
}
}
}
}
labels(last: 10) {
nodes {
name
color
}
}
repository {
name
}
}
}
}
}
}
One improvement I can make is, on the query part add is:issue. This will . save me all the empty nodes at the root edges array, but I don't see how to do the same for the nested timeline.
Thanks in advance

ExtJs 6 doSort method

I didn't find doSort function available in EXT 6 with respect to the grid columns and also didnt find it in any upgrade notes. may be because it is a private function, can anyone please tell me what is the alternative to do the same thing what doSort was doing in Ext 4 ?
I tried to use sorters instead,
{
text: 'columnText',
dataIndex: 'columnIndex',
sorter: me.sort
}
sort: function(v1,v2) {
...
}
but i didn't found smth like dataIndex or columnName in v1, v2 parameters to do sort. (it's just a model)
I need empty cell be from below after Sort Ascending, empty cell be from above after Sort Descending
Thanks.
What is the problem here? You can use the model object to retrieve your column data to sort. From the docs:
sorter: function(record1, record2) {
var name1 = record1.data.columnIndex;
var name2 = record2.data.columnIndex;
return name1 > name2 ? 1 : (name1 === name2) ? 0 : -1;
}
EDIT: If you dont want to rewrite this for every column, then you can do a trick like this:
sorter: (function(columnIndex){ return function(v1, v2){ me.sort(v1, v2, columnIndex);} })("column1")
Now, you can get the column name as 3rd argument in your sort function.
You want to sort the store, not a column. Have a look at the doSort function in ExtJS 4 for a moment:
doSort: function(state) {
var tablePanel = this.up('tablepanel'),
store = tablePanel.store;
// If the owning Panel's store is a NodeStore, this means that we are the unlocked side
// of a locked TreeGrid. We must use the TreeStore's sort method because we cannot
// reorder the NodeStore - that would break the tree.
if (tablePanel.ownerLockable && store.isNodeStore) {
store = tablePanel.ownerLockable.lockedGrid.store;
}
store.sort({
property: this.getSortParam(),
direction: state
});
},
/**
* Returns the parameter to sort upon when sorting this header. By default this returns the dataIndex and will not
* need to be overriden in most cases.
* #return {String}
*/
getSortParam: function() {
return this.dataIndex;
},
Now, this code is still working in ExtJS 6, just that it is no longer part of the framework. You can "put it back into the framework" (e.g. as an override) and it should work again. Or you can use the relevant parts directly from the column event, e.g.
columns:[{
dataIndex:'ABC',
listeners:{
headercontextmenu:function(ct, column) {
column.mySortState = column.mySortState=='ASC'?'DESC':'ASC';
ct.up('grid').getStore().sort({
property:column.dataIndex;
direction:column.mySortState
});
}
}
}]
Maybe you need to define it in the model like this:
fields: [{
name: "textField",
type: 'string',
//sortType: 'asInt'
}]

Why session.getSaveBatch() is undefined when child record was added - Ext 5.1.1

Well the title says it all, details following.
I have two related models, User & Role.
User has roles defined as:
Ext.define('App.model.security.User', {
extend: 'App.model.Base',
entityName: 'User',
fields: [
{ name: 'id' },
{ name: 'email'},
{ name: 'name'},
{ name: 'enabled', type: 'bool'}
],
manyToMany: 'Role'
});
Then I have a grid of users and a form to edit user's data including his roles.
The thing is, when I try to add or delete a role from the user a later call to session.getSaveBatch() returns undefined and then I cannot start the batch to send the modifications to the server.
How can I solve this?
Well after reading a lot I found that Ext won't save the changed relationships between two models at least on 5.1.1.
I've had to workaround this by placing an aditional field on the left model (I named it isDirty) with a default value of false and set it true to force the session to send the update to the server with getSaveBatch.
Later I'll dig into the code to write an override to BatchVisitor or a custom BatchVisitor class that allow to save just associations automatically.
Note that this only occurs when you want to save just the association between the two models and if you also modify one of the involved entities then the association will be sent on the save batch.
Well this was interesting, I've learned a lot about Ext by solving this simple problem.
The solution I came across is to override the BatchVisitor class to make use of an event handler for the event onCleanRecord raised from the private method visitData of the Session class.
So for each record I look for left side entities in the matrix and if there is a change then I call the handler for onDirtyRecord which is defined on the BatchVisitor original class.
The code:
Ext.define('Ext.overrides.data.session.BatchVisitor', {
override: 'Ext.data.session.BatchVisitor',
onCleanRecord: function (record) {
var matrices = record.session.matrices
bucket = null,
ops = [],
recordId = record.id,
className = record.$className;
// Before anything I check that the record does not exists in the bucket
// If it exists then any change on matrices will be considered (so leave)
try {
bucket = this.map[record.$className];
ops.concat(bucket.create || [], bucket.destroy || [], bucket.update || []);
var found = ops.findIndex(function (element, index, array) {
if (element.id === recordId) {
return true;
}
});
if (found != -1) {
return;
}
}
catch (e) {
// Do nothing
}
// Now I look for changes on matrices
for (name in matrices) {
matrix = matrices[name].left;
if (className === matrix.role.cls.$className) {
slices = matrix.slices;
for (id in slices) {
slice = slices[id];
members = slice.members;
for (id2 in members) {
id1 = members[id2][0]; // This is left side id, right side is index 1
state = members[id2][2];
if (id1 !== recordId) { // Not left side => leave
break;
}
if (state) { // Association changed
this.onDirtyRecord(record);
// Same case as above now it exists in the bucket (so leave)
return;
}
}
}
}
}
}
});
It works very well for my needs, probably it wont be the best solution for others but can be a starting point anyways.
Finally, if it's not clear yet, what this does is give the method getSaveBatch the ability to detect changes on relationships.

CouchDB "Join" two documents

I have two documents that looks a bit like so:
Doc
{
_id: AAA,
creator_id: ...,
data: ...
}
DataKey
{
_id: ...,
credits_left: 500,
times_used: 0,
data_id: AAA
}
What I want to do is create a view which would allow me to pass the DataKey id (key=DataKey _id) and get both the information of the DataKey and the Doc.
My attempt:
I first tried embedding the DataKey inside the Doc and used a map function like so:
function (doc)
{
if (doc.type == "Doc")
{
var ids = [];
for (var i in doc.keys)
ids.push(doc.keys[i]._id);
emit(ids, doc);
}
}
But i ran into two problems:
There can be multiple DataKey's per
Doc so using startkey=[idhere...]
and endkey=[idhere..., {}] didn't
work (only worked if the key happend
to be the first one in the array).
All the data keys need to be unique, and I would prefer not making a seperate document like {_id = datakey} to reserve the key.
Does anyone have ideas how I can accomplish this? Let me know if anything is unclear.
-----EDIT-----
I forgot to mention that in my application I do not know what the Doc ID is, so I need to be able to search on the DataKey's ID.
I think what you want is
function (doc)
{
if (doc.type == "Doc")
{
emit([doc._id, 0], doc);
}
if(doc.type == "DataKey")
{
emit([doc.data_id, 1], doc);
}
}
Now, query the view with key=["AAA"] and you will see a list of all docs. The first one will be the real "Doc" document. All the rest will be "DataKey" documents which reference the first doc.
This is a common technique, called CouchDB view collation.

Resources