1 /* 2 Geo Mashup - Adds a Google Maps mashup of geocoded blog posts. 3 Copyright (c) 2005-2010 Dylan Kuhn 4 5 This program is free software; you can redistribute it 6 and/or modify it under the terms of the GNU General Public 7 License as published by the Free Software Foundation; 8 either version 2 of the License, or (at your option) any 9 later version. 10 11 This program is distributed in the hope that it will be 12 useful, but WITHOUT ANY WARRANTY; without even the implied 13 warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 14 PURPOSE. See the GNU General Public License for more 15 details. 16 */ 17 18 /** 19 * @fileOverview 20 * The base Geo Mashup code that is independent of mapping API. 21 */ 22 23 /*global jQuery, GeoMashup: true */ 24 // These globals are retained for backward custom javascript compatibility 25 /*global customizeGeoMashup, customizeGeoMashupMap, customGeoMashupColorIcon, customGeoMashupCategoryIcon */ 26 /*global customGeoMashupSinglePostIcon, customGeoMashupMultiplePostImage */ 27 /*jslint browser: true, white: true, sloppy: true */ 28 29 var GeoMashup, customizeGeoMashup, customizeGeoMashupMap, customGeoMashupColorIcon, customGeoMashupCategoryIcon, 30 customGeoMashupSinglePostIcon, customGeoMashupMultiplePostImage; 31 32 /** 33 * @name GeoMashupObject 34 * @class This type represents an object Geo Mashup can place on the map. 35 * It has no constructor, but is instantiated as an object literal. 36 * Custom properties can be added, but some are present by default. 37 * 38 * @property {String} object_name The type of object: post, user, comment, etc. 39 * @property {String} object_id A unique identifier for the object 40 * @property {String} title 41 * @property {Number} lat Latitude 42 * @property {Number} lng Longitude 43 * @property {String} author_name The name of the object author 44 * @property {Array} categories Deprecated, use terms. The object's category IDs if any. 45 * @property {Object} terms The object's terms by taxonomy, e.g. { "tax1": [ "2", "5" ], "tax2": [ "1" ] } 46 * @property {GeoMashupIcon} icon The default icon to use for the object 47 */ 48 49 /** 50 * @name GeoMashupIcon 51 * @class This type represents an icon that can be used for a map marker. 52 * It has no constructor, but is instantiated as an object literal. 53 * @property {String} image URL of the icon image 54 * @property {String} iconShadow URL of the icon shadow image 55 * @property {Array} iconSize Pixel width and height of the icon 56 * @property {Array} shadowSize Pixel width and height of the icon shadow 57 * @property {Array} iconAnchor Pixel offset from top left: [ right, down ] 58 * @property {Array} infoWindowAnchor Pixel offset from top left: [ right, down ] 59 */ 60 61 /** 62 * @name GeoMashupOptions 63 * @class This type represents options used for a specific Geo Mashup map. 64 * It has no constructor, but is instantiated as an object literal. 65 * Properties reflect the <a href="http://code.google.com/p/wordpress-geo-mashup/wiki/TagReference#Map">map tag parameters</a>. 66 */ 67 68 /** 69 * @name VisibilityFilter 70 * @class This type represents objects used to filter object visibility. 71 * It has no constructor, but is instantiated as an object literal. 72 * 73 * @name ContentFilter#visible 74 * @property {Boolean} visible Whether the object is currently visible 75 */ 76 77 /** 78 * @namespace Used more as a singleton than a namespace for data and methods for a single Geo Mashup map. 79 * 80 * <p>Violates the convention that capitalized objects are designed to be used with the 81 * 'new' keyword - an artifact of the age of the project. :o</p> 82 * 83 * <p><strong>Note: Events are Actions</strong></p> 84 * 85 * <p>Actions available for use with GeoMashup.addAction() are documented as events. 86 * See the <a href="http://code.google.com/p/wordpress-geo-mashup/wiki/JavaScriptApi">javascript API documentation</a> 87 * for an example. 88 * </p> 89 */ 90 GeoMashup = { 91 /** 92 * Access to the options for this map. 93 * Properties reflect the <a href="http://code.google.com/p/wordpress-geo-mashup/wiki/TagReference#Map">map tag parameters</a>. 94 * @property {GeoMashupOptions} 95 */ 96 opts: {}, 97 actions : {}, 98 objects : {}, 99 object_count : 0, 100 locations : {}, 101 open_attachments : [], 102 errors : [], 103 color_names : ['red','lime','blue','orange','yellow','aqua','green','silver','maroon','olive','navy','purple','gray','teal','fuchsia','white','black'], 104 colors : { 105 'red':'#ff071e', 106 'lime':'#62d962', 107 'blue':'#9595ff', 108 'orange':'#fe8f00', 109 'yellow':'#f2f261', 110 'aqua':'#8eeff0', 111 'green':'#459234', 112 'silver':'#c2c2c2', 113 'maroon':'#ae1a40', 114 'olive':'#9f9b46', 115 'navy':'#30389d', 116 'purple':'#a54086', 117 'gray':'#9b9b9b', 118 'teal':'#13957b', 119 'fuchsia':'#e550e5', 120 'white':'#ffffff', 121 'black':'#000000' 122 }, 123 firstLoad : true, 124 125 clone : function( obj ) { 126 var ClonedObject = function(){}; 127 ClonedObject.prototype = obj; 128 return new ClonedObject(); 129 }, 130 131 forEach : function( obj, callback ) { 132 var key; 133 for( key in obj ) { 134 if ( obj.hasOwnProperty( key ) && typeof obj[key] !== 'function' ) { 135 callback.apply( this, [key, obj[key]] ); 136 } 137 } 138 }, 139 140 locationCache : function( latlng, key ) { 141 if ( !this.locations.hasOwnProperty( latlng ) ) { 142 return false; 143 } 144 if ( !this.locations[latlng].cache ) { 145 this.locations[latlng].cache = {}; 146 } 147 if ( !this.locations[latlng].cache.hasOwnProperty( key ) ) { 148 this.locations[latlng].cache[key] = {}; 149 } 150 return this.locations[latlng].cache[key]; 151 }, 152 153 /** 154 * Add an action callback to extend Geo Mashup functionality. 155 * 156 * Essentially an event interface. Might make sense to convert to 157 * Mapstraction events in the future. 158 * 159 * @param {String} name The name of the action (event). 160 * @param {Function} callback The function to call when the action occurs 161 */ 162 addAction : function ( name, callback ) { 163 if ( typeof callback !== 'function' ) { 164 return false; 165 } 166 if ( typeof this.actions[name] !== 'object' ) { 167 this.actions[name] = [callback]; 168 } else { 169 this.actions[name].push( callback ); 170 } 171 return true; 172 }, 173 174 /** 175 * Fire all callbacks for an action. 176 * 177 * Essentially an event interface. Might make sense to convert to 178 * Mapstraction events in the future. 179 * 180 * @param {String} name The name of the action (event). 181 */ 182 doAction : function ( name ) { 183 var args, i; 184 185 if ( typeof this.actions[name] !== 'object' ) { 186 return false; 187 } 188 args = Array.prototype.slice.apply( arguments, [1] ); 189 for ( i = 0; i < this.actions[name].length; i += 1 ) { 190 if ( typeof this.actions[name][i] === 'function' ) { 191 this.actions[name][i].apply( null, args ); 192 } 193 } 194 return true; 195 }, 196 197 parentScrollToGeoPost : function () { 198 var geo_post; 199 if ( this.have_parent_access ) { 200 geo_post = parent.document.getElementById('gm-post'); 201 if (geo_post) { 202 parent.focus(); 203 parent.scrollTo(geo_post.offsetLeft, geo_post.offsetTop); 204 } 205 } 206 return false; 207 }, 208 209 /** 210 * Get the DOM element where the full post content should be displayed, if any. 211 * @returns {DOMElement} The element, or undefined if none. 212 */ 213 getShowPostElement : function() { 214 if ( this.have_parent_access && !this.show_post_element && this.opts.name) { 215 this.show_post_element = parent.document.getElementById(this.opts.name + '-post'); 216 } 217 if ( this.have_parent_access && !this.show_post_element) { 218 this.show_post_element = parent.document.getElementById('gm-post'); 219 } 220 return this.show_post_element; 221 }, 222 223 /** 224 * Change the target of links in HTML markup to target the parent frame. 225 * @param {String} markup 226 * @returns {String} Modified markup 227 */ 228 parentizeLinksMarkup : function( markup ) { 229 var container = document.createElement( 'div' ); 230 container.innerHTML = markup; 231 this.parentizeLinks( container ); 232 return container.innerHTML; 233 }, 234 235 /** 236 * Change the target of links in a DOM element to target the parent frame. 237 * @param {DOMElement} node The element to change 238 */ 239 parentizeLinks : function( node ) { 240 var i, links = node.getElementsByTagName('a'); 241 if ( parent ) { 242 for (i=0; i<links.length; i += 1) { 243 if ( links[i].target.length === 0 || links[i].target === '_self' ) { 244 links[i].target = "_parent"; 245 } 246 } 247 } 248 }, 249 250 /** 251 * Display a spinner icon for the map. 252 */ 253 showLoadingIcon : function() { 254 if ( ! this.spinner_div.parentNode ) { 255 this.container.appendChild( this.spinner_div ); 256 } 257 }, 258 259 /** 260 * Hide the spinner icon for the map. 261 */ 262 hideLoadingIcon : function() { 263 if ( this.spinner_div.parentNode ) { 264 this.spinner_div.parentNode.removeChild( this.spinner_div ); 265 } 266 }, 267 268 /** 269 * Get the objects at a specified location. 270 * @param {LatLonPoint} point The query location 271 * @returns {Array} The mapped objects at the query location 272 */ 273 getObjectsAtLocation : function( point ) { 274 return this.locations[point].objects; 275 }, 276 277 /** 278 * Get the objects at the location of a specified marker. 279 * @param {Marker} marker 280 * @returns {Array} The mapped objects at the marker location 281 */ 282 getMarkerObjects : function( marker ) { 283 return this.getObjectsAtLocation( this.getMarkerLatLng( marker ) ); 284 }, 285 286 /** 287 * Get the location coordinates for a marker. 288 * @param {Marker} marker 289 * @returns {LatLonPoint} The marker coordinates 290 */ 291 getMarkerLatLng : function( marker ) { 292 // Provider override 293 }, 294 295 /** 296 * Obscure an existing marker with the highlighted "glow" marker. 297 * @param {Marker} marker The existing marker 298 */ 299 addGlowMarker : function( marker ) { 300 // Provider override 301 }, 302 303 /** 304 * Open the info bubble for a marker. 305 * @param {Marker} marker 306 */ 307 openInfoWindow : function( marker ) { 308 // Provider override 309 }, 310 311 /** 312 * Close the info bubble for a marker. 313 * @param {Marker} marker 314 */ 315 closeInfoWindow : function( marker ) { 316 // provider override 317 }, 318 319 /** 320 * Remove the highlighted "glow" marker from the map if it exists. 321 */ 322 removeGlowMarker : function() { 323 // Provider override 324 }, 325 326 /** 327 * Hide any visible attachment layers on the map. 328 */ 329 hideAttachments : function() { 330 // Provider override 331 }, 332 333 /** 334 * Show any attachment layers associated with the objects represented 335 * by a marker, loading the layer if necessary. 336 * @param {Marker} marker 337 */ 338 showMarkerAttachments : function( marker ) { 339 // Provider override 340 }, 341 342 /** 343 * Load full content for the objects/posts at a location into the 344 * full post display element. 345 * @param {LatLonPoint} point 346 */ 347 loadFullPost : function( point ) { 348 // jQuery or provider override 349 }, 350 351 /** 352 * Select a marker. 353 * @param {Marker} marker 354 */ 355 selectMarker : function( marker ) { 356 var point = this.getMarkerLatLng( marker ); 357 358 this.selected_marker = marker; 359 if ( this.opts.marker_select_info_window ) { 360 this.openInfoWindow( marker ); 361 } 362 if ( this.opts.marker_select_attachments ) { 363 this.showMarkerAttachments( marker ); 364 } 365 if ( this.opts.marker_select_highlight ) { 366 this.addGlowMarker( marker ); 367 } 368 if ( this.opts.marker_select_center ) { 369 this.centerMarker( marker ); 370 } 371 if ('full-post' !== this.opts.template && this.getShowPostElement()) { 372 this.loadFullPost( point ); 373 } 374 /** 375 * A marker was selected. 376 * @name GeoMashup#selectedMarker 377 * @event 378 * @param {GeoMashupOptions} properties Geo Mashup configuration data 379 * @param {Marker} marker The selected marker 380 * @param {Map} map The map containing the marker 381 */ 382 this.doAction( 'selectedMarker', this.opts, this.selected_marker, this.map ); 383 }, 384 385 /** 386 * Center and optionally zoom to a marker. 387 * @param {Marker} marker 388 * @param {Number} zoom Optional zoom level 389 */ 390 centerMarker : function ( marker, zoom ) { 391 // provider override 392 }, 393 394 /** 395 * De-select the currently selected marker if there is one. 396 */ 397 deselectMarker : function() { 398 var i, post_element = GeoMashup.getShowPostElement(); 399 if ( post_element ) { 400 post_element.innerHTML = ''; 401 } 402 if ( this.glow_marker ) { 403 this.removeGlowMarker(); 404 } 405 if ( this.selected_marker ) { 406 this.closeInfoWindow( this.selected_marker ); 407 } 408 this.hideAttachments(); 409 this.selected_marker = null; 410 }, 411 412 addObjectIcon : function( obj ) { 413 // provider override 414 }, 415 416 createMarker : function( point, obj ) { 417 var marker; 418 // provider override 419 return marker; 420 }, 421 422 checkDependencies : function () { 423 // provider override 424 }, 425 426 /** 427 * Simulate a user click on the marker that represents a specified object. 428 * @param {String} object_id The ID of the object. 429 * @param {Number} try_count Optional number of times to try (in case the object 430 * is still being loaded). 431 */ 432 clickObjectMarker : function(object_id, try_count) { 433 // provider override 434 }, 435 436 /** 437 * Backward compatibility for clickObjectMarker(). 438 * @deprecated 439 */ 440 clickMarker : function( object_id, try_count ) { 441 this.clickObjectMarker( object_id, try_count ); 442 }, 443 444 /** 445 * Get the name of a category, if loaded. 446 * @param {String} category_id 447 * @return {String} The ID or null if not available. 448 */ 449 getCategoryName : function (category_id) { 450 if ( !this.opts.term_properties.hasOwnProperty( 'category' ) ) { 451 return null; 452 } 453 return this.opts.term_properties.category[category_id]; 454 }, 455 456 /** 457 * Hide a marker. 458 * @param {Marker} marker 459 */ 460 hideMarker : function( marker ) { 461 // Provider override 462 }, 463 464 /** 465 * Show a marker. 466 * @param {Marker} marker 467 */ 468 showMarker : function( marker ) { 469 // Provider override 470 }, 471 472 /** 473 * Hide a line. 474 * @param {Polyline} line 475 */ 476 hideLine : function( line ) { 477 // Provider override 478 }, 479 480 /** 481 * Show a line. 482 * @param {Polyline} line 483 */ 484 showLine : function( line ) { 485 // Provider override 486 }, 487 488 /** 489 * Get a line's current visibility. 490 * @param {Poloyline} line 491 */ 492 isLineVisible : function( line ) { 493 // Provider override 494 }, 495 496 /** 497 * Create a new geo coordinate object. 498 * @param {Number} lat Latitude 499 * @param {Number} lng Longitude 500 * @returns {LatLonPoint} Coordinates 501 */ 502 newLatLng : function( lat, lng ) { 503 var latlng; 504 // Provider override 505 return latlng; 506 }, 507 508 extendLocationBounds : function( ) { 509 // Provider override 510 }, 511 512 addMarkers : function( ) { 513 // Provider override 514 }, 515 516 makeMarkerMultiple : function( marker ) { 517 // Provider override 518 }, 519 520 setMarkerImage : function( marker, image_url ) { 521 // Provider override 522 }, 523 524 /** 525 * Zoom the map to loaded content. 526 */ 527 autoZoom : function( ) { 528 // Provider override 529 }, 530 531 /** 532 * If clustering is active, refresh clusters. 533 */ 534 recluster : function( ) { 535 // Provider override 536 }, 537 538 /** 539 * Show or hide markers according to current visibility criteria. 540 */ 541 updateMarkerVisibilities : function( ) { 542 this.forEach( this.locations, function( point, loc ) { 543 GeoMashup.updateMarkerVisibility( loc.marker, point ); 544 } ); 545 this.updateVisibleList(); 546 }, 547 548 updateMarkerVisibility : function( marker ) { 549 if ( this.isMarkerOn( marker ) ) { 550 this.showMarker( marker ); 551 } else { 552 this.hideMarker( marker ); 553 } 554 }, 555 556 isMarkerOn : function( marker ) { 557 var i, objects, visible_object_indices = [], filter = { 558 visible: false 559 }; 560 561 objects = this.getMarkerObjects( marker ); 562 for ( i = 0; i < objects.length; i += 1 ) { 563 if ( this.isObjectOn( objects[i] ) ) { 564 filter.visible = true; 565 visible_object_indices.push( i ); 566 } 567 } 568 569 // Adjust marker icon based on current visible contents 570 if ( filter.visible ) { 571 572 if ( objects.length > 1 ) { 573 574 if ( visible_object_indices.length === 1 ) { 575 GeoMashup.setMarkerImage( marker, objects[visible_object_indices[0]].icon.image ); 576 } else { 577 GeoMashup.makeMarkerMultiple( marker ); 578 } 579 580 } else if ( objects[0].combined_term_count > 1 ) { 581 582 if ( objects[0].visible_term_count === 1 ) { 583 584 jQuery.each( objects[0].visible_terms, function( taxonomy, term_ids ) { 585 586 if ( term_ids.length === 1 ) { 587 GeoMashup.setMarkerImage( marker, GeoMashup.term_manager.getTermData( taxonomy, term_ids[0], 'icon' ).image ); 588 } 589 590 } ); 591 592 } else { 593 GeoMashup.setMarkerImage( marker, objects[0].icon.image ); 594 } 595 } 596 } 597 /** 598 * Visibility is being tested for a marker. 599 * @name GeoMashup#markerVisibilityOptions 600 * @event 601 * @param {GeoMashupOptions} properties Geo Mashup configuration data 602 * @param {VisibilityFilter} filter Test and set filter.visible 603 * @param {Marker} marker The marker being tested 604 * @param {Map} map The map for context 605 */ 606 this.doAction( 'markerVisibilityOptions', this.opts, filter, marker, this.map ); 607 608 return filter.visible; 609 }, 610 611 isObjectOn : function( obj ) { 612 var filter = { 613 visible: false 614 }; 615 616 obj.visible_terms = {}; 617 obj.visible_term_count = 0; 618 619 if ( !GeoMashup.term_manager || 0 === obj.combined_term_count ) { 620 621 // Objects without terms are visible by default 622 filter.visible = true; 623 624 } else { 625 626 // Check term visibility 627 jQuery.each( obj.terms, function( taxonomy, term_ids ) { 628 629 obj.visible_terms[taxonomy] = []; 630 631 jQuery.each( term_ids, function( i, term_id ) { 632 633 if ( GeoMashup.term_manager.getTermData( taxonomy, term_id, 'visible' ) ) { 634 obj.visible_terms[taxonomy].push( term_id ); 635 obj.visible_term_count += 1; 636 filter.visible = true; 637 } 638 639 }); 640 641 }); 642 643 } 644 645 /** 646 * Visibility is being tested for an object. 647 * @name GeoMashup#objectVisibilityOptions 648 * @event 649 * @param {GeoMashupOptions} properties Geo Mashup configuration data 650 * @param {VisibilityFilter} filter Test and set filter.visible 651 * @param {Object} object The object being tested 652 * @param {Map} map The map for context 653 */ 654 this.doAction( 'objectVisibilityOptions', this.opts, filter, obj, this.map ); 655 656 return filter.visible; 657 }, 658 659 /** 660 * Extract the IDs of objects that are "on" (not filtered by a control). 661 * @since 1.4.2 662 * @param {Array} objects The objects to check 663 * @returns {Array} The IDs of the "on" objects 664 */ 665 getOnObjectIDs : function( objects ) { 666 var i, object_ids = []; 667 for( i = 0; i < objects.length; i += 1 ) { 668 if ( this.isObjectOn( objects[i] ) ) { 669 object_ids.push( objects[i].object_id ); 670 } 671 } 672 return object_ids; 673 }, 674 675 /** 676 * Add objects to the map. 677 * @param {Object} response_data Data returned by a geo query. 678 * @param {Boolean} add_term_info Whether to build and show term 679 * data for these objects, for legend or other term controls. 680 */ 681 addObjects : function(response_data, add_term_info) { 682 var i, k, object_id, point, taxonomy, term_ids, term_id, marker, plus_image, 683 added_markers = []; 684 685 if ( add_term_info && this.term_manager ) { 686 this.term_manager.reset(); 687 } 688 689 for (i = 0; i < response_data.length; i+=1) { 690 // Make a marker for each new object location 691 object_id = response_data[i].object_id; 692 point = this.newLatLng( 693 parseFloat(response_data[i].lat), 694 parseFloat(response_data[i].lng) 695 ); 696 697 // Back compat for categories API 698 response_data[i].categories = []; 699 700 response_data[i].combined_term_count = 0; 701 if ( this.term_manager ) { 702 // Add terms 703 for( taxonomy in response_data[i].terms ) { 704 if ( response_data[i].terms.hasOwnProperty( taxonomy ) && typeof taxonomy !== 'function' ) { 705 706 term_ids = response_data[i].terms[taxonomy]; 707 for (k = 0; k < term_ids.length; k+=1) { 708 GeoMashup.term_manager.extendTerm( point, taxonomy, term_ids[k], response_data[i] ); 709 } 710 711 response_data[i].combined_term_count += term_ids.length; 712 713 if ( 'category' === taxonomy ) { 714 response_data[i].categories = term_ids; 715 } 716 } 717 } 718 } 719 720 if (this.opts.max_posts && this.object_count >= this.opts.max_posts) { 721 break; 722 } 723 724 if (!this.objects[object_id]) { 725 // This object has not yet been loaded 726 this.objects[object_id] = response_data[i]; 727 this.object_count += 1; 728 if (!this.locations[point]) { 729 // There are no other objects yet at this point, create a marker 730 this.extendLocationBounds( point ); 731 this.locations[point] = { 732 objects : [ response_data[i] ], 733 loaded_content: {} 734 }; 735 marker = this.createMarker(point, response_data[i]); 736 this.objects[object_id].marker = marker; 737 this.locations[point].marker = marker; 738 added_markers.push( marker ); 739 } else { 740 // There is already a marker at this point, add the new object to it 741 this.locations[point].objects.push( response_data[i] ); 742 marker = this.locations[point].marker; 743 this.makeMarkerMultiple( marker ); 744 this.objects[object_id].marker = marker; 745 this.addObjectIcon( this.objects[object_id] ); 746 } 747 } 748 } // end for each marker 749 750 // Openlayers at least only gets clicks on the top layer, so add markers last 751 this.addMarkers( added_markers ); 752 753 if ( this.term_manager ) { 754 this.term_manager.populateTermElements(); 755 } 756 757 if (this.firstLoad) { 758 this.firstLoad = false; 759 if ( this.opts.auto_info_open && this.object_count > 0 ) { 760 if ( !this.opts.open_object_id ) { 761 if ( this.opts.context_object_id && this.objects[ this.opts.context_object_id ] ) { 762 this.opts.open_object_id = this.opts.context_object_id; 763 } else { 764 this.opts.open_object_id = response_data[0].object_id; 765 } 766 } 767 this.clickObjectMarker(this.opts.open_object_id); 768 } 769 if ( this.opts.zoom === 'auto' ) { 770 this.autoZoom(); 771 } else { 772 if ( this.opts.context_object_id && this.objects[ this.opts.context_object_id ] ) { 773 this.centerMarker( this.objects[ this.opts.context_object_id ].marker, parseInt( this.opts.zoom, 10 ) ); 774 } 775 this.updateVisibleList(); 776 } 777 } 778 }, 779 780 requestObjects : function(use_bounds) { 781 // provider override (maybe jQuery?) 782 }, 783 784 /** 785 * Hide all markers. 786 */ 787 hideMarkers : function() { 788 var point; 789 790 for (point in this.locations) { 791 if ( this.locations.hasOwnProperty( point ) && this.locations[point].marker ) { 792 this.hideMarker( this.locations[point].marker ); 793 } 794 } 795 this.recluster(); 796 this.updateVisibleList(); 797 }, 798 799 /** 800 * Show all unfiltered markers. 801 */ 802 showMarkers : function() { 803 804 jQuery.each( this.locations, function( point, location ) { 805 if ( GeoMashup.isMarkerOn( location.marker ) ) { 806 GeoMashup.showMarker( location.marker ); 807 } 808 }); 809 this.recluster(); 810 this.updateVisibleList(); 811 812 }, 813 814 adjustZoom : function() { 815 var old_level, new_level; 816 new_level = this.map.getZoom(); 817 if ( typeof this.last_zoom_level === 'undefined' ) { 818 this.last_zoom_level = new_level; 819 } 820 old_level = this.last_zoom_level; 821 822 if ( this.term_manager ) { 823 this.term_manager.updateLineZoom( old_level, new_level ); 824 } 825 826 if ( this.clusterer && 'google' === this.opts.map_api ) { 827 if ( old_level <= this.opts.cluster_max_zoom && 828 new_level > this.opts.cluster_max_zoom ) { 829 this.clusterer.clusteringEnabled = false; 830 this.clusterer.refresh( true ); 831 } else if ( old_level > this.opts.cluster_max_zoom && 832 new_level <= this.opts.cluster_max_zoom ) { 833 this.clusterer.clusteringEnabled = true; 834 this.clusterer.refresh( true ); 835 } 836 } 837 this.last_zoom_level = new_level; 838 }, 839 840 objectLinkHtml : function(object_id) { 841 return ['<a href="#', 842 this.opts.name, 843 '" onclick="frames[\'', 844 this.opts.name, 845 '\'].GeoMashup.clickObjectMarker(', 846 object_id, 847 ');">', 848 this.objects[object_id].title, 849 '</a>'].join(''); 850 }, 851 852 /** 853 * Whether a marker is currently visible on the map. 854 * @param {Marker} marker 855 * @return {Boolean} False if the marker is hidden or outside the current viewport. 856 */ 857 isMarkerVisible : function( marker ) { 858 // Provider override 859 return false; 860 }, 861 862 /** 863 * Recompile the list of objects currently visible on the map. 864 */ 865 updateVisibleList : function() { 866 var list_element, header_element, list_html, list_items, list_titles, list_count = 0; 867 868 if (this.have_parent_access && this.opts.name) { 869 header_element = parent.document.getElementById(this.opts.name + "-visible-list-header"); 870 list_element = parent.document.getElementById(this.opts.name + "-visible-list"); 871 } 872 if (header_element) { 873 header_element.style.display = 'block'; 874 } 875 if (list_element) { 876 list_html = ['<ul class="gm-visible-list">']; 877 list_items = []; 878 list_titles = []; 879 this.forEach( this.objects, function (object_id, obj) { 880 if ( this.isObjectOn( obj ) && this.isMarkerVisible( obj.marker ) ) { 881 var list_item = []; 882 list_item.push('<li><img src="'); 883 list_item.push(obj.icon.image); 884 list_item.push('" alt="'); 885 list_item.push(obj.title); 886 list_item.push('" />'); 887 list_item.push(this.objectLinkHtml(object_id)); 888 list_item.push('</li>'); 889 list_count += 1; 890 list_items[obj.title] = list_item.join(''); 891 list_titles.push(obj.title); 892 } 893 }); 894 list_titles.sort(); 895 var list_merged = ''; 896 list_titles.forEach( function(index, title){ 897 list_merged += list_items[index]; 898 }); 899 list_html.push(list_merged); 900 list_html.push('</ul>'); 901 list_element.innerHTML = list_html.join(''); 902 /** 903 * The visible posts list was updated. 904 * @name GeoMashup#updatedVisibleList 905 * @event 906 * @param {GeoMashupOptions} properties Geo Mashup configuration data 907 * @param {Number} list_count The number of items in the list 908 */ 909 this.doAction( 'updatedVisibleList', this.opts, list_count ); 910 } 911 }, 912 913 adjustViewport : function() { 914 this.updateVisibleList(); 915 }, 916 917 createMap : function(container, opts) { 918 // Provider override 919 } 920 }; 921