diff --git a/app/assets/javascripts/app/alert-manager.js b/app/assets/javascripts/app/alert-manager.js index 0b224548f..fff26cc6d 100644 --- a/app/assets/javascripts/app/alert-manager.js +++ b/app/assets/javascripts/app/alert-manager.js @@ -6,6 +6,9 @@ define( function () { 'use strict'; + // The events the alert broadcasts. + var _events = {}; + // HTML element for the alert container. var _alertContainer; @@ -59,6 +62,7 @@ function () { // Hiding the alert box clears its content and hides it using CSS. function hide() { + _broadcastEvent('close'); _alertContainer.classList.add('hide'); _content.innerHTML = ''; } @@ -68,10 +72,24 @@ function () { hide(); } + // @param evt [String] The type of event to broadcast. + // Supports 'close'. + function _broadcastEvent(evt) { + if (_events[evt]) + _events[evt].call(); + } + + // @param event [String] The event name to listen for. Supports 'close'. + // @param callback [Function] The function called when the event has fired. + function addEventListener(event, callback) { + _events[event] = callback; + } + return { init:init, show:show, hide:hide, - type:type + type:type, + addEventListener:addEventListener }; }); diff --git a/app/assets/javascripts/app/app-init.js b/app/assets/javascripts/app/app-init.js index 22f4f4540..b63fdfa07 100644 --- a/app/assets/javascripts/app/app-init.js +++ b/app/assets/javascripts/app/app-init.js @@ -1,17 +1,18 @@ // Manages the application initialization for most pages. // This script is called by the homepage, search result // and search details pages. It is not called by -// the about page, because that page does not have popups to manage. +// the about page. require([ + 'util/translation/google-translate-manager', 'app/popup-manager', 'app/alert-manager' ], -function (pm, alert) { +function (googleTranslate, popups, alert) { 'use strict'; - // If box-shadow CSS is supported, initialize the popups. - if (Modernizr.boxshadow) - pm.init(); + // If page is not translated, initialize the header popups. + if (!googleTranslate.isTranslated()) + popups.init(); alert.init(); }); diff --git a/app/assets/javascripts/app/popup-manager.js b/app/assets/javascripts/app/popup-manager.js index 7d8217f7b..48f4d102f 100644 --- a/app/assets/javascripts/app/popup-manager.js +++ b/app/assets/javascripts/app/popup-manager.js @@ -17,21 +17,8 @@ function (util, feedback) { var PADDING = 20; function init() { - if (!_isTranslated()) { - _addPopups(); - feedback.init(); - } - } - - // Check if the page is currently translated using Google Translation. - function _isTranslated() { - var translate = util.getQueryParams().translate; - if (translate && translate !== 'en') - return true; - var googtrans = util.getCookie('googtrans'); - if (googtrans && googtrans !== '/en/en') - return true; - return false; + _addPopups(); + feedback.init(); } // Adds hooks for triggering popups present on the page. diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index e9ac1a359..0ef894ff1 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -70,11 +70,16 @@ // require([ 'util/translation/google-translate-manager', + 'util/browser-upgrade-notice', 'domReady!' ], -function (googleTranslate) { +function (googleTranslate, browserUpgradeNotice) { 'use strict'; + // Show a browser upgrade notice on IE8 and below. + if (document.documentElement.classList.contains('lt-ie9')) + browserUpgradeNotice.show(); + // The Google translate manager handles loading of the // Google Website Translator Gadget at the bottom of the page's body. // The layout settings passed in as an argument to the initialization diff --git a/app/assets/javascripts/search/filter/TextInput.js b/app/assets/javascripts/search/filter/TextInput.js index 2bddd0b6d..4016ebbb3 100644 --- a/app/assets/javascripts/search/filter/TextInput.js +++ b/app/assets/javascripts/search/filter/TextInput.js @@ -32,7 +32,7 @@ function () { _input.value = ''; } - function _initCloseButton() { + function _initClearButton() { // Retrieve first and only input element. // Throw an error if it isn't found. _input = _container.getElementsByTagName('input')[0]; @@ -44,7 +44,6 @@ function () { if (_input.value === '') _buttonClear.className += ' hide'; _container.appendChild(_buttonClear); - _buttonClear.addEventListener('click', function (evt) { evt.preventDefault(); reset(); @@ -85,7 +84,7 @@ function () { } // Initialize TextInput instance. - if (_container) _initCloseButton(); + if (_container) _initClearButton(); else _throwInitializationError(); return { diff --git a/app/assets/javascripts/util/browser-upgrade-notice.js b/app/assets/javascripts/util/browser-upgrade-notice.js new file mode 100644 index 000000000..d2588c9fb --- /dev/null +++ b/app/assets/javascripts/util/browser-upgrade-notice.js @@ -0,0 +1,31 @@ +// Provides a upgrade notice that can be shown to outdated browsers (e.g. IE8). +define([ + 'util/cookie', + 'app/alert-manager' +], +function (cookie, alert) { + 'use strict'; + + // Show upgrade notice if browser-upgrade-notice cookie isn't set. + function show() { + if (cookie.read('browser-upgrade-notice') === null) { + var notice = " " + + 'Your browser is out-of-date and has known security issues.' + + '
' + + "" + + 'Please visit this page to download an up-to-date browser.' + + ''; + alert.addEventListener('close', _alertClosed); + alert.show(notice, alert.type.INFO); + } + } + + // Create a cookie when the alert is closed that will hide the alert for the next day. + function _alertClosed() { + cookie.create('browser-upgrade-notice', 'true', true, 1); + } + + return { + show:show + }; +}); diff --git a/app/assets/javascripts/util/cookie.js.erb b/app/assets/javascripts/util/cookie.js.erb new file mode 100644 index 000000000..74ad1b188 --- /dev/null +++ b/app/assets/javascripts/util/cookie.js.erb @@ -0,0 +1,52 @@ +// Cookie CRUD functions, from http://www.quirksmode.org/js/cookies.html +// ERB needed to retrieve domain name that the cookie is saved under. +define( +function () { + 'use strict'; + + // @param name [String] The cookie's name. + // @param value [String] The cookie's value. + // @param useDomain [Boolean] Whether set under subdomains or not. + // @param days [Number] Number of days till the cookie expires. + // Can be negative. + function create(name, value, useDomain, days) { + if (days) { + var date = new Date(); + date.setTime(date.getTime() + (days*24*60*60*1000)); + var expires = '; expires=' + date.toGMTString(); + } + else var expires = ''; + + var setting = name + '=' + value + expires + '; path=/'; + + // Sets the cookie under domain and subdomains (if useDomain parameter is present). + document.cookie = setting; + if (useDomain) + document.cookie = setting + "; domain=.<%= ENV['DOMAIN_NAME'] %>"; + } + + // @param name [String] The cookie's name to read. + // @return [String] The named cookie's value, or null. + function read(name) { + var nameEQ = name + "="; + var ca = document.cookie.split(';'); + for(var i=0;i < ca.length;i++) { + var c = ca[i]; + while (c.charAt(0)==' ') c = c.substring(1,c.length); + if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length); + } + return null; + } + + // @param name [String] The cookie's name to remove. + // @param usedDomain [Boolean] Whether to clear subdomains also. + function erase(name, useDomain) { + create(name, '', !!useDomain, -1); + } + + return { + create:create, + read:read, + erase:erase + }; +}); diff --git a/app/assets/javascripts/util/geolocation/geolocator.js b/app/assets/javascripts/util/geolocation/geolocator.js index 77c15d7bb..b683fd388 100644 --- a/app/assets/javascripts/util/geolocation/geolocator.js +++ b/app/assets/javascripts/util/geolocation/geolocator.js @@ -8,7 +8,6 @@ define(function () { function locate(pCallBack) { _callBack = pCallBack; - // Modernizr should pick this up, but just in case... var geolocator = navigator.geolocation; if (geolocator) { var geoOptions = { diff --git a/app/assets/javascripts/util/translation/google-translate-manager.js b/app/assets/javascripts/util/translation/google-translate-manager.js index d45b102b6..177d644dd 100644 --- a/app/assets/javascripts/util/translation/google-translate-manager.js +++ b/app/assets/javascripts/util/translation/google-translate-manager.js @@ -1,8 +1,10 @@ // Manages behavior of the Google Website Translator Gadget. define([ + 'util/util', + 'util/cookie', 'util/translation/layout/DropDownLayout' ], -function (DropDownLayout) { +function (util, cookie, DropDownLayout) { 'use strict'; // The layout object in use. @@ -26,7 +28,7 @@ function (DropDownLayout) { function init(layoutType) { _layoutType = layoutType; - _deleteTranslateCookies(); + _writeTranslateCookies(); _layout = DropDownLayout.create(); _layout.init(GOOGLE_TRANSLATE_ELEMENT_ID); @@ -45,6 +47,18 @@ function (DropDownLayout) { window.GoogleTranslate = GoogleTranslate; } + // Checks if the 'googtrans' cookie is set to English or not, + // indicating whether the page has been translated using the + // Google Website Translator Gadget. + // @return [Boolean] true if Google Translation has translated the page. + // Returns false if the page is not translated and is in English. + function isTranslated() { + var googtrans = cookie.read('googtrans'); + if (googtrans && decodeURIComponent(googtrans) === '/en/en') + return true; + return false; + } + // Initialize the Google Website Translator Gadget. function _googleTranslateElementInit() { var gadgetOptions = { @@ -69,22 +83,23 @@ function (DropDownLayout) { return google.translate.TranslateElement.InlineLayout.HORIZONTAL; } - // Removes the Google Website Translator cookies by setting their expiration - // date into the past. - function _deleteTranslateCookies() { - var cookies, cookie, eqPos, name; - cookies = document.cookie.split('; '); - for (var i = 0, len = cookies.length; i < len; i++) { - cookie = cookies[i]; - eqPos = cookie.indexOf('='); - name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie; - if (name === 'googtrans') - document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT'; + // Overwrite/Create Google Website Translator Gadget cookies if the + // 'translate' URL parameter is present. + function _writeTranslateCookies() { + var translateRequested = util.getParameterByName('translate'); + if (translateRequested) { + var newCookieValue = '/en/'+translateRequested; + var oldCookieValue = decodeURIComponent(cookie.read('googtrans')); + if(newCookieValue !== oldCookieValue) { + cookie.create('googtrans', newCookieValue, true, 30); + window.location.reload(); + } } } return { init:init, + isTranslated:isTranslated, InlineLayout:InlineLayout }; }); diff --git a/app/assets/javascripts/util/util.js b/app/assets/javascripts/util/util.js index 4d93c6ea2..329161775 100644 --- a/app/assets/javascripts/util/util.js +++ b/app/assets/javascripts/util/util.js @@ -2,18 +2,6 @@ define( function () { 'use strict'; - // Check if any object is empty of parameters. - // (from http://stackoverflow.com/questions/3426979/ - // javascript-checking-if-an-object-has-no-properties-or-if-a-map- - // associative-arra) - function isEmpty(map) { - for(var key in map) { - if (map.hasOwnProperty(key)) - return false; - } - return true; - } - // Detects whether a particular event is supported. // (from http://stackoverflow.com/questions/2877393/ // detecting-support-for-a-given-javascript-event) @@ -97,16 +85,6 @@ function () { }; } - // @return [Object] Browser-appropriate requestanimationframe implementation. - // @example util.requestAnimationFrame()(_animate_callback); - function requestAnimationFrame() { - return window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.oRequestAnimationFrame || - window.msRequestAnimationFrame; - } - - // Get computed style // (from http://stackoverflow.com/questions/2664045/ // how-to-retrieve-a-styles-value-in-javascript) @@ -141,6 +119,7 @@ function () { } } + // Insert parameters in the URL. // @param params [Object] (optional) // @return [String] the appended URL query string @@ -180,55 +159,22 @@ function () { } } - // Parse query string into object - // (from http://stackoverflow.com/questions/979975/ - // how-to-get-the-value-from-url-parameter) - // @param [String] the query string parameter - // @return [Object] query string as object - function getQueryParams(qs) { - if (!qs) qs = document.location.search; - qs = qs.split('+').join(' '); - - var params = {}, tokens, re = /[?&]?([^=]+)=([^&]*)/g; - - while ( (tokens = re.exec(qs)) ) - params[decodeURIComponent(tokens[1])] = decodeURIComponent(tokens[2]); - - return params; - } - - // Check if a URL parameter is present. - // (from http://stackoverflow.com/questions/1314383/ - // how-to-check-if-a-querystring-value-is-present-via-javascript) - // @param [String] The parameter to check for the existence of. - function isURLParamPresent(param) { - var url = window.location.href; - if (url.indexOf('?' + param + '=') !== -1) - return true; - else if (url.indexOf('&' + param + '=') !== -1) - return true; - return false; + // Retrieve named query parameter. + // (from http://stackoverflow.com/questions/901115/ + // how-can-i-get-query-string-values-in-javascript) + function getParameterByName(name) { + name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); + var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), + results = regex.exec(location.search); + return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); } - // Retrieve a cookie value by name. - // @param [String] the name of the cookie. - // @return [String] the cookie value. - function getCookie(name) { - var parts = document.cookie.split(name + '='); - if (parts.length === 2) return parts.pop().split(';').shift(); - } - - return { - isEmpty:isEmpty, isEventSupported:isEventSupported, getWindowRect:getWindowRect, getOffset:getOffset, - requestAnimationFrame:requestAnimationFrame, getStyle:getStyle, queryString:queryString, - getQueryParams:getQueryParams, - isURLParamPresent:isURLParamPresent, - getCookie:getCookie + getParameterByName:getParameterByName }; }); diff --git a/app/assets/stylesheets/_base.scss b/app/assets/stylesheets/_base.scss index 48afa31c7..142436ee8 100644 --- a/app/assets/stylesheets/_base.scss +++ b/app/assets/stylesheets/_base.scss @@ -329,7 +329,6 @@ sup font-size: $font_size_120; } - .close-button:after, .close-button::after { content: "×"; @@ -351,6 +350,7 @@ sup font-size: $font_size_110; border-bottom: 6px solid $accentA-light; padding-bottom: 10px; + padding-right: 10px; margin-bottom: 10px; text-align: center; } @@ -370,7 +370,7 @@ sup } // Feedback form styles. - input,textarea,button + input, textarea, button { padding: 5px; width: 228px; @@ -1284,7 +1284,6 @@ body { top: 0px !important; } font-family: $font_serif; } - h1.name > a:after, h1.name > a::after { content: " more..."; @@ -1294,7 +1293,6 @@ body { top: 0px !important; } color: rgba($black,.4); } - h1.name > a:hover:after, h1.name > a:hover::after { color: $black; @@ -1302,7 +1300,6 @@ body { top: 0px !important; } border-top: 0; } - h2.agency { font-size: 14px; @@ -1461,7 +1458,6 @@ body { top: 0px !important; } font-family: $font_serif; } - .button-close:after, .button-close::after { content: "×"; @@ -1732,8 +1728,7 @@ body { top: 0px !important; } margin-right: 4px; } - >.fax:after, - >.fax::after + > .fax::after { content: "FAX"; font-size: 10px; @@ -1785,7 +1780,6 @@ body { top: 0px !important; } line-height: normal; } - a:last-child:after, a:last-child::after { content: ">"; @@ -1793,7 +1787,6 @@ body { top: 0px !important; } color: rgba($black,.4); } - a:last-child:before, a:last-child::before { content: "<"; diff --git a/app/assets/stylesheets/components/_alert.scss b/app/assets/stylesheets/components/_alert.scss index bc4a6285b..eae91cda9 100644 --- a/app/assets/stylesheets/components/_alert.scss +++ b/app/assets/stylesheets/components/_alert.scss @@ -51,9 +51,11 @@ { color: lighten($black,50) !important; // IE Fallback color: rgba($white,.5) !important; - border-bottom:none !important; - margin-top:5px; - margin-bottom:5px; + border-bottom: none !important; + margin-top: 5px; + margin-bottom: 5px; + min-width: 28px; + min-height: 32px; @include inline-block(); } diff --git a/app/assets/stylesheets/components/_input-search.scss b/app/assets/stylesheets/components/_input-search.scss index 36fbee5c5..e5a75151e 100644 --- a/app/assets/stylesheets/components/_input-search.scss +++ b/app/assets/stylesheets/components/_input-search.scss @@ -248,13 +248,13 @@ cursor: pointer; @include rounded-except(bottom); - >span + > span { color: $black; // IE fallback color: midtone($black, 0.5); } - >.fa + > .fa { color: $input-fa-color; } @@ -316,7 +316,6 @@ background: transparent; } - .button-clear:after, .button-clear::after { content: "×"; diff --git a/app/assets/stylesheets/ie8.scss b/app/assets/stylesheets/ie8.scss new file mode 100755 index 000000000..b87f92e86 --- /dev/null +++ b/app/assets/stylesheets/ie8.scss @@ -0,0 +1,117 @@ +/* ========================================================================== + Internet Explorer version 8 and earlier. + ========================================================================== */ + +html.lt-ie9 +{ + // Adjustments for popups in header. + #popups + { + .popup-container > article + { + padding: 10px; + @include box-sizing(border-box); + border: 1px solid $black; + } + + .arrow + { + display: none; + } + + .close-button:after + { + color: $greyscale_midtone; + content: "×"; + } + } + + // Adjustments for alert box component. + .alert + { + border: 1px solid $black; + + // Since IE8 doesn't support rbga() notation, make alert links + // lines thicker on roll-over. + a + { + color:$white; + border-bottom:1px solid $white; + text-decoration: none; + } + + a:hover + { + color:$white; + border-bottom:2px solid $white; + } + } + + // Adjustments location details + #detail-info + { + // The < and > around the "more" + // button on the description are not included + // because IE8 doesn't support :last-child + // .description a:last-child:after + // .description a:last-child:before + + .fax:after + { + content: "FAX"; + font-size: 10px; + font-family: $font_san_serif; + @include inline-block(); + } + } + + // Adjustments for search result list. + #list-view + { + .results-entry + { + > header + { + >hgroup + { + h1.name > a:after + { + content: " more..."; + font-size: $font_size_90; + font-family: $font_san_serif; + color: $greyscale_midtone; // IE fallback + color: rgba($black,.4); + } + + h1.name > a:hover:after + { + color: $black; + border-right: 0; + border-top: 0; + } + } + } + } + } + + // Clearable search input fields. + .clearable + { + .button-clear:after + { + content: "×"; + } + } + + // Search results map infobox. + #map-canvas + { + .infoBox + { + .button-close:after + { + content: "×"; + } + } + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/main.css.scss b/app/assets/stylesheets/main.css.scss index cc9ad5ad1..4d925e9b9 100755 --- a/app/assets/stylesheets/main.css.scss +++ b/app/assets/stylesheets/main.css.scss @@ -30,3 +30,8 @@ ========================================================================== */ @import "responsive"; @import "print"; + +/* ========================================================================== + Browser-specific override imports + ========================================================================== */ +@import "ie8" \ No newline at end of file diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 75bb0feb9..d097cd5ba 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -8,12 +8,14 @@ %head = render 'shared/head' - %body{class: "#{controller_name == 'home' ? '' : 'inside '}#{controller_name} #{action_name}"} + %body{ class: "#{controller_name == 'home' ? '' : 'inside '}#{controller_name} #{action_name}" } %section#content = render 'shared/alert' = render 'shared/header' %main.main= yield = render 'shared/footer' + = requirejs_include_tag("routes/#{controller_name}/#{action_name}") + - if Rails.env.production? && ENV['GOOGLE_ANALYTICS_ID'].present? - = render 'shared/google_analytics/event_tracking' \ No newline at end of file + = render 'shared/google_analytics/event_tracking' diff --git a/app/views/shared/_footer.html.haml b/app/views/shared/_footer.html.haml index b57a732c4..2b87ab424 100644 --- a/app/views/shared/_footer.html.haml +++ b/app/views/shared/_footer.html.haml @@ -15,7 +15,4 @@ for your city or #{link_to 'view project details', 'http://ohanapi.org'}. - -# This line needs to go ABOVE the Google Translate code, or it can take too long to load (see issue #462) - = requirejs_include_tag("routes/#{controller_name}/#{action_name}").gsub("/assets/routes/#{controller_name}/#{action_name}", "routes/#{controller_name}/#{action_name}").html_safe - #google-translate-container diff --git a/vendor/assets/javascripts/google-maps/OverlappingMarkerSpiderfier.js b/vendor/assets/javascripts/google-maps/OverlappingMarkerSpiderfier.js index 58f28167d..292ce1661 100644 --- a/vendor/assets/javascripts/google-maps/OverlappingMarkerSpiderfier.js +++ b/vendor/assets/javascripts/google-maps/OverlappingMarkerSpiderfier.js @@ -35,4 +35,4 @@ e;a=++f)if(g=b[a],g===c)return a;return-1};w.g=function(b){return this.setMap(b) // ------------------------------------------------------------------ return OverlappingMarkerSpiderfier; -}); \ No newline at end of file +}); diff --git a/vendor/assets/javascripts/google-maps/infobox.js b/vendor/assets/javascripts/google-maps/infobox.js index 5b49983ab..a91aef8f7 100644 --- a/vendor/assets/javascripts/google-maps/infobox.js +++ b/vendor/assets/javascripts/google-maps/infobox.js @@ -14,4 +14,4 @@ eval(function(p,a,c,k,e,r){e=function(c){return(c