├── .gitignore
├── README.md
├── examples
├── abq.html
├── auto-display-fields.html
├── cloudmade-styled-map.html
├── common
│ ├── handlebars.js
│ └── tabletop.js
├── google-custom-color.html
├── google-function-for-popup.html
├── google-with-callback-and-jquery.html
├── google-with-click-callback-and-map-options.html
├── google-with-info-callback.html
├── google-with-marker-customization.html
├── google.html
├── leaflet-with-everything.html
├── leaflet-with-marker-customization.html
├── leaflet.html
├── mapbox.html
├── mapquest-with-click.html
└── mapquest.html
└── src
└── mapsheet.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.swp
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # **Mapsheet.js**
2 |
3 | **Mapsheet.js** makes an interactive map out of a Google Spreadsheet. It depends on [Tabletop.js](http://github.com/jsoma/tabletop) and whatever map provider you're using (supports [Leaflet](http://leafletjs.com), [Google Maps](https://maps.google.com), [Mapbox](https://www.mapbox.com) (which is basically Leaflet!), and [MapQuest](http://developer.mapquest.com)).
4 |
5 | It is also **dead simple**.
6 |
7 | *Optional but fun:* Mapsheet supports [Handlebars](http://handlebarsjs.com) for info window templates.
8 |
9 | ## So how do I do this?
10 |
11 | First, you make a published Google Spreadsheet. You can find out how to do this in the [Tabletop docs](http://github.com/jsoma/tabletop). The only columns you actually need are *latitude* and *longitude*. We're also friendly enough to allow *lat*, *lng*, *long*, any probably anything else you can think of.
12 |
13 | A pretty full-fledged example might be: let's say you have some restaurants you'd like to map, and you want some info to show up in a popup when you click on the marker. Here goes nothing:
14 |
15 | ```html
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
37 |
38 |
40 |
45 |
46 |
47 |
64 |
65 |
66 | ```
67 |
68 | Most of the code is just including libraries! And if you'd like to map pet stores or murder scenes instead? Just make a different spreadsheet, change the URL, and edit the popup template. Voila: a brand new map.
69 |
70 | # Details
71 |
72 | ## The Moving Parts
73 |
74 | ### Mapsheet itself
75 |
76 | #### Initialization options
77 |
78 | When you initialize Mapsheet you have plenty of options to pick through. `key` and `element` are the only two required parameters, though! I've tried to note the ones that are provider-specific.
79 |
80 | `key` is the URL to the *published* Google Spreadsheet you're dealing with
81 |
82 | `element` is the id of the element that's going to become the map. You can also pass in an element.
83 |
84 | `click` is a callback for when a *marker* is clicked. **this** is the default **this**, the first parameter is the event and the second is the Mapsheet.Point (check the examples!)
85 |
86 | `fields` is an array of columns to display in the info window if you don't feel like using a template. Check the examples!
87 |
88 | `map` is the map, if you feel like rendering it without using a Mapsheet provider.
89 |
90 | `popupContent` is a function that returns content for the info window popup. It's passed the Tabletop model for the given row. It overrides both `fields` and `popupTemplate`.
91 |
92 | `popupTemplate` is the id of a [Handlebars](http://handlebarsjs.com) template that will be used for the info window that pops up when you click the marker on the map. You can also provider the compiled template instead.
93 |
94 | `provider` is the provider of the map service. I detail the providers a bit more below, but your options are
95 |
96 | Mapsheet.Providers.Leaflet
97 | Mapsheet.Providers.Google
98 | Mapsheet.Providers.MapBox
99 | Mapsheet.Providers.MapQuest
100 |
101 | `markerOptions` are passed through to the marker, and are used for things like specifying marker shadows and other very-specific-to-the-provider details.
102 |
103 | `titleColumn` is the column that you'd like to use as the title of the point (e.g., the name of the place). This is only important if using `fields`, or if you want hover effects for MapQuest or Google.
104 |
105 | `callback` is the callback for when the map has been successfully drawn. It will be passed two objects: the mapsheet object and the tabletop object. See [the Tabletop.js](http://github.com/jsoma/tabletop) docs if you'd like more info on that part.
106 |
107 | `callbackContext` provides the context of the callback, if necessary.
108 |
109 | `proxy` passes right through to Tabletop.
110 |
111 | `markerLayer` is the layer that you'd like to render your markers on **(not supported by Google)**
112 |
113 | `layerOptions` are options passed passed to the marker layer if you want a custom backing map, or specify attribution, etc **(Leaflet only)**
114 |
115 | #### Methods/Properties
116 |
117 | `.map()` is the rendered map
118 |
119 | `.points` is an array of Mapsheet.Point objects
120 |
121 | ### Mapsheet.Point
122 |
123 | #### Methods/Properties
124 |
125 | `.model` is the Tabletop model of the row the marker is associated with
126 |
127 | `.longitude()` is the longitude, pulled from a column named `lng`, `long` or `longitude`
128 |
129 | `.latitude()` is the latitude, pulled from a column named `lat` or `latitude`
130 |
131 | `.title()` gets the title of the marker - this column is set through `titleColumn`
132 |
133 | `.coords()` gives you the marker's `[ lat, lng ]`
134 |
135 | `.get(name)` returns the value of the given column. Not that it matters to you, but: Multi-word and capitalized things, like 'Web URL' are fixed up in an attempt to successfully read from tabletop, i.e. converted into lowercase with spaces removed (`Web URL` turns into `weburl`).
136 |
137 | `.content()` uses `popupContent`, `popupTemplate`, or `titleColumn` and `fields` to create content for the info window popup
138 |
139 | `.isValid()` returns true if latitude() and longitude() both return valid floats
140 |
141 | `.marker` returns the raw marker (from Google or MapQuest etc)
142 |
143 | ### Mapsheet.Provider.Whatever
144 |
145 | Must respond to `.initialize(id)` and `.drawPoints(pointsArray)`.
146 |
147 | Their constructor sets up the options, `initialize` draws the map, and `drawPoints` puts the points on it and auto-fits the zoom and bounds.
148 |
149 | ## Providers
150 |
151 | [Google Maps](https://developers.google.com/maps/) as `Mapsheet.Providers.Google` (default)
152 |
153 | [Leaflet](http://leafletjs.com) (using MapQuest tiles by default, but supports CloudMade, etc) as `Mapsheet.Providers.Leaflet` (be sure to include the CSS)
154 |
155 | [MapBox](http://mapbox.com) as `Mapsheet.Providers.MapBox`
156 |
157 | [MapQuest](http://developer.mapquest.com/web/products/open) as `Mapsheet.Providers.MapQuest`
158 |
159 | # Customizing Your Map
160 |
161 | ## Styling info windows
162 |
163 | All of the info boxes live inside of a ``, so feel free to apply styles to `.mapsheet-popup h3`, etc
164 |
165 | ## Customizing the map itself (roads, water, etc)
166 |
167 | You'll need to pick a provider that allows map customization
168 |
169 | [CloudMade](http://www.cloudmade.com) has a billion styles over at their [map style editor](http://maps.cloudmade.com/editor), you'll just have to check out the `cloudmade.html` example. You need an API key, but it ain't tough beyond that.
170 |
171 | [MapBox](http://www.mapbox.com) could also work for you, especially if you have a lot of stuff going on.
172 |
173 | ## Custom Markers
174 |
175 | I think it's *really stupid* that, generally speaking, providers make it so tough to have custom-colored markers. So I tried to make it easy!
176 |
177 | Custom markers are supported for Google Maps, Leaflet, and MapBox (to varying degrees).
178 |
179 | ### Possibilities
180 |
181 | **Custom color:** Add a `hex color` column in your spreadsheet to customize the color of each marker.
182 |
183 | **Custom icon:** If you want to an specify icon for each point, add an `icon url` column in your spreadsheet. You'll probably want to make it the full path, i.e. `http://blahblah.com/images/icons/blah.png`
184 |
185 | **Custom default icon:** If want every point to have the same marker, just not the default, you can pass a default icon URL through `markerOptions`, typically as `iconUrl`
186 |
187 | **Custom icon + shadows:** Check the `examples/leaflet-with-marker-customization.html` example, it's complicated.
188 |
189 | ### Provider support
190 |
191 | **Google Maps:** custom colors, custom icons, custom default icon
192 |
193 | **MapBox:** custom icons
194 |
195 | **Leaflet:** custom icons, custom icons + shadows, custom default icon
196 |
197 | ### General notes on using colored/custom icons
198 |
199 | If you're unsure of what hex colors are, pop on over to [http://www.colorpicker.com](http://www.colorpicker.com) or [Wikipedia](http://en.wikipedia.org/wiki/Web_colors#X11_color_names) - they're just strings of numbers/letters like FF36A2 that end up as colors.*
200 |
201 | When you customize your icons using Leaflet, you'll probably want to pass some `markerOptions` as well to make sure everything lines up nicely. `markerOptions: { iconSize: [32, 37], iconAnchor: [16, 37] }` works for the icons from the [Map Icons Collection](http://mapicons.nicolasmollet.com). [Read more on the Leaflet site](http://leafletjs.com/examples/custom-icons.html).
202 |
203 | ### Using other Leaflet tile providers
204 |
205 | Leaflet defaults to serving tiles from MapQuest, because it's free, and doesn't require an API key. If you'd like to use [CloudMade](http://www.cloudmade.com) or something instead, you'll need to make some changes. You'll need to pass in a mapOptions along the lines of the following
206 |
207 | mapOptions: {
208 | subdomains: 'abc',
209 | tilePath: 'http://{s}.tile.cloudmade.com/{apikey}/{styleId}/256/{z}/{x}/{y}.png',
210 | styleId: 1930,
211 | apikey: 'Your CloudMade API Key',
212 | attribution: 'An appropriate credit line without MapQuest'
213 | }
214 |
215 | And then you should be all set!
216 |
217 | # Examples
218 |
219 | There are a lot of examples in `/examples`, check them out!
220 |
221 | If you're looking for the least complicated one, check out `fields.html`. It isn't too customizable but it gets the job done. `leaflet-with-marker-customization.html` is the zaniest of the bunch.
222 |
223 | # Etc
224 |
225 | ## Credits
226 |
227 | Hi, I'm [Jonathan Soma](http://twitter.com/dangerscarf). I run the [Brooklyn Brainery](http://brooklynbrainery.com), and make all sorts of nonsense.
228 |
229 | ## Todo
230 |
231 | Fix up the docs to actually be nice and pleasant
232 |
233 | Don't show popups if there's no content in the popup
234 |
235 | Replace the sample map with some quality, dedicated sample maps
236 |
237 | Callbacks callbacks everywhere
238 |
239 | Notes about styling your infoboxes
--------------------------------------------------------------------------------
/examples/abq.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 | Interactive Map: 'Bad' on location
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | You must have JavaScript enabled to view this page.
47 |
48 |
49 |
50 |
64 |
65 |
66 |
67 | X
68 |
69 |
70 |
74 |
75 |
76 |
77 |
78 |
79 |
87 |
88 |
89 |
90 |
124 |
125 |
126 |
--------------------------------------------------------------------------------
/examples/auto-display-fields.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/examples/cloudmade-styled-map.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
30 |
31 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/examples/common/handlebars.js:
--------------------------------------------------------------------------------
1 | // lib/handlebars/base.js
2 | var Handlebars = {};
3 |
4 | Handlebars.VERSION = "1.0.beta.6";
5 |
6 | Handlebars.helpers = {};
7 | Handlebars.partials = {};
8 |
9 | Handlebars.registerHelper = function(name, fn, inverse) {
10 | if(inverse) { fn.not = inverse; }
11 | this.helpers[name] = fn;
12 | };
13 |
14 | Handlebars.registerPartial = function(name, str) {
15 | this.partials[name] = str;
16 | };
17 |
18 | Handlebars.registerHelper('helperMissing', function(arg) {
19 | if(arguments.length === 2) {
20 | return undefined;
21 | } else {
22 | throw new Error("Could not find property '" + arg + "'");
23 | }
24 | });
25 |
26 | var toString = Object.prototype.toString, functionType = "[object Function]";
27 |
28 | Handlebars.registerHelper('blockHelperMissing', function(context, options) {
29 | var inverse = options.inverse || function() {}, fn = options.fn;
30 |
31 |
32 | var ret = "";
33 | var type = toString.call(context);
34 |
35 | if(type === functionType) { context = context.call(this); }
36 |
37 | if(context === true) {
38 | return fn(this);
39 | } else if(context === false || context == null) {
40 | return inverse(this);
41 | } else if(type === "[object Array]") {
42 | if(context.length > 0) {
43 | for(var i=0, j=context.length; i 0) {
60 | for(var i=0, j=context.length; i 2) {
235 | expected.push("'" + this.terminals_[p] + "'");
236 | }
237 | var errStr = "";
238 | if (this.lexer.showPosition) {
239 | errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + this.terminals_[symbol] + "'";
240 | } else {
241 | errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'");
242 | }
243 | this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
244 | }
245 | }
246 | if (action[0] instanceof Array && action.length > 1) {
247 | throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol);
248 | }
249 | switch (action[0]) {
250 | case 1:
251 | stack.push(symbol);
252 | vstack.push(this.lexer.yytext);
253 | lstack.push(this.lexer.yylloc);
254 | stack.push(action[1]);
255 | symbol = null;
256 | if (!preErrorSymbol) {
257 | yyleng = this.lexer.yyleng;
258 | yytext = this.lexer.yytext;
259 | yylineno = this.lexer.yylineno;
260 | yyloc = this.lexer.yylloc;
261 | if (recovering > 0)
262 | recovering--;
263 | } else {
264 | symbol = preErrorSymbol;
265 | preErrorSymbol = null;
266 | }
267 | break;
268 | case 2:
269 | len = this.productions_[action[1]][1];
270 | yyval.$ = vstack[vstack.length - len];
271 | yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column};
272 | r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
273 | if (typeof r !== "undefined") {
274 | return r;
275 | }
276 | if (len) {
277 | stack = stack.slice(0, -1 * len * 2);
278 | vstack = vstack.slice(0, -1 * len);
279 | lstack = lstack.slice(0, -1 * len);
280 | }
281 | stack.push(this.productions_[action[1]][0]);
282 | vstack.push(yyval.$);
283 | lstack.push(yyval._$);
284 | newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
285 | stack.push(newState);
286 | break;
287 | case 3:
288 | return true;
289 | }
290 | }
291 | return true;
292 | }
293 | };/* Jison generated lexer */
294 | var lexer = (function(){
295 |
296 | var lexer = ({EOF:1,
297 | parseError:function parseError(str, hash) {
298 | if (this.yy.parseError) {
299 | this.yy.parseError(str, hash);
300 | } else {
301 | throw new Error(str);
302 | }
303 | },
304 | setInput:function (input) {
305 | this._input = input;
306 | this._more = this._less = this.done = false;
307 | this.yylineno = this.yyleng = 0;
308 | this.yytext = this.matched = this.match = '';
309 | this.conditionStack = ['INITIAL'];
310 | this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0};
311 | return this;
312 | },
313 | input:function () {
314 | var ch = this._input[0];
315 | this.yytext+=ch;
316 | this.yyleng++;
317 | this.match+=ch;
318 | this.matched+=ch;
319 | var lines = ch.match(/\n/);
320 | if (lines) this.yylineno++;
321 | this._input = this._input.slice(1);
322 | return ch;
323 | },
324 | unput:function (ch) {
325 | this._input = ch + this._input;
326 | return this;
327 | },
328 | more:function () {
329 | this._more = true;
330 | return this;
331 | },
332 | pastInput:function () {
333 | var past = this.matched.substr(0, this.matched.length - this.match.length);
334 | return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
335 | },
336 | upcomingInput:function () {
337 | var next = this.match;
338 | if (next.length < 20) {
339 | next += this._input.substr(0, 20-next.length);
340 | }
341 | return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, "");
342 | },
343 | showPosition:function () {
344 | var pre = this.pastInput();
345 | var c = new Array(pre.length + 1).join("-");
346 | return pre + this.upcomingInput() + "\n" + c+"^";
347 | },
348 | next:function () {
349 | if (this.done) {
350 | return this.EOF;
351 | }
352 | if (!this._input) this.done = true;
353 |
354 | var token,
355 | match,
356 | col,
357 | lines;
358 | if (!this._more) {
359 | this.yytext = '';
360 | this.match = '';
361 | }
362 | var rules = this._currentRules();
363 | for (var i=0;i < rules.length; i++) {
364 | match = this._input.match(this.rules[rules[i]]);
365 | if (match) {
366 | lines = match[0].match(/\n.*/g);
367 | if (lines) this.yylineno += lines.length;
368 | this.yylloc = {first_line: this.yylloc.last_line,
369 | last_line: this.yylineno+1,
370 | first_column: this.yylloc.last_column,
371 | last_column: lines ? lines[lines.length-1].length-1 : this.yylloc.last_column + match[0].length}
372 | this.yytext += match[0];
373 | this.match += match[0];
374 | this.matches = match;
375 | this.yyleng = this.yytext.length;
376 | this._more = false;
377 | this._input = this._input.slice(match[0].length);
378 | this.matched += match[0];
379 | token = this.performAction.call(this, this.yy, this, rules[i],this.conditionStack[this.conditionStack.length-1]);
380 | if (token) return token;
381 | else return;
382 | }
383 | }
384 | if (this._input === "") {
385 | return this.EOF;
386 | } else {
387 | this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(),
388 | {text: "", token: null, line: this.yylineno});
389 | }
390 | },
391 | lex:function lex() {
392 | var r = this.next();
393 | if (typeof r !== 'undefined') {
394 | return r;
395 | } else {
396 | return this.lex();
397 | }
398 | },
399 | begin:function begin(condition) {
400 | this.conditionStack.push(condition);
401 | },
402 | popState:function popState() {
403 | return this.conditionStack.pop();
404 | },
405 | _currentRules:function _currentRules() {
406 | return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;
407 | },
408 | topState:function () {
409 | return this.conditionStack[this.conditionStack.length-2];
410 | },
411 | pushState:function begin(condition) {
412 | this.begin(condition);
413 | }});
414 | lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
415 |
416 | var YYSTATE=YY_START
417 | switch($avoiding_name_collisions) {
418 | case 0:
419 | if(yy_.yytext.slice(-1) !== "\\") this.begin("mu");
420 | if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1), this.begin("emu");
421 | if(yy_.yytext) return 14;
422 |
423 | break;
424 | case 1: return 14;
425 | break;
426 | case 2: this.popState(); return 14;
427 | break;
428 | case 3: return 24;
429 | break;
430 | case 4: return 16;
431 | break;
432 | case 5: return 20;
433 | break;
434 | case 6: return 19;
435 | break;
436 | case 7: return 19;
437 | break;
438 | case 8: return 23;
439 | break;
440 | case 9: return 23;
441 | break;
442 | case 10: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15;
443 | break;
444 | case 11: return 22;
445 | break;
446 | case 12: return 34;
447 | break;
448 | case 13: return 33;
449 | break;
450 | case 14: return 33;
451 | break;
452 | case 15: return 36;
453 | break;
454 | case 16: /*ignore whitespace*/
455 | break;
456 | case 17: this.popState(); return 18;
457 | break;
458 | case 18: this.popState(); return 18;
459 | break;
460 | case 19: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 28;
461 | break;
462 | case 20: return 30;
463 | break;
464 | case 21: return 30;
465 | break;
466 | case 22: return 29;
467 | break;
468 | case 23: return 33;
469 | break;
470 | case 24: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 33;
471 | break;
472 | case 25: return 'INVALID';
473 | break;
474 | case 26: return 5;
475 | break;
476 | }
477 | };
478 | lexer.rules = [/^[^\x00]*?(?=(\{\{))/,/^[^\x00]+/,/^[^\x00]{2,}?(?=(\{\{))/,/^\{\{>/,/^\{\{#/,/^\{\{\//,/^\{\{\^/,/^\{\{\s*else\b/,/^\{\{\{/,/^\{\{&/,/^\{\{![\s\S]*?\}\}/,/^\{\{/,/^=/,/^\.(?=[} ])/,/^\.\./,/^[\/.]/,/^\s+/,/^\}\}\}/,/^\}\}/,/^"(\\["]|[^"])*"/,/^true(?=[}\s])/,/^false(?=[}\s])/,/^[0-9]+(?=[}\s])/,/^[a-zA-Z0-9_$-]+(?=[=}\s\/.])/,/^\[[^\]]*\]/,/^./,/^$/];
479 | lexer.conditions = {"mu":{"rules":[3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"INITIAL":{"rules":[0,1,26],"inclusive":true}};return lexer;})()
480 | parser.lexer = lexer;
481 | return parser;
482 | })();
483 | if (typeof require !== 'undefined' && typeof exports !== 'undefined') {
484 | exports.parser = handlebars;
485 | exports.parse = function () { return handlebars.parse.apply(handlebars, arguments); }
486 | exports.main = function commonjsMain(args) {
487 | if (!args[1])
488 | throw new Error('Usage: '+args[0]+' FILE');
489 | if (typeof process !== 'undefined') {
490 | var source = require('fs').readFileSync(require('path').join(process.cwd(), args[1]), "utf8");
491 | } else {
492 | var cwd = require("file").path(require("file").cwd());
493 | var source = cwd.join(args[1]).read({charset: "utf-8"});
494 | }
495 | return exports.parser.parse(source);
496 | }
497 | if (typeof module !== 'undefined' && require.main === module) {
498 | exports.main(typeof process !== 'undefined' ? process.argv.slice(1) : require("system").args);
499 | }
500 | };
501 | ;
502 | // lib/handlebars/compiler/base.js
503 | Handlebars.Parser = handlebars;
504 |
505 | Handlebars.parse = function(string) {
506 | Handlebars.Parser.yy = Handlebars.AST;
507 | return Handlebars.Parser.parse(string);
508 | };
509 |
510 | Handlebars.print = function(ast) {
511 | return new Handlebars.PrintVisitor().accept(ast);
512 | };
513 |
514 | Handlebars.logger = {
515 | DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3,
516 |
517 | // override in the host environment
518 | log: function(level, str) {}
519 | };
520 |
521 | Handlebars.log = function(level, str) { Handlebars.logger.log(level, str); };
522 | ;
523 | // lib/handlebars/compiler/ast.js
524 | (function() {
525 |
526 | Handlebars.AST = {};
527 |
528 | Handlebars.AST.ProgramNode = function(statements, inverse) {
529 | this.type = "program";
530 | this.statements = statements;
531 | if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); }
532 | };
533 |
534 | Handlebars.AST.MustacheNode = function(params, hash, unescaped) {
535 | this.type = "mustache";
536 | this.id = params[0];
537 | this.params = params.slice(1);
538 | this.hash = hash;
539 | this.escaped = !unescaped;
540 | };
541 |
542 | Handlebars.AST.PartialNode = function(id, context) {
543 | this.type = "partial";
544 |
545 | // TODO: disallow complex IDs
546 |
547 | this.id = id;
548 | this.context = context;
549 | };
550 |
551 | var verifyMatch = function(open, close) {
552 | if(open.original !== close.original) {
553 | throw new Handlebars.Exception(open.original + " doesn't match " + close.original);
554 | }
555 | };
556 |
557 | Handlebars.AST.BlockNode = function(mustache, program, close) {
558 | verifyMatch(mustache.id, close);
559 | this.type = "block";
560 | this.mustache = mustache;
561 | this.program = program;
562 | };
563 |
564 | Handlebars.AST.InverseNode = function(mustache, program, close) {
565 | verifyMatch(mustache.id, close);
566 | this.type = "inverse";
567 | this.mustache = mustache;
568 | this.program = program;
569 | };
570 |
571 | Handlebars.AST.ContentNode = function(string) {
572 | this.type = "content";
573 | this.string = string;
574 | };
575 |
576 | Handlebars.AST.HashNode = function(pairs) {
577 | this.type = "hash";
578 | this.pairs = pairs;
579 | };
580 |
581 | Handlebars.AST.IdNode = function(parts) {
582 | this.type = "ID";
583 | this.original = parts.join(".");
584 |
585 | var dig = [], depth = 0;
586 |
587 | for(var i=0,l=parts.length; i": ">",
646 | '"': """,
647 | "'": "'",
648 | "`": "`"
649 | };
650 |
651 | var badChars = /&(?!\w+;)|[<>"'`]/g;
652 | var possible = /[&<>"'`]/;
653 |
654 | var escapeChar = function(chr) {
655 | return escape[chr] || "&";
656 | };
657 |
658 | Handlebars.Utils = {
659 | escapeExpression: function(string) {
660 | // don't escape SafeStrings, since they're already safe
661 | if (string instanceof Handlebars.SafeString) {
662 | return string.toString();
663 | } else if (string == null || string === false) {
664 | return "";
665 | }
666 |
667 | if(!possible.test(string)) { return string; }
668 | return string.replace(badChars, escapeChar);
669 | },
670 |
671 | isEmpty: function(value) {
672 | if (typeof value === "undefined") {
673 | return true;
674 | } else if (value === null) {
675 | return true;
676 | } else if (value === false) {
677 | return true;
678 | } else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) {
679 | return true;
680 | } else {
681 | return false;
682 | }
683 | }
684 | };
685 | })();;
686 | // lib/handlebars/compiler/compiler.js
687 | Handlebars.Compiler = function() {};
688 | Handlebars.JavaScriptCompiler = function() {};
689 |
690 | (function(Compiler, JavaScriptCompiler) {
691 | Compiler.OPCODE_MAP = {
692 | appendContent: 1,
693 | getContext: 2,
694 | lookupWithHelpers: 3,
695 | lookup: 4,
696 | append: 5,
697 | invokeMustache: 6,
698 | appendEscaped: 7,
699 | pushString: 8,
700 | truthyOrFallback: 9,
701 | functionOrFallback: 10,
702 | invokeProgram: 11,
703 | invokePartial: 12,
704 | push: 13,
705 | assignToHash: 15,
706 | pushStringParam: 16
707 | };
708 |
709 | Compiler.MULTI_PARAM_OPCODES = {
710 | appendContent: 1,
711 | getContext: 1,
712 | lookupWithHelpers: 2,
713 | lookup: 1,
714 | invokeMustache: 3,
715 | pushString: 1,
716 | truthyOrFallback: 1,
717 | functionOrFallback: 1,
718 | invokeProgram: 3,
719 | invokePartial: 1,
720 | push: 1,
721 | assignToHash: 1,
722 | pushStringParam: 1
723 | };
724 |
725 | Compiler.DISASSEMBLE_MAP = {};
726 |
727 | for(var prop in Compiler.OPCODE_MAP) {
728 | var value = Compiler.OPCODE_MAP[prop];
729 | Compiler.DISASSEMBLE_MAP[value] = prop;
730 | }
731 |
732 | Compiler.multiParamSize = function(code) {
733 | return Compiler.MULTI_PARAM_OPCODES[Compiler.DISASSEMBLE_MAP[code]];
734 | };
735 |
736 | Compiler.prototype = {
737 | compiler: Compiler,
738 |
739 | disassemble: function() {
740 | var opcodes = this.opcodes, opcode, nextCode;
741 | var out = [], str, name, value;
742 |
743 | for(var i=0, l=opcodes.length; i 0) {
1128 | this.source[1] = this.source[1] + ", " + locals.join(", ");
1129 | }
1130 |
1131 | // Generate minimizer alias mappings
1132 | if (!this.isChild) {
1133 | var aliases = []
1134 | for (var alias in this.context.aliases) {
1135 | this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
1136 | }
1137 | }
1138 |
1139 | if (this.source[1]) {
1140 | this.source[1] = "var " + this.source[1].substring(2) + ";";
1141 | }
1142 |
1143 | // Merge children
1144 | if (!this.isChild) {
1145 | this.source[1] += '\n' + this.context.programs.join('\n') + '\n';
1146 | }
1147 |
1148 | if (!this.environment.isSimple) {
1149 | this.source.push("return buffer;");
1150 | }
1151 |
1152 | var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"];
1153 |
1154 | for(var i=0, l=this.environment.depths.list.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
1406 | return "stack" + this.stackSlot;
1407 | },
1408 |
1409 | popStack: function() {
1410 | return "stack" + this.stackSlot--;
1411 | },
1412 |
1413 | topStack: function() {
1414 | return "stack" + this.stackSlot;
1415 | },
1416 |
1417 | quotedString: function(str) {
1418 | return '"' + str
1419 | .replace(/\\/g, '\\\\')
1420 | .replace(/"/g, '\\"')
1421 | .replace(/\n/g, '\\n')
1422 | .replace(/\r/g, '\\r') + '"';
1423 | }
1424 | };
1425 |
1426 | var reservedWords = (
1427 | "break else new var" +
1428 | " case finally return void" +
1429 | " catch for switch while" +
1430 | " continue function this with" +
1431 | " default if throw" +
1432 | " delete in try" +
1433 | " do instanceof typeof" +
1434 | " abstract enum int short" +
1435 | " boolean export interface static" +
1436 | " byte extends long super" +
1437 | " char final native synchronized" +
1438 | " class float package throws" +
1439 | " const goto private transient" +
1440 | " debugger implements protected volatile" +
1441 | " double import public let yield"
1442 | ).split(" ");
1443 |
1444 | var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
1445 |
1446 | for(var i=0, l=reservedWords.length; i 1 && this.debug) {
271 | this.log("WARNING You have more than one sheet but are using simple sheet mode! Don't blame me when something goes wrong.");
272 | }
273 | return this.models[ this.model_names[0] ].all();
274 | } else {
275 | return this.models;
276 | }
277 | },
278 |
279 | /*
280 | Add another sheet to the wanted list
281 | */
282 | addWanted: function(sheet) {
283 | if(ttIndexOf(this.wanted, sheet) === -1) {
284 | this.wanted.push(sheet);
285 | }
286 | },
287 |
288 | /*
289 | Load all worksheets of the spreadsheet, turning each into a Tabletop Model.
290 | Need to use injectScript because the worksheet view that you're working from
291 | doesn't actually include the data. The list-based feed (/feeds/list/key..) does, though.
292 | Calls back to loadSheet in order to get the real work done.
293 |
294 | Used as a callback for the worksheet-based JSON
295 | */
296 | loadSheets: function(data) {
297 | var i, ilen;
298 | var toLoad = [];
299 | this.foundSheetNames = [];
300 |
301 | for(i = 0, ilen = data.feed.entry.length; i < ilen ; i++) {
302 | this.foundSheetNames.push(data.feed.entry[i].title.$t);
303 | // Only pull in desired sheets to reduce loading
304 | if( this.isWanted(data.feed.entry[i].content.$t) ) {
305 | var sheet_id = data.feed.entry[i].link[3].href.substr( data.feed.entry[i].link[3].href.length - 3, 3);
306 | var json_path = "/feeds/list/" + this.key + "/" + sheet_id + "/public/values?sq=" + this.query + '&alt='
307 | if (inNodeJS || supportsCORS) {
308 | json_path += 'json';
309 | } else {
310 | json_path += 'json-in-script';
311 | }
312 | if(this.orderby) {
313 | json_path += "&orderby=column:" + this.orderby.toLowerCase();
314 | }
315 | if(this.reverse) {
316 | json_path += "&reverse=true";
317 | }
318 | toLoad.push(json_path);
319 | }
320 | }
321 |
322 | this.sheetsToLoad = toLoad.length;
323 | for(i = 0, ilen = toLoad.length; i < ilen; i++) {
324 | this.requestData(toLoad[i], this.loadSheet);
325 | }
326 | },
327 |
328 | /*
329 | Access layer for the this.models
330 | .sheets() gets you all of the sheets
331 | .sheets('Sheet1') gets you the sheet named Sheet1
332 | */
333 | sheets: function(sheetName) {
334 | if(typeof sheetName === "undefined") {
335 | return this.models;
336 | } else {
337 | if(typeof(this.models[ sheetName ]) === "undefined") {
338 | // alert( "Can't find " + sheetName );
339 | return;
340 | } else {
341 | return this.models[ sheetName ];
342 | }
343 | }
344 | },
345 |
346 | /*
347 | Parse a single list-based worksheet, turning it into a Tabletop Model
348 |
349 | Used as a callback for the list-based JSON
350 | */
351 | loadSheet: function(data) {
352 | var model = new Tabletop.Model( { data: data,
353 | parseNumbers: this.parseNumbers,
354 | postProcess: this.postProcess,
355 | tabletop: this } );
356 | this.models[ model.name ] = model;
357 | if(ttIndexOf(this.model_names, model.name) === -1) {
358 | this.model_names.push(model.name);
359 | }
360 | this.sheetsToLoad--;
361 | if(this.sheetsToLoad === 0)
362 | this.doCallback();
363 | },
364 |
365 | /*
366 | Execute the callback upon loading! Rely on this.data() because you might
367 | only request certain pieces of data (i.e. simpleSheet mode)
368 | Tests this.sheetsToLoad just in case a race condition happens to show up
369 | */
370 | doCallback: function() {
371 | if(this.sheetsToLoad === 0) {
372 | this.callback.apply(this.callbackContext || this, [this.data(), this]);
373 | }
374 | },
375 |
376 | log: function(msg) {
377 | if(this.debug) {
378 | if(typeof console !== "undefined" && typeof console.log !== "undefined") {
379 | Function.prototype.apply.apply(console.log, [console, arguments]);
380 | }
381 | }
382 | }
383 |
384 | };
385 |
386 | /*
387 | Tabletop.Model stores the attribute names and parses the worksheet data
388 | to turn it into something worthwhile
389 |
390 | Options should be in the format { data: XXX }, with XXX being the list-based worksheet
391 | */
392 | Tabletop.Model = function(options) {
393 | var i, j, ilen, jlen;
394 | this.column_names = [];
395 | this.name = options.data.feed.title.$t;
396 | this.elements = [];
397 | this.raw = options.data; // A copy of the sheet's raw data, for accessing minutiae
398 |
399 | if(typeof(options.data.feed.entry) === 'undefined') {
400 | options.tabletop.log("Missing data for " + this.name + ", make sure you didn't forget column headers");
401 | this.elements = [];
402 | return;
403 | }
404 |
405 | for(var key in options.data.feed.entry[0]){
406 | if(/^gsx/.test(key))
407 | this.column_names.push( key.replace("gsx$","") );
408 | }
409 |
410 | for(i = 0, ilen = options.data.feed.entry.length ; i < ilen; i++) {
411 | var source = options.data.feed.entry[i];
412 | var element = {};
413 | for(var j = 0, jlen = this.column_names.length; j < jlen ; j++) {
414 | var cell = source[ "gsx$" + this.column_names[j] ];
415 | if (typeof(cell) !== 'undefined') {
416 | if(options.parseNumbers && cell.$t !== '' && !isNaN(cell.$t))
417 | element[ this.column_names[j] ] = +cell.$t;
418 | else
419 | element[ this.column_names[j] ] = cell.$t;
420 | } else {
421 | element[ this.column_names[j] ] = '';
422 | }
423 | }
424 | if(element.rowNumber === undefined)
425 | element.rowNumber = i + 1;
426 | if( options.postProcess )
427 | options.postProcess(element);
428 | this.elements.push(element);
429 | }
430 |
431 | };
432 |
433 | Tabletop.Model.prototype = {
434 | /*
435 | Returns all of the elements (rows) of the worksheet as objects
436 | */
437 | all: function() {
438 | return this.elements;
439 | },
440 |
441 | /*
442 | Return the elements as an array of arrays, instead of an array of objects
443 | */
444 | toArray: function() {
445 | var array = [],
446 | i, j, ilen, jlen;
447 | for(i = 0, ilen = this.elements.length; i < ilen; i++) {
448 | var row = [];
449 | for(j = 0, jlen = this.column_names.length; j < jlen ; j++) {
450 | row.push( this.elements[i][ this.column_names[j] ] );
451 | }
452 | array.push(row);
453 | }
454 | return array;
455 | }
456 | };
457 |
458 | if(inNodeJS) {
459 | module.exports = Tabletop;
460 | } else {
461 | global.Tabletop = Tabletop;
462 | }
463 |
464 | })(this);
465 |
--------------------------------------------------------------------------------
/examples/google-custom-color.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
25 |
26 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/examples/google-function-for-popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
24 |
25 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/examples/google-with-callback-and-jquery.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
25 |
26 |
32 |
33 | The towns are:
34 |
36 |
37 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/examples/google-with-click-callback-and-map-options.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
27 |
28 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/examples/google-with-info-callback.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
24 |
25 | The towns are:
26 |
27 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/examples/google-with-marker-customization.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/examples/google.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
24 |
25 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/examples/leaflet-with-everything.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
34 |
35 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/examples/leaflet-with-marker-customization.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
33 |
34 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/examples/leaflet.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
36 |
37 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/examples/mapbox.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 | Because of how the mapbox CSS works, you can't see markers unless it's uploaded online
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
28 |
29 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/examples/mapquest-with-click.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
27 |
28 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/examples/mapquest.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
27 |
28 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/src/mapsheet.js:
--------------------------------------------------------------------------------
1 | (function(global) {
2 | "use strict";
3 |
4 | function merge_options(obj1, obj2) {
5 | var obj3 = {};
6 | var attrname;
7 | for (attrname in obj1) { obj3[attrname] = obj1[attrname]; }
8 | for (attrname in obj2) { obj3[attrname] = obj2[attrname]; }
9 | return obj3;
10 | }
11 |
12 | var Mapsheet = global.Mapsheet = function(options) {
13 | // Make sure Mapsheet is being used as a constructor no matter what.
14 | if(!this || !(this instanceof Mapsheet)) {
15 | return new Mapsheet(options);
16 | }
17 |
18 | this.key = options.key;
19 | this.click = options.click;
20 | this.passedMap = options.map;
21 | this.element = options.element;
22 | this.sheetName = options.sheetName;
23 | this.provider = options.provider || Mapsheet.Providers.Google;
24 | this.renderer = new this.provider( { map: options.passedMap, mapOptions: options.mapOptions, layerOptions: options.layerOptions, markerLayer: options.markerLayer } );
25 | this.fields = options.fields;
26 | this.titleColumn = options.titleColumn;
27 | this.popupContent = options.popupContent;
28 | this.popupTemplate = options.popupTemplate;
29 | this.callbackContext = options.callbackContext;
30 | this.callback = options.callback;
31 |
32 | // Let's automatically engage simpleSheet mode,
33 | // which allows for easier using of data later on
34 | // if you have multiple sheets, you'll want to
35 | // disable this
36 | var simpleSheet = true;
37 |
38 | if(typeof(this.popupTemplate) === 'string') {
39 | var source = document.getElementById(this.popupTemplate).innerHTML;
40 | this.popupTemplate = Handlebars.compile(source);
41 | }
42 | this.markerOptions = options.markerOptions || {};
43 |
44 | if(typeof(this.element) === 'string') {
45 | this.element = document.getElementById(this.element);
46 | };
47 |
48 | this.tabletop = new Tabletop( { key: this.key, callback: this.loadPoints, callbackContext: this, simpleSheet: simpleSheet, proxy: options.proxy } );
49 | };
50 |
51 |
52 | Mapsheet.prototype = {
53 |
54 | loadPoints: function(data, tabletop) {
55 | this.points = [];
56 |
57 | if(typeof(this.sheetName) === 'undefined') {
58 | this.sheetName = tabletop.model_names[0];
59 | }
60 |
61 | var elements = tabletop.sheets(this.sheetName).elements;
62 |
63 | for(var i = 0; i < elements.length; i++) {
64 | var point = new Mapsheet.Point( { model: elements[i], fields: this.fields, popupContent: this.popupContent, popupTemplate: this.popupTemplate, markerOptions: this.markerOptions, titleColumn: this.titleColumn, click: this.click } );
65 | this.points.push(point);
66 | };
67 |
68 | this.draw();
69 | },
70 |
71 | draw: function() {
72 | this.renderer.initialize(this.element);
73 | this.renderer.drawPoints(this.points);
74 | if(this.callback) {
75 | this.callback.apply(this.callbackContext || this, [this, this.tabletop]);
76 | }
77 | },
78 |
79 | log: function(msg) {
80 | if(this.debug) {
81 | if(typeof console !== "undefined" && typeof console.log !== "undefined") {
82 | Function.prototype.apply.apply(console.log, [console, arguments]);
83 | }
84 | }
85 | },
86 |
87 | map: function() {
88 | return (this.passedMap || this.renderer.map);
89 | }
90 |
91 | };
92 |
93 | Mapsheet.Point = function(options) {
94 | this.model = options.model;
95 | this.fields = options.fields;
96 | this.popupContent = options.popupContent;
97 | this.popupTemplate = options.popupTemplate;
98 | this.titleColumn = options.titleColumn;
99 | this.markerOptions = options.markerOptions;
100 | this.click = options.click
101 | };
102 |
103 | Mapsheet.Point.prototype = {
104 | coords: function() {
105 | return [ this.latitude(), this.longitude() ];
106 | },
107 |
108 | latitude: function() {
109 | return parseFloat( this.model["latitude"] || this.model["lat"] );
110 | },
111 |
112 | longitude: function() {
113 | return parseFloat( this.model["longitude"] || this.model["lng"] || this.model["long"] );
114 | },
115 |
116 | get: function(fieldName) {
117 | if(typeof(fieldName) === 'undefined') {
118 | return;
119 | }
120 | return this.model[fieldName.toLowerCase().replace(/ +/,'')];
121 | },
122 |
123 | title: function() {
124 | return this.get(this.titleColumn);
125 | },
126 |
127 | isValid: function() {
128 | return !isNaN( this.latitude() ) && !isNaN( this.longitude() )
129 | },
130 |
131 | content: function() {
132 | var html = "";
133 | if(typeof(this.popupContent) !== 'undefined') {
134 | html = this.popupContent.call(this, this.model);
135 | } else if(typeof(this.popupTemplate) !== 'undefined') {
136 | html = this.popupTemplate.call(this, this.model);
137 | } else if(typeof(this.fields) !== 'undefined') {
138 | if(typeof(this.title()) !== 'undefined' && this.title() !== '') {
139 | html += "" + this.title() + " ";
140 | }
141 | for(var i = 0; i < this.fields.length; i++) {
142 | html += "" + this.fields[i] + " : " + this.get(this.fields[i]) + "
";
143 | }
144 | } else {
145 | return '';
146 | }
147 | return ""
148 | }
149 | };
150 |
151 | /*
152 |
153 | Providers only need respond to initialize and drawPoints
154 |
155 | */
156 |
157 | Mapsheet.Providers = {};
158 |
159 |
160 | /*
161 |
162 | Google Maps
163 |
164 | */
165 |
166 | Mapsheet.Providers.Google = function(options) {
167 | this.map = options.map;
168 | this.mapOptions = merge_options( { mapTypeId: google.maps.MapTypeId.ROADMAP }, options.mapOptions || {} );
169 | // We'll be nice and allow center to be a lat/lng array instead of a Google Maps LatLng
170 | if(this.mapOptions.center && this.mapOptions.center.length == 2) {
171 | this.mapOptions.center = new google.maps.LatLng(this.mapOptions.center[0], this.mapOptions.center[1]);
172 | }
173 | };
174 |
175 | Mapsheet.Providers.Google.prototype = {
176 | initialize: function(element) {
177 | if(typeof(this.map) === 'undefined') {
178 | this.map = new google.maps.Map(element, this.mapOptions);
179 | }
180 | this.bounds = new google.maps.LatLngBounds();
181 | this.infowindow = new google.maps.InfoWindow({ content: "loading...", maxWidth: '300' });
182 | },
183 |
184 | /*
185 | Google Maps only colors markers #FE7569, but turns out you can use
186 | the Google Charts API to make markers any hex color! Amazing.
187 |
188 | This code was pulled from
189 | http://stackoverflow.com/questions/7095574/google-maps-api-3-custom-marker-color-for-default-dot-marker/7686977#7686977
190 | */
191 |
192 | setMarkerIcon: function(marker) {
193 | if(typeof(marker.point.get('icon url')) !== 'undefined' && marker.point.get('icon url') !== '') {
194 | marker.setIcon(marker.point.get('icon url'));
195 | return;
196 | };
197 |
198 | if(typeof(marker.point.markerOptions['iconUrl']) !== 'undefined' && marker.point.markerOptions['iconUrl'] !== '') {
199 | marker.setIcon( marker.point.markerOptions['iconUrl']);
200 | return;
201 | };
202 |
203 | var pinColor = marker.point.get('hexcolor') || "FE7569";
204 | pinColor = pinColor.replace('#','');
205 |
206 | var pinImage = new google.maps.MarkerImage("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=|" + pinColor,
207 | new google.maps.Size(21, 34),
208 | new google.maps.Point(0,0),
209 | new google.maps.Point(10, 34));
210 | var pinShadow = new google.maps.MarkerImage("http://chart.apis.google.com/chart?chst=d_map_pin_shadow",
211 | new google.maps.Size(40, 37),
212 | new google.maps.Point(0, 0),
213 | new google.maps.Point(12, 35));
214 | marker.setShadow(pinShadow);
215 | marker.setIcon(pinImage);
216 | },
217 |
218 | drawMarker: function(point) {
219 | var latLng = new google.maps.LatLng(point.latitude(), point.longitude());
220 |
221 | var marker = new google.maps.Marker({
222 | position: latLng,
223 | point: point,
224 | title: point.title()
225 | });
226 |
227 | this.setMarkerIcon(marker);
228 | this.initInfoWindow(marker);
229 |
230 | if(point.click) {
231 | google.maps.event.addListener(marker, 'click', function(e) {
232 | point.click.call(this, e, point);
233 | });
234 | }
235 |
236 | return marker;
237 | },
238 |
239 | /*
240 | Google only lets you draw one InfoWindow on the page, so you
241 | end up having to re-write to the original one each time.
242 | */
243 |
244 | initInfoWindow: function(marker) {
245 | var infowindow = this.infowindow;
246 | var clickedOpen = false;
247 |
248 | // All of the extra blah blahs are for making sure to not repopulate
249 | // the infowindow when it's already opened and populated with the
250 | // right content
251 |
252 | google.maps.event.addListener(marker, 'click', function() {
253 | if(infowindow.getAnchor() === marker && infowindow.opened) {
254 | return;
255 | }
256 | infowindow.setContent(this.point.content());
257 | infowindow.open(this.map, this);
258 | clickedOpen = true;
259 | infowindow.opened = true;
260 | });
261 |
262 | },
263 |
264 | drawPoints: function(points) {
265 | for(var i = 0; i < points.length; i++) {
266 | if(!points[i].isValid()) { continue; }
267 | var marker = this.drawMarker(points[i]);
268 | marker.setMap(this.map);
269 | this.bounds.extend(marker.position);
270 | points[i].marker = marker;
271 | };
272 |
273 | if(!this.mapOptions.zoom && !this.mapOptions.center) {
274 | this.map.fitBounds(this.bounds);
275 | }
276 | }
277 | }
278 |
279 | /*
280 |
281 | MapQuest (OpenStreetMaps & free)
282 |
283 | */
284 |
285 | Mapsheet.Providers.MapQuest = function(options) {
286 | this.map = options.map;
287 | this.mapOptions = merge_options({ mapTypeId: 'osm', zoom: 13, bestFitMargin: 0, zoomOnDoubleClick: true, latLng:{lat:40.735383, lng:-73.984655} }, options.mapOptions || {});
288 | };
289 |
290 | Mapsheet.Providers.MapQuest.prototype = {
291 | initialize: function(element) {
292 | if(typeof(this.map) === 'undefined') {
293 | this.map = new MQA.TileMap( merge_options({ elt: element }, this.mapOptions) );
294 | }
295 | },
296 |
297 | // We need custom icons!
298 |
299 | drawMarker: function(point) {
300 | var marker = new MQA.Poi( { lat: point.latitude(), lng: point.longitude() } );
301 |
302 | marker.setRolloverContent(point.title());
303 | marker.setInfoContentHTML(point.content());
304 |
305 | if(point.click) {
306 | MQA.EventManager.addListener(marker, 'click', function(e) {
307 | point.click.call(this, e, point);
308 | });
309 | }
310 |
311 | return marker;
312 | },
313 |
314 | drawPoints: function(points) {
315 | for(var i = 0; i < points.length; i++) {
316 | if(!points[i].isValid()) { continue; }
317 | var marker = this.drawMarker(points[i]);
318 | this.map.addShape(marker);
319 | points[i].marker = marker;
320 | };
321 | this.map.bestFit();
322 | }
323 | }
324 |
325 | /*
326 |
327 | MapBox
328 |
329 | */
330 |
331 | Mapsheet.Providers.MapBox = function(options) {
332 | this.map = options.map;
333 | this.mapOptions = merge_options({ mapId: 'examples.map-vyofok3q'}, options.mapOptions || {});
334 | this.markerLayer = options.markerLayer || L.mapbox.markerLayer();
335 | this.bounds = new L.LatLngBounds();
336 | };
337 |
338 | Mapsheet.Providers.MapBox.prototype = {
339 | initialize: function(element) {
340 | if(typeof(this.map) === 'undefined') {
341 | this.map = L.mapbox.map( element );
342 | this.map.addLayer(L.mapbox.tileLayer(this.mapOptions['mapId'])); // add the base layer
343 | // this.map.ui.zoomer.add();
344 | // this.map.ui.zoombox.add();
345 | }
346 | },
347 |
348 | drawMarker: function(point) {
349 | var marker = L.marker(point.coords())
350 | .bindPopup(point.content())
351 |
352 | if(typeof(point.get('icon url')) !== 'undefined' && point.get('icon url') !== '') {
353 | var options = merge_options( point.markerOptions, { iconUrl: point.get('icon url') } );
354 | var icon = L.icon(options);
355 | marker.setIcon(icon);
356 | } else if(typeof(point.markerOptions['iconUrl']) !== 'undefined') {
357 | var icon = L.icon(point.markerOptions);
358 | marker.setIcon(icon);
359 | }
360 |
361 | if(point.click) {
362 | marker.on('click', function(e) {
363 | point.click.call(this, e, point);
364 | });
365 | }
366 | // var icon = L.icon();
367 | // marker.setIcon(icon);
368 |
369 | return marker;
370 | },
371 |
372 | drawPoints: function(points) {
373 | for(var i = 0; i < points.length; i++) {
374 | if(!points[i].isValid()) { continue; }
375 | var marker = this.drawMarker(points[i]);
376 | marker.addTo(this.markerLayer);
377 | this.bounds.extend(marker.getLatLng());
378 | points[i].marker = marker;
379 | };
380 |
381 | this.markerLayer.addTo(this.map);
382 |
383 | if(!this.mapOptions.zoom && !this.mapOptions.center) {
384 | this.map.fitBounds(this.bounds);
385 | }
386 | }
387 | }
388 |
389 | /*
390 |
391 | Did you know you can pass in your own map?
392 | Check out https://gist.github.com/1804938 for some tips on using different tile providers
393 |
394 | */
395 |
396 | Mapsheet.Providers.Leaflet = function(options) {
397 | this.map = options.map;
398 |
399 | var attribution = 'Map data © OpenStreetMap contributors, CC-BY-SA , tiles © MapQuest ';
400 |
401 | var layerDefaults = {
402 | styleId: 998,
403 | attribution: attribution,
404 | type: 'osm'
405 | };
406 |
407 | this.layerOptions = merge_options(layerDefaults, options.layerOptions || {});
408 |
409 | // Only overwrite if there's no tilePath, because the default subdomains is 'abc'
410 | if(!this.layerOptions.tilePath) {
411 | this.layerOptions.tilePath = 'http://otile{s}.mqcdn.com/tiles/1.0.0/{type}/{z}/{x}/{y}.png';
412 | this.layerOptions.subdomains = '1234';
413 | this.layerOptions.type = 'osm';
414 | }
415 | this.markerLayer = options.markerLayer || new L.LayerGroup();
416 | this.mapOptions = options.mapOptions || {};
417 | this.bounds = new L.LatLngBounds();
418 | };
419 |
420 | Mapsheet.Providers.Leaflet.prototype = {
421 | initialize: function(element) {
422 | if(typeof(this.map) === 'undefined') {
423 | this.map = new L.Map('map', this.mapOptions);
424 | this.tileLayer = new L.TileLayer(this.layerOptions['tilePath'], this.layerOptions).addTo(this.map);
425 | }
426 | },
427 |
428 | drawMarker: function(point) {
429 | var marker = L.marker(point.coords())
430 | .bindPopup(point.content())
431 |
432 | if(typeof(point.get('icon url')) !== 'undefined' && point.get('icon url') !== '') {
433 | var options = merge_options( point.markerOptions, { iconUrl: point.get('icon url') } );
434 | var icon = L.icon(options);
435 | marker.setIcon(icon);
436 | } else if(typeof(point.markerOptions['iconUrl']) !== 'undefined') {
437 | var icon = L.icon(point.markerOptions);
438 | marker.setIcon(icon);
439 | }
440 |
441 | if(point.click) {
442 | marker.on('click', function(e) {
443 | point.click.call(this, e, point);
444 | });
445 | }
446 | // var icon = L.icon();
447 | // marker.setIcon(icon);
448 |
449 | return marker;
450 | },
451 |
452 | drawPoints: function(points) {
453 | for(var i = 0; i < points.length; i++) {
454 | if(!points[i].isValid()) { continue; }
455 | var marker = this.drawMarker(points[i]);
456 | marker.addTo(this.markerLayer);
457 | this.bounds.extend(marker.getLatLng());
458 | points[i].marker = marker;
459 | };
460 |
461 | this.markerLayer.addTo(this.map);
462 |
463 | if(!this.mapOptions.zoom && !this.mapOptions.center) {
464 | this.map.fitBounds(this.bounds);
465 | }
466 | }
467 | }
468 |
469 | })(this);
--------------------------------------------------------------------------------