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/Documentation#Custom_JavaScript">custom javascript 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 * Create a new geo coordinate object. 490 * @param {Number} lat Latitude 491 * @param {Number} lng Longitude 492 * @returns {LatLonPoint} Coordinates 493 */ 494 newLatLng : function( lat, lng ) { 495 var latlng; 496 // Provider override 497 return latlng; 498 }, 499 500 extendLocationBounds : function( ) { 501 // Provider override 502 }, 503 504 addMarkers : function( ) { 505 // Provider override 506 }, 507 508 makeMarkerMultiple : function( marker ) { 509 // Provider override 510 }, 511 512 setMarkerImage : function( marker, image_url ) { 513 // Provider override 514 }, 515 516 /** 517 * Zoom the map to loaded content. 518 */ 519 autoZoom : function( ) { 520 // Provider override 521 }, 522 523 /** 524 * If clustering is active, refresh clusters. 525 */ 526 recluster : function( ) { 527 // Provider override 528 }, 529 530 /** 531 * Show or hide markers according to current visibility criteria. 532 */ 533 updateMarkerVisibilities : function( ) { 534 this.forEach( this.locations, function( point, loc ) { 535 GeoMashup.updateMarkerVisibility( loc.marker, point ); 536 } ); 537 this.updateVisibleList(); 538 }, 539 540 updateMarkerVisibility : function( marker ) { 541 if ( this.isMarkerOn( marker ) ) { 542 this.showMarker( marker ); 543 } else { 544 this.hideMarker( marker ); 545 } 546 }, 547 548 isMarkerOn : function( marker ) { 549 var i, objects, visible_object_indices = [], filter = { 550 visible: false 551 }; 552 553 objects = this.getMarkerObjects( marker ); 554 for ( i = 0; i < objects.length; i += 1 ) { 555 if ( this.isObjectOn( objects[i] ) ) { 556 filter.visible = true; 557 visible_object_indices.push( i ); 558 } 559 } 560 561 // Adjust marker icon based on current visible contents 562 if ( filter.visible ) { 563 564 if ( objects.length > 1 ) { 565 566 if ( visible_object_indices.length === 1 ) { 567 GeoMashup.setMarkerImage( marker, objects[visible_object_indices[0]].icon.image ); 568 } else { 569 GeoMashup.makeMarkerMultiple( marker ); 570 } 571 572 } else if ( objects[0].combined_term_count > 1 ) { 573 574 if ( objects[0].visible_term_count === 1 ) { 575 576 jQuery.each( objects[0].visible_terms, function( taxonomy, term_ids ) { 577 578 if ( term_ids.length === 1 ) { 579 GeoMashup.setMarkerImage( marker, GeoMashup.term_manager.getTermData( taxonomy, term_ids[0], 'icon' ).image ); 580 } 581 582 } ); 583 584 } else { 585 GeoMashup.setMarkerImage( marker, objects[0].icon.image ); 586 } 587 } 588 } 589 /** 590 * Visibility is being tested for a marker. 591 * @name GeoMashup#markerVisibilityOptions 592 * @event 593 * @param {GeoMashupOptions} properties Geo Mashup configuration data 594 * @param {VisibilityFilter} filter Test and set filter.visible 595 * @param {Marker} marker The marker being tested 596 * @param {Map} map The map for context 597 */ 598 this.doAction( 'markerVisibilityOptions', this.opts, filter, marker, this.map ); 599 600 return filter.visible; 601 }, 602 603 isObjectOn : function( obj ) { 604 var filter = { 605 visible: false 606 }; 607 608 obj.visible_terms = {}; 609 obj.visible_term_count = 0; 610 611 if ( !GeoMashup.term_manager || 0 === obj.combined_term_count ) { 612 613 // Objects without terms are visible by default 614 filter.visible = true; 615 616 } else { 617 618 // Check term visibility 619 jQuery.each( obj.terms, function( taxonomy, term_ids ) { 620 621 obj.visible_terms[taxonomy] = []; 622 623 jQuery.each( term_ids, function( i, term_id ) { 624 625 if ( GeoMashup.term_manager.getTermData( taxonomy, term_id, 'visible' ) ) { 626 obj.visible_terms[taxonomy].push( term_id ); 627 obj.visible_term_count += 1; 628 filter.visible = true; 629 } 630 631 }); 632 633 }); 634 635 } 636 637 /** 638 * Visibility is being tested for an object. 639 * @name GeoMashup#objectVisibilityOptions 640 * @event 641 * @param {GeoMashupOptions} properties Geo Mashup configuration data 642 * @param {VisibilityFilter} filter Test and set filter.visible 643 * @param {Object} object The object being tested 644 * @param {Map} map The map for context 645 */ 646 this.doAction( 'objectVisibilityOptions', this.opts, filter, obj, this.map ); 647 648 return filter.visible; 649 }, 650 651 /** 652 * Extract the IDs of objects that are "on" (not filtered by a control). 653 * @since 1.4.2 654 * @param {Array} objects The objects to check 655 * @returns {Array} The IDs of the "on" objects 656 */ 657 getOnObjectIDs : function( objects ) { 658 var i, object_ids = []; 659 for( i = 0; i < objects.length; i += 1 ) { 660 if ( this.isObjectOn( objects[i] ) ) { 661 object_ids.push( objects[i].object_id ); 662 } 663 } 664 return object_ids; 665 }, 666 667 /** 668 * Add objects to the map. 669 * @param {Object} response_data Data returned by a geo query. 670 * @param {Boolean} add_term_info Whether to build and show term 671 * data for these objects, for legend or other term controls. 672 */ 673 addObjects : function(response_data, add_term_info) { 674 var i, k, object_id, point, taxonomy, term_ids, term_id, marker, plus_image, 675 added_markers = []; 676 677 if ( add_term_info && this.term_manager ) { 678 this.term_manager.reset(); 679 } 680 681 for (i = 0; i < response_data.length; i+=1) { 682 // Make a marker for each new object location 683 object_id = response_data[i].object_id; 684 point = this.newLatLng( 685 parseFloat(response_data[i].lat), 686 parseFloat(response_data[i].lng) 687 ); 688 689 // Back compat for categories API 690 response_data[i].categories = []; 691 692 response_data[i].combined_term_count = 0; 693 if ( this.term_manager ) { 694 // Add terms 695 for( taxonomy in response_data[i].terms ) { 696 if ( response_data[i].terms.hasOwnProperty( taxonomy ) && typeof taxonomy !== 'function' ) { 697 698 term_ids = response_data[i].terms[taxonomy]; 699 for (k = 0; k < term_ids.length; k+=1) { 700 GeoMashup.term_manager.extendTerm( point, taxonomy, term_ids[k], response_data[i] ); 701 } 702 703 response_data[i].combined_term_count += term_ids.length; 704 705 if ( 'category' === taxonomy ) { 706 response_data[i].categories = term_ids; 707 } 708 } 709 } 710 } 711 712 if (this.opts.max_posts && this.object_count >= this.opts.max_posts) { 713 break; 714 } 715 716 if (!this.objects[object_id]) { 717 // This object has not yet been loaded 718 this.objects[object_id] = response_data[i]; 719 this.object_count += 1; 720 if (!this.locations[point]) { 721 // There are no other objects yet at this point, create a marker 722 this.extendLocationBounds( point ); 723 this.locations[point] = { 724 objects : [ response_data[i] ], 725 loaded_content: {} 726 }; 727 marker = this.createMarker(point, response_data[i]); 728 this.objects[object_id].marker = marker; 729 this.locations[point].marker = marker; 730 added_markers.push( marker ); 731 } else { 732 // There is already a marker at this point, add the new object to it 733 this.locations[point].objects.push( response_data[i] ); 734 marker = this.locations[point].marker; 735 this.makeMarkerMultiple( marker ); 736 this.objects[object_id].marker = marker; 737 this.addObjectIcon( this.objects[object_id] ); 738 } 739 } 740 } // end for each marker 741 742 // Openlayers at least only gets clicks on the top layer, so add markers last 743 this.addMarkers( added_markers ); 744 745 if ( this.term_manager ) { 746 this.term_manager.populateTermElements(); 747 } 748 749 if (this.firstLoad) { 750 this.firstLoad = false; 751 if ( this.opts.auto_info_open && this.object_count > 0 ) { 752 if ( !this.opts.open_object_id ) { 753 if ( this.opts.context_object_id && this.objects[ this.opts.context_object_id ] ) { 754 this.opts.open_object_id = this.opts.context_object_id; 755 } else { 756 this.opts.open_object_id = response_data[0].object_id; 757 } 758 } 759 this.clickObjectMarker(this.opts.open_object_id); 760 } 761 if ( this.opts.zoom === 'auto' ) { 762 this.autoZoom(); 763 } else { 764 if ( this.opts.context_object_id && this.objects[ this.opts.context_object_id ] ) { 765 this.centerMarker( this.objects[ this.opts.context_object_id ].marker, parseInt( this.opts.zoom, 10 ) ); 766 } 767 this.updateVisibleList(); 768 } 769 } 770 }, 771 772 requestObjects : function(use_bounds) { 773 // provider override (maybe jQuery?) 774 }, 775 776 /** 777 * Hide all markers. 778 */ 779 hideMarkers : function() { 780 var point; 781 782 for (point in this.locations) { 783 if ( this.locations.hasOwnProperty( point ) && this.locations[point].marker ) { 784 this.hideMarker( this.locations[point].marker ); 785 } 786 } 787 this.recluster(); 788 this.updateVisibleList(); 789 }, 790 791 /** 792 * Show all unfiltered markers. 793 */ 794 showMarkers : function() { 795 796 jQuery.each( this.locations, function( point, location ) { 797 if ( GeoMashup.isMarkerOn( location.marker ) ) { 798 GeoMashup.showMarker( location.marker ); 799 } 800 }); 801 this.recluster(); 802 this.updateVisibleList(); 803 804 }, 805 806 adjustZoom : function() { 807 var old_level, new_level; 808 new_level = this.map.getZoom(); 809 if ( typeof this.last_zoom_level === 'undefined' ) { 810 this.last_zoom_level = new_level; 811 } 812 old_level = this.last_zoom_level; 813 814 if ( this.term_manager ) { 815 this.term_manager.updateLineZoom( old_level, new_level ); 816 } 817 818 if ( this.clusterer && 'google' === this.opts.map_api ) { 819 if ( old_level <= this.opts.cluster_max_zoom && 820 new_level > this.opts.cluster_max_zoom ) { 821 this.clusterer.clusteringEnabled = false; 822 this.clusterer.refresh( true ); 823 } else if ( old_level > this.opts.cluster_max_zoom && 824 new_level <= this.opts.cluster_max_zoom ) { 825 this.clusterer.clusteringEnabled = true; 826 this.clusterer.refresh( true ); 827 } 828 } 829 this.last_zoom_level = new_level; 830 }, 831 832 objectLinkHtml : function(object_id) { 833 return ['<a href="#', 834 this.opts.name, 835 '" onclick="frames[\'', 836 this.opts.name, 837 '\'].GeoMashup.clickObjectMarker(', 838 object_id, 839 ');">', 840 this.objects[object_id].title, 841 '</a>'].join(''); 842 }, 843 844 /** 845 * Whether a marker is currently visible on the map. 846 * @param {Marker} marker 847 * @return {Boolean} False if the marker is hidden or outside the current viewport. 848 */ 849 isMarkerVisible : function( marker ) { 850 // Provider override 851 return false; 852 }, 853 854 /** 855 * Recompile the list of objects currently visible on the map. 856 */ 857 updateVisibleList : function() { 858 var list_element, header_element, list_html, list_count = 0; 859 860 if (this.have_parent_access && this.opts.name) { 861 header_element = parent.document.getElementById(this.opts.name + "-visible-list-header"); 862 list_element = parent.document.getElementById(this.opts.name + "-visible-list"); 863 } 864 if (header_element) { 865 header_element.style.display = 'block'; 866 } 867 if (list_element) { 868 list_html = ['<ul class="gm-visible-list">']; 869 this.forEach( this.objects, function (object_id, obj) { 870 if ( this.isObjectOn( obj ) && this.isMarkerVisible( obj.marker ) ) { 871 list_html.push('<li><img src="'); 872 list_html.push(obj.icon.image); 873 list_html.push('" alt="'); 874 list_html.push(obj.title); 875 list_html.push('" />'); 876 list_html.push(this.objectLinkHtml(object_id)); 877 list_html.push('</li>'); 878 list_count += 1; 879 } 880 }); 881 list_html.push('</ul>'); 882 list_element.innerHTML = list_html.join(''); 883 /** 884 * The visible posts list was updated. 885 * @name GeoMashup#updatedVisibleList 886 * @event 887 * @param {GeoMashupOptions} properties Geo Mashup configuration data 888 * @param {Number} list_count The number of items in the list 889 */ 890 this.doAction( 'updatedVisibleList', this.opts, list_count ); 891 } 892 }, 893 894 adjustViewport : function() { 895 this.updateVisibleList(); 896 }, 897 898 createMap : function(container, opts) { 899 // Provider override 900 } 901 }; 902