Sorting an async relationship - asynchronous

I have an ember app which shows 'competitions', each 'competition' object has many 'competitor' objects:
app/models/competition.js
import DS from "ember-data";
export default DS.Model.extend({
name: DS.attr('string'),
competitors: DS.hasMany('competitor', {async: true}),
});
I have a league-table component which is passed a 'competition' object as its main data.
app/components/league-table.js
import Ember from "ember";
export default Ember.Component.extend({
classNames: ['league-table'],
competition: null,
visibleCompetitors: Ember.computed.filterBy('competition.competitors', 'hidden', false),
sortProperties: ['score:desc'],
sortedCompetitors: Ember.computed.sort('visibleCompetitors', 'sortProperties'),
});
I would like to present the competitors in the correct order as a simple list. The sort order is determined by a complicated 'score' property of the competitor which is calculated based on a significant amount of data which is embedded in the competitor object when it is loaded.
app/templates/components/league-table.hbs
<header>
<h2>{{competition.name}}</h2>
</header>
<ul>
{{#each sortedCompetitors as |competitor index|}}
{{competitor-item competitor=competitor}}
{{else}}
<li class="centered">competition has no competitors</li>
{{/each}}
</ul>
The server takes a long time to load the 'competition.competitors' list so I was hoping to have the league table populate gradually and have competitors slot into the correct positions as they are loaded.
My problem is that competitor objects are not correctly ordered. I can see this is something to do with the promise but I can't realistically wait for all the competitors to load before rendering something.
Is there a way to restructure this so the competitors are slotted into the correct positions as they are loaded or do I need to think about the problem in a different way?

Related

Using pick in array Nuxt 3 useFetch

I'm trying to replicate the nuxt 3 useFetch sample but no luck so far.
https://v3.nuxtjs.org/getting-started/data-fetching#usefetch
My post.vue page contains the below code:
<template>
<section>
<div v-for="mountain in mountains" :key="mountain">{{mountain}}</div>
</section>
</template>
<script setup>
const runtimeConfig = useRuntimeConfig()
const { data: mountains, pending, error, refresh } = await useFetch('/mountains',{
baseURL: runtimeConfig.public.apiBase,
pick: ['title']
})
console.log(mountains.value)
</script>
For some reason the data is not shown in template.
The console.log() shows Proxy {title: undefined}
I've realized that removing the pick option solves the issue, so I'm wondering if pick only works for objects and can't be used in arrays.
It's odd because the sample is using an array https://v3.nuxtjs.org/api/composables/use-fetch.
The pick option currently works only if you are fetching one document (one Js object).
In the official docs you can see they are fetching a specific document in their API: https://nuxt.com/docs/getting-started/data-fetching.
One option you have is to make an API route that returns one object only.
Inside of your <div>, you are trying to return {{mountain}}, but at the time this page loads there is no such variable--because the const mountains hasn't been fetched yet. What you need to do is add a <div v-if="mountains" v-for="mountain in mountains" :key="mountain">{{mountain}}</div>
For the brief moment it takes before the useFetch function to return mountains,the <div> will not try to show {{mountains}}, because the v-if prevents the <div> from being shown in the first place.

Meteor - handling events in nested templates... without polluting 'Session' variable

I've come across this situation several times now and I realise I'm not really confident about the 'meteor/right' way to handle it.
Suppose I have a form with several parts - each represented by a template - and within each part there are more templates representing eg. datepickers etc.
<template name='myForm'>
{{>partOne}}
{{>partTwo}}
<button class='submit'>Submit</button>
</template>
<template name='partOne'>
{{>widget}}
{{>widget}}
</template>
<template name='widget'>
<input class='datepicker' />
</template>
I want to keep track of my form as the user fills it out - on the level of the 'myForm' template - but all the events are happening at the level of 'widget'.
One solution I keep seeing (e.g. in this SO answer) is to just put everything in the global Session variable. Like so
Template.widget.events({
'click .select' : function(event, template){
var name = template.data.name;
Session.set(name, $(event.currentTarget).val());
}
});
And then in myForm I should do something like this
Template.myForm.rendered = function(){
Tracker.autorun(function(){
var name = Session.get('name');
// do something
});
}
But as my forms are getting more complicated, I find this is really turning into a mess on the myForm template level, all while filling up my Session variable with data that isn't really application-global.
I'd be really grateful for any ideas on how others deal with this ! Keeping templates and widgets modular while still being able to follow and react to their triggered events from parent templates...
You're not alone in feeling like something just isn't right. This is one of the reasons there's a lot of talk about a Blaze 2. Here's what I do:
Create an app global namespace (e.g. G = {}). I usually use the first letter of the app name & do this in lib/config/_namespace.js
Put your collections in G.Collections or G.C,
Put your shared functions in G.Fx, etc...
Put your template vars in G.T.
Then, save that variable to G.T.varName. In doing so, you can use it in rendered as well as events and helpers. As a perk, it's super easy to find all your "globals" because they're all in the G object. Additionally, you can now 'use strict' again.
Then, to keep it clean:
Template.parentTemplate.destroyed = function() {
G.T = {};
};
So if you need reactivity, just make a ReactiveDict:
Template.parentTemplate.created = function() {
G.T.RD = new ReactiveDict();
};
You can use a file-level ReactiveVar or ReactiveDict, instead of the Session object.

sending information through events in meteor

Ok so I'm working with meteor! Woo hoo I love it so far, but I've actually run into an architecture problem (or maybe its super simple and i just dont know it yet).
I have a list of names that belong to a user. And a delete button that is aligned next to the name
name - x
name - x
name - x
and I want a functionality to click the 'x', and then proceed to clearing the name from the database using the meteor event handler. I'm finding trouble thinking about how I'm going to pass the name along with the click to proceed to delete it from the database.
I can't use a unique id in the template to call a document.getElementById() (unless I came up with an integer system that followed the database.)
Does anyone have a good thought on this?
Here is a complete working example:
html
<body>
{{> userEdit}}
</body>
<template name="nameChoice">
<p>
<span>{{name}}</span>
x
</p>
</template>
<template name="userEdit">
{{#each nameChoices}}
{{> nameChoice name=this}}
{{/each}}
</template>
js
Users = new Meteor.Collection(null);
if (Meteor.isClient) {
Meteor.startup(function () {
Users.insert({nameChoices: ['foo', 'bar', 'baz']});
});
Template.userEdit.nameChoices = function () {
return Users.findOne() && Users.findOne().nameChoices;
};
Template.nameChoice.events({
'click .remove': function () {
_id = Users.findOne()._id;
Users.update(_id, {$pull: {'nameChoices': this.name}});
}
});
}
This actually does a bunch of stuff you wouldn't do in a real application (defined a client-only Users collection, assumes there is only one user, etc). But the main takeaway is that you can use the data context in each nameChoice template to respond to the remove event. This approach can nearly always replace the need for coming up with your own artificial id system. Feel free to ask questions if any of this is unclear.

Meteor template gets rendered twice

My template is getting rendered twice on first load. I notice this because in
Template.home.rendered = function() {
console.log('rendered'); // => this is printed out twice
console.log(Data.find({}).count()); // => this is initially 0, then 1 the second time
}
Furthermore, on the first load, no Data is available. Yet on the second load, the Data is there.
Does anyone know what this problem might be, and why the data only appears the second time?
You need to find a way to render the template when your data is available.
Using this template structure, the if block content, which happens to be the template displaying your data, will be rendered only when the myDataIsReady helper returns true. (thus triggering the rendered callback only once, with data immediately available).
<template name="displayData">
<p>This is my data : {{this}}</p>
</template>
<template name="home">
{{#if myDataIsReady}}
{{#each data}}
{{> displayData}}
{{/each}}
{{/if}}
</template>
You have to define a subscription handle (an object returned by Meteor.subscribe) in order to use it's reactive ready method : we'll reference it in the myDataIsReady helper to track data availability, and the helper will automatically rerun when the state of ready changes.
Meteor.startup(function(){
// this subscription should return your data subset
myDataHandle=Meteor.subscribe("myData");
});
Template.home.myDataIsReady=function(){
return myDataHandle.ready();
}
Template.home.data=function(){
return Data.find({});
}
But that's quite annoying for such a simple task.
That's why I suggest using the Iron Router which makes things way simpler !
Add it to your project using "mrt add iron-router", then in a client/router.js and client/router.html, use this boilerplate code :
Router.configure({
loadingTemplate:"loading"
});
Router.map(function(){
this.route("home",{
path:"/",
// we indicate which subscription has to be marked ready in order to load the template
waitOn:function(){
return Meteor.subscribe("myData");
}
// the result of this function will become our target template data context
data:function(){
return Data.find({});
}
});
});
<template name="home">
{{#each this}}
{{> displayData}}
{{/each}}
</template>
<template name="loading">
<p>Data isn't ready yet...</p>
</template>
As you can see, the Iron Router allows us to specify simply what we painfully achieved manually in the first code example (waiting on a particular subscription to render a template), and of course we get free routing, loading mechanisme, layout management, etc...
Search the web for a complete iron-router tutorial (my code is untested, but I hope it is ok and should get you started), it's so awesome that it's gonna be merged to Meteor ultimately.
I had a body.html in /client and a appBody.html in /client/templates, with, in iron router:
Router.configure({
layoutTemplate: 'appBody',
});
Both body templates were rendered (and happened to be the same). Obviously, the body.html in /client needed to be removed.

Rendering an array inside a collection in Meteor

I have a collection of decks, and each deck has a number of cards.
The field "cards" is an array holding the IDs of the cards in the deck.
Problem is, I have a card list from which the user can choose cards to add to a deck. When the user chooses a card to add to the cards array inside Decks collection, Deps throws an exception saying "can't create second landmark in same branch" unless I don't use a partial to render the list, which is an issue for me since each card has its own events. Though the data is added properly to the deck since when I refresh the page, the updates appear.
Decks.js
Template.deckList.deck = () ->
Decks.findOne(_id: Session.get "deck").cards
Deck-list.html
<template name="deckList">
<section class="deck-list"><h1>deck</h1>
<ul class="cards">
{{#each deck}}
{{> cardInList}}
{{/each}}
</ul></section>
</template>
Now I thought of making a separate collection to hold both IDs (card and deck), but that might not work for future collections with the same issues (hand for example in the game collection)
Thanks!
You're on the right track, but if I've understood you correctly you've got a bad design there. You don't want to have to update an array in the deck document every time you add/delete a card. It would be easier for you to leave out the cards field in the deck documents, and instead add a deckId field to the card documents. While MongoDB often encourages nested/embedded fields, Meteor Collections generally work far better with typical relational-database style schemas. Check out this approach to your problem:
Decks.js
Template.deckList.deck = () ->
Decks.findOne( _id: Session.get "deck" )
Template.deckList.cards = () ->
Cards.find( deckId: Session.get "deck" )
Deck-list.html
<template name="deckList">
<section class="deck-list">
<h1>{{#with deck}} {{title}} {{/with}} Deck</h1>
<ul class="cards">
{{#each cards}}
{{> card }}
{{/each}}
</ul>
</section>
</template>
<template name="card">
<li>{{foobar}}</li>
</template>
Using this approach, you can simply add/delete cards to/from your decks, and the changes will be automatically reflected in realtime without the need to update an additional document in another database collection.
EDIT: If you want a many-to-many collection instead of a one-to-many, you can modify the publish method on the server to return the cards for a particular deck, and avoid the need to publish that connection table to the client. It might look something like:
// Server publish method
// Return just cards that are in deck "deckId"
Meteor.publish('cards', function (deckId) {
var cardIds = CardsDecks.find({ deckId: deckId }).map(function (connector) {
return connector.cardId;
});
return Cards.find({ _id: {$in: cardIds } });
});
// Client subscribe method
Meteor.subscribe('cards', Session.get('currentDeckId')); // Get just the cards related to the current deck
Cheers!
Note: This was originally answered on CodersClan

Resources