Google Places photos getUrl() is breaking my javascript - google-maps-api-3

I have the following javascript:
function callback(results, status) {
if (status == google.maps.places.PlacesServiceStatus.OK) {
for (var i = 0; i < results.length; i++) {
place = new Object ({
name: results[i].name,
photo: results[i].photos[0].getUrl({'maxWidth': 100, 'maxHeight': 100}),
loc: results[i].geometry.location,
rating: results[i].rating,
})
places.push(place);
var marker = new google.maps.Marker({
map: map,
position: results[i].geometry.location,
id: i,
visible: false,
});
markers.push(marker);
}
}
newresult();
}
If i comment out the following line the function newresult() will run:
photo: results[i].photos[0].getUrl({'maxWidth': 100, 'maxHeight': 100}),
However, as in the above code, if i do not comment it out the function newresult() will not run. I know that the getUrl function of photo works as it returns a valid url.
Thanks for any help.

I know that the getUrl function of photo works as it returns a valid
url.
Yes, IF there is a photo associated with the place! No photo for a place = no photo array in the results[i] object. And then your code breaks. You must in each iteration check if the photo array is present before using it :
place = new Object ({
name: results[i].name,
photo: typeof results[i].photos !== 'undefined'
? results[i].photos[0].getUrl({'maxWidth': 100, 'maxHeight': 100})
: '' //alternative a "nophoto.jpg"
loc: results[i].geometry.location,
rating: results[i].rating,
});
Here a fiddle based on your code from above -> http://jsfiddle.net/dX9Gu/

Related

Extend object on Meteor

When i add/edit blogPost, i've my object with all properties. My code :
Add post :
Template.postListAdmin.events({
'submit form': (e) => {
// Prevent default browser form submit
e.preventDefault();
let image = $('#js-image-uploaded'),
draft = $('[name="draft"]'),
isSmall = false,
isDrafted = false;
// If post draft, return true
if (draft.is(':checked')) isDrafted = true;
// If post image is small
// return true for add 'small' classe
if (image.height() < 80) isSmall = true;
let post = {
title: $('[name="title"]').val(),
image: image.attr('src'),
isSmall: isSmall,
description: $('[name="description"]').val(),
category: $('[name="category"]').val(),
time: $('[name="time"]').val(),
dateCreated: dateFormat($('[name="dateCreated"]').val(), 'yyyy-mm-dd'),
content: $('[name="content"]').val(),
draft: isDrafted
};
Meteor.call('posts.insert', post);
setTimeout(() => {
$('#js-post-form')
.toggleClass('is-hidden')
.find('input, textarea').val('');
}, 500);
}
});
Edit post :
Template.postEdit.events({
'submit form': function (e) {
e.preventDefault();
let image = $('#js-image-uploaded'),
draft = $('[name="draft"]'),
isSmall = false,
isDrafted;
if (draft.is(':checked')) isDrafted = true;
else isDrafted = false;
if (image.height() < 80) isSmall = true;
let post = {
slug: $('[name="title"]').val(),
title: $('[name="title"]').val(),
image: image.attr('src'),
isSmall: isSmall,
description: $('[name="description"]').val(),
category: $('[name="category"]').val(),
time: $('[name="time"]').val(),
dateCreated: dateFormat($('[name="dateCreated"]').val(), 'yyyy-mm-dd'),
dateModified: new Date(),
content: $('[name="content"]').val(),
draft: isDrafted
};
Meteor.call('posts.edit', this._id, post);
Router.go('postListAdmin');
},
});
I would like optimize my code and avoid creating my object 'post' 2x.
Do you have any idea how i can optim this ?
Thank you every boby :)
You should be able to achieve what you want to do by defining post without the let keyword.
For example:
post = {
title: $('[name="title"]').val(),
image: image.attr('src'),
isSmall: isSmall,
description: $('[name="description"]').val(),
category: $('[name="category"]').val(),
time: $('[name="time"]').val(),
dateCreated: dateFormat($('[name="dateCreated"]').val(), 'yyyy-mm-dd'),
content: $('[name="content"]').val(),
draft: isDrafted
};
You will have to decide how you want to handle the two instances, though, since they are not exactly the same. Also, moving the variable definition outside of either file might be helpful for organizing your code. You could use a directory on the client named utils, and add a file that contains your global variable definitions.

Jasmine - Testing links via Webdriver I/O

I have been working on a end-to-end test using Webdriver I/O from Jasmine. One specific scenario has been giving me significant challenges.
I have a page with 5 links on it. The number of links actually challenges as the page is dynamic. I want to test the links to see if each links' title matches the title of the page that it links to. Due to the fact that the links are dynamically generated, I cannot just hard code tests for each link. So, I'm trying the following:
it('should match link titles to page titles', function(done) {
client = webdriverio.remote(settings.capabilities).init()
.url('http://www.example.com')
.elements('a').then(function(links) {
var mappings = [];
// For every link store the link title and corresponding page title
var results = [];
for (var i=0; i<links.value.length; i++) {
mappings.push({ linkTitle: links.value[0].title, pageTitle: '' });
results.push(client.click(links.value[i])
.getTitle().then(function(title, i) {
mappings[i].pageTitle = title;
});
);
}
// Once all promises have resolved, compared each link title to each corresponding page title
Promise.all(results).then(function() {
for (var i=0; i<mappings.length; i++) {
var mapping = mappings[i];
expect(mapping.linkTitle).toBe(mapping.pageTitle);
}
done();
});
});
;
});
I'm unable to even confirm if I'm getting the link title properly. I believe there is something I entirely misunderstand. I am not even getting each links title property. I'm definately not getting the corresponding page title. I think I'm lost in closure world here. Yet, I'm not sure.
UPDATE - NOV 24
I still have not figured this out. However, i believe it has something to do with the fact that Webdriver I/O uses the Q promise library. I came to this conclusion because the following test works:
it('should match link titles to page titles', function(done) {
var promise = new Promise(function(resolve, reject) {
setTimeout(function() { resolve(); }, 1000);
});
promise.then(function() {
var promises = [];
for (var i=0; i<3; i++) {
promises.push(
new Promise(function(resolve, reject) {
setTimeout(function() {
resolve();
}, 500);
})
);
}
Promise.all(promises).then(function() {
expect(true).toBe(true)
done();
});
});
However, the following does NOT work:
it('should match link titles to page titles', function(done) {
client = webdriverio.remote(settings.capabilities).init()
.url('http://www.example.com')
.elements('a').then(function(links) {
var mappings = [];
// For every link store the link title and corresponding page title
var results = [];
for (var i=0; i<links.value.length; i++) {
mappings.push({ linkTitle: links.value[0].title, pageTitle: '' });
results.push(client.click(links.value[i])
.getTitle().then(function(title, i) {
mappings[i].pageTitle = title;
});
);
}
// Once all promises have resolved, compared each link title to each corresponding page title
Q.all(results).then(function() {
for (var i=0; i<mappings.length; i++) {
var mapping = mappings[i];
expect(mapping.linkTitle).toBe(mapping.pageTitle);
}
done();
});
})
;
});
I'm not getting any exceptions. Yet, the code inside of Q.all does not seem to get executed. I'm not sure what to do here.
Reading the WebdriverIO manual, I feel like there are a few things wrong in your approach:
elements('a') returns WebElement JSON objects (https://code.google.com/p/selenium/wiki/JsonWireProtocol#WebElement_JSON_Object) NOT WebElements, so there is no title property thus linkTitle will always be undefined - http://webdriver.io/api/protocol/elements.html
Also, because it's a WebElement JSON object you cannot use it as client.click(..) input, which expects a selector string not an object - http://webdriver.io/api/action/click.html. To click a WebElement JSON Object client.elementIdClick(ID) instead which takes the ELEMENT property value of the WebElement JSON object.
When a client.elementIdClick is executed, the client will navigate to the page, trying to call client.elementIdClick in the next for loop cycle with next ID will fail, cause there is no such element as you moved away from the page. It will sound something like invalid element cache.....
So, I propose another solution for your task:
Find all elements as you did using elements('a')
Read href and title using client.elementIdAttribute(ID) for each of the elements and store in an object
Go through all of the objects, navigate to each of the href-s using client.url('href'), get the title of the page using .getTitle and compare it with the object.title.
The source I experimented with, not run by Jasmine, but should give an idea:
var client = webdriverio
.remote(options)
.init();
client
.url('https://www.google.com')
.elements('a')
.then(function (elements) {
var promises = [];
for (var i = 0; i < elements.value.length; i++) {
var elementId = elements.value[i].ELEMENT;
promises.push(
client
.elementIdAttribute(elementId, 'href')
.then(function (attributeRes) {
return client
.elementIdAttribute(elementId, 'title')
.then(function (titleRes) {
return {href: attributeRes.value, title: titleRes.value};
});
})
);
}
return Q
.all(promises)
.then(function (results) {
console.log(arguments);
var promises = [];
results.forEach(function (result) {
promises.push(
client
.url(result.href)
.getTitle()
.then(function (title) {
console.log('Title of ', result.href, 'is', title, 'but expected', result.title);
})
);
});
return Q.all(promises);
});
})
.then(function () {
client.end();
});
NOTE:
This fails to solve your problem, when the links trigger navigation with JavaScript event handlers not the href attributes.

Google Embed API format data before calling .execute()

I need to format the response I get from Analytics before showing it inside a Google Chart, I tried editing the response when the on("success"... method gets fired but I found that it gets called after the .execute().
Is there any way to edit the response after receiving it and before it populates the chart?
This is my function:
var dataChart5 = new gapi.analytics.googleCharts.DataChart({
reportType: 'ga',
query: {
'ids': 'ga:***', // My ID
'start-date': '31daysAgo',
'end-date': 'yesterday',
'metrics': 'ga:users,ga:percentNewSessions,ga:sessions,ga:bounceRate,ga:avgSessionDuration,ga:pageviews,ga:pageviewsPerSession',
'prettyPrint':'true',
},
chart: {
'container': 'chart-5-container',
'type': 'TABLE',
'options': {
'width': '100%',
'title': 'test'
}
}
});
dataChart5.on('success', function(response) {
response.data.cols[0].label = "test1"; //here I edit the response
console.log(response);
});
dataChart5.execute();
Using the console.log(response); I can see that the record label gets modified but the chart gets populated before the edit.
I think a have a workaround. It has problems, but might be useful. While handling the success event, call a function that will recursively walk through the child elements of $('#chart-5-container') and apply your formatting there.
One problem with that approach is that the positions of the elements won't be recalculated. Therefore, with different string sizes you might get overlapping strings. Moreover, it seems not to be affecting the tooltip.
I'm using this approach to translate to Portuguese.
function recursiveTranslate(e) {
var key = e.html(),
dict = {};
dict['Date'] = 'Data';
dict['Users'] = 'Visitantes';
dict['Sessions'] = 'Visitas';
dict['Pageviews'] = 'Visualizações';
if (key in dict) {
e.html(dict[key]);
}
for (var i = 0; i < e.children().length; i++) {
recursiveTranslate($(e.children()[i]));
}
}
Then I call recursiveTranslate inside the success event:
dataChart5.on('success', function h(obj) {
recursiveTranslate($('#chart-5-container'));
});
It is not elegant and has a lot of issues. I would really like to get my hands on the proper solution.

Filter results from Google Autocomplete

Is there a way to get the results from Google Autocomplete API before it's displayed below the input? I want to show results from any country except U.S.A.
I found this question: Google Maps API V3 - Anyway to retrieve Autocomplete results instead of dropdown rendering it? but it's not useful, because the method getQueryPredictions only returns 5 elements.
This is an example with UK and US Results: http://jsfiddle.net/LVdBK/
Is it possible?
I used the jquery autocomplete widget and called the google methods manually.
For our case, we only wanted to show addresses in Michigan, US.
Since Google doesn't allow filtering out responses to that degree you have to do it manually.
Override the source function of the jquery autocomplete
Call the google autocompleteService.getQueryPredictions method
Filter out the results you want and return them as the "response" callback of the jquery autocomplete.
Optionally, if you need more detail about the selected item from Google, override the select function of the jquery autocomplete and make a call to Google's PlacesService.getDetails method.
The below assumes you have the Google api reference with the "places" library.
<script src="https://maps.googleapis.com/maps/api/js?key=[yourKeyHere]&libraries=places&v=weekly" defer></script>
var _autoCompleteService; // defined globally in script
var _placesService; // defined globally in script
//...
// setup autocomplete wrapper for google places
// starting point in our city
var defaultBounds = new google.maps.LatLngBounds(
new google.maps.LatLng('42.9655426','-85.6769166'),
new google.maps.LatLng('42.9655426','-85.6769166'));
if (_autoCompleteService == null) {
_autoCompleteService = new google.maps.places.AutocompleteService();
}
$("#CustomerAddress_Street").autocomplete({
minLength: 2,
source: function (request, response) {
if (request.term != '') {
var googleRequest = {
input: request.term,
bounds: defaultBounds,
types: ["geocode"],
componentRestrictions: { 'country': ['us'] },
fields: ['geometry', 'formatted_address']
}
_autoCompleteService.getQueryPredictions(googleRequest, function (predictions) {
var michiganOnly = new Array(); // array to hold only addresses in Michigan
for (var i = 0; i < predictions.length; i++) {
if (predictions[i].terms.length > 0) {
// find the State term. Could probably assume it's predictions[4], but not sure if it is guaranteed.
for (var j = 0; j < predictions[i].terms.length; j++) {
if (predictions[i].terms[j].value.length == 2) {
if (predictions[i].terms[j].value.toUpperCase() == 'MI') {
michiganOnly.push(predictions[i]);
}
}
}
}
}
response(michiganOnly);
});
}
},
select: function (event, ui) {
if (ui != null) {
var item = ui.item;
var request = {
placeId: ui.item.place_id
}
if (_placesService == null) {
$("body").append("<div id='GoogleAttribution'></div>"); // PlacesService() requires a field to put it's attribution image in. For now, just put on on the body
_placesService = new google.maps.places.PlacesService(document.getElementById('GoogleAttribution'));
}
_placesService.getDetails(request, function (result, status) {
if (result != null) {
const place = result;
if (!place.geometry) {
// User entered the name of a Place that was not suggested and
// pressed the Enter key, or the Place Details request failed.
//window.alert("No details available for input: '" + place.name + "'");
return;
}
else {
var latitude = place.geometry.location.lat();
var longitude = place.geometry.location.lng();
// do something with Lat/Lng
}
}
});
}
}
}).autocomplete("instance")._renderItem = function (ul, item) {
// item is the prediction object returned from our call to getQueryPredictions
// return the prediction object's "description" property or do something else
return $("<li>")
.append("<div>" + item.description + "</div>")
.appendTo(ul);
};
$("#CustomerAddress_Street").autocomplete("instance")._renderMenu = function (ul, items) {
// Google's terms require attribution, so when building the menu, append an item pointing to their image
var that = this;
$.each(items, function (index, item) {
that._renderItemData(ul, item);
});
$(ul).append("<li class='ui-menu-item'><div style='display:flex;justify-content:flex-end;'><img src='https://maps.gstatic.com/mapfiles/api-3/images/powered-by-google-on-white3.png' /></div></li>")
}

How do I make a collection reactive based on the contents of another?

In short, I want to do:
Meteor.publish('items', function(){
return Item.find({categoryId: Categories.find({active: true} });
});
The flag 'active' as part of 'Categories' changes regularly.
I also tried unsub/resub to the Items collection by leveraging reactivity on the Categories collections, and it works, unfortunately it re-triggers on ANY modification to the Categories collection, regardless if it affected the 'active' flag or not.
What are my options?
Nothing solved the issue of the items not being 'deleted' locally when the category is flagged as inactive on the server. Solution (ish) is to:
Client:
Categories.find({active: true}).observeChanges({
added: function(){
itemsHandle && itemsHandle.stop();
itemsHandle = Meteor.subscribe("items");
}
});
Server:
Meteor.publish('items', function(){
var category = Categories.findOne({active: true});
return category && Items.find({categoryId: Categories.findOne({active: true}._id);
});
I realize this isn't perfect (still uses client side code), but it works and its the cleanest I could think of. I hope it helps someone!
A possible solution is to create a dependency object, watch for all categories change, and trigger the dep change if the active flag was toggled. Something along these lines:
var activeCount = Categories.find({active: true}).count();
var activeDep = new Deps.Dependency();
Deps.autorun(function() {
var activeCountNow = Categories.find({active: true}).count();
if(activeCountNow !== activeCount) {
activeCount = activeCountNow;
activeDep.changed();
}
});
Meteor.publish('items', function(){
activeDep.depend();
return Item.find({categoryId: Categories.find({active: true} });
});
Note: I'm only verifying whether the number of active categories have changes so that I don't have to keep the active list in the memory. This may or may not be appropriate depending on how your app works.
Edit: Two-sided flavor mentioned in the comments:
Client:
var activeCount = Categories.find({active: true}).count();
var activeDep = new Deps.Dependency();
Deps.autorun(function() {
var activeCountNow = Categories.find({active: true}).count();
if(activeCountNow !== activeCount) {
activeCount = activeCountNow;
activeDep.changed();
}
});
Deps.autorun(function(){
activeDep.depend();
Meteor.subscribe('items', new Date().getTime());
});
Server:
Meteor.publish('items', function(timestamp) {
var t = timestamp;
return Item.find({categoryId: Categories.find({active: true} });
});
Meteor.startup(function() {
Categories.find().observe({
addedAt: function(doc) {
trigger();
},
changedAt: function(doc, oldDoc) {
if(doc.active != oldDoc.active) {
trigger();
}
},
removedAt: function(oldDoc) {
trigger();
}
});
});
Now, the trigger function should cause the publish to rerun. This time it's easy when it's on the client (change subscription param). I'm not sure how to do this on the server - perhaps run publish again.
I use the following publish to solve a similar issue. I think it is only the one line nesting of queries that limits the reactivity. Breaking one query out inside the publish function seems to avoid the issue.
//on server
Meteor.publish( "articles", function(){
var self= this;
var subscriptions = [];
var observer = Feeds.find({ subscribers: self.userId }, {_id: 1}).observeChanges({
added: function (id){
subscriptions.push(id);
},
removed: function (id){
subscriptions.splice( subscriptions.indexOf(id)) , 1);
}
});
self.onStop( function() {
observer.stop();
});
var visibleFields = {_id: 1, title: 1, source: 1, date: 1, summary: 1, link: 1};
return Articles.find({ feed_id: {$in: subscriptions} }, { sort: {date: -1}, limit: articlePubLimit, fields: visibleFields } );
});
//on client anywhere
Meteor.subscribe( "articles" );
Here is another SO example which gets the search criteria from the client through subscribe if you decide that is acceptable.
Update: Since the OP struggled to get this going I made a gist and launched a working version on meteor.com. If you just need the publish function it is as above.

Resources