The pub/sub to pull all posts (geo tagged) within a certain distance is not working. The aim is to get user's current location and then use the pub (server calculation) to pull the relevant data + use sort on router to sort it.
Helpers/ Events in js (This portion, Im still tweaking it to see what works)
Template.postsList.onCreated(function() {
this.interval = Meteor.setInterval(function (){
var currentLocation = Geolocation.latLng();
if(currentLocation) {
Session.set('currentLocation', currentLocation);
}
}, 2000
);
Session.set('postSubmitErrors', {});
});
Template.postsList.helpers({
loc : function () {
return Session.get('currentLocation');... // may not be needed if get in router
Template.postsList.events({
postsByDistance : function() { // may not be needed if get in router
var loc = Session.get('currentLocation');...
The error given in the terminal is
Exception from sub postsByDistance id bsWXKgw5QboNzCRXw Error:
Exception while polling query
{"collectionName":"posts","selector":{"loc":{"$near":{"$geometry":{"type":"Point","coordinates":{"currentLocation":{"lat":xxx,"lng":xxx}}},"$maxDistance":500}}},"options":{"transform":null}}:
$near requires a point, given { type: "Point", coordinates: {
currentLocation: { lat: xxx, lng: xxx } } }
If I change pub line to [anything.lng, anything.lat] it says lat or lng of undefined
Occasionally I get a Exception while polling query meteor error when I change the argument in the function().
16 Aug 15 - Updates after suggestions:
new pub
Posts._ensureIndex({'loc' : '2dsphere'});
Meteor.publish('postsByDistance', function(options) {
check(options, Object);
return Posts.find({
loc: {
$near: {
$geometry: {
type: "Point",
coordinates: [currentLocation.lng, currentLocation.lat]
Error now says currentLocation is not defined. But I did define in the helper?
I also think within the error it says I have "options":{"transform":null}} which if correct is not supposed to appear?
In your mongo query the coordinates field should be definitely an array containing two numbers. But as I understand that's something you've already tried. It didn't work for a different reason.
It looks like your problem is caused by a simple "race condition". Namely, the first time you're subscribing to postsByDistance the session variable currentLocation is not yet set and that's the reason you may be getting undefined values for latitude and longitude.
Also - it's probably a typo error - looking at your code you pass an object with currentLocation filed to subscription. On the other hand in your corresponding publish function you are referring to the whole thing as currentLocation but it should be
currentLocation.currentLocation.lng
currentLocation.currentLocation.lat
with your current variables setup. I suggest you change those names because it may lead to a lot of unwanted errors.
Related
I have a piece of code show below that creates a Mongo collection as show below. However whenever I try to access the collection from inside of the Meteor.isClient scope I get an error. Please can anyone spot my mistake.
ImagesCollection = new Mongo.Collection("Images");
Images = new Mongo.Collection("Images");
if(Meteor.isClient){
Template.body.helpers({ images :
function() {
console.log("Template Loade");
return Images.find({},{sort: -1 });
}
}) ;
Template.Images.events({
'click .js-image' : function(event){
$(event.target).css("Width", "50px");
} ,
'click .js-del-image' : function(event){
var image_id = this._id ;
$("#"+image_id).hide({slow });
Images.remove({"_id" : image_id});
},
'click .js-rate-image' : function(event){
var rating = $(event.currentTarget).data("userrating");
var image_id = this.id ;
Images.find({"_id": image_id});
}
});
}
The content of my Startup.js is as below as well
if(Meteor.isServer){
Meteor.startup(function(){
for(var i = 0 ; i<=23 ; i++)
{
Images.insert({
'img_src' : 'img_'+i+'.jpg' ,
'img_alt' : 'Image number' + i
});
console.log(Images.find().count);
}
});
}
consle.log("Template Loade");
Since you don't specify your error, the line above will raise an error.
From the code that you have provided, there are two problems that I can see.
First, in your images template helper, your second parameter for the Images.find() function call is incorrect. You are missing a document field specification for the sort operation. This second parameter needs to be in the format {sort: {'document_field': -1}}. Although you have not provided the error text that you are seeing, I suspect the error has something to do with Mongo not being able to process the query, and this would be the reason for that.
Second, although this is less severe and should not be causing the problems with your inability to access the Images collection on the client, in your console.log() statement in your server Meteor.startup() code you are accessing count as if it is a property on the cursor returned from the Images.find() function call. In actuality, it is a function and should be called like so: Images.find().count().
Also, as an aside, I would suggest that you give different names to your two collections that you have defined. Giving them both the same name may cause issues for you if you are trying to manipulate data through the Mongo shell.
I am not sure if this is an issue, but why are you initializing twice the "images" collection ?
ImagesCollection = new Mongo.Collection("Images");
Images = new Mongo.Collection("Images");
And ImagesCollection is not used anywhere in you're code.
Try to remove one of this lines.
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 years ago.
Improve this question
I am trying retrieve a value from one function and would like to use that value in another function. This function returns a city, state and I present that on screen.
<script>
$.get("http://ipinfo.io", function (response) {
$("#address").html(response.city + ", " + response.region);
}, "jsonp");
</script>
<div id="address"></div>
But I would like to use the city value for another piece of JS that uses city values to return current weather. That code is here:
$(document).ready(function() {
loadWeather('Baltimore',''); //#params location, woeid
});
'Baltimore' is currently hard-coded in the JS and I would like the returned city value from above to be placed in the weather function. The weather function only seems to accept the city as a string and not a code snippet like the answers below have presented. Can I make the resulting city into a variable that is then passed to the weather function?
You could consider calling your loadWeather() function within the callback function of your get() AJAX call :
<script>
$(function(){
// Get your city information
$.get("http://ipinfo.io", function (response) {
// At this point you have a response, so use it
$("#address").html(response.city + ", " + response.region);
// Pass the city to your loadWeather function
loadWeather(response.city,'');
}, "jsonp");
});
</script>
The order in which you call these functions is important as your loadWeather() function clearly depends on you retreiving a value from your get() call (as you actually need the city to load the weather for).
You can't use a value before you have that value. Call the other function when you have the value you need.
$(document).ready(function() {
$.get("http://ipinfo.io", function (response) {
$("#address").html(response.city + ", " + response.region);
loadWeather(response.city,''); //#params location, woeid
}, "jsonp");
});
Calling loadWeather within your jQuery.get() callback will give loadWeather access to the data in response, but doesn't really answer your original question: how do you use info from one function in another? The simplest solution would be to pull things apart into separate functions, so you could use the output of one function as an argument of the other:
loadWeather(getLocationFromIp());
That won't work here, though, since the operation to get location data is asynchronous -- there's no way to be sure when you'll get the data you need from the server. That's why you used a callback function in the first place.
Sharing Asynchronous Data with Promises
You can get around this, while still keeping the weather update and location update functions separated, by using jQuery's deferred/promise features. This also gives you a chance to handle the location request to ipinfo.io failing.
And while we're at it, let's handle cases (like mine) where ipinfo.io returns '' for city and region. In that case, falling back to latitude & longitude will still give the simpleWeather plugin what it needs.
$(document).ready(function() {
getLocationFromIp()
.done(function(response) {
var has_city = (response.city && response.region);
var location = has_city ? (response.city + ', ' + response.region) : response.loc;
$('#address').html(location);
loadWeather(location);
})
.fail(function() {
alert('Error getting location info. Try reloading in a moment.');
});
});
function getLocationFromIp() {
return $.getJSON('http://ipinfo.io');
}
Okay, but what's the point of using promises and a separate getLocationFromIp function? Except for the failure message (which simpleWeather could handle better than alert()), you could do all that in the original $.get() callback.
Reusing Data with Caching
The benefit of breaking things into smaller, independent pieces appears when you move a little beyond what your pen is currently doing. Now you can use other methods of passing one function's data to another to do things like update the weather without having to re-request location data from ipinfo.io.
I'm guessing, from the location search field and use of Google Maps autocomplete, that you have more in mind for this code. So let's go ahead and expand things a little. For an example, we'll cache (store) the user's detected location with jQuery.data() then refresh the weather every 10 minutes using that information:
$(document).ready(function() {
getLocationFromIp()
.done(function(response) {
var has_city = (response.city && response.region);
var location = has_city ? (response.city + ', ' + response.region) : response.loc;
$('#address')
.html(location)
.data('location', location);
loadWeather(location);
// update every 10 min
setInterval(loadWeather, 60000);
})
.fail(function() {
// Updating simpleWeather with an invalid location triggers the plugin's error message:
loadWeather('0');
});
});
function getLocationFromIp() {
return $.getJSON('http://ipinfo.io');
}
function loadWeather(location) {
// if no location argument passed, use cached location
var loc = location || $('#address').data('location');
$.simpleWeather({
location: loc,
// more simpleWeather config...
});
}
You can see the above approach in full here: http://codepen.io/adammessinger/pen/VaOJdq
Sharing Data Among Functions with Encapsulation
If you wanted to get a bit fancier, you could also cache and reuse the location info without touching the DOM by leveraging the Revealing Module Pattern and closure.
All the functions in that module would have access to the current location data. You could expose some or all of them as public methods for manipulating the state of your weather widget. For example, updating the weather info to match whatever location the user types into input#searchTextField with WeatherWidget.updateLocation(input.value) or turning timed refresh of the weather data on and off when the user toggles a checkbox.
This bundling of data together with relevant functions is a form of encapsulation, a facet of object-oriented programming. But I'm getting far from the original question, so I'll leave it at that.
There is an existing Parse.com class that needs to be copied to a new Parse.com class with some new columns and the transformation of one of the columns. The code currently works and uses the Parse.Query.each method to iterate over all records as detailed in the Parse.com documentation but it stops processing at 831 records although there are 12k+ records in the class. This is odd given each should not have a limit and other default limits are 100 or 1000 for find. Should another method be used to iterate over all records or is there something wrong with the code?
var SourceObject = Parse.Object.extend("Log_Old_Class");
var source_query = new Parse.Query(SourceObject);
var TargetObject = Parse.Object.extend("Log_New_Class")
source_query.each(function(record) {
//save record to new class code works fine
var target_query = new TargetObject();
target_query.set("col1_new",record.col1);
target_query.set("col2_new",record.col2);
//etc...
target_query.save(null, {
success: function(obj) {
//SAVED
},
error: function(obj, error) {
//ERROR
}
});
}).then(function() {
//DONE
},
function(error) {
//error
});
One thing that comes to my mind immediately is that the function is getting timed-out. Parse has time limitations on each function. If I were you, I'd first load all the objects in the source class and then add them separately by having a delay between to API calls (server overload issues can also be present).
This is my working code: http://jsfiddle.net/spadez/5ntLetey/1/
It works perfectly but I'm trying to pull the remaining information into the fields. This is the code I had previously for the lat and long that I found online:
lat.val(results[0].geometry.location.lat());
lng.val(results[0].geometry.location.lng());
How can I pull the remaining information in? This is one example of what I tried and didn't work:
country_short.val(results[0].address_components.country());
Here is the API documentation, what am I doing wrong?
You're not doing anything particularly wrong, unfortunately the returned address components can vastly differ. For example if you were to geocode a coordinate set which might be in the middle of an ocean, you;'re not going to get many address components and perhaps nothing at all, whereas in the middle of somewhere like New York City there are many components that get returned.
What you need to do is to parse the returned response to find something you want like country and only insert that into your fields if and only if there is an address component that has a type of "country".
So for example to get country short and long you would do something like this:
// Get Country value.
var country = getCountry(results[0].address_components)
$('#country_long').val(country.long);
$('#country_short').val(country.short);
calling the function which looks something like this:
function getCountry(addressComponents) {
var returnCountry = {
'long': 'N/A',
'short': 'N/A'
};
// Loop through all address components and find country if possible.
$(addressComponents).each(function (componentIndex, component) {
// loop through all "types" of each component to find if there is a type of "country"
$(component.types).each(function (indexTypes, type) {
if (type == 'country') {
// Then return the long_name and short_name of the component
returnCountry.long = component.long_name;
returnCountry.short = component.short_name;
}
});
});
return returnCountry;
}
Demo: http://jsfiddle.net/5ntLetey/3/
I've been trying to do Meteor's leaderboard example, and I'm stuck at the second exercise, resetting the scores. So far, the furthest I've got is this:
// On server startup, create some players if the database is empty.
if (Meteor.isServer) {
Meteor.startup(function () {
if (Players.find().count() === 0) {
var names = ["Ada Lovelace",
"Grace Hopper",
"Marie Curie",
"Carl Friedrich Gauss",
"Nikola Tesla",
"Claude Shannon"];
for (var i = 0; i < names.length; i++)
Players.insert({name: names[i]}, {score: Math.floor(Random.fraction()*10)*5});
}
});
Meteor.methods({
whymanwhy: function(){
Players.update({},{score: Math.floor(Random.fraction()*10)*5});
},
}
)};
And then to use the whymanwhy method I have a section like this in if(Meteor.isClient)
Template.leaderboard.events({
'click input#resetscore': function(){Meteor.call("whymanwhy"); }
});
The problem with this is that {} is supposed to select all the documents in MongoDB collection, but instead it creates a new blank scientist with a random score. Why? {} is supposed to select everything. I tried "_id" : { $exists : true }, but it's a kludge, I think. Plus it behaved the same as {}.
Is there a more elegant way to do this? The meteor webpage says:
Make a button that resets everyone's score to a random number. (There
is already code to do this in the server startup code. Can you factor
some of this code out and have it run on both the client and the
server?)
Well, to run this on the client first, instead of using a method to the server and having the results pushed back to the client, I would need to explicitly specify the _ids of each document in the collection, otherwise I will run into the "Error: Not permitted. Untrusted code may only update documents by ID. [403]". But how can I get that? Or should I just make it easy and use collection.allow()? Or is that the only way?
I think you are missing two things:
you need to pass the option, {multi: true}, to update or it will only ever change one record.
if you only want to change some fields of a document you need to use $set. Otherwise update assumes you are providing the complete new document you want and replaces the original.
So I think the correct function is:
Players.update({},{$set: {score: Math.floor(Random.fraction()*10)*5}}, {multi:true});
The documentation on this is pretty thorough.