diff --git a/src/main/webapp/css_new/geosearch.css b/src/main/webapp/css_new/geosearch.css new file mode 100644 index 0000000000000000000000000000000000000000..fb748db882ce6339d37100c346af88de39997a64 --- /dev/null +++ b/src/main/webapp/css_new/geosearch.css @@ -0,0 +1,55 @@ +.leaflet-control-geosearch, .leaflet-control-geosearch ul { + background: none repeat scroll 0 0 #FFFFFF; + border-radius: 5px 0px 0px 5px; + box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.65); + margin: 10px 0 0 0; + padding: 5px; + width: 390px; + height: auto; +} + +.leaflet-control-geosearch-msg ul { + list-style: none; + display: none; + height: auto; + background: none; + padding: 0px; +} + +.leaflet-control-geosearch-btn:before { + background: url("//maps.gstatic.com/tactile/omnibox/search-button.png") repeat scroll 0 0 rgba(0, 0, 0, 0); + content: ""; + display: block; + height: 17px; + margin: 0 auto; + width: 17px; +} + +.leaflet-control-geosearch-btn { + background-color: #4D90FE; + border: 0px none; + border-radius: 0px 5px 5px 0px; + height: 38px; + left: 400px; + position: absolute; + text-align: center; + top: 0px; + vertical-align: top; + width: 72px; +} + +.leaflet-control-geosearch ul li { + border-radius: 4px; + margin: 2px 0px; + padding: 4px; + font: 12px arial; + text-indent: 4px; +} + +.leaflet-container .leaflet-control-geosearch input { + width: 100%; + height: 28px; + padding: 0px; + text-indent: 8px; + border: none; +} diff --git a/src/main/webapp/css_new/isoga.css b/src/main/webapp/css_new/isoga.css new file mode 100644 index 0000000000000000000000000000000000000000..9ab4fa363ebe79bdaef244d3eea7a224bb3290c3 --- /dev/null +++ b/src/main/webapp/css_new/isoga.css @@ -0,0 +1,53 @@ +html { + height: 100%; + width: 100%; +} + +body { + margin: 0px; + padding: 0px; +} + +div.map { + display: block; + height: 100%; +} + +div.clipped { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + width: 200px; +} + +div.page-wrapper { + font-family: Helvetica; + margin-left: auto; + margin-right: auto; + padding: 0px; + width: 100%; +} + +fieldset { padding:0; border:0; margin-top:25px; } + +.help-control-div { + background: url('/isochrones/img_new/help_filled.png') no-repeat scroll 0px 0px transparent; + background-position: center; + background-size: 90%; + height: 24px; + width: 24px; +} + +.settings-control-div { + background: url('/isochrones/img_new/settings.png') no-repeat scroll 0px 0px #FFFFFF; + background-position: center; + background-size: 90%; + border-radius: 5px; + box-shadow: 0px 1px 5px rgba(0, 0, 0, 0.65); + height: 36px; + width: 36px; +} + +.olControlLayerSwitcher .layersDiv { + background-color: #191919; +} diff --git a/src/main/webapp/css_new/jquery-ui-timepicker-addon.css b/src/main/webapp/css_new/jquery-ui-timepicker-addon.css new file mode 100644 index 0000000000000000000000000000000000000000..da12d98330773a47b7419a5fc5423ec84f44ff8a --- /dev/null +++ b/src/main/webapp/css_new/jquery-ui-timepicker-addon.css @@ -0,0 +1,11 @@ +.ui-timepicker-div .ui-widget-header { margin-bottom: 8px; } +.ui-timepicker-div dl { text-align: left; } +.ui-timepicker-div dl dt { float: left; clear:left; padding: 0 0 0 5px; } +.ui-timepicker-div dl dd { margin: 0 10px 10px 40%; } +.ui-timepicker-div td { font-size: 90%; } +.ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; } + +.ui-timepicker-rtl{ direction: rtl; } +.ui-timepicker-rtl dl { text-align: right; padding: 0 5px 0 0; } +.ui-timepicker-rtl dl dt{ float: right; clear: right; } +.ui-timepicker-rtl dl dd { margin: 0 40% 10px 10px; } \ No newline at end of file diff --git a/src/main/webapp/img_new/help_filled.png b/src/main/webapp/img_new/help_filled.png new file mode 100644 index 0000000000000000000000000000000000000000..5567cdf7442cb7318e8e1c984a12a6892e2977c6 Binary files /dev/null and b/src/main/webapp/img_new/help_filled.png differ diff --git a/src/main/webapp/img_new/settings.png b/src/main/webapp/img_new/settings.png new file mode 100644 index 0000000000000000000000000000000000000000..c463df2ad0e74e0f974d980e139e076506f86a83 Binary files /dev/null and b/src/main/webapp/img_new/settings.png differ diff --git a/src/main/webapp/index_new.html b/src/main/webapp/index_new.html new file mode 100644 index 0000000000000000000000000000000000000000..2bb3375e07e69fc5e48ec744f0bd28962127660b --- /dev/null +++ b/src/main/webapp/index_new.html @@ -0,0 +1,104 @@ +<!DOCTYPE html PUBLIC> + +<html> +<head> + <title>ISOGA New: Using Isochrones for Geospatial Analysis</title> + <link type="text/css" rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.1/leaflet.css" /> + <link type="text/css" rel="stylesheet" href="http://code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css"> + <link type="text/css" rel="stylesheet" href="css_new/jquery-ui-timepicker-addon.css" /> + <link type="text/css" rel="stylesheet" href="css_new/geosearch.css" /> + <link type="text/css" rel="stylesheet" href="css_new/isoga.css" /> + + <script type="text/javascript" src="http://maps.google.com/maps/api/js?v=3&sensor=false"></script> + <script type="text/javascript" src="http://code.jquery.com/jquery-2.0.3.min.js"></script> + <script type="text/javascript" src="http://code.jquery.com/ui/1.10.3/jquery-ui.js"></script> + <script type="text/javascript" src="http://cdn.leafletjs.com/leaflet-0.7.1/leaflet.js"></script> + <script type="text/javascript" src="http://maps.stamen.com/js/tile.stamen.js"></script> + <script type="text/javascript" src="js_new/jquery-ui-timepicker-addon.js"></script> + <script type="text/javascript" src="js_new/console.js"></script> + <script type="text/javascript" src="js_new/geosearch.js"></script> + <script type="text/javascript" src="js_new/geosearch_provider_osm.js"></script> + <script type="text/javascript" src="js_new/geosearch_reverseProvider_osm.js"></script> + <script type="text/javascript" src="js_new/controls.js"></script> + <script type="text/javascript" src="js_new/bing.js"></script> + <script type="text/javascript" src="js_new/google.js"></script> + <script type="text/javascript" src="js_new/geolocator.js"></script> + <script type="text/javascript" src="js_new/map.js"></script> + <script type="text/javascript"> + $(function() { + $('#help-dialog').dialog({ + autoOpen: false, + modal: true, + resizable: false + }); + + $('#settings-dialog').dialog({ + autoOpen: false, + modal: true, + resizable: false + }); + + $('#speed').spinner({ + step: 0.1, + numberFormat: "n" + }); + + $('#settings-dialog select').css({'width':'100%'}); + $('#settings-dialog input').css({'width':'100%'}); + $('#datetime').datetimepicker({ + dateFormat: 'dd.mm.yy', + timeFormat: 'HH:mm' + }); + $('#datetime .ui-datepicker-today').click(); + }) + </script> +</head> + +<body onload="javascript:createMap();"> + <div class="page-wrapper"> + <div id="map" class="map"></div> + <div id='help-dialog' title="Hilfe"> + <p>Momentan ist keine Hilfe verfügbar!</p> + </div> + <div id="settings-dialog" title="Einstellungen"> + <form> + <fieldset> + <label for="email">Algorithmus</label><br> + <select name="algorithm" id="algorithm" class="ui-widget-content ui-corner-all"> + <option>MineX</option> + <option>MrneX</option> + <option>MDijkstra</option> + </select><br> + <label for="name">Dataset</label><br> + <select name="dataset" id="dataset" class="ui-widget-content ui-corner-all"> + <option value="BZ">Bozen</option> + <option value="ST">Südtirol</option> + <option value="IT">Italien</option> + </select><br> + <label for="name">Datum/Zeit</label><br> + <input name="datetime" id="datetime" class="ui-widget-content ui-corner-all"/><br> + <label for="name">Geschwindigkeit</label><br> + <input name="speed" id="speed"/><br> + <label for="name">T-Mode</label><br> + <select name="tmode" id="tmode" class="ui-widget-content ui-corner-all"> + <option>Unimodal</option> + <option>Multimodal</option> + </select><br> + <label for="name">Richtung</label><br> + <select name="direction" id="direction" class="ui-widget-content ui-corner-all"> + <option>Incoming</option> + <option>Outgoing</option> + </select><br> + <label for="name">Enclosure</label><br> + <select name="enclosure" id="enclosure" class="ui-widget-content ui-corner-all"> + <option>Buffer</option> + <option>Surface</option> + </select><br> + <label for="name">Expiration Mode</label><br> + <input type="checkbox" name="expirationMode" id="expirationMode" class="ui-widget-content ui-corner-all" /> + </fieldset> + </form> + </div> + </div> +</body> +</html> \ No newline at end of file diff --git a/src/main/webapp/js_new/bing.js b/src/main/webapp/js_new/bing.js new file mode 100644 index 0000000000000000000000000000000000000000..03f8845bfd91c9a40f47fc2fa5d3626b33a8b704 --- /dev/null +++ b/src/main/webapp/js_new/bing.js @@ -0,0 +1,117 @@ +L.BingLayer = L.TileLayer.extend({ + options: { + subdomains: [0, 1, 2, 3], + type: 'Aerial', + attribution: 'Bing', + culture: '' + }, + + initialize: function(key, options) { + L.Util.setOptions(this, options); + + this._key = key; + this._url = null; + this.meta = {}; + this.loadMetadata(); + }, + + tile2quad: function(x, y, z) { + var quad = ''; + for (var i = z; i > 0; i--) { + var digit = 0; + var mask = 1 << (i - 1); + if ((x & mask) != 0) digit += 1; + if ((y & mask) != 0) digit += 2; + quad = quad + digit; + } + return quad; + }, + + getTileUrl: function(p, z) { + var z = this._getZoomForUrl(); + var subdomains = this.options.subdomains, + s = this.options.subdomains[Math.abs((p.x + p.y) % subdomains.length)]; + return this._url.replace('{subdomain}', s) + .replace('{quadkey}', this.tile2quad(p.x, p.y, z)) + .replace('{culture}', this.options.culture); + }, + + loadMetadata: function() { + var _this = this; + var cbid = '_bing_metadata_' + L.Util.stamp(this); + window[cbid] = function (meta) { + _this.meta = meta; + window[cbid] = undefined; + var e = document.getElementById(cbid); + e.parentNode.removeChild(e); + if (meta.errorDetails) { + alert("Got metadata" + meta.errorDetails); + return; + } + _this.initMetadata(); + }; + var url = "http://dev.virtualearth.net/REST/v1/Imagery/Metadata/" + this.options.type + "?include=ImageryProviders&jsonp=" + cbid + "&key=" + this._key; + var script = document.createElement("script"); + script.type = "text/javascript"; + script.src = url; + script.id = cbid; + document.getElementsByTagName("head")[0].appendChild(script); + }, + + initMetadata: function() { + var r = this.meta.resourceSets[0].resources[0]; + this.options.subdomains = r.imageUrlSubdomains; + this._url = r.imageUrl; + this._providers = []; + for (var i = 0; i < r.imageryProviders.length; i++) { + var p = r.imageryProviders[i]; + for (var j = 0; j < p.coverageAreas.length; j++) { + var c = p.coverageAreas[j]; + var coverage = {zoomMin: c.zoomMin, zoomMax: c.zoomMax, active: false}; + var bounds = new L.LatLngBounds( + new L.LatLng(c.bbox[0]+0.01, c.bbox[1]+0.01), + new L.LatLng(c.bbox[2]-0.01, c.bbox[3]-0.01) + ); + coverage.bounds = bounds; + coverage.attrib = p.attribution; + this._providers.push(coverage); + } + } + this._update(); + }, + + _update: function() { + if (this._url == null || !this._map) return; + this._update_attribution(); + L.TileLayer.prototype._update.apply(this, []); + }, + + _update_attribution: function() { + var bounds = this._map.getBounds(); + var zoom = this._map.getZoom(); + for (var i = 0; i < this._providers.length; i++) { + var p = this._providers[i]; + if ((zoom <= p.zoomMax && zoom >= p.zoomMin) && + bounds.intersects(p.bounds)) { + if (!p.active) + this._map.attributionControl.addAttribution(p.attrib); + p.active = true; + } else { + if (p.active) + this._map.attributionControl.removeAttribution(p.attrib); + p.active = false; + } + } + }, + + onRemove: function(map) { + for (var i = 0; i < this._providers.length; i++) { + var p = this._providers[i]; + if (p.active) { + this._map.attributionControl.removeAttribution(p.attrib); + p.active = false; + } + } + L.TileLayer.prototype.onRemove.apply(this, [map]); + } +}); \ No newline at end of file diff --git a/src/main/webapp/js_new/console.js b/src/main/webapp/js_new/console.js new file mode 100644 index 0000000000000000000000000000000000000000..b33b25cc33222f5ef1139b5e29a56c6a7b17cf48 --- /dev/null +++ b/src/main/webapp/js_new/console.js @@ -0,0 +1,52 @@ +/* Copyright (c) 2012 Sergiy Kovalchuk (serg472@gmail.com) under the Apache License 2.0 */ +(function() { + if(window.location.hash.toLowerCase() == "#debug" || window.location.href.match(/[&?]debug\b/i)) { + DEBUG = true; + } + + var logProps = ["log", "debug", "info", "warn", "error"]; + var generalProps = ["assert", "clear", "count", "dir", "dirxml", "exception", "group", "groupCollapsed", "groupEnd", "markTimeline", "memoryProfile", "memoryProfileEnd", "profile", "profileEnd", "table", "time", "timeEnd", "timeStamp", "trace"]; + + //console is not available or disabled + if(typeof(window.console) === "undefined" || typeof(DEBUG) === "undefined" || (typeof(DEBUG) === "boolean" && !DEBUG) || (typeof(DEBUG) === "string" && DEBUG.toLowerCase() == "off")) { + //disable all + window.console = {}; + var props = generalProps.concat(logProps); + for(var i=0; i<props.length; i++) { + disableProp(props[i]); + } + } else if(typeof(window.console) !== "undefined") { + //console is available and not disabled + + for(var i=0; i<generalProps.length; i++) { + //enable all general console props if debug mode is on, disable otherwise + if((typeof(DEBUG) === "boolean" && DEBUG) || (typeof(DEBUG) === "string" && DEBUG.toLowerCase() == "on")) { + enableProp(generalProps[i]); + } else { + disableProp(generalProps[i]); + } + } + + //enable log props above logging level + var level = logProps[0]; + if(typeof(DEBUG) === "string") { + level = (DEBUG.toLowerCase() == "on" ? logProps[0] : DEBUG); + } + + var levelReached = false; + for(var i=0; i<logProps.length; i++) { + if(logProps[i] == level) { + levelReached = true; + } + levelReached ? enableProp(logProps[i]) : disableProp(logProps[i]); + } + } + + function disableProp(prop) { + window.console[prop] = function(){}; + } + + function enableProp(prop) { + if(typeof(window.console[prop]) === "undefined") disableProp(prop); + } +})(); \ No newline at end of file diff --git a/src/main/webapp/js_new/controls.js b/src/main/webapp/js_new/controls.js new file mode 100644 index 0000000000000000000000000000000000000000..71a01be21dbff48e659732764ba0b512dcc5206b --- /dev/null +++ b/src/main/webapp/js_new/controls.js @@ -0,0 +1,43 @@ +var HelpControl = L.Control.extend({ + options: { + position: 'bottomright' + }, + + onAdd: function (map) { + this._container = L.DomUtil.create('div', 'help-control'); + this._container.id = 'help-control-div'; + this._container.className = 'help-control-div'; + + L.DomEvent.addListener(this._container, 'click', this._onOpen); + L.DomEvent.disableClickPropagation(this._container); + + return this._container; + }, + + _onOpen: function() { + console.log('Showing helper dialog'); + $('#help-dialog').dialog('open'); + } +}); + +var SettingsControl = L.Control.extend({ + options: { + position: 'topright' + }, + + onAdd: function (map) { + this._container = L.DomUtil.create('div', 'settings-control'); + this._container.id = 'settings-control-div'; + this._container.className = 'settings-control-div'; + + L.DomEvent.addListener(this._container, 'click', this._onOpen); + L.DomEvent.disableClickPropagation(this._container); + + return this._container; + }, + + _onOpen: function() { + console.log('Showing settings'); + $('#settings-dialog').dialog('open'); + } +}); diff --git a/src/main/webapp/js_new/geolocator.js b/src/main/webapp/js_new/geolocator.js new file mode 100644 index 0000000000000000000000000000000000000000..7e200b0002ce1edb6a26a7e182c7e6ac8dc5e75c --- /dev/null +++ b/src/main/webapp/js_new/geolocator.js @@ -0,0 +1,352 @@ +/*jslint browser:true, nomen:true */ +//for jsLint implied variable warnings: +/*global google:false */ + +/* + * Geolocator Javascript Lib + * version: 1.1 + * author: Onur YILDIRIM + * contact: onur@cutepilot.com + * project page: https://github.com/onury/geolocator + * copyright: © 2012. MIT License. + */ +var geolocator = (function () { + + 'use strict'; + + /*-------- PRIVATE PROPERTIES & FIELDS --------*/ + + /* Storage for the callback function to be executed when the location is successfully fetched. */ + var onSuccess, + /* Storage for the callback function to be executed when the location could not be fetched due to an error. */ + onError, + /* HTML element ID for the Google Maps. */ + mCanvasId, + /* Google Maps URL. */ + googleLoaderURL = 'https://www.google.com/jsapi', + /* Array of source services that provide location-by-IP information. */ + ipGeoSources = [ + {url: 'http://freegeoip.net/json/', cbParam: 'callback'}, // 0 + {url: 'http://www.geoplugin.net/json.gp', cbParam: 'jsoncallback'}, // 1 + {url: 'http://geoiplookup.wikimedia.org/', cbParam: ''} // 2 + //,{url: 'http://j.maxmind.com/app/geoip.js', cbParam: ''} // Not implemented. Requires attribution. See http://dev.maxmind.com/geoip/javascript + ], + /* The index of the current IP source service. */ + ipGeoSourceIndex = 0; // default (freegeoip) + + /*-------- PRIVATE METHODS --------*/ + + /* Non-blocking method for loading scripts dynamically. + */ + function loadScript(url, callback, type) { + var script = document.createElement('script'); + script.type = (type === undefined) ? 'text/javascript' : type; + + if (typeof callback === 'function') { + if (script.readyState) { + script.onreadystatechange = function () { + if (script.readyState === 'loaded' || script.readyState === 'complete') { + script.onreadystatechange = null; + callback(); + } + }; + } else { + script.onload = function () { callback(); }; + } + } + + script.src = url; + //document.body.appendChild(script); + document.getElementsByTagName('head')[0].appendChild(script); + } + + /* Loads Google Maps API and executes the callback function when done. + */ + function loadGoogleMaps(callback) { + function loadMaps() { + if (geolocator.__glcb) { delete geolocator.__glcb; } + google.load('maps', '3', {other_params: 'sensor=false', callback: callback}); + } + if (window.google !== undefined && google.maps !== undefined) { + if (callback) { callback(); } + } else { + if (window.google !== undefined && google.loader !== undefined) { + loadMaps(); + } else { + geolocator.__glcb = loadMaps; + loadScript(googleLoaderURL + '?callback=geolocator.__glcb'); + } + } + } + + /* Draws the map from the fetched geo information. + */ + function drawMap(elemId, mapOptions, infoContent) { + var map, marker, infowindow, + elem = document.getElementById(elemId); + if (elem) { + map = new google.maps.Map(elem, mapOptions); + marker = new google.maps.Marker({ + position: mapOptions.center, + map: map + }); + infowindow = new google.maps.InfoWindow(); + infowindow.setContent(infoContent); + //infowindow.open(map, marker); + google.maps.event.addListener(marker, 'click', function () { + infowindow.open(map, marker); + }); + geolocator.location.map = { + canvas: elem, + map: map, + options: mapOptions, + marker: marker, + infoWindow: infowindow + }; + } else { + geolocator.location.map = null; + } + } + + /* Runs a reverse-geo lookup for the specified lat-lon coords. + */ + function reverseGeoLookup(latlng, callback) { + var geocoder = new google.maps.Geocoder(); + function onReverseGeo(results, status) { + if (status === google.maps.GeocoderStatus.OK) { + if (callback) { callback(results); } + } + } + geocoder.geocode({'latLng': latlng}, onReverseGeo); + } + + /* Fetches additional details (from the reverse-geo result) for the address property of the location object. + */ + function fetchDetailsFromLookup(data) { + if (data.length > 0) { + var i, comp, comps = data[0].address_components; + geolocator.location.formattedAddress = data[0].formatted_address; + + for (i = 0; i < comps.length; i += 1) { + comp = comps[i]; + if (comp.types.indexOf('route') >= 0) { + geolocator.location.address.street = comp.long_name; + } else if (comp.types.indexOf('neighborhood') >= 0) { + geolocator.location.address.neighborhood = comp.long_name; + } else if (comp.types.indexOf('sublocality') >= 0) { + geolocator.location.address.town = comp.long_name; + } else if (comp.types.indexOf('locality') >= 0) { + geolocator.location.address.city = comp.long_name; + } else if (comp.types.indexOf('administrative_area_level_1') >= 0) { + geolocator.location.address.region = comp.long_name; + } else if (comp.types.indexOf('country') >= 0) { + geolocator.location.address.country = comp.long_name; + geolocator.location.address.countryCode = comp.short_name; + } else if (comp.types.indexOf('postal_code') >= 0) { + geolocator.location.address.postalCode = comp.long_name; + } else if (comp.types.indexOf('street_number') >= 0) { + geolocator.location.address.streetNumber = comp.long_name; + } + } + } + } + + /* Finalizes the location object via reverse-geocoding and draws the map (if required). + */ + function finalize(coords) { + var latlng = new google.maps.LatLng(coords.latitude, coords.longitude); + function onGeoLookup(data) { + if (data.length > 0) { + fetchDetailsFromLookup(data); + } + + var zoom = geolocator.location.ipGeoSource === null ? 14 : 7, //zoom out if we got the lcoation from IP. + mapOptions = { + zoom: zoom, + center: latlng, + mapTypeId: 'roadmap' + }; + drawMap(mCanvasId, mapOptions, data[0].formatted_address); + if (onSuccess) { onSuccess.call(null, geolocator.location); } + } + reverseGeoLookup(latlng, onGeoLookup); + } + + /* Gets the geo-position via HTML5 geolocation (if supported). + */ + function getPosition(fallbackToIP, html5Options) { + geolocator.location = null; + + function fallback(errMsg) { + var ipsIndex = fallbackToIP === true ? 0 : (typeof fallbackToIP === 'number' ? fallbackToIP : -1); + if (ipsIndex >= 0) { + geolocator.locateByIP(onSuccess, onError, ipsIndex, mCanvasId); + } else { + if (onError) { onError.call(null, errMsg); } + } + } + + function geoSuccess(position) { + geolocator.location = { + ipGeoSource: null, + coords: position.coords, + timestamp: (new Date()).getTime(), //overwrite timestamp (Safari-Mac and iOS devices use different epoch; so better use this). + address: {} + }; + finalize(geolocator.location.coords); + } + + function geoError(error) { + // switch (error.code) { + // case error.PERMISSION_DENIED: + // break; + // case error.POSITION_UNAVAILABLE: + // break; + // case error.TIMEOUT: + // break; + // case error.UNKNOWN_ERROR: + // break; + // } + fallback(error.message); + } + + if (navigator.geolocation) { + navigator.geolocation.getCurrentPosition(geoSuccess, geoError, html5Options); + } else { // not supported + fallback('geolocation is not supported.'); + } + } + + /* Builds the location object from the source data. + */ + function buildLocation(sourceIndex, data) { + switch (sourceIndex) { + case 0: // freegeoip + geolocator.location = { + coords: { + latitude: data.latitude, + longitude: data.longitude + }, + address: { + city: data.city, + country: data.country_name, + countryCode: data.country_code, + region: data.region_name + } + }; + break; + case 1: // geoplugin + geolocator.location = { + coords: { + latitude: data.geoplugin_latitude, + longitude: data.geoplugin_longitude + }, + address: { + city: data.geoplugin_city, + country: data.geoplugin_countryName, + countryCode: data.geoplugin_countryCode, + region: data.geoplugin_regionName + } + }; + break; + case 2: // Wikimedia + geolocator.location = { + coords: { + latitude: data.lat, + longitude: data.lon + }, + address: { + city: data.city, + country: '', + countryCode: data.country, + region: '' + } + }; + break; + } + if (geolocator.location) { + geolocator.location.coords.accuracy = null; + geolocator.location.coords.altitude = null; + geolocator.location.coords.altitudeAccuracy = null; + geolocator.location.coords.heading = null; + geolocator.location.coords.speed = null; + geolocator.location.timestamp = new Date().getTime(); + geolocator.location.ipGeoSource = ipGeoSources[sourceIndex]; + geolocator.location.ipGeoSource.data = data; + } + } + + /* The callback that is executed when the location data is fetched from the source. + */ + function onGeoSourceCallback(data) { + var initialized = false; + geolocator.location = null; + delete geolocator.__ipscb; + + function gLoadCallback() { + if (ipGeoSourceIndex === 2) { // Wikimedia + if (window.Geo !== undefined) { + buildLocation(ipGeoSourceIndex, window.Geo); + delete window.Geo; + initialized = true; + } + } else { + if (data !== undefined) { + buildLocation(ipGeoSourceIndex, data); + initialized = true; + } + } + + if (initialized === true) { + finalize(geolocator.location.coords); + } else { + if (onError) { onError('Could not get location.'); } + } + } + + loadGoogleMaps(gLoadCallback); + } + + /* Loads the (jsonp) source. If the source does not support json-callbacks; + * the callback is executed dynamically when the source is loaded completely. + */ + function loadIpGeoSource(source) { + if (source.cbParam === undefined || source.cbParam === null || source.cbParam === '') { + loadScript(source.url, onGeoSourceCallback); + } else { + loadScript(source.url + '?' + source.cbParam + '=geolocator.__ipscb'); //ip source callback + } + } + + return { + + /*-------- PUBLIC PROPERTIES --------*/ + + /* The recent location information fetched as an object. + */ + location: null, + + /*-------- PUBLIC METHODS --------*/ + + /* Gets the geo-location by requesting user's permission. + */ + locate: function (successCallback, errorCallback, fallbackToIP, html5Options, mapCanvasId) { + onSuccess = successCallback; + onError = errorCallback; + mCanvasId = mapCanvasId; + function gLoadCallback() { getPosition(fallbackToIP, html5Options); } + loadGoogleMaps(gLoadCallback); + }, + /* Gets the geo-location from the user's IP. + */ + locateByIP: function (successCallback, errorCallback, sourceIndex, mapCanvasId) { + ipGeoSourceIndex = (sourceIndex === undefined || + (sourceIndex < 0 || sourceIndex >= ipGeoSources.length)) ? 0 : sourceIndex; + onSuccess = successCallback; + onError = errorCallback; + mCanvasId = mapCanvasId; + geolocator.__ipscb = onGeoSourceCallback; + loadIpGeoSource(ipGeoSources[ipGeoSourceIndex]); + } + }; +}()); diff --git a/src/main/webapp/js_new/geosearch.js b/src/main/webapp/js_new/geosearch.js new file mode 100644 index 0000000000000000000000000000000000000000..819eded2f8d41db5f700d6e3673fb6874cb157ad --- /dev/null +++ b/src/main/webapp/js_new/geosearch.js @@ -0,0 +1,253 @@ +/* + * L.Control.GeoSearch - search for an address and zoom to its location + * https://github.com/smeijer/leaflet.control.geosearch + */ + +L.GeoSearch = {}; +L.GeoSearch.Provider = {}; +L.GeoSearch.ReverseProvider = {}; + +L.GeoSearch.ReverseResult = function (address, label) { + this.address = address; + this.Label = label; +}; + +L.GeoSearch.Result = function (x, y, label) { + this.X = x; + this.Y = y; + this.Label = label; +}; + +L.Control.GeoSearch = L.Control.extend({ + options: { + position: 'topleft', + provider: null, + reverseProvider: null, + showMarker: true + }, + + _config: { + country: '', + searchLabel: 'search for address ...', + notFoundMessage: 'Sorry, that address could not be found.', + messageHideDelay: 3000, + zoomLevel: 18 + }, + + initialize: function (options) { + L.Util.extend(this.options, options); + L.Util.extend(this._config, options); + }, + + onAdd: function (map) { + var $controlContainer = map._controlContainer, + nodes = $controlContainer.childNodes; + + this._map = map; + this._container = L.DomUtil.create('div', 'leaflet-control-geosearch'); + + var searchbox = document.createElement('input'); + searchbox.id = 'leaflet-control-geosearch-qry'; + searchbox.type = 'text'; + searchbox.placeholder = this._config.searchLabel; + this._searchbox = searchbox; + + var searchbtn = document.createElement('button'); + searchbtn.id = 'leaflet-control-geosearch-btn'; + searchbtn.className = 'leaflet-control-geosearch-btn'; + this._searchbtn = searchbtn; + + var msgbox = document.createElement('div'); + msgbox.id = 'leaflet-control-geosearch-msg'; + msgbox.className = 'leaflet-control-geosearch-msg'; + this._msgbox = msgbox; + + var resultslist = document.createElement('ul'); + resultslist.id = 'leaflet-control-geosearch-results'; + this._resultslist = resultslist; + + this._msgbox.appendChild(this._resultslist); + this._container.appendChild(this._searchbtn); + this._container.appendChild(this._searchbox); + this._container.appendChild(this._msgbox); + + L.DomEvent + .addListener(this._container, 'click', L.DomEvent.stop) + .addListener(this._searchbtn, 'click', this._onSearchClick, this) + .addListener(this._searchbox, 'keypress', this._onKeyUp, this); + + if (this._config.reverseProvider) { + L.DomEvent.addListener(this._map, 'click', this._onMapClick, this); + } + + L.DomEvent.disableClickPropagation(this._container); + + return this._container; + }, + + geosearch: function (qry) { + try { + var provider = this._config.provider; + + if(typeof provider.GetLocations == 'function') { + var results = provider.GetLocations(qry, function(results) { + this._processResults(results); + }.bind(this)); + } else { + var url = provider.GetServiceUrl(qry); + this.sendRequest(provider, url); + } + } catch (error) { + this._printError(error); + } + }, + + sendRequest: function (provider, url) { + var that = this; + + window.parseLocation = function (response) { + var results = provider.ParseJSON(response); + that._processResults(results); + + document.body.removeChild(document.getElementById('getJsonP')); + delete window.parseLocation; + }; + + function getJsonP (url) { + url = url + '&callback=parseLocation'; + var script = document.createElement('script'); + script.id = 'getJsonP'; + script.src = url; + script.async = true; + document.body.appendChild(script); + } + + if (XMLHttpRequest) { + var xhr = new XMLHttpRequest(); + + if ('withCredentials' in xhr) { + var xhr = new XMLHttpRequest(); + + xhr.onreadystatechange = function () { + if (xhr.readyState == 4) { + if (xhr.status == 200) { + var response = JSON.parse(xhr.responseText), + results = provider.ParseJSON(response); + + that._processResults(results); + } else if (xhr.status == 0 || xhr.status == 400) { + getJsonP(url); + } else { + that._printError(xhr.responseText); + } + } + }; + + xhr.open('GET', url, true); + xhr.send(); + } else if (XDomainRequest) { + var xdr = new XDomainRequest(); + + xdr.onerror = function (err) { + that._printError(err); + }; + + xdr.onload = function () { + var response = JSON.parse(xdr.responseText), + results = provider.ParseJSON(response); + + that._processResults(results); + }; + + xdr.open('GET', url); + xdr.send(); + } else { + getJsonP(url); + } + } + }, + + _processResults: function(results) { + if (results[0] instanceof L.GeoSearch.Result) { + this._processForwardResults(results); + } else { + this._processReverseResults(results); + } + }, + + _processForwardResults: function(results) { + if (results.length > 0) { + this._map.fireEvent('geosearch_foundlocations', {Locations: results}); + this._showLocation(results[0]); + } else { + this._printError(this._config.notFoundMessage); + } + }, + + _processReverseResults: function(result) { + if (!result) { + this._printError(this._config.notFoundMessage); + } + + var address = result.address; + this._searchbox.value = address.road + ', ' + address.postcode + ', ' + address.country; + }, + + _showLocation: function (location) { + if (this.options.showMarker == true) { + if (typeof this._positionMarker === 'undefined') { + this._positionMarker = L.marker([location.Y, location.X]).addTo(this._map); + } else { + this._positionMarker.setLatLng([location.Y, location.X]); + } + } + + this._map.setView([location.Y, location.X], this._config.zoomLevel, false); + this._map.fireEvent('geosearch_showlocation', {Location: location}); + }, + + _printError: function(message) { + var elem = this._resultslist; + elem.innerHTML = '<li>' + message + '</li>'; + elem.style.display = 'block'; + + setTimeout(function () { + elem.style.display = 'none'; + }, 3000); + }, + + _onKeyUp: function (e) { + var esc = 27, + enter = 13, + queryBox = document.getElementById('leaflet-control-geosearch-qry'); + + if (e.keyCode === esc) { // escape key detection is unreliable + queryBox.value = ''; + this._map._container.focus(); + } else if (e.keyCode === enter) { + this.geosearch(queryBox.value); + } + }, + + _onMapClick: function (e) { + var location = e.latlng, + provider = this._config.reverseProvider; + + if (!location || !provider) { + return; + } + + try { + var url = provider.GetServiceUrl(location.lat, location.lng); + this.sendRequest(provider, url); + } catch (error) { + this._printError(error); + } + }, + + _onSearchClick: function (e) { + var queryBox = document.getElementById('leaflet-control-geosearch-qry'); + + this.geosearch(queryBox.value); + } +}); diff --git a/src/main/webapp/js_new/geosearch_provider_osm.js b/src/main/webapp/js_new/geosearch_provider_osm.js new file mode 100644 index 0000000000000000000000000000000000000000..ed4effb37d99d361bcb76afab3986f8337b0bfc8 --- /dev/null +++ b/src/main/webapp/js_new/geosearch_provider_osm.js @@ -0,0 +1,40 @@ +/** + * L.Control.GeoSearch - search for an address and zoom to it's location + * L.GeoSearch.Provider.OpenStreetMap uses openstreetmap geocoding service + * https://github.com/smeijer/leaflet.control.geosearch + */ + +L.GeoSearch.Provider.OpenStreetMap = L.Class.extend({ + options : { + + }, + + initialize : function(options) { + options = L.Util.setOptions(this, options); + }, + + GetServiceUrl : function(qry) { + var parameters = L.Util.extend({ + q : qry, + format : 'json' + }, this.options); + + return 'http://nominatim.openstreetmap.org/search' + + L.Util.getParamString(parameters); + }, + + ParseJSON : function(data) { + if (data.length == 0) + return []; + + var results = []; + for (var i = 0; i < data.length; i++) + results.push(new L.GeoSearch.Result( + data[i].lon, + data[i].lat, + data[i].display_name + )); + + return results; + } +}); diff --git a/src/main/webapp/js_new/geosearch_reverseProvider_osm.js b/src/main/webapp/js_new/geosearch_reverseProvider_osm.js new file mode 100644 index 0000000000000000000000000000000000000000..5d3d2f8af7b0f4900487d194517acb61526e1c3f --- /dev/null +++ b/src/main/webapp/js_new/geosearch_reverseProvider_osm.js @@ -0,0 +1,37 @@ +/** + * L.Control.GeoSearch - search for an address and zoom to it's location + * L.GeoSearch.Provider.OpenStreetMap uses openstreetmap geocoding service + * https://github.com/smeijer/leaflet.control.geosearch + */ + +L.GeoSearch.ReverseProvider.OpenStreetMap = L.Class.extend({ + options : { + + }, + + initialize : function(options) { + options = L.Util.setOptions(this, options); + }, + + GetServiceUrl : function(lat, lon) { + var parameters = L.Util.extend({ + lat: lat, + lon: lon, + format : 'json' + }, this.options); + + return 'http://nominatim.openstreetmap.org/reverse' + + L.Util.getParamString(parameters); + }, + + ParseJSON : function(data) { + if (data.length == 0) + return []; + + return new L.GeoSearch.ReverseResult( + data.address, + data.display_name + ); + } + +}); diff --git a/src/main/webapp/js_new/google.js b/src/main/webapp/js_new/google.js new file mode 100644 index 0000000000000000000000000000000000000000..be69b226921ca583bad9115a6284d31b338f74f2 --- /dev/null +++ b/src/main/webapp/js_new/google.js @@ -0,0 +1,176 @@ +/* + * Google layer using Google Maps API + */ +//(function (google, L) { + +L.Google = L.Class.extend({ + includes: L.Mixin.Events, + + options: { + minZoom: 0, + maxZoom: 18, + tileSize: 256, + subdomains: 'abc', + errorTileUrl: '', + attribution: '', + opacity: 1, + continuousWorld: false, + noWrap: false + }, + + // Possible types: SATELLITE, ROADMAP, HYBRID, TERRAIN + initialize: function(type, options) { + L.Util.setOptions(this, options); + + this._ready = google.maps.Map != undefined; + if (!this._ready) L.Google.asyncWait.push(this); + + this._type = type || 'SATELLITE'; + }, + + onAdd: function(map, insertAtTheBottom) { + this._map = map; + this._insertAtTheBottom = insertAtTheBottom; + + // create a container div for tiles + this._initContainer(); + this._initMapObject(); + + // set up events + map.on('viewreset', this._resetCallback, this); + + this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this); + map.on('move', this._update, this); + //map.on('moveend', this._update, this); + + map._controlCorners['bottomright'].style.marginBottom = "1em"; + + this._reset(); + this._update(); + }, + + onRemove: function(map) { + this._map._container.removeChild(this._container); + //this._container = null; + + this._map.off('viewreset', this._resetCallback, this); + + this._map.off('move', this._update, this); + map._controlCorners['bottomright'].style.marginBottom = "0em"; + //this._map.off('moveend', this._update, this); + }, + + getAttribution: function() { + return this.options.attribution; + }, + + setOpacity: function(opacity) { + this.options.opacity = opacity; + if (opacity < 1) { + L.DomUtil.setOpacity(this._container, opacity); + } + }, + + setElementSize: function(e, size) { + e.style.width = size.x + "px"; + e.style.height = size.y + "px"; + }, + + _initContainer: function() { + var tilePane = this._map._container, + first = tilePane.firstChild; + + if (!this._container) { + this._container = L.DomUtil.create('div', 'leaflet-google-layer leaflet-top leaflet-left'); + this._container.id = "_GMapContainer_" + L.Util.stamp(this); + this._container.style.zIndex = "auto"; + } + + if (true) { + tilePane.insertBefore(this._container, first); + + this.setOpacity(this.options.opacity); + this.setElementSize(this._container, this._map.getSize()); + } + }, + + _initMapObject: function() { + if (!this._ready) return; + this._google_center = new google.maps.LatLng(0, 0); + var map = new google.maps.Map(this._container, { + center: this._google_center, + zoom: 0, + tilt: 0, + mapTypeId: google.maps.MapTypeId[this._type], + disableDefaultUI: true, + keyboardShortcuts: false, + draggable: false, + disableDoubleClickZoom: true, + scrollwheel: false, + streetViewControl: false + }); + + var _this = this; + this._reposition = google.maps.event.addListenerOnce(map, "center_changed", + function() { _this.onReposition(); }); + + map.backgroundColor = '#ff0000'; + this._google = map; + }, + + _resetCallback: function(e) { + this._reset(e.hard); + }, + + _reset: function(clearOldContainer) { + this._initContainer(); + }, + + _update: function() { + if (!this._google) return; + this._resize(); + + var bounds = this._map.getBounds(); + var ne = bounds.getNorthEast(); + var sw = bounds.getSouthWest(); + var google_bounds = new google.maps.LatLngBounds( + new google.maps.LatLng(sw.lat, sw.lng), + new google.maps.LatLng(ne.lat, ne.lng) + ); + var center = this._map.getCenter(); + var _center = new google.maps.LatLng(center.lat, center.lng); + + this._google.setCenter(_center); + this._google.setZoom(this._map.getZoom()); + //this._google.fitBounds(google_bounds); + }, + + _resize: function() { + var size = this._map.getSize(); + if (this._container.style.width == size.x && + this._container.style.height == size.y) + return; + this.setElementSize(this._container, size); + this.onReposition(); + }, + + onReposition: function() { + if (!this._google) return; + google.maps.event.trigger(this._google, "resize"); + } +}); + +L.Google.asyncWait = []; +L.Google.asyncInitialize = function() { + var i; + for (i = 0; i < L.Google.asyncWait.length; i++) { + var o = L.Google.asyncWait[i]; + o._ready = true; + if (o._container) { + o._initMapObject(); + o._update(); + } + } + L.Google.asyncWait = []; +} +//})(window.google, L) \ No newline at end of file diff --git a/src/main/webapp/js_new/jquery-ui-timepicker-addon.js b/src/main/webapp/js_new/jquery-ui-timepicker-addon.js new file mode 100644 index 0000000000000000000000000000000000000000..b7e5adcf2f37bef0d127ccba81fe1aac4b3d4d17 --- /dev/null +++ b/src/main/webapp/js_new/jquery-ui-timepicker-addon.js @@ -0,0 +1,2145 @@ +/*! jQuery Timepicker Addon - v1.4.3 - 2013-11-30 +* http://trentrichardson.com/examples/timepicker +* Copyright (c) 2013 Trent Richardson; Licensed MIT */ +(function ($) { + + /* + * Lets not redefine timepicker, Prevent "Uncaught RangeError: Maximum call stack size exceeded" + */ + $.ui.timepicker = $.ui.timepicker || {}; + if ($.ui.timepicker.version) { + return; + } + + /* + * Extend jQueryUI, get it started with our version number + */ + $.extend($.ui, { + timepicker: { + version: "1.4.3" + } + }); + + /* + * Timepicker manager. + * Use the singleton instance of this class, $.timepicker, to interact with the time picker. + * Settings for (groups of) time pickers are maintained in an instance object, + * allowing multiple different settings on the same page. + */ + var Timepicker = function () { + this.regional = []; // Available regional settings, indexed by language code + this.regional[''] = { // Default regional settings + currentText: 'Now', + closeText: 'Done', + amNames: ['AM', 'A'], + pmNames: ['PM', 'P'], + timeFormat: 'HH:mm', + timeSuffix: '', + timeOnlyTitle: 'Choose Time', + timeText: 'Time', + hourText: 'Hour', + minuteText: 'Minute', + secondText: 'Second', + millisecText: 'Millisecond', + microsecText: 'Microsecond', + timezoneText: 'Time Zone', + isRTL: false + }; + this._defaults = { // Global defaults for all the datetime picker instances + showButtonPanel: true, + timeOnly: false, + showHour: null, + showMinute: null, + showSecond: null, + showMillisec: null, + showMicrosec: null, + showTimezone: null, + showTime: true, + stepHour: 1, + stepMinute: 1, + stepSecond: 1, + stepMillisec: 1, + stepMicrosec: 1, + hour: 0, + minute: 0, + second: 0, + millisec: 0, + microsec: 0, + timezone: null, + hourMin: 0, + minuteMin: 0, + secondMin: 0, + millisecMin: 0, + microsecMin: 0, + hourMax: 23, + minuteMax: 59, + secondMax: 59, + millisecMax: 999, + microsecMax: 999, + minDateTime: null, + maxDateTime: null, + onSelect: null, + hourGrid: 0, + minuteGrid: 0, + secondGrid: 0, + millisecGrid: 0, + microsecGrid: 0, + alwaysSetTime: true, + separator: ' ', + altFieldTimeOnly: true, + altTimeFormat: null, + altSeparator: null, + altTimeSuffix: null, + pickerTimeFormat: null, + pickerTimeSuffix: null, + showTimepicker: true, + timezoneList: null, + addSliderAccess: false, + sliderAccessArgs: null, + controlType: 'slider', + defaultValue: null, + parse: 'strict' + }; + $.extend(this._defaults, this.regional['']); + }; + + $.extend(Timepicker.prototype, { + $input: null, + $altInput: null, + $timeObj: null, + inst: null, + hour_slider: null, + minute_slider: null, + second_slider: null, + millisec_slider: null, + microsec_slider: null, + timezone_select: null, + hour: 0, + minute: 0, + second: 0, + millisec: 0, + microsec: 0, + timezone: null, + hourMinOriginal: null, + minuteMinOriginal: null, + secondMinOriginal: null, + millisecMinOriginal: null, + microsecMinOriginal: null, + hourMaxOriginal: null, + minuteMaxOriginal: null, + secondMaxOriginal: null, + millisecMaxOriginal: null, + microsecMaxOriginal: null, + ampm: '', + formattedDate: '', + formattedTime: '', + formattedDateTime: '', + timezoneList: null, + units: ['hour', 'minute', 'second', 'millisec', 'microsec'], + support: {}, + control: null, + + /* + * Override the default settings for all instances of the time picker. + * @param {Object} settings object - the new settings to use as defaults (anonymous object) + * @return {Object} the manager object + */ + setDefaults: function (settings) { + extendRemove(this._defaults, settings || {}); + return this; + }, + + /* + * Create a new Timepicker instance + */ + _newInst: function ($input, opts) { + var tp_inst = new Timepicker(), + inlineSettings = {}, + fns = {}, + overrides, i; + + for (var attrName in this._defaults) { + if (this._defaults.hasOwnProperty(attrName)) { + var attrValue = $input.attr('time:' + attrName); + if (attrValue) { + try { + inlineSettings[attrName] = eval(attrValue); + } catch (err) { + inlineSettings[attrName] = attrValue; + } + } + } + } + + overrides = { + beforeShow: function (input, dp_inst) { + if ($.isFunction(tp_inst._defaults.evnts.beforeShow)) { + return tp_inst._defaults.evnts.beforeShow.call($input[0], input, dp_inst, tp_inst); + } + }, + onChangeMonthYear: function (year, month, dp_inst) { + // Update the time as well : this prevents the time from disappearing from the $input field. + tp_inst._updateDateTime(dp_inst); + if ($.isFunction(tp_inst._defaults.evnts.onChangeMonthYear)) { + tp_inst._defaults.evnts.onChangeMonthYear.call($input[0], year, month, dp_inst, tp_inst); + } + }, + onClose: function (dateText, dp_inst) { + if (tp_inst.timeDefined === true && $input.val() !== '') { + tp_inst._updateDateTime(dp_inst); + } + if ($.isFunction(tp_inst._defaults.evnts.onClose)) { + tp_inst._defaults.evnts.onClose.call($input[0], dateText, dp_inst, tp_inst); + } + } + }; + for (i in overrides) { + if (overrides.hasOwnProperty(i)) { + fns[i] = opts[i] || null; + } + } + + tp_inst._defaults = $.extend({}, this._defaults, inlineSettings, opts, overrides, { + evnts: fns, + timepicker: tp_inst // add timepicker as a property of datepicker: $.datepicker._get(dp_inst, 'timepicker'); + }); + tp_inst.amNames = $.map(tp_inst._defaults.amNames, function (val) { + return val.toUpperCase(); + }); + tp_inst.pmNames = $.map(tp_inst._defaults.pmNames, function (val) { + return val.toUpperCase(); + }); + + // detect which units are supported + tp_inst.support = detectSupport( + tp_inst._defaults.timeFormat + + (tp_inst._defaults.pickerTimeFormat ? tp_inst._defaults.pickerTimeFormat : '') + + (tp_inst._defaults.altTimeFormat ? tp_inst._defaults.altTimeFormat : '')); + + // controlType is string - key to our this._controls + if (typeof(tp_inst._defaults.controlType) === 'string') { + if (tp_inst._defaults.controlType === 'slider' && typeof($.ui.slider) === 'undefined') { + tp_inst._defaults.controlType = 'select'; + } + tp_inst.control = tp_inst._controls[tp_inst._defaults.controlType]; + } + // controlType is an object and must implement create, options, value methods + else { + tp_inst.control = tp_inst._defaults.controlType; + } + + // prep the timezone options + var timezoneList = [-720, -660, -600, -570, -540, -480, -420, -360, -300, -270, -240, -210, -180, -120, -60, + 0, 60, 120, 180, 210, 240, 270, 300, 330, 345, 360, 390, 420, 480, 525, 540, 570, 600, 630, 660, 690, 720, 765, 780, 840]; + if (tp_inst._defaults.timezoneList !== null) { + timezoneList = tp_inst._defaults.timezoneList; + } + var tzl = timezoneList.length, tzi = 0, tzv = null; + if (tzl > 0 && typeof timezoneList[0] !== 'object') { + for (; tzi < tzl; tzi++) { + tzv = timezoneList[tzi]; + timezoneList[tzi] = { value: tzv, label: $.timepicker.timezoneOffsetString(tzv, tp_inst.support.iso8601) }; + } + } + tp_inst._defaults.timezoneList = timezoneList; + + // set the default units + tp_inst.timezone = tp_inst._defaults.timezone !== null ? $.timepicker.timezoneOffsetNumber(tp_inst._defaults.timezone) : + ((new Date()).getTimezoneOffset() * -1); + tp_inst.hour = tp_inst._defaults.hour < tp_inst._defaults.hourMin ? tp_inst._defaults.hourMin : + tp_inst._defaults.hour > tp_inst._defaults.hourMax ? tp_inst._defaults.hourMax : tp_inst._defaults.hour; + tp_inst.minute = tp_inst._defaults.minute < tp_inst._defaults.minuteMin ? tp_inst._defaults.minuteMin : + tp_inst._defaults.minute > tp_inst._defaults.minuteMax ? tp_inst._defaults.minuteMax : tp_inst._defaults.minute; + tp_inst.second = tp_inst._defaults.second < tp_inst._defaults.secondMin ? tp_inst._defaults.secondMin : + tp_inst._defaults.second > tp_inst._defaults.secondMax ? tp_inst._defaults.secondMax : tp_inst._defaults.second; + tp_inst.millisec = tp_inst._defaults.millisec < tp_inst._defaults.millisecMin ? tp_inst._defaults.millisecMin : + tp_inst._defaults.millisec > tp_inst._defaults.millisecMax ? tp_inst._defaults.millisecMax : tp_inst._defaults.millisec; + tp_inst.microsec = tp_inst._defaults.microsec < tp_inst._defaults.microsecMin ? tp_inst._defaults.microsecMin : + tp_inst._defaults.microsec > tp_inst._defaults.microsecMax ? tp_inst._defaults.microsecMax : tp_inst._defaults.microsec; + tp_inst.ampm = ''; + tp_inst.$input = $input; + + if (tp_inst._defaults.altField) { + tp_inst.$altInput = $(tp_inst._defaults.altField).css({ + cursor: 'pointer' + }).focus(function () { + $input.trigger("focus"); + }); + } + + if (tp_inst._defaults.minDate === 0 || tp_inst._defaults.minDateTime === 0) { + tp_inst._defaults.minDate = new Date(); + } + if (tp_inst._defaults.maxDate === 0 || tp_inst._defaults.maxDateTime === 0) { + tp_inst._defaults.maxDate = new Date(); + } + + // datepicker needs minDate/maxDate, timepicker needs minDateTime/maxDateTime.. + if (tp_inst._defaults.minDate !== undefined && tp_inst._defaults.minDate instanceof Date) { + tp_inst._defaults.minDateTime = new Date(tp_inst._defaults.minDate.getTime()); + } + if (tp_inst._defaults.minDateTime !== undefined && tp_inst._defaults.minDateTime instanceof Date) { + tp_inst._defaults.minDate = new Date(tp_inst._defaults.minDateTime.getTime()); + } + if (tp_inst._defaults.maxDate !== undefined && tp_inst._defaults.maxDate instanceof Date) { + tp_inst._defaults.maxDateTime = new Date(tp_inst._defaults.maxDate.getTime()); + } + if (tp_inst._defaults.maxDateTime !== undefined && tp_inst._defaults.maxDateTime instanceof Date) { + tp_inst._defaults.maxDate = new Date(tp_inst._defaults.maxDateTime.getTime()); + } + tp_inst.$input.bind('focus', function () { + tp_inst._onFocus(); + }); + + return tp_inst; + }, + + /* + * add our sliders to the calendar + */ + _addTimePicker: function (dp_inst) { + var currDT = (this.$altInput && this._defaults.altFieldTimeOnly) ? this.$input.val() + ' ' + this.$altInput.val() : this.$input.val(); + + this.timeDefined = this._parseTime(currDT); + this._limitMinMaxDateTime(dp_inst, false); + this._injectTimePicker(); + }, + + /* + * parse the time string from input value or _setTime + */ + _parseTime: function (timeString, withDate) { + if (!this.inst) { + this.inst = $.datepicker._getInst(this.$input[0]); + } + + if (withDate || !this._defaults.timeOnly) { + var dp_dateFormat = $.datepicker._get(this.inst, 'dateFormat'); + try { + var parseRes = parseDateTimeInternal(dp_dateFormat, this._defaults.timeFormat, timeString, $.datepicker._getFormatConfig(this.inst), this._defaults); + if (!parseRes.timeObj) { + return false; + } + $.extend(this, parseRes.timeObj); + } catch (err) { + $.timepicker.log("Error parsing the date/time string: " + err + + "\ndate/time string = " + timeString + + "\ntimeFormat = " + this._defaults.timeFormat + + "\ndateFormat = " + dp_dateFormat); + return false; + } + return true; + } else { + var timeObj = $.datepicker.parseTime(this._defaults.timeFormat, timeString, this._defaults); + if (!timeObj) { + return false; + } + $.extend(this, timeObj); + return true; + } + }, + + /* + * generate and inject html for timepicker into ui datepicker + */ + _injectTimePicker: function () { + var $dp = this.inst.dpDiv, + o = this.inst.settings, + tp_inst = this, + litem = '', + uitem = '', + show = null, + max = {}, + gridSize = {}, + size = null, + i = 0, + l = 0; + + // Prevent displaying twice + if ($dp.find("div.ui-timepicker-div").length === 0 && o.showTimepicker) { + var noDisplay = ' style="display:none;"', + html = '<div class="ui-timepicker-div' + (o.isRTL ? ' ui-timepicker-rtl' : '') + '"><dl>' + '<dt class="ui_tpicker_time_label"' + ((o.showTime) ? '' : noDisplay) + '>' + o.timeText + '</dt>' + + '<dd class="ui_tpicker_time"' + ((o.showTime) ? '' : noDisplay) + '></dd>'; + + // Create the markup + for (i = 0, l = this.units.length; i < l; i++) { + litem = this.units[i]; + uitem = litem.substr(0, 1).toUpperCase() + litem.substr(1); + show = o['show' + uitem] !== null ? o['show' + uitem] : this.support[litem]; + + // Added by Peter Medeiros: + // - Figure out what the hour/minute/second max should be based on the step values. + // - Example: if stepMinute is 15, then minMax is 45. + max[litem] = parseInt((o[litem + 'Max'] - ((o[litem + 'Max'] - o[litem + 'Min']) % o['step' + uitem])), 10); + gridSize[litem] = 0; + + html += '<dt class="ui_tpicker_' + litem + '_label"' + (show ? '' : noDisplay) + '>' + o[litem + 'Text'] + '</dt>' + + '<dd class="ui_tpicker_' + litem + '"><div class="ui_tpicker_' + litem + '_slider"' + (show ? '' : noDisplay) + '></div>'; + + if (show && o[litem + 'Grid'] > 0) { + html += '<div style="padding-left: 1px"><table class="ui-tpicker-grid-label"><tr>'; + + if (litem === 'hour') { + for (var h = o[litem + 'Min']; h <= max[litem]; h += parseInt(o[litem + 'Grid'], 10)) { + gridSize[litem]++; + var tmph = $.datepicker.formatTime(this.support.ampm ? 'hht' : 'HH', {hour: h}, o); + html += '<td data-for="' + litem + '">' + tmph + '</td>'; + } + } + else { + for (var m = o[litem + 'Min']; m <= max[litem]; m += parseInt(o[litem + 'Grid'], 10)) { + gridSize[litem]++; + html += '<td data-for="' + litem + '">' + ((m < 10) ? '0' : '') + m + '</td>'; + } + } + + html += '</tr></table></div>'; + } + html += '</dd>'; + } + + // Timezone + var showTz = o.showTimezone !== null ? o.showTimezone : this.support.timezone; + html += '<dt class="ui_tpicker_timezone_label"' + (showTz ? '' : noDisplay) + '>' + o.timezoneText + '</dt>'; + html += '<dd class="ui_tpicker_timezone" ' + (showTz ? '' : noDisplay) + '></dd>'; + + // Create the elements from string + html += '</dl></div>'; + var $tp = $(html); + + // if we only want time picker... + if (o.timeOnly === true) { + $tp.prepend('<div class="ui-widget-header ui-helper-clearfix ui-corner-all">' + '<div class="ui-datepicker-title">' + o.timeOnlyTitle + '</div>' + '</div>'); + $dp.find('.ui-datepicker-header, .ui-datepicker-calendar').hide(); + } + + // add sliders, adjust grids, add events + for (i = 0, l = tp_inst.units.length; i < l; i++) { + litem = tp_inst.units[i]; + uitem = litem.substr(0, 1).toUpperCase() + litem.substr(1); + show = o['show' + uitem] !== null ? o['show' + uitem] : this.support[litem]; + + // add the slider + tp_inst[litem + '_slider'] = tp_inst.control.create(tp_inst, $tp.find('.ui_tpicker_' + litem + '_slider'), litem, tp_inst[litem], o[litem + 'Min'], max[litem], o['step' + uitem]); + + // adjust the grid and add click event + if (show && o[litem + 'Grid'] > 0) { + size = 100 * gridSize[litem] * o[litem + 'Grid'] / (max[litem] - o[litem + 'Min']); + $tp.find('.ui_tpicker_' + litem + ' table').css({ + width: size + "%", + marginLeft: o.isRTL ? '0' : ((size / (-2 * gridSize[litem])) + "%"), + marginRight: o.isRTL ? ((size / (-2 * gridSize[litem])) + "%") : '0', + borderCollapse: 'collapse' + }).find("td").click(function (e) { + var $t = $(this), + h = $t.html(), + n = parseInt(h.replace(/[^0-9]/g), 10), + ap = h.replace(/[^apm]/ig), + f = $t.data('for'); // loses scope, so we use data-for + + if (f === 'hour') { + if (ap.indexOf('p') !== -1 && n < 12) { + n += 12; + } + else { + if (ap.indexOf('a') !== -1 && n === 12) { + n = 0; + } + } + } + + tp_inst.control.value(tp_inst, tp_inst[f + '_slider'], litem, n); + + tp_inst._onTimeChange(); + tp_inst._onSelectHandler(); + }).css({ + cursor: 'pointer', + width: (100 / gridSize[litem]) + '%', + textAlign: 'center', + overflow: 'hidden' + }); + } // end if grid > 0 + } // end for loop + + // Add timezone options + this.timezone_select = $tp.find('.ui_tpicker_timezone').append('<select></select>').find("select"); + $.fn.append.apply(this.timezone_select, + $.map(o.timezoneList, function (val, idx) { + return $("<option />").val(typeof val === "object" ? val.value : val).text(typeof val === "object" ? val.label : val); + })); + if (typeof(this.timezone) !== "undefined" && this.timezone !== null && this.timezone !== "") { + var local_timezone = (new Date(this.inst.selectedYear, this.inst.selectedMonth, this.inst.selectedDay, 12)).getTimezoneOffset() * -1; + if (local_timezone === this.timezone) { + selectLocalTimezone(tp_inst); + } else { + this.timezone_select.val(this.timezone); + } + } else { + if (typeof(this.hour) !== "undefined" && this.hour !== null && this.hour !== "") { + this.timezone_select.val(o.timezone); + } else { + selectLocalTimezone(tp_inst); + } + } + this.timezone_select.change(function () { + tp_inst._onTimeChange(); + tp_inst._onSelectHandler(); + }); + // End timezone options + + // inject timepicker into datepicker + var $buttonPanel = $dp.find('.ui-datepicker-buttonpane'); + if ($buttonPanel.length) { + $buttonPanel.before($tp); + } else { + $dp.append($tp); + } + + this.$timeObj = $tp.find('.ui_tpicker_time'); + + if (this.inst !== null) { + var timeDefined = this.timeDefined; + this._onTimeChange(); + this.timeDefined = timeDefined; + } + + // slideAccess integration: http://trentrichardson.com/2011/11/11/jquery-ui-sliders-and-touch-accessibility/ + if (this._defaults.addSliderAccess) { + var sliderAccessArgs = this._defaults.sliderAccessArgs, + rtl = this._defaults.isRTL; + sliderAccessArgs.isRTL = rtl; + + setTimeout(function () { // fix for inline mode + if ($tp.find('.ui-slider-access').length === 0) { + $tp.find('.ui-slider:visible').sliderAccess(sliderAccessArgs); + + // fix any grids since sliders are shorter + var sliderAccessWidth = $tp.find('.ui-slider-access:eq(0)').outerWidth(true); + if (sliderAccessWidth) { + $tp.find('table:visible').each(function () { + var $g = $(this), + oldWidth = $g.outerWidth(), + oldMarginLeft = $g.css(rtl ? 'marginRight' : 'marginLeft').toString().replace('%', ''), + newWidth = oldWidth - sliderAccessWidth, + newMarginLeft = ((oldMarginLeft * newWidth) / oldWidth) + '%', + css = { width: newWidth, marginRight: 0, marginLeft: 0 }; + css[rtl ? 'marginRight' : 'marginLeft'] = newMarginLeft; + $g.css(css); + }); + } + } + }, 10); + } + // end slideAccess integration + + tp_inst._limitMinMaxDateTime(this.inst, true); + } + }, + + /* + * This function tries to limit the ability to go outside the + * min/max date range + */ + _limitMinMaxDateTime: function (dp_inst, adjustSliders) { + var o = this._defaults, + dp_date = new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay); + + if (!this._defaults.showTimepicker) { + return; + } // No time so nothing to check here + + if ($.datepicker._get(dp_inst, 'minDateTime') !== null && $.datepicker._get(dp_inst, 'minDateTime') !== undefined && dp_date) { + var minDateTime = $.datepicker._get(dp_inst, 'minDateTime'), + minDateTimeDate = new Date(minDateTime.getFullYear(), minDateTime.getMonth(), minDateTime.getDate(), 0, 0, 0, 0); + + if (this.hourMinOriginal === null || this.minuteMinOriginal === null || this.secondMinOriginal === null || this.millisecMinOriginal === null || this.microsecMinOriginal === null) { + this.hourMinOriginal = o.hourMin; + this.minuteMinOriginal = o.minuteMin; + this.secondMinOriginal = o.secondMin; + this.millisecMinOriginal = o.millisecMin; + this.microsecMinOriginal = o.microsecMin; + } + + if (dp_inst.settings.timeOnly || minDateTimeDate.getTime() === dp_date.getTime()) { + this._defaults.hourMin = minDateTime.getHours(); + if (this.hour <= this._defaults.hourMin) { + this.hour = this._defaults.hourMin; + this._defaults.minuteMin = minDateTime.getMinutes(); + if (this.minute <= this._defaults.minuteMin) { + this.minute = this._defaults.minuteMin; + this._defaults.secondMin = minDateTime.getSeconds(); + if (this.second <= this._defaults.secondMin) { + this.second = this._defaults.secondMin; + this._defaults.millisecMin = minDateTime.getMilliseconds(); + if (this.millisec <= this._defaults.millisecMin) { + this.millisec = this._defaults.millisecMin; + this._defaults.microsecMin = minDateTime.getMicroseconds(); + } else { + if (this.microsec < this._defaults.microsecMin) { + this.microsec = this._defaults.microsecMin; + } + this._defaults.microsecMin = this.microsecMinOriginal; + } + } else { + this._defaults.millisecMin = this.millisecMinOriginal; + this._defaults.microsecMin = this.microsecMinOriginal; + } + } else { + this._defaults.secondMin = this.secondMinOriginal; + this._defaults.millisecMin = this.millisecMinOriginal; + this._defaults.microsecMin = this.microsecMinOriginal; + } + } else { + this._defaults.minuteMin = this.minuteMinOriginal; + this._defaults.secondMin = this.secondMinOriginal; + this._defaults.millisecMin = this.millisecMinOriginal; + this._defaults.microsecMin = this.microsecMinOriginal; + } + } else { + this._defaults.hourMin = this.hourMinOriginal; + this._defaults.minuteMin = this.minuteMinOriginal; + this._defaults.secondMin = this.secondMinOriginal; + this._defaults.millisecMin = this.millisecMinOriginal; + this._defaults.microsecMin = this.microsecMinOriginal; + } + } + + if ($.datepicker._get(dp_inst, 'maxDateTime') !== null && $.datepicker._get(dp_inst, 'maxDateTime') !== undefined && dp_date) { + var maxDateTime = $.datepicker._get(dp_inst, 'maxDateTime'), + maxDateTimeDate = new Date(maxDateTime.getFullYear(), maxDateTime.getMonth(), maxDateTime.getDate(), 0, 0, 0, 0); + + if (this.hourMaxOriginal === null || this.minuteMaxOriginal === null || this.secondMaxOriginal === null || this.millisecMaxOriginal === null) { + this.hourMaxOriginal = o.hourMax; + this.minuteMaxOriginal = o.minuteMax; + this.secondMaxOriginal = o.secondMax; + this.millisecMaxOriginal = o.millisecMax; + this.microsecMaxOriginal = o.microsecMax; + } + + if (dp_inst.settings.timeOnly || maxDateTimeDate.getTime() === dp_date.getTime()) { + this._defaults.hourMax = maxDateTime.getHours(); + if (this.hour >= this._defaults.hourMax) { + this.hour = this._defaults.hourMax; + this._defaults.minuteMax = maxDateTime.getMinutes(); + if (this.minute >= this._defaults.minuteMax) { + this.minute = this._defaults.minuteMax; + this._defaults.secondMax = maxDateTime.getSeconds(); + if (this.second >= this._defaults.secondMax) { + this.second = this._defaults.secondMax; + this._defaults.millisecMax = maxDateTime.getMilliseconds(); + if (this.millisec >= this._defaults.millisecMax) { + this.millisec = this._defaults.millisecMax; + this._defaults.microsecMax = maxDateTime.getMicroseconds(); + } else { + if (this.microsec > this._defaults.microsecMax) { + this.microsec = this._defaults.microsecMax; + } + this._defaults.microsecMax = this.microsecMaxOriginal; + } + } else { + this._defaults.millisecMax = this.millisecMaxOriginal; + this._defaults.microsecMax = this.microsecMaxOriginal; + } + } else { + this._defaults.secondMax = this.secondMaxOriginal; + this._defaults.millisecMax = this.millisecMaxOriginal; + this._defaults.microsecMax = this.microsecMaxOriginal; + } + } else { + this._defaults.minuteMax = this.minuteMaxOriginal; + this._defaults.secondMax = this.secondMaxOriginal; + this._defaults.millisecMax = this.millisecMaxOriginal; + this._defaults.microsecMax = this.microsecMaxOriginal; + } + } else { + this._defaults.hourMax = this.hourMaxOriginal; + this._defaults.minuteMax = this.minuteMaxOriginal; + this._defaults.secondMax = this.secondMaxOriginal; + this._defaults.millisecMax = this.millisecMaxOriginal; + this._defaults.microsecMax = this.microsecMaxOriginal; + } + } + + if (adjustSliders !== undefined && adjustSliders === true) { + var hourMax = parseInt((this._defaults.hourMax - ((this._defaults.hourMax - this._defaults.hourMin) % this._defaults.stepHour)), 10), + minMax = parseInt((this._defaults.minuteMax - ((this._defaults.minuteMax - this._defaults.minuteMin) % this._defaults.stepMinute)), 10), + secMax = parseInt((this._defaults.secondMax - ((this._defaults.secondMax - this._defaults.secondMin) % this._defaults.stepSecond)), 10), + millisecMax = parseInt((this._defaults.millisecMax - ((this._defaults.millisecMax - this._defaults.millisecMin) % this._defaults.stepMillisec)), 10), + microsecMax = parseInt((this._defaults.microsecMax - ((this._defaults.microsecMax - this._defaults.microsecMin) % this._defaults.stepMicrosec)), 10); + + if (this.hour_slider) { + this.control.options(this, this.hour_slider, 'hour', { min: this._defaults.hourMin, max: hourMax }); + this.control.value(this, this.hour_slider, 'hour', this.hour - (this.hour % this._defaults.stepHour)); + } + if (this.minute_slider) { + this.control.options(this, this.minute_slider, 'minute', { min: this._defaults.minuteMin, max: minMax }); + this.control.value(this, this.minute_slider, 'minute', this.minute - (this.minute % this._defaults.stepMinute)); + } + if (this.second_slider) { + this.control.options(this, this.second_slider, 'second', { min: this._defaults.secondMin, max: secMax }); + this.control.value(this, this.second_slider, 'second', this.second - (this.second % this._defaults.stepSecond)); + } + if (this.millisec_slider) { + this.control.options(this, this.millisec_slider, 'millisec', { min: this._defaults.millisecMin, max: millisecMax }); + this.control.value(this, this.millisec_slider, 'millisec', this.millisec - (this.millisec % this._defaults.stepMillisec)); + } + if (this.microsec_slider) { + this.control.options(this, this.microsec_slider, 'microsec', { min: this._defaults.microsecMin, max: microsecMax }); + this.control.value(this, this.microsec_slider, 'microsec', this.microsec - (this.microsec % this._defaults.stepMicrosec)); + } + } + + }, + + /* + * when a slider moves, set the internal time... + * on time change is also called when the time is updated in the text field + */ + _onTimeChange: function () { + if (!this._defaults.showTimepicker) { + return; + } + var hour = (this.hour_slider) ? this.control.value(this, this.hour_slider, 'hour') : false, + minute = (this.minute_slider) ? this.control.value(this, this.minute_slider, 'minute') : false, + second = (this.second_slider) ? this.control.value(this, this.second_slider, 'second') : false, + millisec = (this.millisec_slider) ? this.control.value(this, this.millisec_slider, 'millisec') : false, + microsec = (this.microsec_slider) ? this.control.value(this, this.microsec_slider, 'microsec') : false, + timezone = (this.timezone_select) ? this.timezone_select.val() : false, + o = this._defaults, + pickerTimeFormat = o.pickerTimeFormat || o.timeFormat, + pickerTimeSuffix = o.pickerTimeSuffix || o.timeSuffix; + + if (typeof(hour) === 'object') { + hour = false; + } + if (typeof(minute) === 'object') { + minute = false; + } + if (typeof(second) === 'object') { + second = false; + } + if (typeof(millisec) === 'object') { + millisec = false; + } + if (typeof(microsec) === 'object') { + microsec = false; + } + if (typeof(timezone) === 'object') { + timezone = false; + } + + if (hour !== false) { + hour = parseInt(hour, 10); + } + if (minute !== false) { + minute = parseInt(minute, 10); + } + if (second !== false) { + second = parseInt(second, 10); + } + if (millisec !== false) { + millisec = parseInt(millisec, 10); + } + if (microsec !== false) { + microsec = parseInt(microsec, 10); + } + if (timezone !== false) { + timezone = timezone.toString(); + } + + var ampm = o[hour < 12 ? 'amNames' : 'pmNames'][0]; + + // If the update was done in the input field, the input field should not be updated. + // If the update was done using the sliders, update the input field. + var hasChanged = ( + hour !== parseInt(this.hour,10) || // sliders should all be numeric + minute !== parseInt(this.minute,10) || + second !== parseInt(this.second,10) || + millisec !== parseInt(this.millisec,10) || + microsec !== parseInt(this.microsec,10) || + (this.ampm.length > 0 && (hour < 12) !== ($.inArray(this.ampm.toUpperCase(), this.amNames) !== -1)) || + (this.timezone !== null && timezone !== this.timezone.toString()) // could be numeric or "EST" format, so use toString() + ); + + if (hasChanged) { + + if (hour !== false) { + this.hour = hour; + } + if (minute !== false) { + this.minute = minute; + } + if (second !== false) { + this.second = second; + } + if (millisec !== false) { + this.millisec = millisec; + } + if (microsec !== false) { + this.microsec = microsec; + } + if (timezone !== false) { + this.timezone = timezone; + } + + if (!this.inst) { + this.inst = $.datepicker._getInst(this.$input[0]); + } + + this._limitMinMaxDateTime(this.inst, true); + } + if (this.support.ampm) { + this.ampm = ampm; + } + + // Updates the time within the timepicker + this.formattedTime = $.datepicker.formatTime(o.timeFormat, this, o); + if (this.$timeObj) { + if (pickerTimeFormat === o.timeFormat) { + this.$timeObj.text(this.formattedTime + pickerTimeSuffix); + } + else { + this.$timeObj.text($.datepicker.formatTime(pickerTimeFormat, this, o) + pickerTimeSuffix); + } + } + + this.timeDefined = true; + if (hasChanged) { + this._updateDateTime(); + this.$input.focus(); + } + }, + + /* + * call custom onSelect. + * bind to sliders slidestop, and grid click. + */ + _onSelectHandler: function () { + var onSelect = this._defaults.onSelect || this.inst.settings.onSelect; + var inputEl = this.$input ? this.$input[0] : null; + if (onSelect && inputEl) { + onSelect.apply(inputEl, [this.formattedDateTime, this]); + } + }, + + /* + * update our input with the new date time.. + */ + _updateDateTime: function (dp_inst) { + dp_inst = this.inst || dp_inst; + var dtTmp = (dp_inst.currentYear > 0? + new Date(dp_inst.currentYear, dp_inst.currentMonth, dp_inst.currentDay) : + new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay)), + dt = $.datepicker._daylightSavingAdjust(dtTmp), + //dt = $.datepicker._daylightSavingAdjust(new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay)), + //dt = $.datepicker._daylightSavingAdjust(new Date(dp_inst.currentYear, dp_inst.currentMonth, dp_inst.currentDay)), + dateFmt = $.datepicker._get(dp_inst, 'dateFormat'), + formatCfg = $.datepicker._getFormatConfig(dp_inst), + timeAvailable = dt !== null && this.timeDefined; + this.formattedDate = $.datepicker.formatDate(dateFmt, (dt === null ? new Date() : dt), formatCfg); + var formattedDateTime = this.formattedDate; + + // if a slider was changed but datepicker doesn't have a value yet, set it + if (dp_inst.lastVal === "") { + dp_inst.currentYear = dp_inst.selectedYear; + dp_inst.currentMonth = dp_inst.selectedMonth; + dp_inst.currentDay = dp_inst.selectedDay; + } + + /* + * remove following lines to force every changes in date picker to change the input value + * Bug descriptions: when an input field has a default value, and click on the field to pop up the date picker. + * If the user manually empty the value in the input field, the date picker will never change selected value. + */ + //if (dp_inst.lastVal !== undefined && (dp_inst.lastVal.length > 0 && this.$input.val().length === 0)) { + // return; + //} + + if (this._defaults.timeOnly === true) { + formattedDateTime = this.formattedTime; + } else if (this._defaults.timeOnly !== true && (this._defaults.alwaysSetTime || timeAvailable)) { + formattedDateTime += this._defaults.separator + this.formattedTime + this._defaults.timeSuffix; + } + + this.formattedDateTime = formattedDateTime; + + if (!this._defaults.showTimepicker) { + this.$input.val(this.formattedDate); + } else if (this.$altInput && this._defaults.timeOnly === false && this._defaults.altFieldTimeOnly === true) { + this.$altInput.val(this.formattedTime); + this.$input.val(this.formattedDate); + } else if (this.$altInput) { + this.$input.val(formattedDateTime); + var altFormattedDateTime = '', + altSeparator = this._defaults.altSeparator ? this._defaults.altSeparator : this._defaults.separator, + altTimeSuffix = this._defaults.altTimeSuffix ? this._defaults.altTimeSuffix : this._defaults.timeSuffix; + + if (!this._defaults.timeOnly) { + if (this._defaults.altFormat) { + altFormattedDateTime = $.datepicker.formatDate(this._defaults.altFormat, (dt === null ? new Date() : dt), formatCfg); + } + else { + altFormattedDateTime = this.formattedDate; + } + + if (altFormattedDateTime) { + altFormattedDateTime += altSeparator; + } + } + + if (this._defaults.altTimeFormat) { + altFormattedDateTime += $.datepicker.formatTime(this._defaults.altTimeFormat, this, this._defaults) + altTimeSuffix; + } + else { + altFormattedDateTime += this.formattedTime + altTimeSuffix; + } + this.$altInput.val(altFormattedDateTime); + } else { + this.$input.val(formattedDateTime); + } + + this.$input.trigger("change"); + }, + + _onFocus: function () { + if (!this.$input.val() && this._defaults.defaultValue) { + this.$input.val(this._defaults.defaultValue); + var inst = $.datepicker._getInst(this.$input.get(0)), + tp_inst = $.datepicker._get(inst, 'timepicker'); + if (tp_inst) { + if (tp_inst._defaults.timeOnly && (inst.input.val() !== inst.lastVal)) { + try { + $.datepicker._updateDatepicker(inst); + } catch (err) { + $.timepicker.log(err); + } + } + } + } + }, + + /* + * Small abstraction to control types + * We can add more, just be sure to follow the pattern: create, options, value + */ + _controls: { + // slider methods + slider: { + create: function (tp_inst, obj, unit, val, min, max, step) { + var rtl = tp_inst._defaults.isRTL; // if rtl go -60->0 instead of 0->60 + return obj.prop('slide', null).slider({ + orientation: "horizontal", + value: rtl ? val * -1 : val, + min: rtl ? max * -1 : min, + max: rtl ? min * -1 : max, + step: step, + slide: function (event, ui) { + tp_inst.control.value(tp_inst, $(this), unit, rtl ? ui.value * -1 : ui.value); + tp_inst._onTimeChange(); + }, + stop: function (event, ui) { + tp_inst._onSelectHandler(); + } + }); + }, + options: function (tp_inst, obj, unit, opts, val) { + if (tp_inst._defaults.isRTL) { + if (typeof(opts) === 'string') { + if (opts === 'min' || opts === 'max') { + if (val !== undefined) { + return obj.slider(opts, val * -1); + } + return Math.abs(obj.slider(opts)); + } + return obj.slider(opts); + } + var min = opts.min, + max = opts.max; + opts.min = opts.max = null; + if (min !== undefined) { + opts.max = min * -1; + } + if (max !== undefined) { + opts.min = max * -1; + } + return obj.slider(opts); + } + if (typeof(opts) === 'string' && val !== undefined) { + return obj.slider(opts, val); + } + return obj.slider(opts); + }, + value: function (tp_inst, obj, unit, val) { + if (tp_inst._defaults.isRTL) { + if (val !== undefined) { + return obj.slider('value', val * -1); + } + return Math.abs(obj.slider('value')); + } + if (val !== undefined) { + return obj.slider('value', val); + } + return obj.slider('value'); + } + }, + // select methods + select: { + create: function (tp_inst, obj, unit, val, min, max, step) { + var sel = '<select class="ui-timepicker-select" data-unit="' + unit + '" data-min="' + min + '" data-max="' + max + '" data-step="' + step + '">', + format = tp_inst._defaults.pickerTimeFormat || tp_inst._defaults.timeFormat; + + for (var i = min; i <= max; i += step) { + sel += '<option value="' + i + '"' + (i === val ? ' selected' : '') + '>'; + if (unit === 'hour') { + sel += $.datepicker.formatTime($.trim(format.replace(/[^ht ]/ig, '')), {hour: i}, tp_inst._defaults); + } + else if (unit === 'millisec' || unit === 'microsec' || i >= 10) { sel += i; } + else {sel += '0' + i.toString(); } + sel += '</option>'; + } + sel += '</select>'; + + obj.children('select').remove(); + + $(sel).appendTo(obj).change(function (e) { + tp_inst._onTimeChange(); + tp_inst._onSelectHandler(); + }); + + return obj; + }, + options: function (tp_inst, obj, unit, opts, val) { + var o = {}, + $t = obj.children('select'); + if (typeof(opts) === 'string') { + if (val === undefined) { + return $t.data(opts); + } + o[opts] = val; + } + else { o = opts; } + return tp_inst.control.create(tp_inst, obj, $t.data('unit'), $t.val(), o.min || $t.data('min'), o.max || $t.data('max'), o.step || $t.data('step')); + }, + value: function (tp_inst, obj, unit, val) { + var $t = obj.children('select'); + if (val !== undefined) { + return $t.val(val); + } + return $t.val(); + } + } + } // end _controls + + }); + + $.fn.extend({ + /* + * shorthand just to use timepicker. + */ + timepicker: function (o) { + o = o || {}; + var tmp_args = Array.prototype.slice.call(arguments); + + if (typeof o === 'object') { + tmp_args[0] = $.extend(o, { + timeOnly: true + }); + } + + return $(this).each(function () { + $.fn.datetimepicker.apply($(this), tmp_args); + }); + }, + + /* + * extend timepicker to datepicker + */ + datetimepicker: function (o) { + o = o || {}; + var tmp_args = arguments; + + if (typeof(o) === 'string') { + if (o === 'getDate') { + return $.fn.datepicker.apply($(this[0]), tmp_args); + } else { + return this.each(function () { + var $t = $(this); + $t.datepicker.apply($t, tmp_args); + }); + } + } else { + return this.each(function () { + var $t = $(this); + $t.datepicker($.timepicker._newInst($t, o)._defaults); + }); + } + } + }); + + /* + * Public Utility to parse date and time + */ + $.datepicker.parseDateTime = function (dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings) { + var parseRes = parseDateTimeInternal(dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings); + if (parseRes.timeObj) { + var t = parseRes.timeObj; + parseRes.date.setHours(t.hour, t.minute, t.second, t.millisec); + parseRes.date.setMicroseconds(t.microsec); + } + + return parseRes.date; + }; + + /* + * Public utility to parse time + */ + $.datepicker.parseTime = function (timeFormat, timeString, options) { + var o = extendRemove(extendRemove({}, $.timepicker._defaults), options || {}), + iso8601 = (timeFormat.replace(/\'.*?\'/g, '').indexOf('Z') !== -1); + + // Strict parse requires the timeString to match the timeFormat exactly + var strictParse = function (f, s, o) { + + // pattern for standard and localized AM/PM markers + var getPatternAmpm = function (amNames, pmNames) { + var markers = []; + if (amNames) { + $.merge(markers, amNames); + } + if (pmNames) { + $.merge(markers, pmNames); + } + markers = $.map(markers, function (val) { + return val.replace(/[.*+?|()\[\]{}\\]/g, '\\$&'); + }); + return '(' + markers.join('|') + ')?'; + }; + + // figure out position of time elements.. cause js cant do named captures + var getFormatPositions = function (timeFormat) { + var finds = timeFormat.toLowerCase().match(/(h{1,2}|m{1,2}|s{1,2}|l{1}|c{1}|t{1,2}|z|'.*?')/g), + orders = { + h: -1, + m: -1, + s: -1, + l: -1, + c: -1, + t: -1, + z: -1 + }; + + if (finds) { + for (var i = 0; i < finds.length; i++) { + if (orders[finds[i].toString().charAt(0)] === -1) { + orders[finds[i].toString().charAt(0)] = i + 1; + } + } + } + return orders; + }; + + var regstr = '^' + f.toString() + .replace(/([hH]{1,2}|mm?|ss?|[tT]{1,2}|[zZ]|[lc]|'.*?')/g, function (match) { + var ml = match.length; + switch (match.charAt(0).toLowerCase()) { + case 'h': + return ml === 1 ? '(\\d?\\d)' : '(\\d{' + ml + '})'; + case 'm': + return ml === 1 ? '(\\d?\\d)' : '(\\d{' + ml + '})'; + case 's': + return ml === 1 ? '(\\d?\\d)' : '(\\d{' + ml + '})'; + case 'l': + return '(\\d?\\d?\\d)'; + case 'c': + return '(\\d?\\d?\\d)'; + case 'z': + return '(z|[-+]\\d\\d:?\\d\\d|\\S+)?'; + case 't': + return getPatternAmpm(o.amNames, o.pmNames); + default: // literal escaped in quotes + return '(' + match.replace(/\'/g, "").replace(/(\.|\$|\^|\\|\/|\(|\)|\[|\]|\?|\+|\*)/g, function (m) { return "\\" + m; }) + ')?'; + } + }) + .replace(/\s/g, '\\s?') + + o.timeSuffix + '$', + order = getFormatPositions(f), + ampm = '', + treg; + + treg = s.match(new RegExp(regstr, 'i')); + + var resTime = { + hour: 0, + minute: 0, + second: 0, + millisec: 0, + microsec: 0 + }; + + if (treg) { + if (order.t !== -1) { + if (treg[order.t] === undefined || treg[order.t].length === 0) { + ampm = ''; + resTime.ampm = ''; + } else { + ampm = $.inArray(treg[order.t].toUpperCase(), o.amNames) !== -1 ? 'AM' : 'PM'; + resTime.ampm = o[ampm === 'AM' ? 'amNames' : 'pmNames'][0]; + } + } + + if (order.h !== -1) { + if (ampm === 'AM' && treg[order.h] === '12') { + resTime.hour = 0; // 12am = 0 hour + } else { + if (ampm === 'PM' && treg[order.h] !== '12') { + resTime.hour = parseInt(treg[order.h], 10) + 12; // 12pm = 12 hour, any other pm = hour + 12 + } else { + resTime.hour = Number(treg[order.h]); + } + } + } + + if (order.m !== -1) { + resTime.minute = Number(treg[order.m]); + } + if (order.s !== -1) { + resTime.second = Number(treg[order.s]); + } + if (order.l !== -1) { + resTime.millisec = Number(treg[order.l]); + } + if (order.c !== -1) { + resTime.microsec = Number(treg[order.c]); + } + if (order.z !== -1 && treg[order.z] !== undefined) { + resTime.timezone = $.timepicker.timezoneOffsetNumber(treg[order.z]); + } + + + return resTime; + } + return false; + };// end strictParse + + // First try JS Date, if that fails, use strictParse + var looseParse = function (f, s, o) { + try { + var d = new Date('2012-01-01 ' + s); + if (isNaN(d.getTime())) { + d = new Date('2012-01-01T' + s); + if (isNaN(d.getTime())) { + d = new Date('01/01/2012 ' + s); + if (isNaN(d.getTime())) { + throw "Unable to parse time with native Date: " + s; + } + } + } + + return { + hour: d.getHours(), + minute: d.getMinutes(), + second: d.getSeconds(), + millisec: d.getMilliseconds(), + microsec: d.getMicroseconds(), + timezone: d.getTimezoneOffset() * -1 + }; + } + catch (err) { + try { + return strictParse(f, s, o); + } + catch (err2) { + $.timepicker.log("Unable to parse \ntimeString: " + s + "\ntimeFormat: " + f); + } + } + return false; + }; // end looseParse + + if (typeof o.parse === "function") { + return o.parse(timeFormat, timeString, o); + } + if (o.parse === 'loose') { + return looseParse(timeFormat, timeString, o); + } + return strictParse(timeFormat, timeString, o); + }; + + /** + * Public utility to format the time + * @param {string} format format of the time + * @param {Object} time Object not a Date for timezones + * @param {Object} [options] essentially the regional[].. amNames, pmNames, ampm + * @returns {string} the formatted time + */ + $.datepicker.formatTime = function (format, time, options) { + options = options || {}; + options = $.extend({}, $.timepicker._defaults, options); + time = $.extend({ + hour: 0, + minute: 0, + second: 0, + millisec: 0, + microsec: 0, + timezone: null + }, time); + + var tmptime = format, + ampmName = options.amNames[0], + hour = parseInt(time.hour, 10); + + if (hour > 11) { + ampmName = options.pmNames[0]; + } + + tmptime = tmptime.replace(/(?:HH?|hh?|mm?|ss?|[tT]{1,2}|[zZ]|[lc]|'.*?')/g, function (match) { + switch (match) { + case 'HH': + return ('0' + hour).slice(-2); + case 'H': + return hour; + case 'hh': + return ('0' + convert24to12(hour)).slice(-2); + case 'h': + return convert24to12(hour); + case 'mm': + return ('0' + time.minute).slice(-2); + case 'm': + return time.minute; + case 'ss': + return ('0' + time.second).slice(-2); + case 's': + return time.second; + case 'l': + return ('00' + time.millisec).slice(-3); + case 'c': + return ('00' + time.microsec).slice(-3); + case 'z': + return $.timepicker.timezoneOffsetString(time.timezone === null ? options.timezone : time.timezone, false); + case 'Z': + return $.timepicker.timezoneOffsetString(time.timezone === null ? options.timezone : time.timezone, true); + case 'T': + return ampmName.charAt(0).toUpperCase(); + case 'TT': + return ampmName.toUpperCase(); + case 't': + return ampmName.charAt(0).toLowerCase(); + case 'tt': + return ampmName.toLowerCase(); + default: + return match.replace(/'/g, ""); + } + }); + + return tmptime; + }; + + /* + * the bad hack :/ override datepicker so it doesn't close on select + // inspired: http://stackoverflow.com/questions/1252512/jquery-datepicker-prevent-closing-picker-when-clicking-a-date/1762378#1762378 + */ + $.datepicker._base_selectDate = $.datepicker._selectDate; + $.datepicker._selectDate = function (id, dateStr) { + var inst = this._getInst($(id)[0]), + tp_inst = this._get(inst, 'timepicker'); + + if (tp_inst) { + tp_inst._limitMinMaxDateTime(inst, true); + inst.inline = inst.stay_open = true; + //This way the onSelect handler called from calendarpicker get the full dateTime + this._base_selectDate(id, dateStr); + inst.inline = inst.stay_open = false; + this._notifyChange(inst); + this._updateDatepicker(inst); + } else { + this._base_selectDate(id, dateStr); + } + }; + + /* + * second bad hack :/ override datepicker so it triggers an event when changing the input field + * and does not redraw the datepicker on every selectDate event + */ + $.datepicker._base_updateDatepicker = $.datepicker._updateDatepicker; + $.datepicker._updateDatepicker = function (inst) { + + // don't popup the datepicker if there is another instance already opened + var input = inst.input[0]; + if ($.datepicker._curInst && $.datepicker._curInst !== inst && $.datepicker._datepickerShowing && $.datepicker._lastInput !== input) { + return; + } + + if (typeof(inst.stay_open) !== 'boolean' || inst.stay_open === false) { + + this._base_updateDatepicker(inst); + + // Reload the time control when changing something in the input text field. + var tp_inst = this._get(inst, 'timepicker'); + if (tp_inst) { + tp_inst._addTimePicker(inst); + } + } + }; + + /* + * third bad hack :/ override datepicker so it allows spaces and colon in the input field + */ + $.datepicker._base_doKeyPress = $.datepicker._doKeyPress; + $.datepicker._doKeyPress = function (event) { + var inst = $.datepicker._getInst(event.target), + tp_inst = $.datepicker._get(inst, 'timepicker'); + + if (tp_inst) { + if ($.datepicker._get(inst, 'constrainInput')) { + var ampm = tp_inst.support.ampm, + tz = tp_inst._defaults.showTimezone !== null ? tp_inst._defaults.showTimezone : tp_inst.support.timezone, + dateChars = $.datepicker._possibleChars($.datepicker._get(inst, 'dateFormat')), + datetimeChars = tp_inst._defaults.timeFormat.toString() + .replace(/[hms]/g, '') + .replace(/TT/g, ampm ? 'APM' : '') + .replace(/Tt/g, ampm ? 'AaPpMm' : '') + .replace(/tT/g, ampm ? 'AaPpMm' : '') + .replace(/T/g, ampm ? 'AP' : '') + .replace(/tt/g, ampm ? 'apm' : '') + .replace(/t/g, ampm ? 'ap' : '') + + " " + tp_inst._defaults.separator + + tp_inst._defaults.timeSuffix + + (tz ? tp_inst._defaults.timezoneList.join('') : '') + + (tp_inst._defaults.amNames.join('')) + (tp_inst._defaults.pmNames.join('')) + + dateChars, + chr = String.fromCharCode(event.charCode === undefined ? event.keyCode : event.charCode); + return event.ctrlKey || (chr < ' ' || !dateChars || datetimeChars.indexOf(chr) > -1); + } + } + + return $.datepicker._base_doKeyPress(event); + }; + + /* + * Fourth bad hack :/ override _updateAlternate function used in inline mode to init altField + * Update any alternate field to synchronise with the main field. + */ + $.datepicker._base_updateAlternate = $.datepicker._updateAlternate; + $.datepicker._updateAlternate = function (inst) { + var tp_inst = this._get(inst, 'timepicker'); + if (tp_inst) { + var altField = tp_inst._defaults.altField; + if (altField) { // update alternate field too + var altFormat = tp_inst._defaults.altFormat || tp_inst._defaults.dateFormat, + date = this._getDate(inst), + formatCfg = $.datepicker._getFormatConfig(inst), + altFormattedDateTime = '', + altSeparator = tp_inst._defaults.altSeparator ? tp_inst._defaults.altSeparator : tp_inst._defaults.separator, + altTimeSuffix = tp_inst._defaults.altTimeSuffix ? tp_inst._defaults.altTimeSuffix : tp_inst._defaults.timeSuffix, + altTimeFormat = tp_inst._defaults.altTimeFormat !== null ? tp_inst._defaults.altTimeFormat : tp_inst._defaults.timeFormat; + + altFormattedDateTime += $.datepicker.formatTime(altTimeFormat, tp_inst, tp_inst._defaults) + altTimeSuffix; + if (!tp_inst._defaults.timeOnly && !tp_inst._defaults.altFieldTimeOnly && date !== null) { + if (tp_inst._defaults.altFormat) { + altFormattedDateTime = $.datepicker.formatDate(tp_inst._defaults.altFormat, date, formatCfg) + altSeparator + altFormattedDateTime; + } + else { + altFormattedDateTime = tp_inst.formattedDate + altSeparator + altFormattedDateTime; + } + } + $(altField).val(altFormattedDateTime); + } + } + else { + $.datepicker._base_updateAlternate(inst); + } + }; + + /* + * Override key up event to sync manual input changes. + */ + $.datepicker._base_doKeyUp = $.datepicker._doKeyUp; + $.datepicker._doKeyUp = function (event) { + var inst = $.datepicker._getInst(event.target), + tp_inst = $.datepicker._get(inst, 'timepicker'); + + if (tp_inst) { + if (tp_inst._defaults.timeOnly && (inst.input.val() !== inst.lastVal)) { + try { + $.datepicker._updateDatepicker(inst); + } catch (err) { + $.timepicker.log(err); + } + } + } + + return $.datepicker._base_doKeyUp(event); + }; + + /* + * override "Today" button to also grab the time. + */ + $.datepicker._base_gotoToday = $.datepicker._gotoToday; + $.datepicker._gotoToday = function (id) { + var inst = this._getInst($(id)[0]), + $dp = inst.dpDiv; + this._base_gotoToday(id); + var tp_inst = this._get(inst, 'timepicker'); + selectLocalTimezone(tp_inst); + var now = new Date(); + this._setTime(inst, now); + $('.ui-datepicker-today', $dp).click(); + }; + + /* + * Disable & enable the Time in the datetimepicker + */ + $.datepicker._disableTimepickerDatepicker = function (target) { + var inst = this._getInst(target); + if (!inst) { + return; + } + + var tp_inst = this._get(inst, 'timepicker'); + $(target).datepicker('getDate'); // Init selected[Year|Month|Day] + if (tp_inst) { + inst.settings.showTimepicker = false; + tp_inst._defaults.showTimepicker = false; + tp_inst._updateDateTime(inst); + } + }; + + $.datepicker._enableTimepickerDatepicker = function (target) { + var inst = this._getInst(target); + if (!inst) { + return; + } + + var tp_inst = this._get(inst, 'timepicker'); + $(target).datepicker('getDate'); // Init selected[Year|Month|Day] + if (tp_inst) { + inst.settings.showTimepicker = true; + tp_inst._defaults.showTimepicker = true; + tp_inst._addTimePicker(inst); // Could be disabled on page load + tp_inst._updateDateTime(inst); + } + }; + + /* + * Create our own set time function + */ + $.datepicker._setTime = function (inst, date) { + var tp_inst = this._get(inst, 'timepicker'); + if (tp_inst) { + var defaults = tp_inst._defaults; + + // calling _setTime with no date sets time to defaults + tp_inst.hour = date ? date.getHours() : defaults.hour; + tp_inst.minute = date ? date.getMinutes() : defaults.minute; + tp_inst.second = date ? date.getSeconds() : defaults.second; + tp_inst.millisec = date ? date.getMilliseconds() : defaults.millisec; + tp_inst.microsec = date ? date.getMicroseconds() : defaults.microsec; + + //check if within min/max times.. + tp_inst._limitMinMaxDateTime(inst, true); + + tp_inst._onTimeChange(); + tp_inst._updateDateTime(inst); + } + }; + + /* + * Create new public method to set only time, callable as $().datepicker('setTime', date) + */ + $.datepicker._setTimeDatepicker = function (target, date, withDate) { + var inst = this._getInst(target); + if (!inst) { + return; + } + + var tp_inst = this._get(inst, 'timepicker'); + + if (tp_inst) { + this._setDateFromField(inst); + var tp_date; + if (date) { + if (typeof date === "string") { + tp_inst._parseTime(date, withDate); + tp_date = new Date(); + tp_date.setHours(tp_inst.hour, tp_inst.minute, tp_inst.second, tp_inst.millisec); + tp_date.setMicroseconds(tp_inst.microsec); + } else { + tp_date = new Date(date.getTime()); + tp_date.setMicroseconds(date.getMicroseconds()); + } + if (tp_date.toString() === 'Invalid Date') { + tp_date = undefined; + } + this._setTime(inst, tp_date); + } + } + + }; + + /* + * override setDate() to allow setting time too within Date object + */ + $.datepicker._base_setDateDatepicker = $.datepicker._setDateDatepicker; + $.datepicker._setDateDatepicker = function (target, date) { + var inst = this._getInst(target); + if (!inst) { + return; + } + + if (typeof(date) === 'string') { + date = new Date(date); + if (!date.getTime()) { + $.timepicker.log("Error creating Date object from string."); + } + } + + var tp_inst = this._get(inst, 'timepicker'); + var tp_date; + if (date instanceof Date) { + tp_date = new Date(date.getTime()); + tp_date.setMicroseconds(date.getMicroseconds()); + } else { + tp_date = date; + } + + // This is important if you are using the timezone option, javascript's Date + // object will only return the timezone offset for the current locale, so we + // adjust it accordingly. If not using timezone option this won't matter.. + // If a timezone is different in tp, keep the timezone as is + if (tp_inst && tp_date) { + // look out for DST if tz wasn't specified + if (!tp_inst.support.timezone && tp_inst._defaults.timezone === null) { + tp_inst.timezone = tp_date.getTimezoneOffset() * -1; + } + date = $.timepicker.timezoneAdjust(date, tp_inst.timezone); + tp_date = $.timepicker.timezoneAdjust(tp_date, tp_inst.timezone); + } + + this._updateDatepicker(inst); + this._base_setDateDatepicker.apply(this, arguments); + this._setTimeDatepicker(target, tp_date, true); + }; + + /* + * override getDate() to allow getting time too within Date object + */ + $.datepicker._base_getDateDatepicker = $.datepicker._getDateDatepicker; + $.datepicker._getDateDatepicker = function (target, noDefault) { + var inst = this._getInst(target); + if (!inst) { + return; + } + + var tp_inst = this._get(inst, 'timepicker'); + + if (tp_inst) { + // if it hasn't yet been defined, grab from field + if (inst.lastVal === undefined) { + this._setDateFromField(inst, noDefault); + } + + var date = this._getDate(inst); + if (date && tp_inst._parseTime($(target).val(), tp_inst.timeOnly)) { + date.setHours(tp_inst.hour, tp_inst.minute, tp_inst.second, tp_inst.millisec); + date.setMicroseconds(tp_inst.microsec); + + // This is important if you are using the timezone option, javascript's Date + // object will only return the timezone offset for the current locale, so we + // adjust it accordingly. If not using timezone option this won't matter.. + if (tp_inst.timezone != null) { + // look out for DST if tz wasn't specified + if (!tp_inst.support.timezone && tp_inst._defaults.timezone === null) { + tp_inst.timezone = date.getTimezoneOffset() * -1; + } + date = $.timepicker.timezoneAdjust(date, tp_inst.timezone); + } + } + return date; + } + return this._base_getDateDatepicker(target, noDefault); + }; + + /* + * override parseDate() because UI 1.8.14 throws an error about "Extra characters" + * An option in datapicker to ignore extra format characters would be nicer. + */ + $.datepicker._base_parseDate = $.datepicker.parseDate; + $.datepicker.parseDate = function (format, value, settings) { + var date; + try { + date = this._base_parseDate(format, value, settings); + } catch (err) { + // Hack! The error message ends with a colon, a space, and + // the "extra" characters. We rely on that instead of + // attempting to perfectly reproduce the parsing algorithm. + if (err.indexOf(":") >= 0) { + date = this._base_parseDate(format, value.substring(0, value.length - (err.length - err.indexOf(':') - 2)), settings); + $.timepicker.log("Error parsing the date string: " + err + "\ndate string = " + value + "\ndate format = " + format); + } else { + throw err; + } + } + return date; + }; + + /* + * override formatDate to set date with time to the input + */ + $.datepicker._base_formatDate = $.datepicker._formatDate; + $.datepicker._formatDate = function (inst, day, month, year) { + var tp_inst = this._get(inst, 'timepicker'); + if (tp_inst) { + tp_inst._updateDateTime(inst); + return tp_inst.$input.val(); + } + return this._base_formatDate(inst); + }; + + /* + * override options setter to add time to maxDate(Time) and minDate(Time). MaxDate + */ + $.datepicker._base_optionDatepicker = $.datepicker._optionDatepicker; + $.datepicker._optionDatepicker = function (target, name, value) { + var inst = this._getInst(target), + name_clone; + if (!inst) { + return null; + } + + var tp_inst = this._get(inst, 'timepicker'); + if (tp_inst) { + var min = null, + max = null, + onselect = null, + overrides = tp_inst._defaults.evnts, + fns = {}, + prop; + if (typeof name === 'string') { // if min/max was set with the string + if (name === 'minDate' || name === 'minDateTime') { + min = value; + } else if (name === 'maxDate' || name === 'maxDateTime') { + max = value; + } else if (name === 'onSelect') { + onselect = value; + } else if (overrides.hasOwnProperty(name)) { + if (typeof (value) === 'undefined') { + return overrides[name]; + } + fns[name] = value; + name_clone = {}; //empty results in exiting function after overrides updated + } + } else if (typeof name === 'object') { //if min/max was set with the JSON + if (name.minDate) { + min = name.minDate; + } else if (name.minDateTime) { + min = name.minDateTime; + } else if (name.maxDate) { + max = name.maxDate; + } else if (name.maxDateTime) { + max = name.maxDateTime; + } + for (prop in overrides) { + if (overrides.hasOwnProperty(prop) && name[prop]) { + fns[prop] = name[prop]; + } + } + } + for (prop in fns) { + if (fns.hasOwnProperty(prop)) { + overrides[prop] = fns[prop]; + if (!name_clone) { name_clone = $.extend({}, name); } + delete name_clone[prop]; + } + } + if (name_clone && isEmptyObject(name_clone)) { return; } + if (min) { //if min was set + if (min === 0) { + min = new Date(); + } else { + min = new Date(min); + } + tp_inst._defaults.minDate = min; + tp_inst._defaults.minDateTime = min; + } else if (max) { //if max was set + if (max === 0) { + max = new Date(); + } else { + max = new Date(max); + } + tp_inst._defaults.maxDate = max; + tp_inst._defaults.maxDateTime = max; + } else if (onselect) { + tp_inst._defaults.onSelect = onselect; + } + } + if (value === undefined) { + return this._base_optionDatepicker.call($.datepicker, target, name); + } + return this._base_optionDatepicker.call($.datepicker, target, name_clone || name, value); + }; + + /* + * jQuery isEmptyObject does not check hasOwnProperty - if someone has added to the object prototype, + * it will return false for all objects + */ + var isEmptyObject = function (obj) { + var prop; + for (prop in obj) { + if (obj.hasOwnProperty(prop)) { + return false; + } + } + return true; + }; + + /* + * jQuery extend now ignores nulls! + */ + var extendRemove = function (target, props) { + $.extend(target, props); + for (var name in props) { + if (props[name] === null || props[name] === undefined) { + target[name] = props[name]; + } + } + return target; + }; + + /* + * Determine by the time format which units are supported + * Returns an object of booleans for each unit + */ + var detectSupport = function (timeFormat) { + var tf = timeFormat.replace(/'.*?'/g, '').toLowerCase(), // removes literals + isIn = function (f, t) { // does the format contain the token? + return f.indexOf(t) !== -1 ? true : false; + }; + return { + hour: isIn(tf, 'h'), + minute: isIn(tf, 'm'), + second: isIn(tf, 's'), + millisec: isIn(tf, 'l'), + microsec: isIn(tf, 'c'), + timezone: isIn(tf, 'z'), + ampm: isIn(tf, 't') && isIn(timeFormat, 'h'), + iso8601: isIn(timeFormat, 'Z') + }; + }; + + /* + * Converts 24 hour format into 12 hour + * Returns 12 hour without leading 0 + */ + var convert24to12 = function (hour) { + hour %= 12; + + if (hour === 0) { + hour = 12; + } + + return String(hour); + }; + + var computeEffectiveSetting = function (settings, property) { + return settings && settings[property] ? settings[property] : $.timepicker._defaults[property]; + }; + + /* + * Splits datetime string into date and time substrings. + * Throws exception when date can't be parsed + * Returns {dateString: dateString, timeString: timeString} + */ + var splitDateTime = function (dateTimeString, timeSettings) { + // The idea is to get the number separator occurrences in datetime and the time format requested (since time has + // fewer unknowns, mostly numbers and am/pm). We will use the time pattern to split. + var separator = computeEffectiveSetting(timeSettings, 'separator'), + format = computeEffectiveSetting(timeSettings, 'timeFormat'), + timeParts = format.split(separator), // how many occurrences of separator may be in our format? + timePartsLen = timeParts.length, + allParts = dateTimeString.split(separator), + allPartsLen = allParts.length; + + if (allPartsLen > 1) { + return { + dateString: allParts.splice(0, allPartsLen - timePartsLen).join(separator), + timeString: allParts.splice(0, timePartsLen).join(separator) + }; + } + + return { + dateString: dateTimeString, + timeString: '' + }; + }; + + /* + * Internal function to parse datetime interval + * Returns: {date: Date, timeObj: Object}, where + * date - parsed date without time (type Date) + * timeObj = {hour: , minute: , second: , millisec: , microsec: } - parsed time. Optional + */ + var parseDateTimeInternal = function (dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings) { + var date, + parts, + parsedTime; + + parts = splitDateTime(dateTimeString, timeSettings); + date = $.datepicker._base_parseDate(dateFormat, parts.dateString, dateSettings); + + if (parts.timeString === '') { + return { + date: date + }; + } + + parsedTime = $.datepicker.parseTime(timeFormat, parts.timeString, timeSettings); + + if (!parsedTime) { + throw 'Wrong time format'; + } + + return { + date: date, + timeObj: parsedTime + }; + }; + + /* + * Internal function to set timezone_select to the local timezone + */ + var selectLocalTimezone = function (tp_inst, date) { + if (tp_inst && tp_inst.timezone_select) { + var now = date || new Date(); + tp_inst.timezone_select.val(-now.getTimezoneOffset()); + } + }; + + /* + * Create a Singleton Instance + */ + $.timepicker = new Timepicker(); + + /** + * Get the timezone offset as string from a date object (eg '+0530' for UTC+5.5) + * @param {number} tzMinutes if not a number, less than -720 (-1200), or greater than 840 (+1400) this value is returned + * @param {boolean} iso8601 if true formats in accordance to iso8601 "+12:45" + * @return {string} + */ + $.timepicker.timezoneOffsetString = function (tzMinutes, iso8601) { + if (isNaN(tzMinutes) || tzMinutes > 840 || tzMinutes < -720) { + return tzMinutes; + } + + var off = tzMinutes, + minutes = off % 60, + hours = (off - minutes) / 60, + iso = iso8601 ? ':' : '', + tz = (off >= 0 ? '+' : '-') + ('0' + Math.abs(hours)).slice(-2) + iso + ('0' + Math.abs(minutes)).slice(-2); + + if (tz === '+00:00') { + return 'Z'; + } + return tz; + }; + + /** + * Get the number in minutes that represents a timezone string + * @param {string} tzString formatted like "+0500", "-1245", "Z" + * @return {number} the offset minutes or the original string if it doesn't match expectations + */ + $.timepicker.timezoneOffsetNumber = function (tzString) { + var normalized = tzString.toString().replace(':', ''); // excuse any iso8601, end up with "+1245" + + if (normalized.toUpperCase() === 'Z') { // if iso8601 with Z, its 0 minute offset + return 0; + } + + if (!/^(\-|\+)\d{4}$/.test(normalized)) { // possibly a user defined tz, so just give it back + return tzString; + } + + return ((normalized.substr(0, 1) === '-' ? -1 : 1) * // plus or minus + ((parseInt(normalized.substr(1, 2), 10) * 60) + // hours (converted to minutes) + parseInt(normalized.substr(3, 2), 10))); // minutes + }; + + /** + * No way to set timezone in js Date, so we must adjust the minutes to compensate. (think setDate, getDate) + * @param {Date} date + * @param {string} toTimezone formatted like "+0500", "-1245" + * @return {Date} + */ + $.timepicker.timezoneAdjust = function (date, toTimezone) { + var toTz = $.timepicker.timezoneOffsetNumber(toTimezone); + if (!isNaN(toTz)) { + date.setMinutes(date.getMinutes() + -date.getTimezoneOffset() - toTz); + } + return date; + }; + + /** + * Calls `timepicker()` on the `startTime` and `endTime` elements, and configures them to + * enforce date range limits. + * n.b. The input value must be correctly formatted (reformatting is not supported) + * @param {Element} startTime + * @param {Element} endTime + * @param {Object} options Options for the timepicker() call + * @return {jQuery} + */ + $.timepicker.timeRange = function (startTime, endTime, options) { + return $.timepicker.handleRange('timepicker', startTime, endTime, options); + }; + + /** + * Calls `datetimepicker` on the `startTime` and `endTime` elements, and configures them to + * enforce date range limits. + * @param {Element} startTime + * @param {Element} endTime + * @param {Object} options Options for the `timepicker()` call. Also supports `reformat`, + * a boolean value that can be used to reformat the input values to the `dateFormat`. + * @param {string} method Can be used to specify the type of picker to be added + * @return {jQuery} + */ + $.timepicker.datetimeRange = function (startTime, endTime, options) { + $.timepicker.handleRange('datetimepicker', startTime, endTime, options); + }; + + /** + * Calls `datepicker` on the `startTime` and `endTime` elements, and configures them to + * enforce date range limits. + * @param {Element} startTime + * @param {Element} endTime + * @param {Object} options Options for the `timepicker()` call. Also supports `reformat`, + * a boolean value that can be used to reformat the input values to the `dateFormat`. + * @return {jQuery} + */ + $.timepicker.dateRange = function (startTime, endTime, options) { + $.timepicker.handleRange('datepicker', startTime, endTime, options); + }; + + /** + * Calls `method` on the `startTime` and `endTime` elements, and configures them to + * enforce date range limits. + * @param {string} method Can be used to specify the type of picker to be added + * @param {Element} startTime + * @param {Element} endTime + * @param {Object} options Options for the `timepicker()` call. Also supports `reformat`, + * a boolean value that can be used to reformat the input values to the `dateFormat`. + * @return {jQuery} + */ + $.timepicker.handleRange = function (method, startTime, endTime, options) { + options = $.extend({}, { + minInterval: 0, // min allowed interval in milliseconds + maxInterval: 0, // max allowed interval in milliseconds + start: {}, // options for start picker + end: {} // options for end picker + }, options); + + function checkDates(changed, other) { + var startdt = startTime[method]('getDate'), + enddt = endTime[method]('getDate'), + changeddt = changed[method]('getDate'); + + if (startdt !== null) { + var minDate = new Date(startdt.getTime()), + maxDate = new Date(startdt.getTime()); + + minDate.setMilliseconds(minDate.getMilliseconds() + options.minInterval); + maxDate.setMilliseconds(maxDate.getMilliseconds() + options.maxInterval); + + if (options.minInterval > 0 && minDate > enddt) { // minInterval check + endTime[method]('setDate', minDate); + } + else if (options.maxInterval > 0 && maxDate < enddt) { // max interval check + endTime[method]('setDate', maxDate); + } + else if (startdt > enddt) { + other[method]('setDate', changeddt); + } + } + } + + function selected(changed, other, option) { + if (!changed.val()) { + return; + } + var date = changed[method].call(changed, 'getDate'); + if (date !== null && options.minInterval > 0) { + if (option === 'minDate') { + date.setMilliseconds(date.getMilliseconds() + options.minInterval); + } + if (option === 'maxDate') { + date.setMilliseconds(date.getMilliseconds() - options.minInterval); + } + } + if (date.getTime) { + other[method].call(other, 'option', option, date); + } + } + + $.fn[method].call(startTime, $.extend({ + onClose: function (dateText, inst) { + checkDates($(this), endTime); + }, + onSelect: function (selectedDateTime) { + selected($(this), endTime, 'minDate'); + } + }, options, options.start)); + $.fn[method].call(endTime, $.extend({ + onClose: function (dateText, inst) { + checkDates($(this), startTime); + }, + onSelect: function (selectedDateTime) { + selected($(this), startTime, 'maxDate'); + } + }, options, options.end)); + + checkDates(startTime, endTime); + selected(startTime, endTime, 'minDate'); + selected(endTime, startTime, 'maxDate'); + return $([startTime.get(0), endTime.get(0)]); + }; + + /** + * Log error or data to the console during error or debugging + * @param {Object} err pass any type object to log to the console during error or debugging + * @return {void} + */ + $.timepicker.log = function (err) { + if (window.console) { + window.console.log(err); + } + }; + + /* + * Add util object to allow access to private methods for testability. + */ + $.timepicker._util = { + _extendRemove: extendRemove, + _isEmptyObject: isEmptyObject, + _convert24to12: convert24to12, + _detectSupport: detectSupport, + _selectLocalTimezone: selectLocalTimezone, + _computeEffectiveSetting: computeEffectiveSetting, + _splitDateTime: splitDateTime, + _parseDateTimeInternal: parseDateTimeInternal + }; + + /* + * Microsecond support + */ + if (!Date.prototype.getMicroseconds) { + Date.prototype.microseconds = 0; + Date.prototype.getMicroseconds = function () { return this.microseconds; }; + Date.prototype.setMicroseconds = function (m) { + this.setMilliseconds(this.getMilliseconds() + Math.floor(m / 1000)); + this.microseconds = m % 1000; + return this; + }; + } + + /* + * Keep up with the version + */ + $.timepicker.version = "1.4.3"; + +})(jQuery); diff --git a/src/main/webapp/js_new/map.js b/src/main/webapp/js_new/map.js new file mode 100644 index 0000000000000000000000000000000000000000..dba23981302f7ff278ac106f95b3b6719c08c4b2 --- /dev/null +++ b/src/main/webapp/js_new/map.js @@ -0,0 +1,206 @@ +function createMap() { + var defaultCenter = [47.265718, 11.391342], // set center to innsbruck, tyrol, austria +// var defaultCenter = [48.186312, 16.317615], // set center to vienna, austria + mapOptions, + zoom = 12; + + console.log('Creating map'); + console.debug(' - setting default center:', defaultCenter); + console.debug(' - using zoom level:', zoom); + + // Map options (including default base layer definition) + mapOptions = { + attributionControl: false, + center: defaultCenter, + zoomControl: false, // hide default zoom control, so we can create a bottomright one + zoom: zoom + }; + + // we do not use the leaflet locate function here, since we want a fallback from HTML5 Geolocation API + // to IP address based geolocation + locateAndDraw(mapOptions); +} + +function drawMap(mapOptions) { + var layerControl = L.control.layers(), + map = null, + zoomControl = L.control.zoom({position: 'bottomright'}); + + if (!mapOptions) { + mapOptions = {}; + } + + console.log('Drawing map'); + console.debug(' - options:', mapOptions); + + // Base + Overlay map definition + mapOptions.layers = addOsmLayer(layerControl); + addStamenLayer(layerControl); + addBlueMarbleLayers(layerControl); + addBingLayers(layerControl); + addGoogleLayers(layerControl); + + // Map creation and control assignment + map = L.map('map', mapOptions); + map.addControl(new SettingsControl()); + map.addControl(new HelpControl()); + map.addControl(layerControl); + map.addControl(zoomControl); + map.addControl(L.control.scale()); + map.addControl(new L.Control.GeoSearch({ + position: 'topleft', + provider: new L.GeoSearch.Provider.OpenStreetMap(), + reverseProvider: new L.GeoSearch.ReverseProvider.OpenStreetMap(), + showMarker: true + })); +} + +function addOsmLayer(c) { + if (!L.tileLayer) { + return; + } + + var layerOsm = new L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', { + attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors' + }); + + console.debug('Adding openstreetmap layer'); + + c.addBaseLayer(layerOsm, 'OpenStreetMap'); + return layerOsm; +} + +function addStamenLayer(c) { + if (!L.StamenTileLayer) { + return; + } + + var layerStamen = new L.StamenTileLayer('watercolor'); + + console.debug('Adding stamen watercolor layer'); + + c.addBaseLayer(layerStamen, 'Stamen Watercolor Map'); + return layerStamen; +} + +function addBlueMarbleLayers(c) { + if (!L.tileLayer) { + return; + } + + var blueMarbleJpg, + blueMarblePng, + openGeoOSM; + + blueMarbleJpg = new L.tileLayer.wms( + 'http://maps.opengeo.org/geowebcache/service/wms', + {layers: 'bluemarble'} + ); + + blueMarblePng = new L.tileLayer.wms( + 'http://maps.opengeo.org/geowebcache/service/wms', + {layers: 'bluemarble', format: 'image/png'} + ); + + openGeoOSM = new L.tileLayer.wms( + 'http://maps.opengeo.org/geowebcache/service/wms', + {layers: 'openstreetmap', format: 'image/png'} + ); + + console.debug('Adding openGeo layers (blueMarbel and openGeo OSM)'); + + c.addBaseLayer(blueMarbleJpg, 'OpenGeo BlueMarble (jpg)'); + c.addBaseLayer(blueMarblePng, 'OpenGeo BlueMarble (png)'); + c.addBaseLayer(openGeoOSM, 'OpenGeo OSM (png)'); + + return openGeoOSM; +} + +function addGoogleLayers(c) { + if (!L.Google) { + return; + } + + var gPhysical, + gStreet, + gHybrid, + gSatellite; + + gPhysical = new L.Google('TERRAIN'); + gStreet = new L.Google('ROADMAP', + {maxZoom: 20 } + ); + gHybrid = new L.Google('HYBRID', + {maxZoom: 20 } + ); + gSatellite = new L.Google('SATELLITE', + {maxZoom: 22 } + ); + + console.debug('Adding google layers'); + + c.addBaseLayer(gPhysical, 'Google Physical'); + c.addBaseLayer(gStreet, 'Google Streets'); + c.addBaseLayer(gHybrid, 'Google Hybrid'); + c.addBaseLayer(gSatellite, 'Google Satellite'); + + return gSatellite; +} + +function addBingLayers(c) { + if (!L.BingLayer) { + return; + } + + var apiKey = 'AqTGBsziZHIJYYxgivLBf0hVdrAk9mWO5cQcb8Yux8sW5M8c8opEC2lZqKR1ZZXf', + bAerial, + bHybrid, + bRoad; + + bRoad = new L.BingLayer(apiKey, { + type: 'Road' + }); + bHybrid = new L.BingLayer(apiKey, { + type: 'AerialWithLabels' + }); + bAerial = new L.BingLayer(apiKey, { + type: 'Aerial' + }); + + console.debug('Adding bing layers'); + + c.addBaseLayer(bRoad, 'Bing Road'); + c.addBaseLayer(bHybrid, 'Bing Hybrid'); + c.addBaseLayer(bAerial, 'Bing Aerial'); + + return bAerial; +} + +function locateAndDraw(mapOptions) { + var fnSuccess = function(location) { onLocationSuccess(mapOptions, location); }, + fnError = function(message) { onLocationError(mapOptions, message); }, + html5Options = { enableHighAccuracy: true, timeout: 3000, maximumAge: 0 }; + + geolocator.locate(fnSuccess, fnError, true, html5Options); +} + +function onLocationSuccess(mapOptions, location) { + if (!mapOptions) mapOptions = {}; + if (!location || !location.coords) { + console.debug('Invalid geoLocation found... no coordinates returned!'); + return; + } + + console.log('GeoLocation found. It will be used as center'); + console.debug(' - location:', location); + + mapOptions.center = [location.coords.latitude, location.coords.longitude]; + drawMap(mapOptions); +} + +function onLocationError(mapOptions, message) { + console.info('Could not get GeoLocation', message); + console.debug(' - will continue with default location'); + + drawMap(mapOptions); +} diff --git a/src/main/webapp/js_new/versions.txt b/src/main/webapp/js_new/versions.txt new file mode 100644 index 0000000000000000000000000000000000000000..390c2b30abc18b5b36abe7b8b6d27d1f98e9b953 --- /dev/null +++ b/src/main/webapp/js_new/versions.txt @@ -0,0 +1,7 @@ +Sources: +######### + +console.js: https://code.google.com/p/console-js/ (v. 0.1) +bing.js: https://github.com/shramov/leaflet-plugins/ +google.js: https://github.com/shramov/leaflet-plugins/ +geosearch.js: based on https://github.com/smeijer/L.GeoSearch