I have a code which I request data from a geoserver and display geoJSON on a map. To this request I'm using three AJAX calls like the following:
$(document).ready(function(){
//BASEMAP
var center = new L.LatLng(-0.8936,119.8638);
var map = new L.Map('map', { center: center, zoom: 14, attributionControl:true, zoomControl:false});
osmTile = "http://tile.openstreetmap.org/{z}/{x}/{y}.png";
osmCopyright = "Map data © 2016 OpenStreetMap contributors";
osmLayer = new L.TileLayer(osmTile, { maxZoom: 18, attribution: osmCopyright } );
map.addLayer(osmLayer);
//URL SERVICE
var owsrootUrl = 'http://sample.com/geoserver/ows';
//different color in layers
function getColor(d) {
return d > 10000 ? '#FF0000' :
d > 5000 ? '#FF9900' :
d > 2000 ? '#E31A1C' :
d > 1000 ? '#00FF00' :
d > 500 ? '#FD8D3C' :
d > 200 ? '#0000CC' :
d > 100 ? '#FED976' :
'#FFEDA0';
}
function style(feature) {
return {
weight: 1,
opacity: 3,
color: 'white',
dashArray: '3',
fillOpacity: 3,
fillColor: getColor(feature.properties.luas_tanah)
};
}
var lyr_1 = new L.LayerGroup();
var lyr_2 = new L.LayerGroup();
var lyr_3 = new L.LayerGroup();
var lyr_4 = new L.LayerGroup();
var defaultParameters = {
service : 'WFS',
version : '1.0.0',
request : 'GetFeature',
typeName : 'pbb:view_map',
maxFeatures: 1000,
outputFormat : 'text/javascript',
format_options : 'callback:getJson',
SrsName : 'EPSG:4326'
};
var parameters = L.Util.extend(defaultParameters);
var URL = owsrootUrl + L.Util.getParamString(parameters);
var lyr_1 = new L.LayerGroup();
var ajax1 = $.ajax({
type: 'GET',
url : URL,
dataType : 'jsonp',
async: false,
jsonpCallback : 'getJson',
success : function (response) {
console.log("Layer 1");
L.geoJson(response, {
style: function (feature) {
return {
stroke: true,
color: getColor(feature.properties.luas_tanah),
opacity: 1,
weight: 1
};
},
onEachFeature: function (feature, layer) {
popupOptions = {maxWidth: 200};
layer.bindPopup("<b>D NOP : </b> " + feature.properties.d_nop
,popupOptions);
}
}).addTo(lyr_1);
},
error: function (xhr, status) {
alert("Failed call Layer A");
}
});
var defaultParameters1 = {
service : 'WFS',
version : '1.0.0',
request : 'GetFeature',
typeName : 'pbb:view_map',
maxFeatures: 1000,
outputFormat : 'text/javascript',
format_options : 'callback:getJson',
SrsName : 'EPSG:4326'
};
var parameters1 = L.Util.extend(defaultParameters1);
var URL1 = owsrootUrl + L.Util.getParamString(parameters1);
var lyr_2 = new L.LayerGroup();
var ajax2 = $.ajax({
type: 'GET',
url : URL1,
dataType : 'jsonp',
async: false,
jsonpCallback : 'getJson',
success : function (response) {
console.log("Layer 2");
L.geoJson(response, {
style: function (feature) {
return {
stroke: true,
color: getColor(feature.properties.luas_tanah),
opacity: 0.8,
weight: 1
};
},
onEachFeature: function (feature, layer) {
popupOptions = {maxWidth: 200};
layer.bindPopup("<b>D NOP : </b> " + feature.properties.d_nop
,popupOptions);
}
}).addTo(lyr_2);
},
error: function (xhr, status) {
alert("Failed call Layer B");
}
});
var defaultParameters2 = {
service : 'WFS',
version : '1.0.0',
request : 'GetFeature',
typeName : 'pbb:view_map',
maxFeatures: 1000,
outputFormat : 'text/javascript',
format_options : 'callback:getJson',
SrsName : 'EPSG:4326'
};
var parameters2 = L.Util.extend(defaultParameters2);
var URL2 = owsrootUrl + L.Util.getParamString(parameters2);
var lyr_3 = new L.LayerGroup();
var ajax3 = $.ajax({
type: 'GET',
url : URL2,
dataType : 'jsonp',
async: false,
jsonpCallback : 'getJson',
success : function (response) {
console.log("Layer 3");
L.geoJson(response, {
style: function (feature) {
return {
stroke: true,
color: getColor(feature.properties.luas_tanah),
opacity: 0.8,
weight: 1
};
},
onEachFeature: function (feature, layer) {
popupOptions = {maxWidth: 200};
layer.bindPopup("<b>D NOP : </b> " + feature.properties.d_nop
,popupOptions);
}
}).addTo(lyr_3);
},
error: function (xhr, status) {
alert("Failed call Layer C");
}
});
var defaultParameters3 = {
service : 'WFS',
version : '1.0.0',
request : 'GetFeature',
typeName : 'pbb:view_map',
maxFeatures: 1000,
outputFormat : 'text/javascript',
format_options : 'callback:getJson',
SrsName : 'EPSG:4326'
};
var parameters3 = L.Util.extend(defaultParameters3);
var URL3 = owsrootUrl + L.Util.getParamString(parameters3);
var lyr_4 = new L.LayerGroup();
var ajax4 = $.ajax({
url : URL3,
dataType : 'jsonp',
async: false,
jsonpCallback : 'getJson',
success : function (response) {
console.log("Layer 4");
L.geoJson(response, {
style: function (feature) {
return {
stroke: true,
color: getColor(feature.properties.luas_tanah),
opacity: 0.8,
weight: 1
};
},
onEachFeature: function (feature, layer) {
popupOptions = {maxWidth: 200};
layer.bindPopup("<b>D NOP : </b> " + feature.properties.d_nop
,popupOptions);
}
}).addTo(lyr_4);
},
error: function (xhr, status) {
alert("Failed call Layer D");
}
});
var baseMaps = [
{
groupName : "OSM Base Maps",
layers : {
"OpenStreetMaps" : osmLayer
}
}
];
var overlays = [
{
groupName : "Kecamatan",
expanded : true,
layers : {
"Layer A" : lyr_1,
"Layer B" : lyr_2,
"Layer C" : lyr_3,
"Layer D" : lyr_4
}
}
];
var options = {
container_width : "300px",
group_maxHeight : "800px",
exclusive : true
};
var control = L.Control.styledLayerControl(baseMaps, overlays, options);
map.addControl(control);
var legend = L.control({position: 'bottomright'});
legend.onAdd = function (map) {
var div = L.DomUtil.create('div', 'info legend'),
grades = [0, 100, 200, 500, 1000, 2000, 5000, 10000],
labels = [],
from, to;
for (var i = 0; i < grades.length; i++) {
from = grades[i];
to = grades[i + 1];
labels.push(
'<i style="background:' + getColor(from + 1) + '"></i> ' +
from + (to ? '–' + to : '+'));
}
div.innerHTML = labels.join('<br>');
return div;
};
legend.addTo(map);
});
Now my problem is every time I open the page everything work just fine, but sometimes when I refresh the page everything breaks apart. For example some times "Layer A" data is displayed, some times data ends up on the wrong dataset. When I get this bug I get the following message in the console:
ows?service=WFS&version=1.0.0&request=GetFeature&typeName=pbb%3Aview_map&maxFeatures=1000&outputFor…:1 Uncaught TypeError: getJson is not a function
On my research it seems that this type of error is quite common when running multiple AJAX calls. What I don't understand is why this error does not occur 100% of the time. What type of techniques can be used to fix it? I heard of Deffered Objects but could not apply it on my code, my expertise level on this is far from great. Although this may tend towards a GIS question I believe that this type of issue is more related to ordinary jQuery and Asynchronous calls.
jsonpCallback Type: String or Function() Specify the callback function
name for a JSONP request. This value will be used instead of the
random name automatically generated by jQuery. It is preferable to let
jQuery generate a unique name as it'll make it easier to manage the
requests and provide callbacks and error handling.
Don't use it.
Remove this line:
jsonpCallback : 'getJson',
When you have multiple requests in flight at once, their callback functions (since you are forcing them to use the same name) overwrite each other.
async: false
Making a synchronous request would resolve the problem (since you can't have multiple synchronous requests in flight at once (they queue)) … but they are:
Horrible (they are deprecated in XHR on the main browser thread with good reason)
Completely incompatible with JSONP (so your instruction is ignored).
You should remove the attempt to make the request synchronous too.
Related
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 5 years ago.
All,
I have some code that uses multiple variables, objects and arrays.
Somehow my object which was filled is now empty.
var items = [];
var countryCode = "";
var message = "";
var dataset = {};
var countryData = {};
countryData.fillKey = 'NEW';
function getItems(url) {
$.ajax({
url: _spPageContextInfo.webAbsoluteUrl + url,
type: "GET",
headers: {
"accept": "application/json;odata=verbose",
},
success: function (data) {
var items = data.d.results;
for(var i = 0; i < items.length;i++) {
countryCode = items[i].Country0.Column2;
message = countryData.fillKey;
dataset[countryCode] = message;
}
},
error: function (error) {
alert(JSON.stringify(error));
}
});
}
When I test dataset after this code it's empty. It should be something like:
dataset['UKR'] = countryData;
dataset['USA'] = countryData;
dataset['RUS'] = countryData;
Why doesn't this work?
Marco
My next code has to use the dataset:
var map = new Datamap({
element: document.getElementById('container'),
geographyConfig: {
hideAntarctica: true,
highlightFillColor: false,
popupOnHover: true,
highlightOnHover: false,
borderColor: '#000000',
borderWidth: 0.5
},
fills: {
'NEW': '#FF0000',
'OLD': '#FF7F7F',
defaultFill: '#FFFED9'
},
data: dataset
});
map.svg.call(d3.behavior.zoom().on("zoom", redraw));
function redraw() {
map.svg.selectAll("g").attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
});
You can pass a callback to the getItems function to call once the request is successful.
Here is a short example of a scenario where you need to get items, then do something with that data.
function processItems() {
getItems('the/items/url', (dataset) => {
var map = new Datamap({
element: document.getElementById('container'),
geographyConfig: {
hideAntarctica: true,
highlightFillColor: false,
popupOnHover: true,
highlightOnHover: false,
borderColor: '#000000',
borderWidth: 0.5
},
fills: {
'NEW': '#FF0000',
'OLD': '#FF7F7F',
defaultFill: '#FFFED9'
},
data: dataset
});
map.svg.call(d3.behavior.zoom().on("zoom", redraw));
function redraw() {
map.svg.selectAll("g").attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
});
}
function getItems(url, callback) {
$.ajax({
url: _spPageContextInfo.webAbsoluteUrl + url,
type: "GET",
headers: {
"accept": "application/json;odata=verbose",
},
success: function(data) {
var items = data.d.results;
for (var i = 0; i < items.length; i++) {
countryCode = items[i].Country0.Column2;
message = countryData.fillKey;
dataset[countryCode] = message;
}
callback(dataset);
},
error: function(error) {
alert(JSON.stringify(error));
}
});
}
So I solved it. Sometimes things are right in front of you but you don't see it.
Just needed to set async to FALSE.
function getItems(url) {
$.ajax({
url: _spPageContextInfo.webAbsoluteUrl + url,
type: "GET",
async: false,
headers: {
"accept": "application/json;odata=verbose",
},
success: function (data) {
var items = data.d.results;
for(var i = 0; i < items.length;i++) {
countryCode = items[i].Country0.Column2;
message = countryData.fillKey;
dataset[countryCode] = message;
}
},
error: function (error) {
alert(JSON.stringify(error));
}
});
}
I am plotting LineStrings in Mapbox.So now I am updating the color of the lines when the property is changed.
function update_the_map(){
$.ajax({
url:"http://dataurl",
contentType : "application/json; charset=utf-8",
type: "GET",
dataformat:"JSON",
async : false,
success: function(data){
for (i = 0; i <lines.features.length; i++) {
lines['features'][i]['properties']['points']=data[i].points;
var style_update =getColor( lines['features'][i]['properties']['points']);
geojson.setFeatureStyle(lines['features'][i]['properties']['id'], style_update);
}
setTimeout( update_the_map, 10000);
console.log("updated");
},
error:function(){}
});
}
But this changes all the color of the lines and not those points are greater than 5.Because my get color function is like
function getColor(d) {
if(d==10 || d==9 || d==8 || d==7 || d==6){
return '#ff0000';
}
else {
return '#00a1ff';
}
}
So it returns red if points>5 else it returns blue.But this returns blue for everything and the whole lines color is changed.ANy help is appreciated.This is how i create the layer.
geojson = L.vectorGrid.slicer(lines, {
vectorTileLayerStyles: {
sliced: style},
maxZoom: 24, // max zoom to preserve detail on
interactive: true,
getFeatureId: function(f) {
return f.properties.id;
}
}).on('mouseover', mouseover_function).addTo(map);
My lines is a variable as below :
var lines= {
"type":"FeatureCollection","features": [{"type": "Feature","geometry":{"type":"LineString",
"coordinates":[[ 101.942139,4.252606],[101.766357,3.134346]]},
"properties": {"id":"01","points":10}},....
]};
Can you show that what is inside the line.features array OR check the value of d in console, passed to getColor function.
The following did make the update work like a charm. So insteading of passing only the color I passed the weight and opacity and color It worked fine.
function update_the_map(){
$.ajax({
url:"http://dataurl",
contentType : "application/json; charset=utf-8",
type: "GET",
dataformat:"JSON",
async : false,
success: function(data){
var style_update; //changed declaration here
for (i = 0; i <lines.features.length; i++) {
lines['features'][i]['properties']['points']=data[i].points;
style_update = {
weight: 2,
opacity: 1,
color: getColor(links['features'][i]['properties']['score']),
fillOpacity: 0.7
};//added this whole for variable style update
geojson.setFeatureStyle(links['features'][i]['properties']['id'],style_update);
}
setTimeout( update_the_map, 10000);
console.log("updated");
},
error:function(){}
});
}
I have function to show map on my website. It works well for desktop version, but does not on mobile. It does not affect nor bounds nor zoom level. Map is centered somewhere...
services_vars.search_lat = ''
services_vars.search_lng = ''
parseInt(services_vars.zoom) = 14
if($('#mapView').length > 0) {
var propsAjaxURL = services_vars.admin_url + 'admin-ajax.php';
var propsSecurity = $('#securityAppMap').val();
$.ajax({
type: 'POST',
dataType: 'json',
url: propsAjaxURL,
data: {
'action': 'reales_get_searched_properties',
'country': services_vars.search_country,
'state': services_vars.search_state,
'city': services_vars.search_city,
'category': services_vars.search_category,
'type': services_vars.search_type,
'min_price': services_vars.search_min_price,
'max_price': services_vars.search_max_price,
'bedrooms' : services_vars.search_bedrooms,
'bathrooms' : services_vars.search_bathrooms,
'neighborhood' : services_vars.search_neighborhood,
'min_area' : services_vars.search_min_area,
'max_area' : services_vars.search_max_area,
'amenities': services_vars.search_amenities,
'page': services_vars.page,
'sort': services_vars.sort,
'security': propsSecurity
},
success: function(data) {
appMap = new google.maps.Map(document.getElementById('mapView'), options);
var styledMapType = new google.maps.StyledMapType(styles, {
name : 'Styled'
});
appMap.mapTypes.set('Styled', styledMapType);
appMap.getStreetView().setOptions(panoramaOptions);
var searchedPosition = new google.maps.LatLng(services_vars.search_lat, services_vars.search_lng);
appMap.setCenter(searchedPosition);
appMap.setZoom(parseInt(services_vars.zoom));
if(data.getprops === true) {
addMarkers(data.props, appMap);
appMap.fitBounds(markers.reduce(function(bounds, marker) {
return bounds.extend(marker.getPosition());
}, new google.maps.LatLngBounds()));
google.maps.event.trigger(appMap, 'resize');
}
},
error: function(errorThrown) {
}
});
}
I have been using the example shown here (https://astuntechnology.github.io/osgis-ol3-leaflet/ol3/05-WMS-INFO.html) to try and retrieve features at a coordinate from multiple TileWMS layers I have set up in my application.
This example has been tweaked so it now returns data in JSONP using the reqwest library, but now I am trying to figure out the best way to adapt this to include multiple layers and multiple features.
I am thinking of using the map.forEachLayerAtPixel function to retrieve all layers present at the map click location, and then within if statements call the feature and add this to a variable to build a dynamic html table of results.
I don't know if this is the best approach but is the only way I can think of doing it so I am able to retrieve the information in a way I can lay it out specifically.
Below is the javascript for my on map click function but it is not returning the pop up and doesn't display any errors.
I am not sure if I am using the correct approach, and does anything look incorrect with the below?
Thanks
var popup = new ol.Overlay.Popup();
map.addOverlay(popup);
map.on('singleclick', function(evt) {
if($(window).width() <= 767 && document.getElementById('sidebar').style.display == 'block') {
$('#sidebar').toggle();
$(".navbar-collapse.in").collapse("hide");
map.updateSize();
return false;
}
// Hide existing popup and reset it's offset
popup.hide();
popup.setOffset([0, 0]);
var displayedLayers = [];
var content = "";
map.forEachLayerAtPixel(evt.pixel, function(layer) {
displayedLayers.push(layer.get('name'));
});
if ($.inArray('layer62', displayedLayers) > -1) {
var url = layer62
.getSource()
.getGetFeatureInfoUrl(
evt.coordinate,
map.getView().getResolution(),
map.getView().getProjection(),
{
'INFO_FORMAT': 'text/javascript',
'format_options': 'callback:results',
'propertyName': 'definition'
}
);
reqwest({
url: url,
type: 'jsonp',
jsonpCallbackName: 'results'
}).then(function (data) {
var feature = data.features[0];
var props = feature.properties;
content += "<h4>Flood Zone 3</h4><p>" + props.definition + "</p>";
});
}
if ($.inArray('layer63', displayedLayers) > -1) {
var url = layer63
.getSource()
.getGetFeatureInfoUrl(
evt.coordinate,
map.getView().getResolution(),
map.getView().getProjection(),
{
'INFO_FORMAT': 'text/javascript',
'format_options': 'callback:results',
'propertyName': 'definition'
}
);
reqwest({
url: url,
type: 'jsonp',
jsonpCallbackName: 'results'
}).then(function (data) {
var feature = data.features[0];
var props = feature.properties;
content += "<h4>Flood Zone 2</h4><p>" + props.definition + "</p>";
});
}
return content;
popup.show(evt.coordinate, content);
});
EDITED original answer as it wasn't correct, this one seems to work. It's jus a test based in your code but changes the way the popup is handled:
var layers = [
new ol.layer.Tile({
source: new ol.source.TileWMS({
url: 'http://demo.opengeo.org/geoserver/wms?',
params: {
'LAYERS': 'ne:ne',
'TILED': true,
'version': '1.1.0'
},
serverType: 'geoserver',
})
}),
new ol.layer.Tile({
source: new ol.source.TileWMS({
url: 'http://demo.opengeo.org/geoserver/wms?',
params: {
'LAYERS': 'ne:ne',
'TILED': true,
'version': '1.1.0'
},
serverType: 'geoserver',
})
})
];
var container = document.getElementById('popup');
var content = document.getElementById('popup-content');
var popup = new ol.Overlay( /** #type {olx.OverlayOptions} */ ({
element: container,
autoPan: true,
autoPanAnimation: {
duration: 250
}
}));
var map = new ol.Map({
layers: layers,
target: 'map',
overlays: [popup],
view: new ol.View({
center: [327641, 4149464],
zoom: 3,
//EPSG: 25830
})
});
map.on('singleclick', function (evt) {
content.innerHTML = "";
var displayedLayers = [];
var responses = 0;
var url = layers[0].getSource()
.getGetFeatureInfoUrl(
evt.coordinate,
map.getView().getResolution(),
map.getView().getProjection(), {
'INFO_FORMAT': 'text/javascript',
'format_options': 'callback:parseResponse',
'propertyName': 'name'
});
reqwest({
url: url,
type: 'jsonp',
jsonpCallbackName: 'parseResponse'
}).then(function (data) {
var feature = data.features[0];
var props = feature.properties;
content.innerHTML += "<h4>First Layer</h4><p>" + props.name + "</p>";
popup.setPosition(evt.coordinate);
});
// Second layer
var url = layers[1].getSource()
.getGetFeatureInfoUrl(
evt.coordinate,
map.getView().getResolution(),
map.getView().getProjection(), {
'INFO_FORMAT': 'text/javascript',
'format_options': 'callback:parseResponse',
'propertyName': 'name'
});
reqwest({
url: url,
type: 'jsonp',
jsonpCallbackName: 'parseResponse'
}).then(function (data) {
var feature = data.features[0];
var props = feature.properties;
content.innerHTML += "<h4>Second layer</h4><p>" + props.name + "</p>";
popup.setPosition(evt.coordinate);
});
});
Jsfiddle here: http://jsfiddle.net/fbma/1pchmpoo
for anyone struggling with openlayers 3+ getGetFeatureInfoUrl function with multiple layers, the solution for me was passing the parameters LAYERS, QUERY_LAYERS and FEATURE_COUNT. Specially the last one as even including all layers in the two former ones, geoserver indeed takes all the layers BUT assumes tha FEATURE_COUNT is 1
So the nex example did the work
var url = layers[0].getSource()
.getGetFeatureInfoUrl(
evt.coordinate,
map.getView().getResolution(),
map.getView().getProjection(), {
**'LAYERS':'ne:ne,ne:ne2,ne:ne3,ne:ne4,ne:ne5',
'QUERY_LAYERS': 'ne:ne,ne:ne2,ne:ne3,ne:ne4,ne:ne5',
'FEATURE_COUNT':100000**,
'INFO_FORMAT': 'text/javascript',
'format_options': 'callback:parseResponse',
'propertyName': 'name'
});
I'm using FullCalendar.js to display Google Calendar events from multiple sources. It's been working OK up until today. For some reason FullCalendar started popping up the "there was an error while fetching events" error message and all the events are obviously gone. Here is a jsfiddle.
http://jsfiddle.net/mlk4343/1wko0z1j/1/
$(document).ready(function() {
$('#calendar').fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,agendaDay'
},
contentHeight: 600,
eventMouseover: function(calEvent, jsEvent) {
var tooltip = '<div class="tooltipevent">' + calEvent.title + '</div>';
$("body").append(tooltip);
$(this).mouseover(function(e) {
$(this).css('z-index', 10000);
$('.tooltipevent').fadeIn('500');
$('.tooltipevent').fadeTo('10', 1.9);
}).mousemove(function(e) {
$('.tooltipevent').css('top', e.pageY + 10);
$('.tooltipevent').css('left', e.pageX + 20);
});
},
eventMouseout: function(calEvent, jsEvent) {
$(this).css('z-index', 8);
$('.tooltipevent').remove();
},
eventSources: [
{
// Adele H
url: 'https://www.google.com/calendar/feeds/sonomaschools.org_u030vtntt1tp7gjn8cnqrr9nsk%40group.calendar.google.com/public/basic',
type: 'POST',
error: function() {
alert('there was an error while fetching events!');
},
color: 'yellow', // a non-ajax option
textColor: 'black' // a non-ajax option
},
{
// Altimira
url: 'https://www.google.com/calendar/feeds/sonomaschools.org_e6j3ejc40g02v4sdo0n3cakgag%40group.calendar.google.com/public/basic',
type: 'POST',
error: function() {
alert('there was an error while fetching events!');
},
color: 'red', // a non-ajax option
textColor: 'white' // a non-ajax option
},
{
// Charter
url: 'https://www.google.com/calendar/feeds/sonomacharterschool.org_0p2f56g5tg8pgugi1okiih2fkg%40group.calendar.google.com/public/basic',
type: 'POST',
error: function() {
alert('there was an error while fetching events!');
},
color: 'LightSalmon', // a non-ajax option
textColor: 'black' // a non-ajax option
},
{// Dunbar
url: 'https://www.google.com/calendar/feeds/sonomaschools.org_4tmsks5b9s70k6armb6jkvo9p0%40group.calendar.google.com/public/basic',
type: 'POST',
error: function() {
alert('there was an error while fetching events!');
},
color: 'green', // a non-ajax option
textColor: 'white' // a non-ajax option
},
{// El Verano
url: 'https://www.google.com/calendar/feeds/pv2hfl7brn6dj8ia3mqksp9fl0%40group.calendar.google.com/public/basic',
type: 'POST',
error: function() {
alert('there was an error while fetching events!');
},
color: 'LightBlue', // a non-ajax option
textColor: 'black' // a non-ajax option
},
{ // Flowery
url: 'https://www.google.com/calendar/feeds/sonomaschools.org_v0a2nmtu4jrca90lui62tccbd4%40group.calendar.google.com/public/basic',
type: 'POST',
error: function() {
alert('there was an error while fetching events!');
},
color: 'blue', // a non-ajax option
textColor: 'white' // a non-ajax option
},
{ // Prestwood
url:'https://www.google.com/calendar/feeds/sonomaschools.org_25rjgf4pu3vsa5i7r7itnqkigs%40group.calendar.google.com/public/basic',
type: 'POST',
error: function() {
alert('there was an error while fetching events!');
},
color: 'purple', // a non-ajax option
textColor: 'white' // a non-ajax option
},
{ // Sassarini
url: 'https://www.google.com/calendar/feeds/sonomaschools.org_18a25r5mrc084gn4ekegadpfm8%40group.calendar.google.com/public/basic',
type: 'POST',
error: function() {
alert('there was an error while fetching events!');
},
color: 'Aqua ', // a non-ajax option
textColor: 'black' // a non-ajax option
},
{ // SVHS
url: 'https://www.google.com/calendar/feeds/sonomaschools.org_h450occacktra5errgbhsrv3k4%40group.calendar.google.com/public/basic',
type: 'POST',
error: function() {
alert('there was an error while fetching events!');
},
color: 'Chartreuse', // a non-ajax option
textColor: 'black' // a non-ajax option
},
{ // SVUSD
url: 'https://www.google.com/calendar/feeds/sonomaschools.org_2i1596pg2fokba99kvatqn45bk%40group.calendar.google.com/public/basic',
type: 'POST',
error: function() {
alert('there was an error while fetching events!');
},
color: 'MediumVioletRed', // a non-ajax option
textColor: 'white' // a non-ajax option
},
]
});
});
The events show OK on Google Calendar.
I tried the other solutions, which got me close to a fix but not entirely there. The results were fetching the entire set of calendar events and not a set number in a certain date-range.
What I discovered was that the names of the Parameters have also changed in the new API.
See: https://developers.google.com/google-apps/calendar/v3/reference/events/list
My fix involves adding the new APIv3 parameters to the data variable. Also the date-format for timeMin and timeMax are RFC3339/ATOM and not ISO 8601 (which Moment.js outputs by default) so I have added a format string to produce RFC3339 formatted dates.
Use the APIv3 URL format in your HTML/PHP file:
https://www.googleapis.com/calendar/v3/calendars/CALENDAR-ID/events?key=API-KEY
Update your gcal.js to the following code. This is based on the fixes posted by user4263042 and Stephen (Thanks guys!)
(function(factory) {
if (typeof define === 'function' && define.amd) {
define([ 'jquery' ], factory);
}
else {
factory(jQuery);
}
})(function($) {
var fc = $.fullCalendar;
var applyAll = fc.applyAll;
fc.sourceNormalizers.push(function(sourceOptions) {
if (sourceOptions.dataType == 'gcal' ||
sourceOptions.dataType === undefined &&
(sourceOptions.url || '').match(/^(http|https):\/\/www.googleapis.com\/calendar\/v3\/calendars/)) {
sourceOptions.dataType = 'gcal';
if (sourceOptions.editable === undefined) {
sourceOptions.editable = false;
}
}
});
fc.sourceFetchers.push(function(sourceOptions, start, end, timezone) {
if (sourceOptions.dataType == 'gcal') {
return transformOptions(sourceOptions, start, end, timezone);
}
});
function transformOptions(sourceOptions, start, end, timezone) {
var success = sourceOptions.success;
var data = $.extend({}, sourceOptions.data || {}, {
'singleEvents' : true,
'maxResults': 250,
'timeMin': start.format('YYYY-MM-DD[T]HH:mm:ssZ'),
'timeMax': end.format('YYYY-MM-DD[T]HH:mm:ssZ'),
});
return $.extend({}, sourceOptions, {
url: sourceOptions.url + '&callback=?',
dataType: 'jsonp',
data: data,
success: function(data) {
var events = [];
if (data.items) {
$.each(data.items, function(i, entry) {
events.push({
id: entry.id,
title: entry.summary,
start: entry.start.dateTime || entry.start.date,
end: entry.end.dateTime || entry.end.date,
url: entry.htmlLink,
location: entry.location,
description: entry.description || '',
});
});
}
var args = [events].concat(Array.prototype.slice.call(arguments, 1));
var res = applyAll(success, this, args);
if ($.isArray(res)) {
return res;
}
return events;
}
});
}
// legacy
fc.gcalFeed = function(url, sourceOptions) {
return $.extend({}, sourceOptions, { url: url, dataType: 'gcal' });
};
});
Here's the fix everyone:
https://github.com/jonnyhweiss/fullcalendar/commit/520022a4da81ded61f3a1cc7020df4df54726fbc?diff=split
It requires editing of gcal.js and gcal.html to get the demo's working; from those demos you should be able to fix your own broken calendars, hopefully ; )
Please note:
Requires Full-Calendar 2.2.0
I quickly discovered it will not work on Full Calendar 1.x.x, or if it will, I'm not code savvy enough to figure it out. Full Calendar 2.2.0 adds moment.js as a dependent JS link, which is not a part of Full Calendar 1.x.x, so copy and pasting what is available on the link above into your Full Calendar 1.x.x files will not work.
Happy coding and fixing your Google Calendars!
I believe I have the solution.
After a little digging I found a this page, but written as is, the code failed to work correctly. BUT after a little modification, see below, I now have things in working order again.
To use the new piece of code one needs to change the source URL for ones calendar to the form:
https://www.googleapis.com/calendar/v3/calendars/CALENDAR-ID/events?key=API-KEY
Insert your own calendar id and public API key into the URL as indicated. Your API-KEY can be obtained by setting up a project inside your Google Developers Console and then creating a public access API browser key.
Here is the actual code one needs to use in place of ones in the current gcal.js file.
(function(factory) {
if (typeof define === 'function' && define.amd) {
define([ 'jquery' ], factory);
} else {
factory(jQuery);
}
})
(function($) {
var fc = $.fullCalendar;
var applyAll = fc.applyAll;
fc.sourceNormalizers.push(function(sourceOptions) {
if (sourceOptions.dataType == 'gcalv3'
|| (sourceOptions.dataType === undefined
&& (sourceOptions.url || '').match(/^(http|https):\/\/www.googleapis.com\/calendar\/v3\/calendars\//))) {
sourceOptions.dataType = 'gcalv3';
if (sourceOptions.editable === undefined) {
sourceOptions.editable = false;
}
}
});
fc.sourceFetchers.push(function(sourceOptions, start, end, timezone) {
if (sourceOptions.dataType == 'gcalv3') {
return transformOptionsV3(sourceOptions, start, end, timezone);
}
});
function transformOptionsV3(sourceOptions, start, end, timezone) {
var success = sourceOptions.success;
var data = $.extend({}, sourceOptions.data || {}, {
singleevents: true,
'max-results': 9999
});
return $.extend({}, sourceOptions, {
url: sourceOptions.url,
dataType: 'json',
data: data,
startParam: 'start-min',
endParam: 'start-max',
success: function(data) {
var events = [];
if (data.items) {
$.each(data.items, function(i, entry) {
events.push({
id: entry.id,
title: entry.summary || '', // must allow default to blank, if it's not set it doesn't exist in the json and will error here
start: entry.start.dateTime || entry.start.date,
end: entry.end.dateTime || entry.start.date, // because end.date may be the next day, cause a '2-all-day' event, we use start.date here.
url: entry.htmlLink,
location: entry.location || '', // must allow default to blank, if it's not set it doesn't exist in the json and will error here
description: entry.description || '' // must allow default to blank, if it's not set it doesn't exist in the json and will error here
});
});
}
var args = [events].concat(Array.prototype.slice.call(arguments, 1));
var res = applyAll(success, this, args);
if ($.isArray(res)) {
return res;
}
return events;
}
});
}
});
To fix comment out the Google Holiday feed if you are using it. That fixed it for us. Evidently they are having feed issues. That is the only feed from Google I use, so other Google feeds may be impacted also.
Version 2 of the API was deprecated today.