Google Autocomplete with Apartment Number - google-maps-api-3

When a user adds an apartment number to their address, the autocomplete seems to think it's looking at a zipcode, and changes the suggested matches:
6250 Hollywood Boulevard #20
Is there a way to get this to work, or if not, a way to ignore the apartment number and send it along with the form submission as a separate value? (keeping in mind a user might use #, Unit, Apt, etc to define their unit number)
var placeSearch, autocomplete;
var componentForm = {
street_number: 'short_name',
route: 'short_name',
locality: 'long_name',
administrative_area_level_1: 'short_name',
country: 'long_name',
postal_code: 'short_name'
};
function initialize(){
autocomplete = new google.maps.places.Autocomplete(
(document.getElementById('autocomplete-address')),
{ types: ['geocode'] });
google.maps.event.addListener(autocomplete, 'place_changed', function(){
fillInAddress();
}
);
}
function fillInAddress(){
var place = autocomplete.getPlace();
for(var component in componentForm){
document.getElementById(component).value = '';
document.getElementById(component).disabled = false;
}
for(var i = 0; i < place.address_components.length; i++){
var addressType = place.address_components[i].types[0];
if(componentForm[addressType]){
var val = place.address_components[i][componentForm[addressType]];
document.getElementById(addressType).value = val;
}
}
}

There does not seem to exist a setting to set the Autocomplete API to ignore the apt number, however, we can trim the text before we pass the string to the API.
in your autocomplete = new google.maps.places.Autocomplete(, you can do something like:
document.getElementById('autocomplete-address').replace(/#.*/g,"");
since document.getElementById('autocomplete-address') returns a string, we can replace the #number paer with an empty string with .replace(/#.*/g,"")

Related

How to browse to the next page in a datasource that is loaded into table in Google AppMaker

I'm working on a requirement where I have a datasource named 'emailSearchResults' where I search for email messages metadata and load the results in the datasource.
The fields in the datasource are not relevant, however I set the datasource to have 50 records per page as per the below screenshot:
The script I used to load the datasource is shown in the query field, that call the following script:
function getMessageDetails(userId, msgID)
{
var messageDetails = [];
var messageData;
var msgID_,subject_,from_,date_;
messageData=Gmail.Users.Messages.get(userId,msgID,{format:"metadata", metadataHeaders:["Message-ID", "Subject", "From", "Date"]});
console.log(messageData.payload.headers);
//console.log(msgID);
//console.log(messageData.payload.headers[3].value);
date_="<na>";
from_="<na>";
subject_="<na>";
msgID_="<na>";
for (var counter =0;counter<4;counter++)
{
if (messageData.payload.headers[counter].name=="Message-ID")
{
msgID_=messageData.payload.headers[counter].value;
}
if (messageData.payload.headers[counter].name=="Subject")
{
subject_=messageData.payload.headers[counter].value;
}
if (messageData.payload.headers[counter].name=="From")
{
from_=messageData.payload.headers[counter].value;
}
if (messageData.payload.headers[counter].name=="Date")
{
date_=messageData.payload.headers[counter].value;
}
}
messageDetails.push(date_);
messageDetails.push(from_);
messageDetails.push(subject_);
messageDetails.push(msgID_);
return messageDetails;
}
function searchMessages(userId,condition)
{
//
// first we build the conditions
// we can make it fixed
// or we can make it dynamic
var searchResult;
var deleteResult;
var currentMessage;
var results = [];
var pageToken;
var params = {};
var _stat;
var options = {
includeSpamTrash: "true",
pageToken: pageToken
};
var msgRecord = [];
do
{
searchResult=Gmail.Users.Messages.list(userId,options);
for (var i = 0; i < searchResult.messages.length; i++)
{
var record=app.models.emailSearchResults.newRecord();
msgRecord=getMessageDetails(userId,searchResult.messages[i].id);
record.msgMainID=searchResult.messages[i].id;
record.msgID=msgRecord[3];
record.subject=msgRecord[2];
record.senderAddress=msgRecord[1];
record.msgDate=msgRecord[0];
/*console.log(searchResult.messages[i].id);
console.log(msgRecord[3]);
console.log(msgRecord[2]);
console.log(msgRecord[1]);
console.log(msgRecord[0]);
return;*/
results.push(record);
msgRecord=null;
}
if (searchResult.nextPageToken) {
options.pageToken = searchResult.nextPageToken;
}
} while (searchResult.pageToken);
searchResult=null;
return results;
}
On the main page I put a table and linked it to the datasource, and I enabled pagination on the table, so I get the pager buttons at the bottom of the table as below:
When I execute the app and the datasource is filled, I see the first page results in a correct way, however when I want to move to the next page, I click the next page button and once the loading is complete I find out that I still see the same results from the first page on the table.
I am not familiar with how to make the table show the results of the second page then the third page, and I am going in circles on this...
Hope the explanation is clear and addresses the issue..
I would really appreciate any help on this!
Regards
Currently pagination isn't working as expected with calculated datasources. You can, however, build your own. There are several changes you'll need to make to accomplish this. First you'll want to refactor your searchMessages function to something like this:
function searchMessages(userId, pageToken){
var results = [];
var options = {
includeSpamTrash: "true",
pageToken: pageToken,
maxResults: 50
};
var searchResult = Gmail.Users.Messages.list(userId, options);
for (var i = 0; i < searchResult.messages.length; i++){
var record = app.models.emailSearchResults.newRecord();
var msgRecord = getMessageDetails(userId,searchResult.messages[i].id);
record.msgMainID = searchResult.messages[i].id;
record.msgID = msgRecord[3];
record.subject = msgRecord[2];
record.senderAddress = msgRecord[1];
record.msgDate = msgRecord[0];
results.push(record);
}
return {records: results, nextPageToken: searchResult.nextPageToken};
}
Then you'll want to change your datasource query. You'll need to add a number parameter called page.
var cache = CacheService.getUserCache();
var page = query.parameters.page || 1;
var pageToken;
if(page > 1){
pageToken = cache.get('pageToken' + page.toString());
}
var results = searchMessages('me', pageToken);
var nextPage = (page + 1).toString();
cache.put('pageToken' + nextPage, results.nextPageToken);
return results.records;
You'll need to modify the pagination widget's various attributes. Here are the previous/next click functions:
Previous:
widget.datasource.query.pageIndex--;
widget.datasource.query.parameters.page = widget.datasource.query.pageIndex;
widget.datasource.load();
Next:
widget.datasource.query.pageIndex++;
widget.datasource.query.parameters.page = widget.datasource.query.pageIndex;
widget.datasource.load();
You should be able to take it from there.

places api Unable to get property 'address_components' of undefined or null reference

working with the google places api and cannot figure why autocomplete is returning undefined here on call to get places.
what developer tools shows is.
address_components is what should be returned on a call to autocomplete.getPlace
Unable to get property 'address_components' of undefined or null reference
function initAutoCompleteDynamic() {
var slideID = 99;
var idx = 99 - slideID;
var propcount = 5;
for (var i = 0; i < propcount; i++) {
var propaddress = "prop1address" + i;
var autocomplete = autocomplete + i;
autocomplete = new google.maps.places.Autocomplete(
document.getElementById(propaddress)),
{ types: ['geocode'] };
autocomplete.addListener('place_changed', fillinAddressDynamic);
}
}
and in fillinAddressDynamic
var place=autocomplete.getPlace():
for (var i = 0; i < place.address_components.length; i++) {
alert("i am in the loop");
var addressType = place.address_components[i].types[0];
var field = addressType;
var completeaddress1 = '';
var propaddress = 'prop1address' + i;
var strnum = 'streetnumber' + i;
CR(i);//calling component resolver.
if (componentFormProduction[addressType]) {
var val = place.address_components[i][componentFormProduction[addressType]];
document.getElementById(CR[addressType]).value = val;
if (field == "street_number") {
var streetnum = document.getElementById(strnum).value = val;
}
if (field == "route") {
if (streetnum) {
completeaddress1 = streetnum + ' ' + val;
}
else {
completeaddress1 = val;
}
document.getElementById('prop1address0').value = completeaddress1;
}
}
}
This would happen if the user (or you) hits Enter without clicking on a suggestion.
Typically the sequence of event is like this:
user enters input
JavaScript queries Autocomplete for suggestions
user clicks on a suggestion
JavaScript queries Details, replaces user input with Details responses' fields (incl. address_components) and fires the places_changed event
handler for places_changed will obtain the Place object from Details response by calling getPlace()
However, it may also be like this:
user enters input
JavaScript queries Autocomplete for suggestions
user disregards suggestions and hits Enter without clicking on one
JavaScript fires the places_changed event without querying Details or modifying user input
handler for places_changed calls getPlace() and gets a nearly empty Place object, with only the name field containing the raw user input.
It is for you to decide what to do with raw user input, here are some examples:
This tool uses the JavaScript Geocoding service to search for that input:
https://google-developers.appspot.com/maps/documentation/utils/geocoder/
This example (address form) does nothing with it:
https://google-developers.appspot.com/maps/documentation/javascript/examples/places-autocomplete-addressform
This (very basic) example will show an error message reporting no details:
https://google-developers.appspot.com/maps/documentation/javascript/examples/full/places-autocomplete

How to bind/referece grand parent property to/inside child knockout

Hi guys looking for some basic advice.
I have four models: BoardViewModel, List, Card, Member
var Member = function (id, name, avatar) {
var self = this;
self.id = id;
self.name = name;
self.avatar = avatar;
self.isChecked = ko.observable(false);
};
I am instantiating members property inside BoardViewModel. But I want to use a copy of this model inside each Card model to instantiate a list of assigned members.
Each card stores comma separated list of member references like
",1,2,4,5"
I am writing a loop to BoardViewModel.members and mark members as checked if id references match bore I assign it as Card.members.
The last piece of the puzzle I am missing is reference to the BoardViwModel.members.
I have a lovely example fiddler that would somewhat help to build a picture of what I am talking about.
Just bear in mind that once I have this working properly I want to replace view() binding
foreach: $root.members
with
foreach: members
If at all possible I would like to avoid passing BoardViewModel.members as parameter into List and then into Card.
Update 1
As suggested by #Jeroen here's a simplified version of my fiddler.
The top view() model which encompases a concept of lists:
var BoardViewModel = function (lists, members) {
var self = this;
// in reality members are fetched via ajax call to the server
// and instantiate as a ko.observableArray()
self.groupMembers = ko.observableArray(members);
self.lists = ko.observableArray(lists);
...
}
In reality this has a signature like this:
var boardViewModel = function (initialData)
moving on.
The child List model which encompases a concept of cards:
var List = function (id, name, cards, sortOrder, groupMembers) {
var self = this;
self.cards = ko.observableArray(cards);
...
}
in reality:
var list = function (item, groupMembers)
nothing special there really.
The child Card model which encompases the concept of card items (but lets not go there yet):
var Card = function (id, title, description, contentItemId, members, groupMembers) {
var self = this;
self.members = ko.observableArray(parseMembers(members));
// now remember each card has a members property
// which stores comma separated list ",1,4"
function (members) {
var memberList = groupMembers;
var memberRefList = members.split(',');
ko.utils.arrayForEach(memberList, function(member){
ko.utils.arrayForEach(memberRefList, function(memberId){
if(member.id === meberId) {
member.isChecked(true);
}
});
});
return memberList;
}
...
}
in reality:
var card = function (item, groupMembers)
nothing too fancy there either.
I currently have something like this working on my dev environment.
Problem:
Those with keen eyes probably noticed the way I was passing groupMembers all the way up. I am not particularly hyped about the idea.
Anyone know a better way of implementing this? i.e. why can't I just do something like
var memberList = self.parent.parent.groupMembers;
for instance.
As per me, the better way to do is to have the child viewmodels inside the parent view-model. like this where you can access the parent data members as well as methods directly.
ViewModel
var BoardViewModel = function(){
var self = this;
this.members = ko.observableArray();
this.lists = ko.observableArray();
// Child View Models
this.Member = function(member){
this.id = member.id;
this.name = member.name;
this.avatar = member.avatar;
this.isChecked = ko.observable(false);
}
this.List = function(list){
// same for this
};
this.Card = function(card){
// same for this
};
// a Method to bind the data with the observables and arrays
// Assuming data is a json object having Members, List objects
this.applyData = function(data){
self.members(jQuery.map(data.Members, function(item){
return new self.Member(item);
}));
self.lists(jQuery.map(data.Lists, function(item){
return new self.List(item);
}));
}
}
onDom ready
// json object holding your data
var data = {
"Members" : [
],
"Lists" : [
],
"Cards" : [
]
};
var vm = new BoardViewModel();
$(function(){
ko.applyBindings(vm, document.getElementById('myModule'));
vm.applyData(data);
});

google places api many filters by type Ex.: restaurant,cafe

I'm trying to change the radius category/type filter for a checkbox, so I changed the var type to an array.
ORIGINAL WORKING:
var type;
for (var i = 0; i < document.controls.type.length; i++){
if (document.controls.type[i].checked){
type = document.controls.type[i].value;
}
}
startBox.setBounds(map.getBounds());
var search = {
// keyword: 'comocomo', // not needed with the autocomplete / startBox
bounds: map.getBounds()
};
if (type != 'establishment'){
search.types = [ type ];
}
places.search(search, function(placesArr, status){
THE ONE WITH THE ARRAY NOT WORKING: edited:
var type=[];
for (var i = 0; i < document.controls.type.length; i++){
if (document.controls.type[i].checked){
type.push(document.controls.type[i].value)
}
}
startBox.setBounds(map.getBounds());
var search = {
bounds: map.getBounds()
};
var quotedAndCommaSeparated = "'" + type.join("','") + "'";
alert(quotedAndCommaSeparated); // 'establishment','restaurant','lodging'
search.types = [ quotedAndCommaSeparated ];
I made many tests, and I don't see what I'm doing wrong. the map doesn't even show.
What's this meant to be, doesn't look like valid Javascript to me:
var type[];
Should be
var type = [];
Fix the javascript errors in your code otherwise the map won't show up.
Update:
What you have in quotedAndCommaSeparated is a string like "'a','b','c'" that looks a bit like the contents of an array: ['a','b','c']. But it's not an array, it's just a single string. If you check the length of your search.type array, I'm guessing it equals 1.
What you can do is split your string on the comma to turn it into an array:
search.types = quotedAndCommaSeparated.split(",");

asp.net mvc - how to update dropdown list in tinyMCE

Scenario: I have a standard dropdown list and when the value in that dropdownlist changes I want to update another dropdownlist that exists in a tinyMCE control.
Currently it does what I want when I open the page (i.e. the first time)...
function changeParent() {
}
tinymce.create('tinymce.plugins.MoePlugin', {
createControl: function(n, cm) {
switch (n) {
case 'mylistbox':
var mlb = cm.createListBox('mylistbox', {
title: 'Inserts',
onselect: function(v) {
tinyMCE.execCommand("mceInsertContent",false,v);
}
});
<% foreach (var insert in (ViewData["Inserts"] as List<String>)) { %> // This is .NET
yourobject = '<%= insert %>'; // This is JS AND .NET
mlb.add(yourobject, yourobject); // This is JavaScript
<% } %>
// Return the new listbox instance
return mlb;
}
return null;
}
});
<%= Html.DropDownList(Model.Record[184].ModelEntity.ModelEntityId.ToString(), ViewData["Containers"] as SelectList, new { onchange = "changeParent(); return false;" })%>
I am thinking the way to accomplish this (in the ChangeParentFunction) is to call a controller action to get a new list, then grab the 'mylistbox' object and reassign it, but am unsure how to put it all together.
As far as updating the TinyMCE listbox goes, you can try using a tinymce.ui.NativeListBox instead of the standard tinymce.ui.ListBox. You can do this by setting the last argument to cm.createListBox to tinymce.ui.NativeListBox. This way, you'll have a regular old <select> that you can update as you normally would.
The downside is that it looks like you'll need to manually hook up your own onchange listener since NativeListBox maintains its own list of items internally.
EDIT:
I played around a bit with this last night and here's what I've come up with.
First, here's how to use a native list box and wire up our own onChange handler, the TinyMCE way:
// Create a NativeListBox so we can easily modify the contents of the list.
var mlb = cm.createListBox('mylistbox', {
title: 'Inserts'
}, tinymce.ui.NativeListBox);
// Set our own change handler.
mlb.onPostRender.add(function(t) {
tinymce.dom.Event.add(t.id, 'change', function(e) {
var v = e.target.options[e.target.selectedIndex].value;
tinyMCE.activeEditor.execCommand("mceInsertContent", false, v);
e.target.selectedIndex = 0;
});
});
As far as updating the list box at runtime, your idea of calling a controller action to get the new items is sound; I'm not familiar with ASP.NET, so I can't really help you there.
The ID of the <select> that TinyMCE creates takes the form editorId_controlId, where in your case controlId is "mylistbox". Firebug in Firefox is the easiest way to find the ID of the <select> :)
Here's the test button I added to my page to check if the above code was working:
<script type="text/javascript">
function doFoo() {
// Change "myEditor" below to the ID of your TinyMCE instance.
var insertsElem = document.getElementById("myEditor_mylistbox");
insertsElem.options.length = 1; // Remove all but the first option.
var optElem = document.createElement("option");
optElem.value = "1";
optElem.text = "Foo";
insertsElem.add(optElem, null);
optElem = document.createElement("option");
optElem.value = "2";
optElem.text = "Bar";
insertsElem.add(optElem, null);
}
</script>
<button onclick="doFoo();">FOO</button>
Hope this helps, or at least gets you started.
Step 1 - Provide a JsonResult in your controller
public JsonResult GetInserts(int containerId)
{
//some code to get list of inserts here
List<string> somedata = doSomeStuff();
return Json(somedata);
}
Step 2 - Create javascript function to get Json results
function getInserts() {
var params = {};
params.containerId = $("#184").val();
$.getJSON("GetInserts", params, updateInserts);
};
updateInserts = function(data) {
var insertsElem = document.getElementById("183_mylistbox");
insertsElem.options.length = 1; // Remove all but the first option.
var optElem = document.createElement("option");
for (var item in data) {
optElem = document.createElement("option");
optElem.value = item;
optElem.text = data[item];
try {
insertsElem.add(optElem, null); // standards compliant browsers
}
catch(ex) {
insertsElem.add(optElem, item+1); // IE only (second paramater is the items position in the list)
}
}
};
Step 3 - Create NativeListBox (code above provided by ZoogieZork above)
var mlb = cm.createListBox('mylistbox', {
title: 'Inserts'
}, tinymce.ui.NativeListBox);
// Set our own change handler.
mlb.onPostRender.add(function(t) {
tinymce.dom.Event.add(t.id, 'change', function(e) {
var v = e.target.options[e.target.selectedIndex].value;
tinyMCE.activeEditor.execCommand("mceInsertContent", false, v);
e.target.selectedIndex = 0;
});
});
//populate inserts on listbox create
getInserts();

Resources