{
this.scope.html( response );
$( document ).trigger( 'contentChange', [ this.scope ] );
})
.always(() => {
this._setLoading( false );
});
},
/**
* Toggles the loading state on the view
*
* @param {boolean} state Enable the loading state?
* @returns {void}
*/
_setLoading(state) {
if( state ){
this.scope.animate( { opacity: "0.6" }, 'fast' );
} else {
this.scope.animate( { opacity: "1" }, 'fast' );
}
}
});
}(jQuery, _));]]>",
initialize: function () {
this.setup();
},
/**
* Setup method
*
* @returns {void}
*/
setup: function () {
this._alignEvents();
},
_alignEvents: function () {
// Lets start by getting all active days in the current calendar
var days = this.scope.find('.cCalendar_date');
var currentPositions = [];
var self = this;
_.each( days, function (day) {
var day = $( day );
var dayNumber = day.find('.cCalendar_dayNumber').text();
var weekStart = false;
if( day.closest('td').is('tr > td:first-child') ){
weekStart = true;
}
// Get events for this day
var events = day.find('.cEvents_ranged [data-eventID]');
// If there's no events, we can skip this day
if( !events.length ){
currentPositions = [];
return;
}
// Build a wrapper into which we'll move our events
var wrapper = $("
").addClass('cEvents');
var spaces = 0;
// Now we loop over currentPositions (from the previous day), and try and arrange today's events
// in the same order
if( currentPositions.length ){
var doneEvent = false;
for( var i = 0; i < currentPositions.length; i++ ){
if( events.filter('[data-eventID="' + currentPositions[i] + '"]').length ){
wrapper.append( events.filter('[data-eventID="' + currentPositions[i] + '"]') );
doneEvent = true;
} else {
// If this is the first day of the week, we won't bother adding spacers unless we've already done an event today
// This prevents lots of unnecessary spacers carrying over from the previous row
if( !weekStart || doneEvent ){
wrapper.append( self._emptyEvent );
spaces++;
}
}
}
}
var remainingEvents = day.find('.cEvents_ranged [data-eventID]');
// If we have any remaining events, and there's gaps available, we can move those events into the gaps rather than
// just putting them at the end
if( spaces && remainingEvents.length ){
var availableSpaces = wrapper.find('[data-eventID="0"]');
// If we have a space and an event, move the event into that space
for( var i = 0; i <= spaces; i++ ){
if( remainingEvents[ i ] && availableSpaces[ i ] ){
$( availableSpaces[ i ] ).replaceWith( $( remainingEvents[ i ] ) );
}
}
// Update remaining events again
remainingEvents = day.find('.cEvents_ranged [data-eventID]');
}
// Add in remaining events
wrapper.append( remainingEvents );
// Replace the existing wrapper with the new, correctly-ordered one
day.find('.cEvents_ranged > .cEvents').replaceWith( wrapper );
// Now we need to build a new currentPositions array for this day, so that the following
// day can use it to do its thing
currentPositions = [];
_.each( day.find('.cEvents_ranged [data-eventID]'), function (event) {
var eventID = parseInt( $( event ).attr('data-eventID') );
if( eventID === 0 ){
currentPositions.push('-');
} else if( _.isNumber( eventID ) && !_.isNaN( eventID ) ){
currentPositions.push( eventID );
}
});
});
}
});
}(jQuery, _));]]> this.setupGoogleMaps())
}
else {
// Pull in the CSS
$('head').append(
`
`
);
// Now the JS
window.ips.loader.get( [ 'https://api.mapbox.com/mapbox.js/v3.3.1/mapbox.js' ] )
.then(
() => ips.loader.get([
'https://api.mapbox.com/mapbox.js/plugins/leaflet-markercluster/v1.0.0/leaflet.markercluster.js',
'https://api.mapbox.com/mapbox.js/plugins/leaflet-fullscreen/v1.0.1/Leaflet.fullscreen.min.js'
])
)
.then( () => {
if ( this.useGeolocation ) {
this.getEventsByLocation();
} else {
this.fetchEvents();
}
this._mapReady = true;
});
}
},
googleMap: undefined,
/**
* setup the map for google maps; really just a callback for the google maps api response
*/
setupGoogleMaps: function() {
// just enter the fetch event flow; the response will be handled on return
if ( this.useGeolocation ) {
this.getEventsByLocation();
} else {
this.fetchEvents();
}
window.ipsGoogleMapsCallback = undefined;
this._mapReady = true;
},
/**
* Handles use current location checkbox
*/
useCurrentLocation: function() {
// Not the best way to go about this but we clear the map with fetchEvents response
if ( this.useGoogleMaps ) {
this.googleMap = false;
}
if( this.scope.find('#useCurrentLocation').is(':checked') ) {
this.getEventsByLocation();
} else {
ips.utils.geolocation.setGeolocationIsAllowed( ips.utils.geolocation.permissions.DENIED );
this.fetchEvents();
}
},
/**
* Uses the browser's geolocation to set the lat and long of the map; those are then sent to community to get a listing of nearby events
*/
getEventsByLocation: function () {
// Save the contents just in case we need it again
this.showPlaceholders();
ips.utils.geolocation.getCurrentPosition().then( coords => {
var lat = coords.latitude;
var long = coords.longitude;
// Update the near me link
this.scope.find('[data-role="nearMeLink"]')
.attr('href', ips.getSetting('baseURL') + '?app=calendar&do=search&form_submitted=1&lat=' + lat + '&lon=' + long )
.attr('data-lat', lat)
.attr('data-long', long);
this.fetchEvents(lat, long);
}).catch( () => {
ips.ui.alert.show( {
type: 'alert',
message: ips.getString('event_your_current_location_sorry'),
icon: 'warn'
});
this.scope.find('#useCurrentLocation').closest('li').remove();
this.fetchEvents();
});
},
fetchEvents: function (lat, long) {
var self = this;
this.showPlaceholders();
ips.getAjax()( ips.getSetting('baseURL') + '?app=calendar&module=calendar&controller=view&view=overview&get=nearMe' + ( lat && long ? '&lat=' + lat + '&lon=' + long : '' ) )
.done( function (response) {
self.scope.find('[data-role="locationEvents"]').html(response.content);
self.setUpMap(response.lat, response.long);
$(document).trigger('contentChange', [self.scope]);
});
},
setUpMap: function (lat, long) {
var self = this;
var mapDiv = this.scope.find('#map');
if (!mapDiv.length) {
Debug.log("No map container");
return;
}
if ( this.useGoogleMaps ) {
// Make sure these are numbers. They might come in as string encoded Floats
lat = Number(lat);
long = Number(long);
if ( !this.googleMap ) {
const mapContainer = mapDiv.get(0);
mapContainer.style.width = '100%';
mapContainer.style.height = '400px';
this.googleMap = new window.google.maps.Map(mapContainer, {zoom: 4, center: {lat, lng: long}});
} else {
this.googleMap.setCenter({ lat, lng: long });
}
// Update the near me link
self.scope.find('[data-role="nearMeLink"]')
.attr('href', ips.getSetting('baseURL') + '?app=calendar&do=search&form_submitted=1&lat=' + lat + '&lon=' + long )
.attr('data-lat', lat)
.attr('data-long', long);
// If we have the map now, set its contents
if ( this.googleMap ) {
let markers;
try {
// We first copy the JSON and make sure lat an long are all numeric
let _markers = JSON.parse(mapDiv.attr('data-markers'));
let markers = [];
for ( let marker of Object.values(_markers) ) {
markers.push({
...marker,
lat: Number(marker.lat),
long: Number(marker.long)
});
}
for ( let marker of Object.values(markers) ) {
let mapMarker = new window.google.maps.Marker({
position: {lat: marker.lat, lng: marker.long},
map: this.googleMap,
title: marker.title
});
}
if ( Object.values(markers).length ) {
const cb = (() => {
let bounds = this.googleMap.getBounds();
if ( !bounds ) {
return window.setTimeout( cb, 200 );
}
let firstMarker = null;
let markerVisible = false;
let markerBounds = {latMax: null, latMin: null, lngMax: null, lngMin: null};
for (let marker of Object.values(markers)) {
markerBounds.latMax = markerBounds.latMax === null ? marker.lat : Math.max(markerBounds.latMax, marker.lat);
markerBounds.latMin = markerBounds.latMin === null ? marker.lat : Math.min(markerBounds.latMin, marker.lat);
markerBounds.lngMax = markerBounds.lngMax === null ? marker.long : Math.max(markerBounds.lngMax, marker.long);
markerBounds.lngMin = markerBounds.lngMin === null ? marker.long : Math.min(markerBounds.lngMin, marker.long);
firstMarker = firstMarker || marker;
if ( bounds.contains({lat: marker.lat, lng: marker.long}) ) {
markerVisible = true;
}
}
if (!markerVisible && firstMarker) {
// find the centermost event
let evtMidpoint = {
lat: ((markerBounds.latMax + markerBounds.latMin) / 2),
lng: ((markerBounds.lngMax + markerBounds.lngMin) / 2)
};
let minDistance = null;
let mostMiddleMarker = null;
if ( Object.values(markers).length > 1 ) {
for (let marker of Object.values(markers)) {
// we need to use the haversine formula to get he distance
let distance = this.getDistance(evtMidpoint.lat, marker.lat, evtMidpoint.lng, marker.long);
if (minDistance === null || distance < minDistance) {
minDistance = distance;
mostMiddleMarker = {lat: marker.lat, lng: marker.long};
}
}
} else {
mostMiddleMarker = {lat: firstMarker.lat, lng: firstMarker.long};
}
this.googleMap.panTo(mostMiddleMarker);
}
}).bind(this);
window.setTimeout(cb, 0);
}
} catch (e) {}
}
} else {
L.mapbox.accessToken = ips.getSetting('mapApiKey');
this._map = L.mapbox
.map(mapDiv.get(0))
.addLayer(L.mapbox.styleLayer('mapbox://styles/mapbox/streets-v11'));
// Map prefs
this._map.zoomControl = false;
this._map.minZoom = 2;
this._map.dragging = false;
this._map.zoomControl = false;
// Reposition zoom control
/*L.control.zoom({
position: 'bottomright'
}).addTo(this._map);*/
// Add and position fullscreen control
/*this._map.addControl(new L.Control.Fullscreen({
position: 'bottomleft'
}));*/
var markersGroup = new L.featureGroup([]);
var markers = $.parseJSON(mapDiv.attr('data-markers'));
for (var id in markers) {
var marker = L.marker([Number(markers[id].lat), Number(markers[id].long)], {
title: markers[id].title,
draggable: false,
eventId: parseInt(id)
});
//self._map.addLayer( marker );
markersGroup.addLayer(marker);
// Build info popup for this marker
marker.on('mouseover', _.bind(this._markerMouseOver, this));
marker.on('mouseout', _.bind(this._markerMouseOut, this));
}
markersGroup.addTo(this._map);
this._map.fitBounds(markersGroup.getBounds(), {maxZoom: 5});
}
},
/**
* Use the haversine formula to get a COOEFFICIENT of the distance between two points. This coefficient must be multiplied by 2*3956 to get the miles and by 2*6371 to get the kilometers
*
* @param lat1
* @param lat2
* @param lng1
* @param lng2
*
*
* @return {number}
*/
getDistance: function (lat1, lat2, lng1, lng2) {
// The math module contains a function
// named toRadians which converts from
// degrees to radians.
lng1 = lng1 * Math.PI / 180;
lng2 = lng2 * Math.PI / 180;
lat1 = lat1 * Math.PI / 180;
lat2 = lat2 * Math.PI / 180;
// Haversine formula
let dlng = lng2 - lng1;
let dlat = lat2 - lat1;
let a = Math.pow(Math.sin(dlat / 2), 2)
+ Math.cos(lat1) * Math.cos(lat2)
* Math.pow(Math.sin(dlng / 2),2);
let c = Math.asin(Math.sqrt(a));
// Radius of earth in kilometers. Use 3956
// for miles
//let r = 6371;
// calculate the result; multiply by 2*6371 for kilometers and 2*3956 for miles; at the time of writing this, the distance is only being compared so no need to factor in all these constants
return c;
},
_markerMouseOver: function (e) {
var eventId = e.target.options.eventId;
// Fade out all events except this one
this.scope.find('[data-eventid]').stop(false, true);
this.scope.find('[data-eventid]:not([data-eventid="' + eventId + '"])').animate({ opacity: 0.4 }, 'fast');
},
_markerMouseOut: function (e) {
this.scope.find('[data-eventid]').stop(false, true);
this.scope.find('[data-eventid]').animate({ opacity: 1 }, 'fast');
},
showPlaceholders: function (count) {
var events = [];
for( var i = 0; i < 6; i++ ){
events.push( ips.templates.render('eventLoading') );
}
this.scope
.find('[data-role="locationEvents"]')
.removeClass('ipsJS_hide')
.html( ips.templates.render('nearMe', { events: events.join('') }) );
}
});
}(jQuery, _));]]>
`;
ips.loader.get( [ 'https://api.mapbox.com/mapbox.js/v3.3.1/mapbox.js' ] ).then(() => {
ips.loader.get( [ 'https://api.mapbox.com/mapbox.js/plugins/leaflet-markercluster/v1.0.0/leaflet.markercluster.js',
'https://api.mapbox.com/mapbox.js/plugins/leaflet-fullscreen/v1.0.1/Leaflet.fullscreen.min.js' ] ).then(() => {
this._mapReady = true;
if( this._resultsContainer.find('[data-eventid]').length && this._mapContainer.attr('data-markers') ){
try {
const markers = JSON.parse( this._mapContainer.attr('data-markers') );
this._updateMapboxMap( markers );
} catch (err) {
Debug.log("Invalid marker JSON");
Debug.log(err);
}
}
});
});
},
getInitialLocation() {
if ( ips.utils.geolocation.getGeolocationIsAllowed() ) {
ips.utils.geolocation.getCurrentPosition()
.then( coords => {
this.setCoords( coords.latitude, coords.longitude );
})
// Update useMyLocation link
this.setSearchToLocation();
this._locationLink.attr('hidden', true);
this._cancelLocationLink.removeAttr('hidden');
}
},
setCoords(lat, long) {
this.scope.prepend("");
this.scope.prepend("");
this._gotCoords = true;
},
clearCoords() {
this.scope.find( 'input[name="lat"]' ).remove();
this.scope.find( 'input[name="lon"]' ).remove();
this._gotCoords = false;
},
moreNearMe(e) {
e.preventDefault();
// Get the lat/long from the link
var lat = $( e.currentTarget ).attr('data-lat');
var long = $( e.currentTarget ).attr('data-long');
var self = this;
this.setSearchToLocation();
this.scope.find('[name="date[start]"]').val('');
this.scope.find('[name="date[end]"]').val('');
this.scope.find('[name="show"]').val('regular');
$("html, body").animate({ scrollTop: "300px" }, function () {
self.doSearch();
});
},
moreExhibitions(e) {
e.preventDefault();
this.cancelLocation();
this.scope.find('[name="date[start]"]').val('');
this.scope.find('[name="date[end]"]').val('');
this.scope.find('[name="show"]').val('exhibitions');
$("html, body").animate({ scrollTop: "300px" }, () => {
this.doSearch();
});
},
/**
* Submit handler for the search form
*
* @param {Event} [e] Event object
* @returns {void}
*/
doSearch (e) {
e?.preventDefault()
const url = this._getUrlFromData();
ips.utils.history.replaceState( {
controller: 'calendar.front.overview.search',
url
}, 'calendarSearch', url );
},
/**
* Build placeholder elements from a template
*
* @param {number} [count] Number to build (10 default)
* @returns {string}
*/
getPlaceholders(count) {
let events = '';
count = typeof count === 'number' ? 10 : count;
for (let i = 0; i < count; i++) {
events += ips.templates.render('eventLoading');
}
return events;
},
/**
* Main state change event handler that responds to URL changes
*
* @param {event} e Event object
* @param {object} data Event data object
* @returns {void}
*/
stateChange() {
if (!('calendarSearch' in ips.utils.history.getState()) && this._initialURL !== window.location.href) {
return;
}
const state = ips.utils.history.getState('calendarSearch')
if (this._initialURL === state?.url) {
// If our URLs match but we have no state data, we can assume we've gone back to the initial search form, so let's reset
this.hideResults();
} else if (this._initialURL === window.location.href && state?.url === undefined) {
// If we don't have a URL, get it from our initial data
this._loadResults( this._getUrlFromData( this._initialData ) );
} else {
// Otherwise use the state url
this._loadResults( state?.url );
}
},
/**
* Builds a search URL based on on the form values
*
* @param {Event} e Event object
* @param {object} data Event data object
* @returns {string}
*/
changeSort(e, data) {
e.preventDefault();
this._currentSort = data.selectedItemID;
const title = data.menuElem.find('[data-ipsMenuValue="' + this._currentSort + '"]').text();
$('body').find('[data-role="searchOrder"]').text( title );
this.doSearch();
},
/**
* Builds a search URL based on on the form values
*
* @returns {string}
*/
_getUrlFromData() {
const formData = this.scope.serializeArray();
const params = [];
_.each( formData, function (item){
if( $.trim( item.value ) !== '' ){
params.push( item.name + "=" + encodeURIComponent( item.value ) );
}
});
params.push("sortBy=" + this._currentSort);
if (this._baseURL.match(/\?/)) {
if (!this._baseURL.endsWith('?')) {
this._baseURL += '&';
}
} else {
this._baseURL += '?';
}
return this._baseURL + params.join('&');
},
/**
* Handles performing a new search
*
* @param {string} url URL to request
* @returns {Promise}
*/
async _loadResults(url) {
// Do we need to hide the overview stuff?
if (this._page.length) {
this._page.find('[data-role="eventsOverview"]').hide();
this._page.find('[data-role="searchResultsWrapper"]').show();
}
this._resultsContainer.removeClass('ipsHide').html( this.getPlaceholders(6) );
if (ips.getSetting('mapProvider') !== 'none') {
this._mapContainer.addClass('cEvents__searchMap--loading').show();
}
this._currentUrl = url;
const response = await ips.fetch( url );
if (response.count) {
this._resultsContainer.html( response.content );
this._noResults.addClass('ipsHide');
this._updateMap( _.extend( {}, this._markers, response.markers ) );
if( response.totalCount > ( response.count + this._offset ) ){
this._loadMore.show();
this._noMore.hide();
} else {
this._loadMore.hide();
this._noMore.hide();
}
} else {
this._resultsContainer.html('').addClass('ipsHide');
this._noResults.removeClass('ipsHide');
this._loadMore.hide();
this._noMore.hide();
this._mapContainer.hide();
}
if (response.virtual || !response.markerCount) {
this._mapContainer.hide();
}
$( document ).trigger('contentChange', [ this._resultsContainer ]);
},
_updateMap(markers){
if (ips.getSetting( 'mapProvider' ) === 'google') {
this._updateGoogleMap( markers );
} else if (ips.getSetting( 'mapProvider' ) === 'mapbox') {
this._updateMapboxMap( markers );
} else {
this._mapContainer.hide(); // we really don't need it.
}
},
_updateGoogleMap(markers){
if( !this._map ){
this._map = new window.google.maps.Map( document.querySelector( '[data-role="searchResultsMap"]' ) );
this._mapMarkers = [];
}
this._mapContainer.removeClass('cEvents__searchMap--loading');
/* Clear existing markers before we rebuild */
if( this._mapMarkers.length ){
for ( let marker of Object.values(this._mapMarkers) ) {
marker.setMap(null);
}
this._mapMarkers = [];
}
const bounds = new google.maps.LatLngBounds();
for ( let marker of Object.values(markers) ) {
let mapMarker = new window.google.maps.Marker({
position: {lat: marker.lat, lng: marker.long},
map: this._map,
title: marker.title
});
this._mapMarkers.push( mapMarker );
const coords = new google.maps.LatLng(marker.lat, marker.long);
if( !( bounds.contains( coords ) ) ){
bounds.extend( coords );
}
}
/* If we have only one marker in here, fitting to the bounds zooms in way too much */
if( this._mapMarkers.length === 1 ){
this._map.setZoom(15);
this._map.setCenter(bounds.getCenter());
} else {
this._map.fitBounds( bounds );
}
this._mapContainer.show();
},
_updateMapboxMap(markers) {
if (!this._map) {
L.mapbox.accessToken = ips.getSetting('mapApiKey');
this._map = L.mapbox
.map( this._mapContainer.get(0) )
.addLayer(L.mapbox.styleLayer('mapbox://styles/mapbox/streets-v11'));
// Map prefs
this._map.zoomControl = false;
this._map.minZoom = 2;
this._map.dragging = false;
this._map.zoomControl = false;
this._markersGroup = new L.featureGroup([]);
this._markersGroup.addTo( this._map );
}
this._mapContainer.removeClass('cEvents__searchMap--loading');
this._markersGroup.clearLayers(); // Remove all existing markers
for (let id in markers) {
const marker = L.marker([markers[id].lat, markers[id].long], {
title: markers[id].title,
draggable: false,
eventId: parseInt(id)
});
this._markersGroup.addLayer( marker );
// Build info popup for this marker
marker.on('click', _.bind( this._markerClick, this ) );
}
this._mapContainer.show();
this._map.fitBounds(this._markersGroup.getBounds(), { maxZoom: 5 });
},
_markerClick(e) {
const targetEvent = e.target.options.eventId;
// Find that event in our listing
const eventBlock = this._resultsContainer.find('[data-eventId="' + targetEvent + '"]');
// Get position
const topPos = eventBlock.offset().top;
// Scroll to it
$('html, body').animate({ scrollTop: topPos }, function () {
eventBlock.css({ transform: 'scale(1.1)' });
setTimeout( function () {
eventBlock.css({ transform: 'scale(1)' });
}, 2000);
});
},
/**
* Handles loading more results into the current search
*
* @param {Event} e Event object
* @returns {Promise}
*/
async loadMore(e) {
e.preventDefault();
const placeholders = this.getPlaceholders(2);
this._resultsContainer.append( placeholders );
this._loadMore.prop('disabled', true);
const currentCount = this._resultsContainer.find('[data-eventid]').length;
const response = await ips.fetch( this._currentUrl + "&offset=" + currentCount + "&limit=" + this._limit);
this._resultsContainer.find('.ipsData__item--loading').remove();
this._resultsContainer.append( response.content );
this._updateMap( response.markers );
this._loadMore.prop('disabled', false);
if (this._resultsContainer.find('[data-eventid]').length >= response.totalCount) {
this._loadMore.hide();
this._noMore.show();
} else {
this._loadMore.show();
this._noMore.hide();
}
$( document ).trigger('contentChange', [ this._resultsContainer ]);
},
/**
* Remove current location as the search term
*
* @param {Event} [e] Event object
* @returns {void}
*/
cancelLocation(e) {
e?.preventDefault()
ips.utils.geolocation.setGeolocationIsAllowed(ips.utils.geolocation.permissions.DENIED);
this._cancelledLookup = true;
this._searchBox.val( this._previousValue ).attr('placeholder', this._originalPlaceholder).prop('disabled', false).removeClass('ipsField_loading');
this._locationLink.removeAttr('hidden');
this._cancelLocationLink.attr('hidden', true);
this.scope.find('[name="searchNearLocation"]').remove();
},
/**
* Fetch current location and use it as the current search param
*
* @param {Event} e Event object
* @returns {void}
*/
useMyLocation(e) {
e.preventDefault();
this._cancelledLookup = false;
this._searchBox.val('').attr('placeholder', ips.getString('event_finding_location')).prop('disabled', true).addClass('ipsField_loading');
this._locationLink.attr('hidden', true);
this._cancelLocationLink.removeAttr('hidden');
const afterCoords = () => {
if( this._cancelledLookup ){
this._cancelledLookup = false;
return;
}
this.setSearchToLocation();
this._cancelledLookup = false;
};
if( this._gotCoords ){
afterCoords()
} else {
ips.utils.geolocation.getCurrentPosition()
.then(coords => {
this.setCoords(coords.latitude, coords.longitude);
this.setSearchToLocation();
});
}
},
setSearchToLocation() {
this._searchBox.val('').attr('placeholder', ips.getString('event_your_current_location')).prop('disabled', true).removeClass('ipsField_loading');
this.scope.prepend("");
},
/**
* Cancel searching and show the overview page again
*
* @param {Event} e Event object
* @returns {void}
*/
backToOverview(e) {
if (!this._page.length) {
return;
}
e.preventDefault();
ips.utils.history.replaceState(
{
controller: 'calendar.front.overview.search',
url: this._initialURL
},
'calendarSearch',
this._initialURL
);
},
/**
* Hide the search results panel
*
* @returns {void}
*/
hideResults() {
this._page.find('[data-role="eventsOverview"]').show();
this._page.find('[data-role="searchResultsWrapper"]').hide();
}
});
}(jQuery, _));
]]> 1 ) {
$('#elSelect_event_repeats').find('option[value="daily"]').prop( 'disabled', true );
$('#elRepeatOn').hide();
$('#elRepeatOn').attr( 'id', 'elRepeatOn_back' );
if( !currentlySelected || currentlySelected == 'daily' )
{
$('#elSelect_event_repeats').find('option[value="daily"]').prop( 'selected', false );
$('#elSelect_event_repeats').find('option[value="weekly"]').prop( 'selected', true );
currentlySelected = 'weekly';
}
} else {
$('#elSelect_event_repeats').find('option[value="daily"]').prop( 'disabled', false );
if( $('#elRepeatOn_back').length )
{
$('#elRepeatOn_back').attr( 'id', 'elRepeatOn' );
}
if( $('#elSelect_event_repeats').val() == 'weekly' )
{
$('#elRepeatOn').show();
}
}
// If the event spans more than one week, remove "daily" and "weekly" as recurrence options
if( dayDifference > 7 ) {
$('#elSelect_event_repeats').find('option[value="weekly"]').prop( 'disabled', true );
if( !currentlySelected || currentlySelected == 'weekly' )
{
$('#elSelect_event_repeats').find('option[value="weekly"]').prop( 'selected', false );
$('#elSelect_event_repeats').find('option[value="monthly"]').prop( 'selected', true );
currentlySelected = 'monthly';
}
} else {
$('#elSelect_event_repeats').find('option[value="weekly"]').prop( 'disabled', false );
}
// If the event spans more than one month, remove "daily", "weekly" and "monthly" recurrence options
if(
// If there's more than one month between the dates, then we block
( endDate.getMonth() - startDate.getMonth() + ( 12 * ( endDate.getFullYear() - startDate.getFullYear() ) ) > 1 )
||
// Else if there is one month between the dates, then we check the end date is greater than the start date
(
endDate.getMonth() - startDate.getMonth() + ( 12 * ( endDate.getFullYear() - startDate.getFullYear() ) ) == 1 && endDate.getDate() > startDate.getDate()
)
) {
$('#elSelect_event_repeats').find('option[value="monthly"]').prop( 'disabled', true );
if( !currentlySelected || currentlySelected == 'monthly' )
{
$('#elSelect_event_repeats').find('option[value="monthly"]').prop( 'selected', false );
$('#elSelect_event_repeats').find('option[value="yearly"]').prop( 'selected', true );
currentlySelected = 'yearly';
}
} else {
$('#elSelect_event_repeats').find('option[value="monthly"]').prop( 'disabled', false );
}
// If the event spans more than one year, remove ability for event to repeat entirely
if( dayDifference > 365 )
{
$('#elSelect_event_repeats').find('option[value="yearly"]').prop( 'selected', false );
this.disableRecurring();
$('#elRepeatRow_hidden').hide();
}
else
{
$('#elRepeatRow_hidden').show();
}
// And update the summary in case anything changed
this._updateSummary();
},
/**
* Finish setting the options
*
* @returns {void}
*/
finishRepeat: function () {
this._updateSummary();
$('#elRecurEdit_menu').trigger('closeMenu');
},
/**
* Setup method
*
* @returns {void}
*/
setup: function () {
this._updateSummary();
this.toggleFields();
this.updateTimezone();
},
/**
* Toggle the end time enabled/disabled status
*
* @param {event} e Event object
* @returns {void}
*/
toggleEndtimeFields: function(e) {
if( this.scope.find('#check_no_end_time').is(':checked') )
{
this.scope.find('#end_time').prop('disabled', true);
}
else
{
this.scope.find('#end_time').prop('disabled', false);
}
},
/**
* Manual toggle functionality for the Single/All Day checkboxes
*
* @param {event} e Event object
* @returns {void}
*/
toggleFields: function (e) {
this.toggleEndtimeFields();
var singleDay = this.scope.find('#check_single_day');
var allDay = this.scope.find('#check_all_day');
var self = this;
var toggles = {
start_time_wrap: true,
end_time_wrap: true,
event_end_date_wrap: true,
elDateGrid_arrow: true,
elDateGrid_end: true,
end_date_controls: true
};
// Single day, not all day
if( singleDay.is(':checked') && !allDay.is(':checked') ){
toggles.event_end_date_wrap = false;
// Single day, all day
} else if( singleDay.is(':checked') && allDay.is(':checked') ){
toggles.elDateGrid_arrow = false;
toggles.elDateGrid_end = false;
toggles.start_time_wrap = false;
// Multiple days, all day
} else if( !singleDay.is(':checked') && allDay.is(':checked') ){
toggles.start_time_wrap = false;
toggles.end_time_wrap = false;
toggles.end_date_controls = false;
}
// Multiple days, not all day
else
{
toggles.end_date_controls = false;
this.scope.find('#end_time').prop('disabled', false);
}
// Hide appropriate elements
_.each( toggles, function (val, key) {
self.scope.find( '#' + key ).toggle( val );
});
},
/**
* Monitor changes to the repeat options and update the summary
*
* @param {event} e Event object
* @param {object} data Event data object
* @returns {void}
*/
checkForSummaryChange: function (e) {
if( $( e.currentTarget ).attr('name').startsWith('event_dates[') ){
this._updateSummary();
}
},
/**
* Updates the timezone value both in the hidden field and in the display to the end user
*
* @param {event} e Event object
* @returns {void}
*/
updateTimezone: function (e) {
if( e ){
e.preventDefault();
}
// Update displayed timezone
this.scope.find('[data-role="timezone_display"]').text( $('#event_timezone option:selected').data('abbreviated') ).trigger('closeMenu');
},
/**
* Removes the Repeat summary and unchecks the Repeat checkbox
*
* @param {event} e Event object
* @param {object} data Event data object
* @returns {void}
*/
disableRecurring: function (e) {
if( e )
{
e.preventDefault();
}
this.scope.find('#elRepeatCb').prop( 'checked', false );
this._updateSummary();
},
/**
* Finalize our recurring options
*
* @param {event} e Event object
* @param {object} data Event data object
* @returns {void}
*/
finishRepeatConfiguration: function (e, data) {
e.preventDefault();
// Copy the summary from the menu to the main display and close the menu
this.scope.find('[data-role="recur_summary_final"]').text( this.scope.find('[data-role="recur_summary"]').text() );
this.scope.find('#elRecurEdit').trigger('closeMenu');
},
/**
* Update the recurring string
*
* @param {event} e Event object
* @param {object} data Event data object
* @returns {void}
*/
_updateSummary: function () {
var summary = this.scope.find('[data-role="recurringSummary"]');
// Update repeating text
if( this.scope.find('#elRepeatCb').is(':checked') ){
summary.text( this._getSummary() );
this.scope.find('#elRepeatRow_hidden').hide();
this.scope.find('#elRepeatRow_shown').show();
} else {
summary.html( "" + ips.getString('doesnt_repeat') + "" );
this.scope.find('#elRepeatRow_hidden').show();
this.scope.find('#elRepeatRow_shown').hide();
}
// Update the dates
this.scope.find('[data-role="dateSummary"]').html( this._dateSummary() );
},
/**
* Builds a summary of the selected dates/times
*
* @returns {string}
*/
_dateSummary: function () {
// Build start date
var startDate = ips.utils.time.getDateFromInput( this.scope.find('input[name="event_dates[start_date]"]') );
var singleDay = this.scope.find('#check_single_day');
var allDay = this.scope.find('#check_all_day');
// If there's no start time, then abandon showing any summary for now
if( !ips.utils.time.isValidDateObj( startDate ) || startDate.getFullYear() < 1900 ) {
return '';
}
ips.utils.time.removeTimezone( startDate );
var startDateString = ips.utils.time.localeDateString( startDate, { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', timeZone: 'UTC' } );
var startTime = this._getTime( this.scope.find('input[name="event_dates[start_time]"]').val() );
var endTime = this._getTime( this.scope.find('input[name="event_dates[end_time]"]').val() );
var endDate = ips.utils.time.getDateFromInput( this.scope.find('input[name="event_dates[end_date]"]') );
var endDateString = '';
// If we have a valid end date...
if( !singleDay.is(':checked') && ips.utils.time.isValidDateObj( endDate ) ){
ips.utils.time.removeTimezone( endDate );
endDateString = ips.utils.time.localeDateString( endDate, { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', timeZone: 'UTC' } );
}
// Now build strings
var finalString = '';
if( singleDay.is(':checked') && !allDay.is(':checked') ){
if( this.scope.find('#check_no_end_time').is(':checked') )
{
finalString = ips.getString( 'single_not_allday_noendtime', { startDate: startDateString, startTime: startTime } );
}
else
{
finalString = ips.getString( 'single_not_allday', { startDate: startDateString, startTime: startTime, endTime: endTime } );
}
} else if ( ( !singleDay.is(':checked') && !allDay.is(':checked') ) && endDateString && startTime && endTime ) {
finalString = ips.getString( 'not_single_not_allday', { startDate: startDateString, endDate: endDateString, startTime: startTime, endTime: endTime } );
} else if( !singleDay.is(':checked') && allDay.is(':checked') && endDateString ) {
finalString = ips.getString( 'not_single_allday', { startDate: startDateString, endDate: endDateString } );
} else {
finalString = ips.getString( 'single_allday', { startDate: startDateString } );
}
return finalString;
},
/**
* Returns the given time, or a placeholder string if empty
*
* @param {string} time Time string
* @returns {string}
*/
_getTime: function (time) {
if( !time ){
return "" + ips.getString('select_time') + "";
}
var date = new Date('1970-01-01T' + time + 'Z');
var time = date.toLocaleTimeString( $('html').attr('lang'), { hour: '2-digit', minute: '2-digit', timeZone: 'UTC' } );
return time;
},
/**
* Returns the summary string for recurring events
*
* @returns {string}
*/
_getSummary: function () {
var type = this.scope.find('#elSelect_event_repeats').val();
var intervalString = '';
var endString = '';
// Build the 'interval' string
switch( type ){
case 'daily':
case 'monthly':
case 'yearly':
intervalString = this._buildString( type );
break;
case 'weekly':
intervalString = this._buildWeekly();
break;
}
// Build the 'end after' string
if( this.scope.find('#event_repeat_end_afterx').is(':checked') ){
var occurrences = parseInt( this.scope.find('input[name="event_dates[repeat_end_occurrences]"]').val() );
if( _.isNumber( occurrences ) && !_.isNaN( occurrences ) ){
endString = ips.pluralize( ips.getString( 'x_times' ), occurrences );
}
} else if( this.scope.find('#event_repeat_end_ondate').is(':checked') ){
var dateObj = ips.utils.time.getDateFromInput( this.scope.find('input[name="event_dates[repeat_end_date]"]') );
if( ips.utils.time.isValidDateObj( dateObj ) && dateObj.getFullYear() > 1900 ){ // > 1900 just so it doesn't start updating on year 19 etc.
ips.utils.time.removeTimezone( dateObj );
endString = ips.getString('until', { date: ips.utils.time.localeDateString( dateObj, { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric' } ) } );
}
}
// Put it together
if( endString ){
return ips.getString( 'with_end', { interval: intervalString, endAfter: endString } );
} else {
return intervalString;
}
},
/**
* Builds a summary string for daily, monthly and yearly repeats
*
* @param {string} type The type of repeat
* @returns {string}
*/
_buildString: function ( type ) {
var val = parseInt( this.scope.find('#elSelect_event_repeat_freq').val() ) || 1;
return ips.pluralize( ips.getString( 'every_x', { period: ips.pluralize( ips.getString( 'x_' + type ), val ) } ), val );
},
/**
* Builds a summary string for weekly repeats
*
* @returns {string}
*/
_buildWeekly: function () {
var selectedDays = this.scope.find('[data-iCal]:checked');
var val = parseInt( this.scope.find('#elSelect_event_repeat_freq').val() ) || 1;
var weekString = '';
weekString = ips.pluralize( ips.getString( 'x_weekly' ), val );
// If no days are selected, we can bypass and finish now
if( !selectedDays.length ){
return weekString;
}
// Get full days
var fullDays = _.map( selectedDays, function (day, key) {
return ips.getString( $( day ).attr('data-iCal') );
});
// Build string
var dayString = '';
if( fullDays.length === 1 ){
dayString = ips.getString( 'one_day', { first: fullDays[0] } );
} else {
dayString = ips.getString( 'multiple_day', { days: fullDays.slice(0, -1).join(', '), last: fullDays[ fullDays.length - 1 ] });
}
return ips.getString( 'week_string', { week: weekString, days: dayString } );
}
});
}(jQuery, _));]]> {
console.log( response );
this.scope.html( response );
$( document ).trigger( 'contentChange', [ self.scope ] );
})
.always(() => {
this._setLoading( false );
});
},
/**
* Toggles the loading state on the view
*
* @param {boolean} state Enable the loading state?
*
* @returns {void}
*/
_setLoading(state) {
this.scope.animate(
{
opacity: state ? "0.6" : "1"
},
"fast"
);
}
});
}(jQuery, _));]]>/**
* Invision Community
* (c) Invision Power Services, Inc. - https://www.invisioncommunity.com
*
* ips.calendar.reminderButton.js - Controller for reminder button
*
* Author: Andrew Millne
*/
;( function($, _, undefined){
"use strict";
ips.controller.register('calendar.front.view.reminderForm', {
initialize: function () {
this.on( 'submit', this.submitForm );
this.on( 'click', '[data-action="removereminder"]', this.removereminder );
this.setup();
},
/**
* Setup method
*
* @returns {void}
*/
setup: function () {
},
/**
* Event handler for removing reminder
*
* @param {event} e Event object
* @returns {void}
*/
removereminder: function (e) {
e.preventDefault();
this._doRemindAction( $( e.currentTarget ).attr('href'), {}, true );
},
/**
* Event handler for submitting the reminder form
*
* @param {event} e Event object
* @returns {void}
*/
submitForm: function (e) {
e.preventDefault();
this._doRemindAction( this.scope.attr('action'), this.scope.serialize(), false );
},
/**
* Performs an ajax action.
*
* @param {string} url URL to call
* @param {object} data Object of data to include in the request
* @returns {void}
*/
_doRemindAction: function (url, data, removereminder ) {
var self = this;
var dims = ips.utils.position.getElemDims( this.scope.parent('div') );
// Set it to loading
this.scope
.hide()
.parent('div')
.css({
width: dims.outerWidth + 'px',
height: dims.outerHeight + 'px'
})
.addClass('ipsLoading');
// Update reminder preference via ajax
ips.getAjax()( url, {
data: data,
type: 'post'
})
.done( function (response, status, jqXHR) {
if( jqXHR.getAllResponseHeaders().indexOf('X-IPS-FormError: true') !== -1 || jqXHR.getAllResponseHeaders().indexOf('X-IPS-FormNoSubmit: true') !== -1 || jqXHR.getAllResponseHeaders().indexOf('x-ips-formerror: true') !== -1 || jqXHR.getAllResponseHeaders().indexOf('x-ips-formnosubmit: true') !== -1 ){
self.scope
.show()
.html( response )
.parent('div')
.removeClass('ipsLoading')
.css({
width: 'auto',
height: 'auto'
});
} else {
// Success, so trigger event to update button
self.trigger('reminderItem');
if( removereminder ) {
ips.ui.flashMsg.show(ips.getString('event_reminder_removed'));
}
else {
ips.ui.flashMsg.show(ips.getString('event_reminder_added'));
}
self.scope.parents('.ipsHovercard').remove();
}
})
.fail( function (jqXHR, textStatus, errorThrown) {
window.location = url;
})
.always( function () {
// If we're in a hovercard, remove it
});
}
});
}(jQuery, _));
\
");]]>