2013-05-27 20:45:59 +00:00
< html >
2017-03-26 22:34:41 +00:00
< head >
< title > NYC Bookstores< / title >
2015-03-08 18:37:54 +00:00
< meta charset = 'utf-8' >
2017-03-27 21:25:36 +00:00
< link rel = "icon" type = "image/png" href = "/img/favicon.png" / >
< link rel = "apple-touch-icon" href = "/img/social.jpg" / >
2017-03-26 22:34:41 +00:00
< script type = "text/javascript" src = 'js/jquery.js' > < / script >
< script type = "text/javascript" src = 'js/mustache.js' > < / script >
2017-04-14 01:32:01 +00:00
< script src = "//cdnjs.cloudflare.com/ajax/libs/jquery-scrollTo/2.1.0/jquery.scrollTo.min.js" > < / script >
2015-03-08 18:37:54 +00:00
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" / >
2017-03-26 22:34:41 +00:00
< script src = 'https://api.mapbox.com/mapbox-gl-js/v0.34.0/mapbox-gl.js' > < / script >
< link href = 'https://api.mapbox.com/mapbox-gl-js/v0.34.0/mapbox-gl.css' rel = 'stylesheet' / >
< link href = "https://fonts.googleapis.com/css?family=Acme|Lato" rel = "stylesheet" >
2015-03-08 18:37:54 +00:00
< link media = "screen" rel = "stylesheet" type = "text/css" href = "css/site.css" >
2017-03-26 23:43:37 +00:00
< meta name = "twitter:card" content = "summary" / >
2017-03-26 23:44:58 +00:00
< meta name = "twitter:site" content = "www.nycbookstores.org" / >
2017-03-26 23:43:37 +00:00
< meta name = "twitter:title" content = "NYC Bookstores" / >
< meta name = "twitter:description" content = "A Guide To The Many Independent Bookstores Of New York City" / >
2017-03-26 23:44:58 +00:00
< meta name = "twitter:image" content = "http://www.nycbookstores.org/img/social.jpg" / >
< meta property = "og:url" content = "http://www.nycbookstores.org/" / >
2017-03-26 23:43:37 +00:00
< meta property = "og:title" content = "NYC Bookstores" / >
< meta property = "og:description" content = "A Guide To The Many Independent Bookstores Of New York City" / >
2017-03-26 23:44:58 +00:00
< meta property = "og:image" content = "http://www.nycbookstores.org/img/social.jpg" / >
2015-03-08 18:37:54 +00:00
< / head >
2013-05-27 20:45:59 +00:00
< body >
2017-03-26 22:34:41 +00:00
< div id = "wrapper" >
< h1 > NYC Bookstores< / h1 >
< div >
< ul class = "nav" >
< li > < strong > The Many Independent Bookstores of New York City< / strong > < / li >
< li > < a id = 'viewInfo' > info< / a > < / li >
< li > < a href = "https://github.com/nyc-bookstores/nyc-bookstores.github.io" target = "_blank" > github< / a > < / li >
< li > < a href = "http://www.twitter.com/alazyreader" target = "_blank" > @alazyreader< / a > < / li >
< / ul >
< / div >
< div class = "container" >
< div id = 'map' > < / div >
< div id = 'info' >
2017-04-30 23:17:15 +00:00
< p > New York City loves its bookstores. We < a href = "http://www.nytimes.com/2006/10/15/nyregion/thecity/15book.html" target = "_blank" > eulogize those that have faded< / a > and celebrate when new ventures are launched. And while the historic < a href = "http://untappedcities.com/2015/08/26/4th-avenue-the-history-of-nycs-book-row/" target = "_blank" > Book Row may have passed away in the 80s< / a > , there are still plenty of stores dotting the map. Here, I have attempted to map out all of the currently-open general-interest independent booksellers in the city. Any store with regular-ish hours (excluding religious booksellers and appointment-only rare book sellers) is included.< / p >
2017-04-30 23:04:07 +00:00
< p > The listings here are kept up-to-date to the best of my ability. However, I make no promises about either the accuracy or reliability of the information. If you spot an error, or I've missed a shop, please let me know by < a href = "mailto:delta.mu.alpha@gmail.com" target = "_blank" > email< / a > or < a href = "https://www.twitter.com/alazyreader" target = "_blank" > twitter< / a > . Based on the "< a href = "https://github.com/jlord/hack-spots" target = "_blank" > Hack Spots< / a > " website by < a href = "http://www.twitter.com/jllord" target = "_blank" > @jllord< / a > .< / p >
2017-03-29 02:21:06 +00:00
< p > There are currently < span id = "storeCount" > < / span > stores indexed on this page.< / p >
2017-03-26 22:34:41 +00:00
< / div >
< div id = 'selected' > < / div >
< / div >
< div class = "clearfix" > < / div >
< div class = "container" >
< div id = "Stores" > < / div >
< / div >
< / div > <!-- end wrapper -->
< script id = "Table" type = "text/html" >
< table >
< tr > < th class = "tHeader" > Name< / th > < th class = "tHeader" > Address< / th > < / tr >
{{#rows}}
< tr id = "{{rowNumber}}" class = "spotRow" >
2017-03-27 02:46:59 +00:00
< td class = "name" > {{name}}< / td >
2017-03-26 22:34:41 +00:00
< td > {{address}}, {{city}}< / td >
< / tr >
{{/rows}}
< / table >
< / script >
< script id = "selectedStore" type = "text/html" >
{{#store}}
< h2 > {{name}}< / h2 >
2017-03-27 02:08:00 +00:00
< p class = "address" > {{address}}< p >
< p class = "address" > {{city}},NY {{#postcode}} {{postcode}} {{/postcode}}< / p >
2017-03-26 22:34:41 +00:00
< p >
< a href = "https://maps.google.com/maps?q={{name}} {{address}},{{city}},NY" target = "_blank" > View in Google Maps< / a >
< / p >
< ul >
< li >
2017-03-27 02:08:00 +00:00
< span class = "store-details" > Events:< / span > {{events}}
< span class = "store-details" > Café :< / span > {{cafe}}
2017-03-26 22:34:41 +00:00
< / li >
2017-03-27 02:08:00 +00:00
{{#website}}
2017-03-27 17:31:00 +00:00
< li > < a href = '{{website}}' target = "_blank" > {{website}}< / a > < / li >
2017-03-27 02:08:00 +00:00
{{/website}}
2017-03-26 22:34:41 +00:00
< / ul >
{{#description}}
< p class = "description" > {{description}}< / p >
{{/description}}
{{/store}}
< / script >
< script >
mapboxgl.accessToken = 'pk.eyJ1IjoiYWxhenlyZWFkZXIiLCJhIjoiY2lucDZhb2JxMHp6MHRxa2pvaTFoOWpuZyJ9.DILGYYxxt7A-A_lHHwp6tQ';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/basic-v9',
2017-03-28 17:06:58 +00:00
center: [-73.957292, 40.729071], // arbitrary center point
2017-03-27 02:46:59 +00:00
zoom: 10,
minZoom: 9,
maxZoom: 17,
dragRotate: false
2016-10-02 21:36:28 +00:00
});
2017-03-26 22:34:41 +00:00
var popup = new mapboxgl.Popup({
closeOnClick: false,
closeButton: false
2016-10-02 21:36:28 +00:00
});
2017-03-26 22:34:41 +00:00
document.addEventListener('DOMContentLoaded', function() {
$.getJSON('./stores.json', function(data) {
data.sort(
function(a, b) {
var aname = a.name.toLowerCase();
var bname = b.name.toLowerCase();
return aname === bname ? 0 : +(aname > bname) || -1;
}
)
2017-04-14 01:32:01 +00:00
$.each(data, function(key, value) {
value.rowNumber = key;
value.slug = slugify(value.name);
});
2017-03-29 02:21:06 +00:00
$('#storeCount').html(data.length);
2017-04-14 01:32:01 +00:00
window.data = data;
2017-03-26 22:34:41 +00:00
loadMap(data);
});
2016-10-02 21:36:28 +00:00
});
2017-04-14 01:32:01 +00:00
window.addEventListener("hashchange", function(e){
updateViewBySlug(e.newURL.split('#')[1]);
}, false);
function updateHash(slug) {
history.pushState(null, null, "#" + slug);
}
function slugify(str) {
return str
.toLowerCase()
2017-04-14 01:49:50 +00:00
.replace(/é/g,'e')
2017-04-14 01:32:01 +00:00
.replace(/& /g,' and ')
.replace(/ /g,'-')
.replace(/[']+/g,'')
.replace(/[^\w-]+/g,'-')
.replace(/-+/g,'-')
.replace(/^-|-$/g,'');
}
function getStoreBySlug(slug) {
var ret = false;
$.each(window.data, function(key, value) {
if (value.slug === slug) {
ret = value;
return false;
}
});
return ret;
}
function updateViewBySlug(slug) {
if (slug === "info" || slug === undefined) {
showInfo(false);
} else {
var store = getStoreBySlug(slug);
if (store) {
updateSelectedStore(store, false);
}
}
}
2017-03-26 22:34:41 +00:00
function boundingBox(point) {
// add some buffer to a point to give the user some leeway
return [[point.x - 5, point.y - 5], [point.x + 5, point.y + 5]]
}
2017-04-14 01:32:01 +00:00
function updateSelectedStore(store, pushState=false) {
2017-03-26 23:58:40 +00:00
map.flyTo({center: [store.long, store.lat]});
2016-10-02 21:36:28 +00:00
2017-03-26 23:58:40 +00:00
popup.setLngLat([store.long, store.lat])
.setHTML(store.name)
2017-03-26 22:34:41 +00:00
.addTo(map);
$('#info').hide();
var template = $('#selectedStore').html();
2017-03-26 23:58:40 +00:00
var rendered = Mustache.render(template, {store: store});
2017-03-26 22:34:41 +00:00
$('#selected').html(rendered);
$('#selected').show();
2017-04-14 01:32:01 +00:00
if (pushState) {
updateHash(store.slug);
}
2016-10-02 21:36:28 +00:00
}
2017-04-14 01:32:01 +00:00
function showInfo(pushState=true) {
2017-03-26 22:34:41 +00:00
$('#selected').hide();
2017-04-14 01:56:10 +00:00
popup.remove();
2017-03-26 22:34:41 +00:00
$('#info').show();
2017-04-14 01:32:01 +00:00
if (pushState) {
updateHash('info');
}
2017-03-26 22:34:41 +00:00
}
function loadMap(data) {
2017-03-27 02:57:03 +00:00
var geolocate = new mapboxgl.GeolocateControl();
2017-03-26 22:34:41 +00:00
var points = [];
$.each(data, function(key, value) {
points.push({
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [value.long, value.lat]
},
"properties": value
});
});
map.on('load', function () {
map.addLayer({
"id": "stores",
"type": "circle",
"source": {
"type": "geojson",
"data": {
"type": "FeatureCollection",
"features": points
}
},
"paint": {
"circle-radius": 5,
"circle-color": "#B9FCFC",
"circle-stroke-width": 2,
"circle-stroke-color": "#000000"
}
})
2017-03-27 02:02:17 +00:00
map.addControl(new mapboxgl.NavigationControl(), 'top-left');
2017-04-30 23:19:33 +00:00
map.addControl(geolocate, 'top-right');
2017-04-14 01:32:01 +00:00
updateViewBySlug(window.location.hash.split("#")[1]);
2017-03-26 22:34:41 +00:00
});
map.on('click', function (e) {
if (!map.getLayer('stores')) { return; }
popup.remove();
// Use queryRenderedFeatures to get features at a click event's point
var features = map.queryRenderedFeatures(boundingBox(e.point), { layers: ['stores'] });
// fly to the location of the click event
if (features.length) {
var store = features[0];
// Get coordinates from the symbol and center the map on those coordinates
2017-04-14 01:32:01 +00:00
updateSelectedStore(store.properties, true);
2017-03-26 22:34:41 +00:00
}
});
// indicate that the symbols are clickable by changing the cursor style to 'pointer'.
map.on('mousemove', function (e) {
if (!map.getLayer('stores')) { return; }
var features = map.queryRenderedFeatures(boundingBox(e.point), { layers: ['stores'] });
map.getCanvas().style.cursor = features.length ? 'pointer' : '';
});
2017-03-27 02:57:03 +00:00
geolocate.on('geolocate', function (e) {
map.setZoom(14);
popup.setLngLat([e.coords.longitude, e.coords.latitude])
.setHTML('Current Location')
.addTo(map);
2017-03-27 03:15:33 +00:00
});
2017-03-26 22:34:41 +00:00
var template = $('#Table').html();
var rendered = Mustache.render(template, {rows: data});
$('#Stores').html(rendered);
2017-04-14 01:32:01 +00:00
$("#Stores tbody tr").not(':first').on("click", function() {
updateSelectedStore(data[$(this)[0].id], true);
$(window).scrollTo($("#selected"), 250, {offset: {top: -15}});
2017-03-26 22:34:41 +00:00
});
$('#viewInfo').on("click", showInfo);
};
2016-10-02 21:36:28 +00:00
2017-03-26 22:34:41 +00:00
< / script >
2013-05-27 20:45:59 +00:00
< / body >
2014-04-10 19:19:24 +00:00
< / html >