How to bind google maps using HotTowel? - javascript

I am trying to show a simple map in HotTowel.
In home.html page I have that:
<section>
<div id="map-canvas" data-bind="map: map"></div>
</section>
In home.js I have that:
define(['services/logger'], function (logger) {
var vm = {
activate: activate,
title: 'Home View',
map: map
};
return vm;
function activate() {
google.maps.event.addDomListener(window, 'load', initialize);
logger.log('Home View Activated', null, 'home', true);
return true;
}
var map;
function initialize() {
var mapOptions = {
zoom: 8,
center: new google.maps.LatLng(-34.397, 150.644),
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
}
});
How to bind model with view to show map?

EDIT**
The below answer was for Durandal 1.2. In durandal 2.0 the viewAttached event was renamed to attached. You can read Durandals documentation about it here.
Durandal has a viewAttached event that is called on your viewmodel once the view has been databound and attached to the dom. That would be a good place to call the google maps api.
define(['services/logger'], function (logger) {
var vm = {
viewAttached: initialize
title: 'Home View',
map: map
};
return vm;
var map;
function initialize(view) {
logger.log('Home View Activated', null, 'home', true);
var mapOptions = {
zoom: 8,
center: new google.maps.LatLng(-34.397, 150.644),
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
}
});
EDIT AGAIN to address peoples comments**
As per Jamie Hammond's comment it is a better practice to scope your DOM transversal to the view that's being attached. If the DOM element is apart of the view.
So, inside your viewAttached (in durandal 1.2) or attached (in durandal 2.0) you would:
var map;
var mapOptions = { /*map options*/ };
var vm = {
attached: function (view) {
var mapCanvas = $('#map-canvas', view).get(0);
map = new google.maps.Map(mapCanvas, mapOptions);
}
}
I haven't messed with Durandal 2.0 at all because I've been pretty busy with work and stuff and when I was messing around with Durandal 1.0 it was just for fun but I do love the framework and hope to one day get to play with 2.0. With that said I did have an issue with generating a map in the viewattached in Durandal 1.0. But, I wasn't using Google maps. I was using Leafletjs. My solution to the problem was creating a delay in the viewAttached that would redraw the map after a short delay. This was because Durandal's transitioning in the view was not working well with leaflets ability to draw the map in the dom element as it was flying and fading in.
So, inside the viewAttached I would draw the map like so:
window.setTimeout(drawMap, 10);
Again, this was a very specific problem I had and not a problem with Durandal. This was more of a problem with Leafletjs not rendering the map correctly when the DOM element was still transitioning in.

Evan,
I'm also trying to get this working but no joy.
I have my html as and viewmodel exactly as you have, and I know the viewAttached composition is being called because I'm getting my logger event - but no map!
The only other thing I can think of is where you call your googlemaps from? I'm doing in in my index.html are you doing the same?
Regards

BrettH,
For me, the problem was that the height was 0px. My module that works looks like this:
define(['plugins/router', 'knockout', 'plugins/utility'], function (router, ko, utility) {
var vm = { };
vm.map = undefined;
vm.compositionComplete = function () {
var myLatlng = new google.maps.LatLng(29.4000, 69.1833);
var mapOptions = {
zoom: 6,
center: myLatlng
}
vm.map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
var georssLayer = new google.maps.KmlLayer({
url: 'http://www.visualtravelguide.com/Pakistan-Islamabad.kmz'
});
georssLayer.setMap(vm.map);
utility.resizeElementHeight(document.getElementById('map-canvas'), 10);
$(window).resize(function () {
utility.resizeElementHeight(document.getElementById('map-canvas'), 10);
});
};
return vm;
});
My utility module looks like this:
define(['jquery','knockout'], function ($,ko) {
return {
//resizes an element so height goes to bottom of screen, got this from a stack overflow
//usage:
// resizeElementHeight(document.getElementById('projectSelectDiv'));
//$(window).resize(function () {
// resizeElementHeight(document.getElementById('projectSelectDiv'));
//});
//adjustpixels is an optional parameter if you want to leave room at the bottom
resizeElementHeight: function (element,adjustPixels) {
var height = 0;
var adjust = 0;
if (adjustPixels != undefined)
adjust = adjustPixels;
var body = window.document.body;
if (window.innerHeight) {
height = window.innerHeight;
} else if (body.parentElement.clientHeight) {
height = body.parentElement.clientHeight;
} else if (body && body.clientHeight) {
height = body.clientHeight;
}
element.style.height = ((height - element.offsetTop-adjust) + "px");
},
//looks up name by id, returns blank string if not found
//pass in a list and an id (they can be observables)
LookupNameById: function (l, wId) {
var list = ko.utils.unwrapObservable(l);
var id = ko.utils.unwrapObservable(wId);
var name = '';
$.each(list, function (key, value) {
if (value.Id() == id)
name = value.Name();
});
return name;
},
//sets the widths of the columns of headertable to those of basetable
setHeaderTableWidth: function (headertableid,basetableid) {
$("#"+headertableid).width($("#"+basetableid).width());
$("#"+headertableid+" tr th").each(function (i) {
$(this).width($($("#"+basetableid+" tr:first td")[i]).width());
});
$("#" + headertableid + " tr td").each(function (i) {
$(this).width($($("#" + basetableid + " tr:first td")[i]).width());
});
}
};
});
Hope this helps you.

first go to your main.js and add 'async': '../Scripts/async',
require.config({
paths: {
'text': '../Scripts/text',
'durandal': '../Scripts/durandal',
'plugins': '../Scripts/durandal/plugins',
'mapping': '../Scripts/knockout.mapping-latest',
'async': '../Scripts/async',
'transitions': '../Scripts/durandal/transitions'
},
shim: { mapping: { deps: ['knockout'] } }
});
notice that we need to add async.js in the scripts folder so go to Download async.js download the file and save it in hottowel script folder as async.js
the in the main.js add this
// convert Google Maps into an AMD module
define('gmaps', ['async!http://maps.google.com/maps/api/js?v=3&sensor=false'],
function(){
// return the gmaps namespace for brevity
return window.google.maps;
});
in any viewmodel you can now use it like this
define(['plugins/router', 'knockout', 'services/logger', 'durandal/app', 'gmaps'], function (router, ko, logger, app, gmaps) {
i hope this will help:

Related

Javascript function Element makes others JS function not working

I'm using a script (not made by myself) in order to show markers in Google MAP API with Markerclustering. I've included this script to one of my page and since then, my jQuery code doesn't work anymore.
I've started debugging with console etc, and I've found what causes the issue, it's this function that is on the top of the page of the marker_cluster JS script:
<script type="text/javascript" src="speed_test.js"></script>
<script type="text/javascript">
google.maps.event.addDomListener(window, 'load', speedTest.init);
</script>
<script src="vendors/markerclustererplus/src/markerclusterer.js"></script>
<script src="https://maps.googleapis.com/maps/api/js?key=API_KEY&callback=speedTest.init">
</script>
function $(element) {
return document.getElementById(element);
}
var speedTest = {};
speedTest.pics = null;
speedTest.map = null;
speedTest.markerClusterer = null;
speedTest.markers = [];
speedTest.infoWindow = null;
speedTest.init = function() {
var latlng = new google.maps.LatLng(39.91, 116.38);
var options = {
// 'zoom': 10,
// 'center': latlng,
// 'mapTypeId': google.maps.MapTypeId.ROADMAP
zoom: 9,
center: new google.maps.LatLng(46.188, 6.12701),
gestureHandling: "greedy",
disableDefaultUI: true,
zoomControl: true,
fullscreenControl: true,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
speedTest.map = new google.maps.Map($('map'), options);
speedTest.pics = data.photos;
var useGmm = document.getElementById('usegmm');
google.maps.event.addDomListener(useGmm, 'click', speedTest.change);
var numMarkers = document.getElementById('nummarkers');
google.maps.event.addDomListener(numMarkers, 'change', speedTest.change);
speedTest.infoWindow = new google.maps.InfoWindow();
speedTest.showMarkers();
};
When I comment this portion of code:
function $(element) {
return document.getElementById(element);
}
then my jquery code works back again, but then the Google Map API doesn't show anymore. I think it's because it's a general function... And it must interract with all the function of the page, and that's causing the issue.
You can find this code here.
The file in question here:
If you have any clue how I can fix this issue I'd really appreciate =)
Thank you and have a good day.
$ refers to jQuery and you are changing it by declaring below function
function $(element) {
return document.getElementById(element);
}
Use some other name for function instead of $.Maybe you can use e
function e(element) {
return document.getElementById(element);
}

Angular Google Maps Directive zoom too close after "place_changed" event

I'm currently working on a store finder app for DHL which can be viewed at storefinder.hashfff.com/app/index.html
In this app I am using the angular-google-maps library which provides some neat features, although I think working with the Google Maps API straight up would have been a better option as Googles API documentation is more detailed, however, being new to Angular I thought it would help.
My searchbox is tied to an event listener called "place_changed", which fires after the Autocomplete is set which takes autocomplete as a parameter.
events: {
place_changed: function(autocomplete) {
var searchString = autocomplete.gm_accessors_.place.Sc.formattedPrediction;
var searchCountry = searchString.split(',').pop().trim();
var searchCity = searchString.split(',');
var jsonQuery = "http://dhl.hashfff.com/api/dhl_store_finder_api.php/?country=" + searchCountry;
// Filter search results by search term. City, Address or Country
$.getJSON(jsonQuery , function(data) {
$scope.$apply(function() {
$scope.stores = _.filter(data, function(search) {
console.log(search.address2.toLowerCase().indexOf(searchCity[0].toLowerCase()));
return search.city.toLowerCase() == searchCity[0].toLowerCase() || search.address2.toLowerCase().indexOf(searchCity[0].toLowerCase()) > -1 || search.country.toLowerCase().indexOf(searchCity[0].toLowerCase()) > -1;
});
$('.cd-panel-search').addClass('is-visible');
});
});
place = autocomplete.getPlace();
if (place.address_components) {
// For each place, get the icon, place name, and location.
newMarkers = [];
var bounds = new google.maps.LatLngBounds();
var marker = {
id:place.place_id,
place_id: place.place_id,
name: place.address_components[0].long_name,
latitude: place.geometry.location.lat(),
longitude: place.geometry.location.lng(),
options: {
visible:false
},
templateurl:'window.tpl.html',
templateparameter: place
};
newMarkers.push(marker);
bounds.extend(place.geometry.location);
$scope.map.bounds = {
northeast: {
latitude: bounds.getNorthEast().lat(),
longitude: bounds.getNorthEast().lng()
},
southwest: {
latitude: bounds.getSouthWest().lat(),
longitude: bounds.getSouthWest().lng()
}
}
_.each(newMarkers, function(marker) {
marker.closeClick = function() {
$scope.selected.options.visible = false;
marker.options.visble = false;
return $scope.$apply();
};
marker.onClicked = function() {
$scope.selected.options.visible = false;
$scope.selected = marker;
$scope.selected.options.visible = true;
};
});
$scope.map.markers = newMarkers;
}
}
}
What happens is that after the autocomplete fires, it goes to the searched place but the zoom is set to maximum which is too close. I am aware that map.setZoom(5) is the usual answer but I do not have the map object available in this event listener.
I hope somebody has experience with the Google Maps Angular directive and could give me a hand. If you require any other code I'll be happy to update the query.
The creation of the bounds is useless, because when a LatLngBounds does have the same NE and SW(that's the case in your example, because you only have a single place/location), you may simply set the center of the map:
$scope.map.center={
latitude: bounds.getNorthEast().lat(),
longitude: bounds.getNorthEast().lng()
};
The difference : the zoom of the map will not be modified(as it does when fitBounds will be used)
when you want to set a zoom use e.g.:
$scope.map.zoom=5;

Loading google map binding too soon

I tried using this code here: fiddle
But it keeps sending me a javascript error "Uncaught TypeError: Cannot read property 'x' of undefined".
I mostly think it because the element didn't have time to load before the init function was executed.
that's my view:
<div id="mapDiv" data-bind="map:myMap"></div>
that's my binding:
ko.bindingHandlers.map = {
init: function (element, valueAccessor, allBindingsAccessor) {
var mapObj = ko.utils.unwrapObservable(valueAccessor());
var latLng = new google.maps.LatLng(
ko.utils.unwrapObservable(mapObj.lat),
ko.utils.unwrapObservable(mapObj.lng));
var mapOptions = { center: latLng,
zoom: 15};
mapObj.googleMap = new google.maps.Map(element, mapOptions);
mapObj.marker = new google.maps.Marker({
map: mapObj.googleMap,
position: latLng,
title: "You Are Here",
});
$("#" + element.id).data("mapObj",mapObj);
}
};
and that's my viewmodel:
module.activate = function(settings){
var self = this;
this.myMap = ko.observable({
lat: 34.1,
lng: 31.6});
this.compositionComplete = function(child, parent, settings){
};
};
return module;
});
I made a fiddle with your code, and once I styled the map div to have height and width, it ran without a problem.
<div id="mapDiv" style="width:300px; height:300px" data-bind="map:myMap"></div>
I never did see any reference to x, so I can't say I've addressed your issue, but if you think you have something running before Google Maps is ready, see this solution for adding a listener for the idle event.

js singleton - how to avoid accessing class members via the namespace

I'm trying to improve my Javascript coding style and have been reading that it's good to namespace stuff.
However I can't seem to use the "this" keyword everywhere that I would like to - instead I can only access my class properties via the namespace (in this case "oldMap") from within anonymous functions. This means that I can't change the namespace id without changing the code too - this seems like it's probably wrong.
Here's the class I built - it actually appears to work properly though. (sorry it's quite long).
Any suggestions / hints on what I'm doing right / wrong gratefully received. Thanks
var oldMap = {
map : null,
center : {lat:50, lng:20, zoom:3},
drawn : false,
data : {},
divId : "oldMap",
url : "php/getMapData.php",
infowindow : new google.maps.InfoWindow({
size: new google.maps.Size(150,50)
}),
init : function () {
if (!this.drawn){
$.getJSON(
this.url,
function(d){
if(d){
$.extend(oldMap.data,d);
var latlng = new google.maps.LatLng(oldMap.center.lat, oldMap.center.lng);
var myOptions = {
zoom: oldMap.center.zoom,
center: latlng,
mapTypeId: google.maps.MapTypeId.TERRAIN
};
// create the map
map = new google.maps.Map(document.getElementById("oldMap"),myOptions);
// create the legend
var legendDiv = document.createElement('DIV');
legendDiv.innerHTML = '<div id="legend"><img src="images/markers/legend-blur.png"></div>';
map.controls[google.maps.ControlPosition.TOP_LEFT].push(legendDiv);
google.maps.event.addListener(map, 'click', function() {
infowindow.close();
});
// Set the info window html template
var infoWindowTemplate = "<div id='balloon'>{{#url2}}<img src='{{url2}}' />{{/url2}}<h2>{{project_name}}</h2><p><b>Amount</b> € {{cost}}</p><p><b>Country</b> {{country}}</p><p><b>Year</b> {{year}}</p><p><b>Project Type</b> {{project_type}}</p><p>{{description}}</p>{{#url}}<p><a target='_blank' href='{{url}}'>More info</a></p>{{/url}}</div>"
// loop through the projects
for(var m in oldMap.data) {
// if the project has a marker type defined
if (oldMap.data[m].marker) {
// point
var point = new google.maps.LatLng(oldMap.data[m].lat, oldMap.data[m].longtd);
// create HTML for info window
var infoHtml = Mustache.to_html(infoWindowTemplate, oldMap.data[m]);
// icon
var icon = new google.maps.MarkerImage(
"images/markers/33px/" + oldMap.data[m].marker + ".png",
new google.maps.Size(33,33)
);
// create a marker for this project
var marker = oldMap.createMarker(point,infoHtml,icon);
}
}
oldMap.drawn = true;
}
}
)
}
},
createMarker : function (latlng, html, icon) {
// create the marker
var marker = new google.maps.Marker({
position: latlng,
icon: icon,
map: map,
zIndex: Math.round(latlng.lat()*-100000)<<5
});
// open info window when marker clicked
google.maps.event.addListener(marker, 'click', function() {
oldMap.infowindow.setContent(html);
oldMap.infowindow.open(map,marker);
});
}
};
First line of your functions directly on the object should be...
function () {
var that = this;
...
}
Then, in your inner functions, swap references to this with that.
This is because inner functions' this points to window.
If you're using jQuery as it looks like you are, take a look at the proxy() method:
http://api.jquery.com/jQuery.proxy/
This method is specifically designed to force the scope of this to a particular object. I personally prefer the syntax of PrototypeJS bind():
http://api.prototypejs.org/language/Function/prototype/bind/
...but much though I prefer PrototypeJS to jQuery, it seems that battle has been fought and lost already.

"a is null" even after Google map is successfully loaded

I have a set of jQuery UI tabs that each load project.php using ajax. Depending on the parameters passed to the script, a different Google map is displayed using the following JavaScript inside project.php:
var tab_index = $('#tabs').tabs('option', 'selected');
$('.site_map:visible').css('height','300px');
MapID = $('.site_map:visible').attr('id');
if (MapID !== 'map-new'){
var map_id = 'map-'+tab_index;
$('.site_map:visible').attr('id', map_id);
} else {
MapNewSite();
}
var latlng = new google.maps.LatLng(19,-70.4);
var myOptions = {
zoom: 8,
center: latlng,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
arrMaps[tab_index] = new google.maps.Map(document.getElementById("map-" + tab_index), myOptions);
arrInfoWindows[tab_index] = new google.maps.InfoWindow();
placeMarker($('.site_details:visible .inpLat').val(), $('.site_details:visible .inpLng').val(), tab_index);
function MapNewSite(){
arrMaps[tab_index] = new google.maps.Map(document.getElementById("map-new"), myOptions);
placeMarker(19,-70.4,tab_index);
arrInfoWindows[tab_index] = new google.maps.InfoWindow();
}
Each map loaded using parameters returned by a query of my database loads without any problems. However, in one last instance, I load project.php in a tab without any parameters so as to have a blank tab for users to manipulate. The signal that the map is not to be loaded using database coordinates is that the id of its div is "map-new".
The map generated in this tab loads, but then gives me the "a is null" error which usually means it couldn't find a div with the id specified to initialize the map. What is causing this error even after the map has loaded? How do I stop the error from occurring?
Here is the JavaScript in the parent page containing the tab site:
var arrMaps = {};
var arrInfoWindows = {};
var arrMarkers = {};
function placeMarker(lat, lng, tab_index){
map = arrMaps[tab_index];
var bounds = new google.maps.LatLngBounds();
var latlng = new google.maps.LatLng(
parseFloat(lat),
parseFloat(lng)
);
bounds.extend(latlng);
createMarker(latlng, tab_index);
map.fitBounds(bounds);
zoomChangeBoundsListener =
google.maps.event.addListener(map, 'bounds_changed', function(event) {
if (this.getZoom()){
this.setZoom(10);
}
google.maps.event.removeListener(zoomChangeBoundsListener);
});
}
function createMarker(latlng, tab_index) {
var html = 'Click here to move marker';
arrMarkers[tab_index] = new google.maps.Marker({
map: arrMaps[tab_index],
position: latlng
});
arrInfoWindows[tab_index] = new google.maps.InfoWindow();
google.maps.event.addListener(arrMarkers[tab_index], 'click', function() {
arrInfoWindows[tab_index].setContent(html);
arrInfoWindows[tab_index].open(arrMaps[tab_index], arrMarkers[tab_index]);
});
}
$(function() {
$( "#tabs" ).tabs({
ajaxOptions: {
error: function( xhr, status, index, anchor ) {
$( anchor.hash ).html(
"Couldn't load this tab. We'll try to fix this as soon as possible. " +
"If this wouldn't be a demo." );
}
},
cache: true
});
});
Take a look to http://www.pittss.lv/jquery/gomap/. Easy to use and very powerful. I myself use it.
It turns out I was accidentally initializing the map both inside the if and outside of it.

Categories