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