Meteor accessing helpers from other helpers - templates

I'm new to Meteor and its API / philosophy, so I might be thinking about this the wrong way.
I want to display some new friend requests on a page or a message if there aren't any. Here's some code:
// addfriend.js
Template.friendRequests.helpers({
friendRequests: function () {
return [
{ username: 'alice' },
{ username: 'carl' },
{ username: 'eve' },
];
},
hasFriendRequests: function () {
var template = Template.instance();
return template.helpers.friendRequests.length > 0;
}
});
// addfriend.html
<template name="friendRequests">
<h2>Friend requests</h2>
{{#if hasFriendRequests}}
<p>Someone's popular today!</p>
<ul>
{{#each friendRequests}}
<li>{{username}}</li>
{{/each}}
</ul>
{{else}}
<p>Sorry, nobody likes you right now.</p>
{{/if}}
</template>
My problem is that friendRequests will ultimately be a MongoDB query and I want hasFriendRequests to not repeat that query. I just want it to act on the friendRequests helper. But the code I have above does not work for that.
More generally, I'm interested in being able to apply any function f to an expensive helper so that I don't have to recompute it. So if you can illuminate me, that'd be awesome!
One way I can think of doing this is by putting the data in Session and working from it there. Is this how this should be done?
Any help would be appreciated!
Thanks!
Alin
Edit: I realize friendRequests.length works here actually, but I'm still interested in how to do this in the general case.

Although this will eventually be a mongodb query, you will most likely set up this friend request list as a published collection. In meteor this means that this collection is being copied over the wire on the initial page load, and only being sent once.
Once on the client, both your friend requests helper and your hasFriendRequests will use this same client side data. The data is actually copied into a client side mini mongodb database that just has the information subscribed to.
So the short answer is, there will only be one DB query for this, because meteor will do all the magic for you.
The key will be in setting up your publication and subscription. So assuming you only play blush and subscribe to it once. You are all set.

Related

Vue3 force reset/clear of keep-alive components

I'm using keep-alive to maintain the state of a multi-step form in Vue3 so users can navigate back and forth as needed.
What I can't figure out, is how to force a clear of the cache. When users complete the form I give them an option to re-start and I currently clear the form submission object and return the users to page 1 of the form but keep-alive is preserving the form state so checkboxes are pre-selected. Is there a call I can make from my reset function to clear the keep-alive cache? Ideally for only some of the form steps, not all.
Hard to do.;) There's no built-in method to clear the keepAlive cache.
Looks like the form is not completely reseted but maybe could be enough to destroy the instance of components wrapped in
Are you using key="x" on the component that's wrapped with ? Like:
<KeepAlive>
<component key="x"/>
</KeepAlive >
reseting the key together with redirecting to 1st page could help.
But also to my mind came an idea that You maybe should re-initialize form initialData
ex:
<script>
const initialState = () => {
return {
name: '',
surename: '',
location: {
name: null,
},
};
};
export default {
data() {
return initialState();
},
methods: {
reset() {
Object.assign(this.$data, initialState());
},
},
};
</script>
let's dive into
https://learnvue.co/tutorials/vue-keep-alive
Found related issue:
https://stackoverflow.com/a/71766767/10900851
https://github.com/vuejs/vue/issues/6259#issuecomment-436209870
I actually ended up using an entirely different method and thought I would put it here in case it is of use to someone else.
I found it here: https://michaelnthiessen.com/force-re-render/
Basically, a reset of a component can be forced by changing its key value. This has the added benefit of letting you selectively force a re-render of any number of child components.
In the parent.
<PageOne :key="page_one_key">
<script>
export default {
...
data() {
return {
page_one_key: 0,
}
},
...
methods: {
myreset(){
this.page_one_key += 1;
}
}
}
</script>
If there are downsides to this approach I would love to know but it seems to work perfectly - allows back/forwards navigation of my form and selective resetting of the cached components.
It is also simple to implement.

How can I do a "where in" type query using ember-data

How can I perform a where-in type query using ember-data?
Say I have a list of tags - how can I use the store to query the API to get all relevant records where they have one of the tags present?
Something like this:
return this.store.find('tags', {
name: {
"in": ['tag1', 'tag2', 'tag3']
}
})
There isn't built in support for something like that. And, I don't think its needed.
The result that you are after can be obtained in two steps.
return this.store.find('posts'); // I guess its a blog
and then in your controller you use a computed property
filteredPosts: function('model', function() {
var tags = ['tag1', 'tag2', 'tag3'];
return this.get('model').filter(function(post) {
if ( /* post has one of tags */ ) {
}
return false;
});
});
Update: What if there are tens of thousands of tags?!
Amother option is to send a list of tags as a single argument to the back end. You'll have to do a bit of data processing before sending a request and before querying.
return this.store.find('tags', {
tags: ['tag1', 'tag2', 'tag3'].join(', ')
})
In your API you'll know that the tags argument needs to be converted into an array before querying the DB.
So, this is better because you avoid the very expensive nested loop caused by the use of filter. (expensive !== bad, it has its benefits)
It is a concern to think that there will be tens of thousands of tags, if those are going to be available in your Ember app they'll have a big memory footprint and maybe something much more advanced is needed in terms of app design.

Where to put code from another JS library in Ember: controller, model, adapter, service?

Wrapping another javascript library to use with Ember bindings, etc, seems like an ordinary thing to do, but I haven't found much discussion of it.
I want to filter an ember record array using distance and travel time from the Google Maps Distance Matrix
service. I'm just not sure where in the application to encapsulate Google's javascript. Note: this is not a question about embedding a google map, it's about getting data into ember that doesn't come from a rest/json or fixtures as in all the tutorials and examples I've found.
Would people typically do this in the controller or create new models/adapters to get benefits from store caching? Or is there another way?
update: in case that's too vague, consider this: 20 records (with a google map component etc) listed by an array controller, a text field where the user types in a home address, a couple of other inputs where they set a maximum time or distance, and a search button which filters the listed records by comparing the user requirements with the result of querying the distance matrix for the home address to the 20 records' addresses, only showing the ones close enough to their home.
Use of the service in an application that doesn't display a Google map is prohibited.
So,the question is really about integrating a Google map to an Ember app.
Without any doubt you'll have to add the Google JS like in any other HTML project with:
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=MYSECRETKEY"></script>
So, the API is in global space and you just use it whenever you need it. Mostly all that will happen in your views, so you could wrap everything in a component. (I'm assuming that all relevant data has been passed from the controller to the view, it all depends on the design of your app.)
The following works, but it seems like it should be in the model/store/adapter layer.
App.DistanceController = Ember.Controller.extend
origin: (->
data = #get('data')
data.origin if data
).property('data')
destinationDistances: (->
data = #get('data')
data.distances if data
).property('data')
data: ((key, value)->
if arguments.length > 1
value
else
_this = this
value = null
service = new google.maps.DistanceMatrixService()
service.getDistanceMatrix(
origins: ["London, England"],
destinations: [
"Bristol, England",
"Edinburgh, Scotland",
"Leeds, England",
"Liverpool, England",
"Manchester, England",
"Newcastle, England",
"Nottingham, England",
"York, England"
],
travelMode: google.maps.TravelMode.DRIVING,
avoidHighways: false,
avoidTolls: false
, (response, status) ->
if (status == google.maps.DistanceMatrixStatus.OK)
distances = []
for destination, n in response.destinationAddresses
distances.push {
destination: destination
distance: response.rows[0].elements[n].distance.text
}
_this.set('data', {
origin: response.originAddresses[0]
distances: distances
})
)
value
).property()
kudos #rlivsey https://stackoverflow.com/a/20623551/395180

Force a controller to always act as a proxy to a model in Ember

I'm looping through a content of an ArrayController whose content is set to a RecordArray. Each record is DS.Model, say Client
{{# each item in controller}}
{{item.balance}}
{{/each}}
balance is a property of the Client model and a call to item.balance will fetch the property from the model directly. I want to apply some formatting to balance to display in a money format. The easy way to do this is to add a computed property, balanceMoney, to the Client object and do the formatting there:
App.Client = DS.Model({
balance: DS.attr('balance'),
balanceMoney: function() {
// format the balance property
return Money.format(this.get('balance');
}.property('balance')
});
This serves well the purpose, the right place for balanceMoney computed property though, is the client controller rather than the client model. I was under the impression that Ember lookup properties in the controller first and then tries to retrieve them in the model if nothing has been found. None of this happen here though, a call to item.balanceMoney will just be ignored and will never reach the controller.
Is it possible to configure somehow a controller to act always as a proxy to the model in all circumstances.
UPDATE - Using the latest version from emberjs master repository you can configure the array controller to resolve records' methods through a controller proxy by overriding the lookupItemController method in the ArrayController. The method should return the name of the controller without the 'controller' suffix i.e. client instead of clientController. Merely setting the itemControllerClass property in the array controller doesn't seem to work for the moment.
lookupItemController: function( object ) {
return 'client';
},
This was recently added to master: https://github.com/emberjs/ember.js/commit/2a75cacc30c8d02acc83094b47ae8a6900c0975b
As of this writing it is not in any released versions. It will mostly likely be part of 1.0.0.pre.3.
If you're only after formatting, another possibility is to make a handlebars helper. You could implement your own {{formatMoney item.balance}} helper, for instance.
For something more general, I made this one to wrap an sprintf implementation (pick one of several out there):
Ember.Handlebars.registerHelper('sprintf', function (/*arbitrary number of arguments*/) {
var options = arguments[arguments.length - 1],
fmtStr = arguments[0],
params = Array.prototype.slice.call(arguments, 1, -1);
for (var i = 0; i < params.length; i++) {
params[i] = this.get(params[i]);
}
return vsprintf(fmtStr, params);
});
And then you can do {{sprintf "$%.2f" item.balance}}.
However, the solution #luke-melia gave will be far more flexible--for example letting you calculate a balance in the controller, as opposed to simply formatting a single value.
EDIT:
A caveat I should have mentioned because it's not obvious: the above solution does not create a bound handlebars helper, so changes to the underlying model value won't be reflected. There's supposed to be a registerBoundHelper already committed to Ember.js which would fix this, but that too is not released yet.

Proper design of REST-powered list in Ember.js

I'm having difficulty wrapping my head around the following:
There's a view that displays the list of items
I take the list of items from the backend via RESTful interface in JSON using ember-data and hand-crafted adapter
In my view I do something like this:
{{#collection contentBinding="App.recentAdditionsController"}}
...
{{/collection}}
App.recentAdditionsController is defined like this:
App.recentAdditionsController = Em.ArrayController.create({
refresh: function(query) {
var items = App.store.findAll(App.Item);
this.set('content', items);
}
});
And... this doesn't work. The reason being App.store.findAll() returning ModelArray which is much like ArrayController itself.
I saw people doing something like this:
App.recentAdditions = App.store.findAll(App.Item);
I could imagine doing it like that, but how would I refresh the list at will (checking if there's anything new).
Hope all is clear more or less.
I've verified that you can use a ModelArray inside an ArrayController. Here's a jsFiddle example: http://jsfiddle.net/ebryn/VkKX2/
"Now the question is how to make the list update itself if there are new objects in the backend?"
Use App.Model.filter to keep your recordArray in sync. Add the query hash when the filter is invoked to ensure than an initial query was made.
model: ->
App.Model.filter {page: 1}, (data) ->
data
edit: Just saw how old the question was, but leaving it here in case it helps someone.