How to show nested data within Vuetify data table item prop? - firebase

We have an app with Users and Events. Users Apply to work Events. I want to create a vuetify data table that shows a User the shows that they have applied to, as well as their application status for each show.
Our firebase data is nested as: events - event.uid - applications - application.id(which matches the user.uid) - status
Our table code:
<v-data-table
v-bind:headers="headers"
:items="filteredEvents"
:rows-per-page-items="[10,25, { text: 'All', value: - 1}]"
>
<template slot="items" slot-scope="props">
<td>{{ props.item.title }}</td>
<td>{{ props.item.city }}</td>
<td>{{ props.item.startDate | moment("dddd, MMMM Do YYYY") }}</td>
<td>{{ props.item.applicants.match(user.id).status }}</td>
</template>
</v-data-table>
The applicants status td is where we would want "Pending" or "Accepted" to show up. The code that's there doesn't work. I wish it worked like that but it doesn't.
Computed:
filteredEvents(){
return this.$store.getters.loadedEvents.filter((event) => {
return this.$store.getters.isUserApplied(event.id);
})
}
Vuex:
getInitialEventsState({ commit }) {
let db = firebase.database();
db.ref("/events").on("value", function (snapshot) {
let data = []
for (var key in snapshot.val()) {
let event = snapshot.val()[key];
event.id = key
data.push(event)
}
commit("setLoadedEvents", data)
commit("setEventsMap", snapshot.val());
})
}
isUserApplied: (state) => (id) => {
let user = store.getters.user
if (user && state.eventsMap[id] && state.eventsMap[id].applicants) {
let status = state.eventsMap[id].applicants[user.id]
if(status && status.status) {
return status.status;
} else {
return null;
}
}
return false
}
loadedEvents(state) {
return state.loadedEvents
}
As a user who has Applied for 2 Events, I can see those two events in the data table. The name, city, and startDate all show, but my status does not.
What is the best way to solve this problem?
Thanks!

This may or may not help you as I can not see your events object structure. But you say the data is nested. It would be helpful to see what is arrays versus what is objects. So if you can show it in code form that would help. So other users here may want to see some kind of answer...
When I use firestore data it is usually in the form of an array of objects with each object having/holding other arrays. Getting to that inner nested data can be challenging. So I pass in my array of documents as items to the data-table and then use this this little bit of code in the td tags:
<td class="text-xs-left">
{{ getNestedObject(props.item, ['someArray', index, 'someProp']) }}
</td>
methods: {
getNestedObject(nestedObj, pathArr) {
return pathArr.reduce((obj, key) => (obj && obj[key] !== 'undefined' ? obj[key] : undefined), nestedObj)
},
}
Here was my source of inspiration: Accessing Nested Objects in JavaScript

Related

How to select an array of objects from inside an array of objects and create another array with it?

I have an array of 3586 objects.
The index 0 is SAPATILHA / SHARP BEGE/TABACO and has an array called products that contains 10 items. Could someone explain to me how I can create and save an array containing the products of the selected index?
console.log
listarPorReferencia() {
this.linxService.listarPorReferencia().subscribe(data => {
this.aux3 = data;
this.aux3.forEach(element => {
this.novasColecoes.push({ // novasColecoes is the array with 3586 objects
"produtos": element.produtos,
"nome": element.nome,
"referencia": element.referencia,
"selecionado": false
});
})
this.colecoes = this.novasColecoes
this.arrayProdutos.forEach(element => {
if (this.novasColecoes.find(m => m.idProduto == element.LinxProdutosId)) {
this.novasColecoes.find(m => m.idProduto == element.LinxProdutosId).selecionado = true;
}
});
});
}
//The code above is taking the array from the backend
// The below code I try to select products inside the main array
setProduct(product) {
product.forEach(getProducts => {
if(idProducts.includes(getProducts.id)){
this.idProducts = this.idProducts.filter(c => c != getProducts.id);
this.arrayProdutos = this.arrayProdutos.filter(c=>c!=getProducts);
}else{
this.idProducts.push(getProducts.id);
this.arrayProdutos.push(getProducts);
}
})
}
I imagine you're using material table with selection (the example of "Table with selection" in the docs of angular), so you can access to the element in "selection.selected"
doSomething()
{
this.selection.selected.forEach(x=>{
console.log(x)
})
}
see stackblitz
If you are not using material table, I suppose you has a table more or less like
<tr *ngFor="let item of novasColecoes|slice:0:(page+1)*20">
<td><input type="checkbox" [(ngModel)]="item.selecionado"></td>
<td>{{item.referencia}}</td>
<td>{{item.nome}}</td>
...
<tr>
See that you use [(ngModel)] to binding the property "selecionado" or each element of the array.
NovasColecoe has an array produtos althought we don't use in the table.
To select the novasColecoes selecionado you only need filter
submit()
{
let products:any[]=[]
this.novasColecoes.filter(x=>x.selecionado).forEach(x=>{
//here concat the two arrays. you can do
products.push(...x.produtos)
//or
products=product.concat(x.produtos)
})
//here you has all the products
}
NOTE: If you want to "merge" two arrays without repeat see,e.g. this another SO

Search and Sort on the same page

I'm trying to implement sort and search to my items, so i started with sort and it works:
Template
<button class="sort">Sort</button>
{{#each cvs}}
{{> Interviu}}
{{/each}}
JS:
Template.Interviuri.onCreated(function () {
var self = this
self.autorun(function () {
self.sortOrder = new ReactiveVar(-1)
})
Template.Interviuri.helpers({
cvs() {
const instance = Template.instance()
return Cvs.find({}, { sort: { createdAt: instance.sortOrder.get() } })
},
})
Template.Interviuri.events({
'click .sort'(event, instance) {
instance.sortOrder.set(instance.sortOrder.get() * -1)
Next i wanted to implement Search on the same page. So the best way i could found was EasySearch.
But using EasySearch, it means i must change the way my items are being displayed. And then the sort doesn't work anymore.
Template
<div class="searchBox pull-right">
{{> EasySearch.Input index=cvsIndex attributes=searchAttributes }}
</div>
{{#EasySearch.Each index=cvsIndex }}
{{> Interviu}}
{{/EasySearch.Each}}
Collection
CvsIndex = new EasySearch.Index({
collection: Cvs,
fields: ['name'],
engine: new EasySearch.Minimongo()
})
JS
cvsIndex: () => CvsIndex,
How can i have both search and sort working at the same time?
With EasySearch you can use two methods on your index, namely getComponentDict() and getComponentMethods().
With getComponentDict() you can access search definition and options:
index.getComponentDict().get('searchDefinition');
index.getComponentDict().get('searchOptions');
You also have the corresponding setters to change the search definition/option.
getComponentMethods has mehods like
index.getComponentMethods().loadMore(integer);
index.getComponentMethods().hasMoreDocuments();
index.getComponentMethods().addProps(prop, value);
index.getComponentMethods().removeProps([prop])
From that you can set your prop, say index.getComponentMethods().addProp('sort', -1) and then on the index definition, in your MongoDB engine, set the sort from that prop:
index = new EasySearch.index({
// other parameters
engine: new EasySearch.MongoDB({
sort: function(searchObject, options) {
if(options.search.props.sort) {
return parseInt(options.search.props.sort);
}
return 1;
}
})
});
See EasySearch Engines for more info.

Avoid repeating queries in meteor template helpers

I'm starting to use meteor for a project, and there are several concepts that are being pretty hard to me coming from angular+php.
I'm trying to handle two helpers, for displaying a list of records in a table, based in a date range that I'm storing on a session variable. My template then looks like this, I'm using a helper called noRecords and another called records, records store the actual document set, and in noRecords I'm trying to store as a boolean whether the record document set is empty or not.
div(class='col-md-8 col-md-offset-2')
if noRecords
p There are no records for the selected date
else
table(class='table table-bordered')
thead
tr
td Id
td Name
tbody
each records
+record
Unfortunately I've not being able to set records and noRecords at the same time without repeating the query, in my javascript code those helpers are now defined like this:
records : function(){
var startDate = Session.get('searchDate').setHours(0,0,0,0);
var endDate = Session.get('searchDate').setHours(23,59,59,999);
var matches = Records.find(
{date : {
$gte : new Date(startDate),
$lte : new Date(endDate)
}
});
return records;
},
noRecords : function(){
var startDate = Session.get('searchDate').setHours(0,0,0,0);
var endDate = Session.get('searchDate').setHours(23,59,59,999);
var matches = Records.find(
{date : {
$gte : new Date(startDate),
$lte : new Date(endDate)
}
});
return records.count() === 0;
}
The date session variable is set by an event.
I guess there must be a better way of doing this instead of executing the query twice, I've tried using reactive variables but I had no luck, since I can't make the reactive variable to update when the minimongo query executes.
Is there any way of achieving this without running two queries?
If you are repeating the same logic in multiple helpers one solution is to reduce them to a single helper and return an object from it, e.g.:
records : function(){
var startDate = Session.get('searchDate').setHours(0,0,0,0);
var endDate = Session.get('searchDate').setHours(23,59,59,999);
var cursor = Records.find(
{date : {
$gte : new Date(startDate),
$lte : new Date(endDate)
}
});
return {
cursor: cursor,
empty: cursor.count() === 0,
one: cursor.count() === 1
}
}
And in your template:
if records.one
p Found One!
Silly example but it can be used more broadly.
Note that in your example you wouldn't actually need the noRecords helper because your template can check for an empty cursor:
each records
p date
else
p No records!
If you were using Blaze, you could use the {{#each records}}...{{else}}...{{/each}} using your records helper only like this.
<template name="myTemplate">
<div class='col-md-8 col-md-offset-2'>
{{#each records}}{{> showRecord}}
{{else}}<p>There are no records for the selected date</p>
{{/each}}
</div>
</template>
You could also use {{#if records}}...{{else}}...{{/if}} since each will iterate over each item in the records cursor
<template name="myTemplate">
<div class='col-md-8 col-md-offset-2'>
{{#if records}}
<table class='table table-bordered'>
<thead>
<tr>
<td>Id</td>
<td>Name</td>
</tr>
</thead>
<tbody>
{{#each}}{{> showRecord}}{{/each}}
</tbody>
</table>
{{else}}<p>There are no records for the selected date</p>
{{/with}}
</div>
</template>

Why isn't the URL being generated for this route?

So, I'm working on a Meteor project and I can't get this route to generate properly, or at all for that matter.
<template name="browseAll">
<h3>List of classes with books available!</h3>
<ul>
{{#each aggCount}}
<li>{{ _id }} ({{ count }})</li>
{{/each}}
</ul>
</template>
The data that is being iterated over is a result of aggregation using MongoInternals, and that is as follows:
(server/methods.js excerpt):
classCount: function() {
// Attempt aggregation of the books table to count by class, maybe.
var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;
var col = db.collection("books");
var aggregateSync = Meteor._wrapAsync(col.aggregate.bind(col));
var pipeline = [
{$group: {_id: "$class", count: {$sum: 1}}},
{$sort: {_id: 1}}
];
var theAnswer = aggregateSync(pipeline);
return theAnswer;
}
It seems that the data is coming through okay, and sample data from aggregation (coming into the template) looks like this:
[ { _id: 'ADNR1234', count: 2 }, { _id: 'ARTH1234', count: 1 } ]
That's the template code I've got, and this is the route that it's supposed to be working with:
this.route('browse-class', {
path: '/browse/:_class',
data: function() {
var booksCursor = Books.find({"class": this.params._class},{sort:{"createdAt": 1}});
return {
theClass: this.params._class,
numBooks: booksCursor.count(),
books: booksCursor
};
}
});
I don't understand it. The data is being SHOWN, and what I want to do is generate a URL for browse-class (route) that takes the value of {{ _id }} in the helper as a parameter, so as to generate something like this:
application.org/browse/CLSS
Be aware that {{pathFor}} must be called with a data context properly set :
{{#with class}}
{{pathFor "browse-class"}}
{{/with}}
Optionnaly it is possible to pass the data context as a parameter :
{{pathFor "browse-class" class}}
The data context provided to pathFor is used when generating the route path, if you defined a route path like this :
path: "/browse/:_id"
Then it will use the _id from the class to properly generate a URL.
For the text of the link, I doubt you want to display the _id, your class documents probably include a "label" so you could use this :
{{ label }}

Get value of an array using dynamic index in handlebar template

I am new to Handlebars.
I have created an ItemView which is used in CompositeView. Values for this template rendering correctly.
var singleMonth = Marionette.ItemView.extend({
tagName: 'tr',
template: {
type: 'handlebars',
template: monthTemplate
},
months: [ 'JAN','FEB','MAR','APR','JUN','JUL','AUG','SEP','OCT','NOV','DEC' ],
templateHelpers: function() {
var helpers = {};
helpers.months = this.months;
return helpers;
}
});
and this is my template
<td>{{ months.#index.[7] }}</td><td>{{ [12] }}</td>
I want to get respective month value based on value of [7] which will be index for months array.
for Ex. if [7] is 3 then I want to get expression value as 'MAR'.
I am not able to get how to do this.
Can you please tell me how can I do this ?
(NOTE: I don't want to use #each or any loop here )
Thanks
Despite the possible syntax error in your template, the template itself should not be so smart.
Keep it stupid like
<td>{{ thisMonth }}</td>
Then build thisMonth in templateHelpers
templateHelpers: function() {
// The reason for _this is: `this` means model in templateHelpers.
_this = this;
return {
thisMonth: _this.months[7]
}
};

Resources