AngularFire Loop denormalized data - firebase

I have categories and subcategories.
The structure of data is like the blog shows:
categories: {
-JF1RmYehtF3IoGN9xHG(categoryId): {
title: "Example",
subcategories: {
JF1RmYehtF3IoGN239GJ(subCategoryId): true
}
To now i retrieved the data this way(for just one category):[controller]
$scope.findOneCategory = function (categoryId) {
angularFire(Categories.find(categoryId), $scope, 'category');
}
And Categories service
find: function(categoryId) {
return FireRef.categories().child('/'+categoryId);
},
This works good!
But now i need to retrieve a list of subcategories in category and bind it to the scope and i dont know how...i have currently this:
The view:
<div ng-init="findSubCategories()">
<li class="list-group-item" ng-repeat="subCategory in subCategories">{{ subCategory.name }}</li>
The controller:
$scope.findSubCategories = function() {
angularFire(Categories.findSubCategories($routeParams.categoryId), $scope, 'subCategories');
}
And the service look like this i tried something but it's wrong and have no idea how it should looks like... if anyone could help i would be so glad!
findSubCategories: function(categoryId) {
var subCategoriesIdsList = FireRef.categories().child('/'+categoryId).child('subcategories');
subCategoriesIdsList.on("child_added", function(snap) {
FireRef.subcategories().child("/"+snap.name()).once("value", function(snap) {
// Render the comment on the link page.
console.log(snap.val());(i see the object in console but have no idea how to bind it now with the view...:(
});
});
},
dont know how to bind this with angular view

First, I'd recommend upgrading to the latest AngularFire version (currently 0.6), the API has changed quite a bit and it might be easier to accomplish what you're trying to do.
Second, you should only create one binding for each category and the subCategory IDs will automatically be included in that data since they're just the child nodes. Then you'll need to create a seperate binding just for the subcategory names, which you can then look up your ID.
Let's say your data looks like this:
categories: {
-JF1RmYehtF3IoGN9xHG: {
title: "Example",
subcategories: {
JF1RmYehtF3IoGN239GJ: true
}
}
},
subCategories: {
JF1RmYehtF3IoGN239GJ: {
name: "Example Subcategory",
}
}
You'll create two bindings, one for categories and another for subcategories:
function MyController($scope, $firebase) {
var ref = new Firebase("https://<my-firebase>.firebaseio.com/");
$scope.category = $firebase(ref.child("categories/" + categoryID));
$scope.subcategories = $firebase(ref.child("subCategories"));
}
And finally, in your view, use the ID you extract from $scope.category to get the sub category name from $scope.subcategories:
<ul ng-repeat="(id, val) in category.subcategories">
<li>{{subcategories[id].name}}</li>
</ul>

Related

Filter nested objects in WPGraphQL

I'm using WPGraphQL to interact with a vanilla wordpress backend. gatsby-source-wordpress is used as a client.
What I would like to achieve with my query:
for a list of category ids, give me a list of posts that belong to each category
each list of posts should be filtered (posts not having a certain id, etc), and limited to a certain number. The filter/limit params would be the same for each category.
Now when I'm constructing my query, it seems like I can only filter the outer node (category), but not the attached post node:
// syntax from the gatsby-source-wordpress plugin
// https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-source-wordpress
query MyQuery {
allWpCategory(filter: {id: {in: ["dGVybTo0Mg=="]}}) {
nodes {
posts { // cannot filter here 🥲
nodes {
title
id
}
}
}
}
}
I tried adding the filter in different locations, without success. Searched for solutions, but didn't find any specific to this GraphQL implementation. Structurally, this looks like what I'm try to achieve, but it's a different platform altogether.
My questions:
is it possible to get this working (it seems like a pretty standard requirement to me)
if yes, how
if no, what's the limiting factor? (GraphQL, WPGraphQL, gatsby-source-wordpress)
🙏
I don't have your data structure and can't test. Does something like this work?
query MyQuery {
allWpCategory(
filter: {
posts: {
elemMatch: {
id: {
ne: "something"
}
}
}, {
id: {
in: ["dGVybTo0Mg=="]
}
}
})
{
nodes {
posts { // cannot filter here 🥲
nodes {
title
id
}
}
}
}
}

Altering a returned collection in onBefore hook or similar hook

Is it possible to alter (add more fields for each record) a returned collection in onBeforeAction or similar hook?
I have InvoiceHistory collection which I am paginating through. I also want to display as part of each invoice the company name, business address, email address and VAT registration number - and these four fields are stored in another collection. So I would like to add these four fields to each record returned from InvoiceHistory. If there is another way to do it I am open to suggestions. I am using Alethes meteor pagination which loses the helper fields returned in its itemTemplate when you navigate/browse/page to the second page. Alethes pagination also relies on iron-router, is it maybe possible to achieve what I want with the help of iron-router
========================================================================
#Sean. Thanks. Below is the code that uses meteor publish composite:
if (Meteor.isServer) {
import { publishComposite } from 'meteor/reywood:publish-composite';
publishComposite('invoicesWithCompanyDetails', function(userId, startingDate,endingDate) {
return {
find() {
// Find purchase history for userId and the two dates that were entered. Note arguments for callback function
// being used in query.
return PurchaseHistory.find({Id:userId,transactionDate:{$gte:new Date(decodeURIComponent(startingDate)),
$lt:new Date(decodeURIComponent(endingDate))}});
},
children: [
{
find() {
return CompanySharedNumbers.find(
{ _id:"firstOccurrence" },
{ fields: { companyName: 1, companyAddress: 1 } }
);
}
}
]
}
});
}
Router.route('/store/invoices/:_username/:startingDate/:endingDate', { //:_startingDate/:_endingDate
name: 'invoices',
template: 'invoices',
onBeforeAction: function()
{
...
},
waitOn: function() {
var startingDate = this.params.startingDate;
var endingDate = this.params.endingDate;
return [Meteor.subscribe('systemInfo'),Meteor.subscribe('testingOn'),Meteor.subscribe('invoicesWithCompanyDetails',startingDate,endingDate)];
}
});
Pages = new Meteor.Pagination(PurchaseHistory, {
itemTemplate: "invoice",
availableSettings: {filters: true},
filters: {},
route: "/store/invoices/:_username/:startingDate/:endingDate/",
router: "iron-router",
routerTemplate: "invoices",
routerLayout: "main",
sort: {
transactionDate: 1
},
perPage: 1,
templateName: "invoices",
homeRoute:"home"
});
Instead of adding new fields to all your document. What you could do is to add a helper to load a company document :
Template.OneInvoice.helpers({
loadCompany(companyId){
return Company.findOne({_id: companyId});
}
});
And use it in your template code:
<template name="OneInvoice">
<p>Invoice id: {{invoice._id}}</p>
<p>Invoice total: {{invoice.total}}</p>
{{#let company=(loadCompany invoice.companyId)}}
<p>Company name: {{company.name}}</p>
<p>Company business address: {{company.businessAddress}}</p>
{{/let}}
</template>
This way, the code is very simple and easily maintainable.
If I'm understanding you correctly, this sounds like a job for a composite publication.
Using a composite publication you can return related data from multiple collections at the same time.
So, you can find all your invoices, then using the company ID stored in the invoice objects you can return the company name and other info from the Companies collection at the same time.
I've used composite publications before for complex data structures and they worked perfectly.
Hope that helps

Displaying data from two collections using each block

So I have 2 collections as follows
Collection user, a document might look like this
{_"id":"xyz",
"name":"sam"
}
Collection items, more that one documents linked to user collection as follows
{_"id":"123345",
"userid":"xyz"
"item":"potato"
}
{_"id":"3456",
"userid":"xyz"
"item":"tomato"
}
now lets say i am running a query to display all documents with name sam as follows, (will be run as a helper)
return User.find({name:"sam"});
and I pass the records into blaze template and display value using {{#each}}.
Now, additionally I want to also display data from collection "items" along with collection "users". So my data in html after using {{#each}} might look like this
<li> sam ...potato, tomato </li>
<li> sam ...potato, tomato, orange </li>
<li> sam ...pineapple </li>
<li> sam ... </li>
i.e data is being displayed on the template using #each but from 2 different collections.
Can anyone tell me how the code in my template should look like?
You can use publish-composite package when you make your users publication. (See example 1 in the link)
So for your use case something like should work:
Meteor.publishComposite('userItems', {
find: function() {
return Users.find({
name: "sam"
},
{
sort: {
...
},
fields: {
...
}
});
},
children: [{
find: function(user) {
return Items.find({
userid: user._id
}, {
fields: {
"item": true,
...
}
});
}}]
});
The solution here is to pass a parameter to the helper. Here it should happen a little bit trickier:
1)Know how to pass parameters:
lets name the helper products
HTML
{{products paramA}}
JS
products: function(userId){
findOne({userId:userId})
}
2)Perfect, but how to pass the _id:
As you are calling the users helper with each you have access by {{_id}} so it should be possible to pass it.
If this somehow does not work. Again try to use the each. Try to reach the element in the products helper by this._id.
I hope the two ways will be enough to solve your problem.

Meteor: Collection loads when I navigate to a page, but not when URL is directly inputted

I'm a Meteor newb and would appreciate any help here.
I'm creating a flashcard app where you can create decks of cards. It saves the progress that one has made through a deck of cards.
It appears that when I navigate into a deck of cards by clicking on the name of a deck (a link), everything works fine. But when I directly paste the URI in, the Deck collection fails to load.
I believe that this is because of routes.js, in the lib folder, is the first to load so there is no data. I tried using the waitOn function in Router.configure, but it's still not working.
Thanks in advance for your help!
Router.route ('/:_id/:wordIndex', {
template: 'wordPage',
data: function () {
var index = this.params.wordIndex;
var id = this.params._id;
console.log(Decks);
var word = Decks.findOne ( id,
{ fields: { 'wordIds': 1 } }
);
}
}
);
I tried using the waitOn function within the router already as well:
Router.configure ( {
layoutTemplate: 'layout',
waitOn: function () { return [
Meteor.subscribe ('words'),
Meteor.subscribe ('decks')
]}
})
EDIT
Ok, it seems like I found a solution by getting the data client side instead of at the route. But I dunno. There's gotta be a better way? This seems excessive to me.
Template.wordItem.helpers({
wordObj: function () {
var controller = Iron.controller();
var index = controller.params.wordIndex;
var id = controller.params._id;
var wordsObject = Decks.findOne ( id,
{ fields: { 'wordIds': 1 } }
);
var wordId = wordsObject.wordIds[index];
return Words.findOne ( wordId );
},
Here is a sample repository I've created which is very similar to what you are trying to do.
http://meteorpad.com/pad/xKS38qEZAieEPDyLs/Leaderboard
It seems to work. I suspect your problem is elsewhere.

Multilingual in Meteor

I'm developing a multilingual app in Meteor.js
I would like to know about best way in your opinion to do that; as example here is wat I'm doing right now (pretty sure that can be done better);
First I save items in mongodb with properties neted in a language root:
{
en: {
name: "english name",
content: "english content"
},
it: {
name: "italian name",
content: "italian content"
},
//since images are the same for both, are not nested
images: {
mainImage: "dataURL",
mainThumb: "dataURL"
}
}
Then I publish a subscription using currentLang session variable:
Meteor.publish("elementsCurrentLang", function(currentLang) {
var projection = {
images: 1
};
projection[currentLang] = 1;
return Elements.find({}, projection);
});
I subscribe on route using Iron Router waitOn hook:
Router.route('/eng/elements', {
waitOn: function() {
return Meteor.subscribe("municipalitiesCurrentLang", Session.get('currentLang'));
},
action: function() {
this.layout('ApplicationLayout');
this.render('elements');
}
});
Now the first problem: I would like to reuse the same template for every language, but I can't simply put in the template {{name}} or {{content}} since the subscription returns the attributes nested under lang root, so it is needed to do for example {{en.name}} for english or {{it.name}} for italian;
To avoid this I use a template helper that buids a new object; essentially it removes attributes from the lang root:
Template.elements.helpers({
elements: function() {
var elements = Elements.find();
var currentLang = Session.get('currentLang');
var resultList = [];
elements.forEach(function(element, index) {
var element = {
name: element[currentLang].name,
content: element[currentLang].nameUrl,
images: element.images
};
resultList.push(element);
});
return resultList;
}
});
And now in the template I can access attributes like wanted:
<h1>{{name}}</h1>
<p>{{content}}</p>
Before continuing with this approach I want to listen for suggestions, since I don't know if this will work well; when Session.currentLang will change, the subscription will be reloaded?
is there a way to avoid the forEach loop in template helpers?
I'm developping a multilangage web app too and I advise you to use a package, like this one : https://atmospherejs.com/tap/i18n
You can change the langage reactively. Have the same template for all your langages, as you want !
You can put it as a parameter in the route.
Personnaly I use it as a Session variable and in the user profile !
If you use this package, you also can export your app, or part of it, more easily as many developpers will use the same code.
you put all your words in json files :
en.i18n.json:
{
"hello": "hello"
}
fr.i18n.json:
{
"hello": "bonjour"
}
and
{{_ "hello" }}
will write hello or bonjour depending of the langage set. You can set it with :
TAPi18n.setLanguage(getUserLanguage())
//getUserLanguage() <- my function to get the current langage in the user profile or
the one used by the navigator
This module does what you're looking for
https://github.com/TAPevents/tap-i18n-db
As the developer says: "Extends the tap:i18n package to allow the translation of collections."
Finally there is a package which is very complete (it also works with number formats, locales...) and is being updated frequently.
https://github.com/vazco/meteor-universe-i18n
You can also install https://atmospherejs.com/universe/i18n-blaze for using it with blade.
Just name your files with the pattern locale.i80n.json and its contents like
{
name: "english name",
content: "english content"
}
then translate your strings with {{__ 'name'}}.

Resources