I am using google places api with knockout.js, but the issue that I am having is the ability to filter by City, State or ZipCode.
In this code:
var options = {
types: ['geocode'],
componentRestrictions: {
country: "us"
}
};
How do you specify that I want 'geocode' and '(cities)'?
So users can type in City Name or Zipcode?
Current code doesn't work for Zipcode!
Also need to format the result to show City, State instead of City, State Country.
ko.bindingHandlers.addressAutocomplete = {
init: function (element, valueAccessor, allBindingsAccessor) {
var value = valueAccessor(),
allBindings = allBindingsAccessor();
var options = {
types: ['geocode'],
componentRestrictions: {
country: "us"
}
};
ko.utils.extend(options, allBindings.autocompleteOptions)
var autocomplete = new google.maps.places.Autocomplete(element, options);
google.maps.event.addListener(autocomplete, 'place_changed', function () {
result = autocomplete.getPlace();
value(result.formatted_address);
});
},
update: function (element, valueAccessor, allBindingsAccessor) {
ko.bindingHandlers.value.update(element, valueAccessor);
}
};
you can't use multiple types(except geocode & establishment)
the types-option applies a filter to the results, not to the input. When you've set the types to geocode , and you don't get a result when a user enters a Zip-code, there are no results for that particular input(the API doesn't recognize that the input is a ZIP-code, it takes the input and searches for anything that matches, no matter if it's a postal_code, a street_number or something else)
To apply a custom format you must parse the address_components, see: Parsing city/state from Google Maps request
Related
I have a FullCalendar scheduler on a webapp which has 2 way databinding for resources and events, all working great. I want to be able to present the user with a dropdown that enables them to toggle the visibility of a column, ideally completely client side.
I have tried a combination of addResource / removeResource however my issue here is that a rerender of the calendar (e.g. when a new event is added) then displays the previously removed resource. I can work around this however would prefer a really simple approach using JS / CSS. I currently cannot find a way to set a resource to not be visible, or to have zero width - is this possible?
There is an easy way to do this:
Store resources in an array variable resourceData.
Create another array called visibleResourceIds to store the ids of any resources you want to show.
In the resources callback function, filter resourceData to only contain the resources where the resource id exists in visibleResourceIds. Return the filtered array and fullcalendar will only add the desired resources for you.
To remove a resource from view, simply remove the resource id from visibleResourceIds and refetchResources. To add the resource back in, add the id to visibleResourceIds and refetchResources. DONE.
JSFiddle
var resourceData = [
{id: "1", title: "R1"},
{id: "2", title: "R2"},
{id: "3", title: "R3"}
];
var visibleResourceIds = ["1", "2", "3"];
// Your button/dropdown will trigger this function. Feed it resourceId.
function toggleResource(resourceId) {
var index = visibleResourceIds.indexOf(resourceId);
if (index !== -1) {
visibleResourceIds.splice(index, 1);
} else {
visibleResourceIds.push(resourceId);
}
$('#calendar').fullCalendar('refetchResources');
}
$('#calendar').fullCalendar({
defaultView: 'agendaDay',
resources: function(callback) {
// Filter resources by whether their id is in visibleResourceIds.
var filteredResources = [];
filteredResources = resourceData.filter(function(x) {
return visibleResourceIds.indexOf(x.id) !== -1;
});
callback(filteredResources);
}
});
I had the same challenge. Instead of a dropdown, I use checkboxes, but the workings will be the same.
My resources are stored in a variable, when I uncheck a box, the resource is removed and the resource's object is added to another array with the resourceId as key, and the index added to the object to restore the object in the same column as it originally was. When re-checking the box, the object is added to the resources array and the resources refetched.
/* retrieve the resources from the server */
var planningResources;
var removedResource = [];
$.ajax({
url: '/planning/resources/',
method: 'get',
success: function (response) {
planningResources = response;
showCalendar();
}
, error: function () {
if (typeof console == "object") {
console.log(xhr.status + "," + xhr.responseText + "," + textStatus + "," + error);
}
}
});
/* create the calendar */
showCalendar = function () {
$('#calendar').fullCalendar({
...
});
}
/* checkbox on click */
$('.resource').click(function() {
var resourceId = $(this).val();
var hideResource = !$(this)[0].checked;
$('.status:checkbox:checked').each(function () {
});
if(hideResource) {
$.each(planningResources, function(index, value){
if( value && value.id == resourceId ) {
value.ndx = index;
removedResource[resourceId] = value;
planningResources.splice(index,1);
return false;
}
});
$('#planningoverview').fullCalendar(
'removeResource',
resourceId
);
}
else {
planningResources.splice(removedResource[resourceId].ndx, 0, removedResource[resourceId]);
$('#planningoverview').fullCalendar('refetchResources');
}
});
showCalendar();
It probably doesn't get first price in a beauty contest, but it works for me ...
Cheers
You can use the resourceColumns option for this. In the column objects you can set the width property to a number of pixels or a percentage. If you pass a function here you can easily handle the width property someplace else. Your hide/show function can then set the width to 0 to hide the column. After that you can trigger reinitView to update the view: $('#calendar').fullCalendar("reinitView");
I'm currently trying to reactively show markers on a Mapbox map. My approach was to observe a collection and while doing so, create a GeoJSON object. Changes in that particular object do not reflect on the map however.
var featureArray = [];
CollectionName.find({}).observe({
added: function(item) {
featureArray.push({
type: "Feature",
geometry: {
type: "Point",
coordinates: [+item.location.coordinates[0], +item.location.coordinates[1]]
},
properties: {
_id: item._id,
}
});
},
changedAt: function(newDocument, oldDocument, atIndex) {
//..
},
removed: function(oldDocument, atIndex) {
//..
}
});
var CollectionGeoJSON = {
'type': 'FeatureCollection',
'features': testmarkers
};
var markers = L.mapbox.featureLayer().setGeoJSON(CollectionGeoJSON);
// add cluster markers and add them to map
My idea was to manually add/remove/change the markers on the client (as changes are synced to the server anyway), however also no success here as I'm not sure how to do that using the Mapbox API.
Any hints are greatly appreciated.
I've created a meteorpad example, showing this.
The reactivity is created by calling template.autorun in the onRendered callback. The template.autorun callback is triggered by any changes to the results of Cities.find(), and updates the map with .setGeoJSON
this.autorun(function () {
if (Mapbox.loaded()) {
geojson = Cities.find().fetch()
view.featureLayer.setGeoJSON(geojson);
}
});
In this example the contents of the Cities collection are already in the correct format to be passed to .setGeoJSON, but if you prefer you could have a different Colleciton schema, and create the list in this format within the template.autorun callback.
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.
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>")
}
In my Meteor app I have Lists which have Items. The Items can belong to multiple Lists, so the Lists contain a field with an array of Item IDs. When I go to a single List view I want a data object with the Items queried from this array of IDs. I think the query would look like this:
Items.find({ _id: { $in: theArrayOfIds } });
However how/where do I make this query when I load my single List view? At the moment this is my route declaration:
this.route('list', {
path: '/list/:_id',
data: function() {
return {
list: Lists.findOne(this.params._id)
}
}
});
Can I point at the future result of the list object somehow? Or do I make this query somewhere else?
In Iron Router, the data value of your route can be an object as well as a function. See here in the documentation. Therefore you can return both the items and the list in the data call as follows:
this.route('list', {
path: '/list/:_id',
data: {
list: function(){
var id = Router._currentController.params._id;
return Lists.findOne(id);
},
items: function(){
var id = Router._currentController.params._id;
var list = Lists.findOne(id);
if (!!list){
/* assuming your array of item ids is a field on list named items */
var theArrayOfIds = list.items;
}
return !!list && Items.find({ _id: {$in: theArrayOfIds}});
}
});