/** * @module nyc */ import $ from 'jquery' import proj4 from 'proj4' proj4.defs([ ['EPSG:2263', '+proj=lcc +lat_1=41.03333333333333 +lat_2=40.66666666666666 +lat_0=40.16666666666666 +lon_0=-74 +x_0=300000.0000000001 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=ft +to_meter=0.3048006096012192 +no_defs'], ['EPSG:6539', '+proj=lcc +lat_1=40.66666666666666 +lat_2=41.03333333333333 +lat_0=40.16666666666666 +lon_0=-74 +x_0=300000 +y_0=0 +ellps=GRS80 +units=us-ft +no_defs'] ]) /** * @desc Top level namespace for NYC mapping * @public * @namespace */ const nyc = { /** * @desc A proj4 instnce with NYC coordinate systems defined * @public * @const {proj4} */ proj4: proj4, /** * @desc Provide inheritance for function-based classes using prototype * @public * @static * @function * @param {function} childCtor The constructor of the subclass * @param {function} parentCtor The constructor of the superclass */ inherits(childCtor, parentCtor) { nyc.copyFromParentProperties(childCtor.prototype, parentCtor.prototype) }, /** * @desc Provide for function-based classes to inherit from ES5 and ES6 classes * @public * @static * @function * @param {Object} childObj An instance of the subclass * @param {Object} parentObj An instance of the superclass */ subclass(childObj, parentObj) { const parentProto = Object.getPrototypeOf(parentObj) nyc.copyFromParentProperties(childObj, parentProto) nyc.copyFromParentKeys(childObj, parentObj) }, /** * @desc Provide mixin functionality * @public * @static * @function * @param {Object} obj The target of the mixins * @param {Array<Object>} mixins An array of mixin objects - last in wins */ mixin(obj, mixins) { mixins.forEach(mixin => { $.extend(obj, mixin) }) }, /** * @private * @static * @function * @param {Object} childObj Child object * @param {Object} parentObj Parent object */ copyFromParentProperties(childObj, parentObj) { Object.getOwnPropertyNames(parentObj).forEach(member => { if (!(member in childObj)) { childObj[member] = parentObj[member] } }) }, /** * @private * @static * @function * @param {Object} childObj Child object * @param {Object} parentObj Parent object */ copyFromParentKeys(childObj, parentObj) { Object.keys(parentObj).forEach(member => { if (!(member in childObj)) { childObj[member] = parentObj[member] } }) }, /** * @public * @static * @function * @param {string} s A string * @return {string} Input string with the first letter of each word capitalized */ capitalize(s) { const words = s.split(' ') let result = '' words.forEach(w => { const word = w.toLowerCase() result += word.substr(0, 1).toUpperCase() result += word.substr(1).toLowerCase() result += ' ' }) return result.trim() }, /** * @public * @static * @function * @param {string} prefix An id prefix * @return {string} A unique id */ nextId(prefix) { const last = nyc.uniqueIds[prefix] nyc.uniqueIds[prefix] = (typeof last === 'number') ? (last + 1) : 0 return `${prefix}-${nyc.uniqueIds[prefix]}` }, /** * @public * @static * @function * @param {Object} object An object * @return {jQuery} HTML string */ html(object) { if (typeof object.html === 'function') { return $(object.html()) } else { const html = $('<div class="nyc-html"></div>') if (object.getProperties) { object = object.getProperties() } else if (object.properties) { object = object.properties } Object.keys(object).forEach(prop => { html.append(`<div><span class="fld">${prop}</span><span class="val">${object[prop]}</span></div>`) }) return html } }, /** * @desc Shows the loading splash screen on eleents with loading class * @public * @static * @function * @param {jQuery} target Target elements */ loading(target) { if (target.hasClass('loading')) { target.append($('#loading').get(0) || nyc.LOADING_HTML) } }, /** * @desc Clears the loading splash screen * @public * @static * @function * @param {jQuery} [target=body] Target elements */ ready(target) { $(target || $('body')).removeClass('loading').attr('aria-hidden', false) $('#loading').fadeOut() }, /** * @desc Returns a cache bust param for query strings * @public * @static * @function * @param {number} minutes Minutes * @return {number} Time in milliseconds */ cacheBust(minutes) { const offset = 1000 * 60 * minutes; return Math.round(new Date().getTime() / offset) * offset; }, /** * @desc Disables scrolling on keypress when user presses the space bar on a button or a link by adding a kepress event handler to the document. The click event is triggered on buttons and links without and href attribute or with an href value of #. If a link has an href the browser will navigate to that location. If a link also has a target attribute the navigation will take place in a new tab. * @public * @static */ noSpaceBarScroll() { $(document).keypress(nyc.noSpaceBarHandler) }, /** * @private * @const * @type {function} * @param {object} event The event object */ noSpaceBarHandler(event) { const tag = event.target.tagName if ($.inArray(tag, ['INPUT', 'TEXTAREA', 'SELECT']) === -1) { const key = event.key if ($.inArray(key, [' ', 'Enter']) > -1) { const elem = $(event.target) const href = elem.attr('href') event.preventDefault() if (!href || href === '#') { elem.trigger('click') } else { const target = elem.attr('target') if (target) { window.open(href) } else { nyc.location(href) } } } } }, /** * @private * @method * @param {string} href Hyperlink */ location(href) { window.location = href }, /** * @desc Returns an object describing the element that currently has focus * @public * @static * @function * @return {Object} An object containing the element and a boolean indicating if it is a text input */ activeElement() { const activeElement = document.activeElement return { activeElement: activeElement, isTextInput: activeElement.tagName === 'INPUT' && activeElement.type === 'text' } }, /** * @desc Returns an array with duplicates removed * @public * @static * @function * @param {Array<string>|Array<number>} arr An array with possible duplicates * @return {Array<string>|Array<number>} An array without duplicates */ removeDups(arr) { const noDups = {} const result = [] arr.forEach(v => { noDups[v] = typeof v }) Object.keys(noDups).forEach(v => { result.push(noDups[v] === 'number' ? v * 1 : v) }) return result }, /** * @private * @static * @member {Object<string, number>} */ uniqueIds: {} } /** * @private * @const * @type {string} */ nyc.LOADING_HTML = '<div id="loading">' + '<div>' + '<svg xmlns="http://www.w3.org/2000/svg" width="152" height="52">' + '<g transform="translate(1.5,0)">' + '<polygon points="15.5,1.2 3.1,1.2 0,4.3 0,47.7 3.1,50.8 15.5,50.8 18.6,47.7 18.6,35.3 34.1,50.8 46.6,50.8 49.7,47.7 49.7,4.3 46.6,1.2 34.1,1.2 31,4.3 31,16.7 "/>' + '<polygon points="83.8,47.7 83.8,38.4 99.3,22.9 99.3,10.5 99.3,4.3 96.2,1.2 83.8,1.2 80.7,4.3 80.7,10.5 74.5,16.7 68.3,10.5 68.3,4.3 65.2,1.2 52.8,1.2 49.7,4.3 49.7,22.9 65.2,38.4 65.2,47.7 68.3,50.8 80.7,50.8 "/>' + '<polygon points="145.9,29.1 130.4,29.1 130.4,32.2 118,32.2 118,19.8 130.4,19.8 130.4,22.9 145.9,22.9 149,19.8 149,10.5 139.7,1.2 108.6,1.2 99.3,10.5 99.3,41.5 108.6,50.8 139.7,50.8 149,41.5 149,32.2 "/>' + '</g>' + '</svg>' + '<h1>maps.nyc.gov</h1>' + '</div>' + '</div>' $(document).ready(() => { nyc.loading($('body')) }) export default nyc