System Alert in sitecore

27. april 2010 by Thomas Stern

When working with large Sitecore implementation and customers that have a lot of editors maintaining the content of their website, often makes updating the system becomes a hurdle. Because editors may or may not work in on the same floor or even the same building, so even if you have an contact person who's responsibility is to notify editor about the downtime, it is possible that one or more editors haven't heart about the downtime and haven't save the what they where doing the second the system is taken offline, a valuable work may have been lost.
So I thought what would be more smart the implementing a System alert/ notifier system that alert the users about the forthcoming downtime. This Alert could as in this example be a simple javascript alert.
My solution is build around simple Javascript calling a webservice which looks for alert placed inside sitecore and display them as a simple javascript alert. Yes some may argue that I have some hardcoded path string and what have we not, but it is left to you to move these to fx. The web.config. Even more this solution I maybe a little over the edge when looking at the implementation, but I se so many usages for this so I went ALL-IN as implemented with interface and using a provider model. The solution is build and tested against a Sitecore 6.2, but nothing wrong with using on other Sitecore versions.
But here goes first the javascript since Sitecore editors have three different editors Page Edit, Content Editor and the Desktop. So we need to include the javascript in three different files, and because of the we need to ensure that the file is only loaded once so logged into the Desktop and opening the content editor doesn't give two warnings, hence the cookie check. Now to javascript, it's all simple stuff.
The javascript should be include in these three files
Webedit:
sitecore\shell\Applications\WebEdit\WebEditRibbon.aspx
Content Editor:
sitecore\shell\Applications\Content Manager\Default.aspx
Desktop:
sitecore\shell\Applications\Startbar\Startbar.xml

/* Function for calling the webservice                             */
function callWebservice() {
  var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
  xmlhttp.open("GET", "/Components/SystemNotifier/AjaxWebservice/SystemNotifier.asmx/GetAlerts", false);
  xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
  xmlhttp.send();

  if (xmlhttp.status == 200) {
    var str = xmlhttp.responseXml.getElementsByTagName("string").item(0).text;
    return str;
  }
}

/* function that Get system alerts */
/* update timer by calling the webservice    */
function GetSystemAlerts() {
  var alertString = callWebservice();
  if (alertString != "") {
     alert(alertString);
     //increase time to next call so we dont get same alert twice
     setTimeout("GetSystemAlerts()", 125000);
  }
  else {
    setTimeout("GetSystemAlerts()", 60000);
  } 
}

var cookieName = "SitecoreSystemNotifier";

function writeCookie() {
 document.cookie = cookieName;
}

function cookieExists()
{

 if (document.cookie.length >0 )
 {
   var offset = document.cookie.indexOf(cookieName);
   if (offset != -1)
     return true;
   return false;
 }
 return false;
}


function init(){
 if(!cookieExists()){
  writeCookie();
  //SetTimeout in ms
  setTimeout("GetSystemAlerts()", 60000);
 }
}

init();

 

Okay now that we have the javascript we need the webservice to be called. It's fairly simple when using the Provider.

 

namespace SystemNotifier.AjaxWebservice
{
  /// 
  /// Summary description for SystemNotifier
  /// 
  [WebService(Namespace = "http://pentia.dk/")]
  [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
  [System.ComponentModel.ToolboxItem(false)]
  // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 
  [System.Web.Script.Services.ScriptService]
  public class SystemNotifier : WebService
  {

    [WebMethod]
    public string GetAlerts()
    {
      ISystemAlert alert = AlertProvider.NextAlert;
      if (alert != null)
        return alert.Text;
      return "";
    }

    private SystemAlertProvider _provider;
    private SystemAlertProvider AlertProvider
    {
      get
      {
        if (_provider == null)
          _provider = new SystemAlertProvider();
        return _provider;
      }
    }
    
  }
}

And now to the provider implementation

 public class SystemAlertProvider
  {
    private IEnumerable _alerts;
    public IEnumerable Alerts
    {
      get {
      if(_alerts == null)
       _alerts = GetAlertsFromSitecore();
       return _alerts;
      }
    }

    private TimeSpan timespan = new TimeSpan(0, 1, 0);
    private IEnumerable GetAlertsFromSitecore()
    {
      ChildList childList = AlertRootItem.Children;
      foreach(Item child in childList)
      {
        ISystemAlert alertItem = new SystemAlert(child);
        if(alertItem.AlertTime > DateTime.Now.Subtract(timespan))
          yield return alertItem;
      }
    }

    private const string sitecoreRootPath = "/sitecore/system/SystemAlertNotifier";
    private Item _rootItem;
    private Item AlertRootItem
    {
      get
      {
        if(_rootItem == null)
         _rootItem = Database.GetItem(sitecoreRootPath);
        return _rootItem;
      }
    }

    private const string _databaseName = "master";
    private Database Database
    {
      get
      {
        return Database.GetDatabase(_databaseName);
      }
    }
    public ISystemAlert NextAlert
    {
      get
      {
        if(Alerts.Count() > 0)
          return Alerts.OrderBy(w => w.AlertTime).First();
        return null;
      }
    }
  }

And finally the Alert interface and implementation of the same.

Inteface

public interface ISystemAlert
  {
    DateTime AlertTime { get; }
    String Text { get; }
  }

Implementaion

public class SystemAlert : ISystemAlert
  {
    public SystemAlert(Item item)
    {
      Item = item;
    }

    private Item Item
    {
      get;
      set;
    }

    private const string _alertTimeField = "SystemAlert_AlertTime";
    public DateTime AlertTime
    {
      get
      {

        DateField dateField = Item.Fields[_alertTimeField];
        return dateField.DateTime;
      }
    }

    private const string _textField = "SystemAlert_Text";
    public string Text
    {
      get { return Item[_textField]; }
    }
  }

Now we got all the code working so now we need to have someway to get the info, let's use a sitecore item. So here is a snapshot of the how my sitecore item looks.

sys2

So this is pretty much everything you need to have a system alert system up and running inside sitecore. Remember to edit hardcode root path to system alert root folder.
You can download the project in the download section link here.

And hope you can see the posiblities in this solution or implementaion, you could scheduled downtown and have email alert, downtime calendar and much much more hope you enjoy,

 

Seamless integration of external photo gallery

12. marts 2010 by Thomas Stern

In this post I will give one way, or my take on how-to integrate an external photo gallery seamless in to your website. Some of the advantage in doing this is you save room/space on your local webhost, and the load on the serve if you have large photo/images since these are not load in another thread on an external server. Off course there are disadvantage as well the gallery provide have to be online for this to work, and you have to maintain gallery content and web content on two different websites. On the other hand you if you choose one of the large providers flickr or picasa, you get some great photo gallery functionality tagging of images and galleries and more.

So i don't what to build the new Picasa or a like, so i will only provide functionality for showing frontend relevant images and associated information. In near future there will be a post on how-to use this post to integrate into Umbraco.

For this post i have chosen to integrate up against Google's Picasa so to start with you can go and download the google-gdata API you can get it here

The documentation i used to for making this post and code can be found here
The main idea for making this seamless is it could be easy to switch gallery provider from picasa to whatever your favorite web-photo gallery you use. Off course the difficulties you meet depend on how well an interface the provider gives you, you could end up in some tricky situations.


Okay let's get started. First of I've created the interfaces I've so necessary for making a good web gallery. You are more than welcome to give feedback if you think something are missing on one these.

There exist three parts. A gallery which consists of one or more photo albums. Next there off course a photo album which contain some album information and one or more Images. Last off is the image it self. So here are the three interfaces.

 

 
public interface IGallery
  {
    String Name { get; }
    String Description { get; }
    int NumberOfAlbums { get; }
    IEnumerable Albums{get;}
    IGalleryAlbum GetAlbumFromId(string id);
  } 
 
public interface IGalleryAlbum
  {
    String Id { get; }
    String Name{ get; }
    String Description { get; }
    String Category { get; }
    IGalleryImage AlbumCoverImage { get; }
    uint NumberOfImages { get; }
    IEnumerable Images { get; }
  }
 
 public interface IGalleryImage
  {
    String Name { get; }
    String Url { get; }
    String Description { get; }
    String ThumbnailUrl { get; }
    Dictionary ExifData { get; }
  }


Again you are more than welcome to give feedback if you think something is missing, keep in mind this is what I think is one a good and simple image gallery should be able to provide of information.
So now we have the interfaces in place lets go ahead an implement them using the Picasa API.
This is a pretty simple task if keep a window open with the documentation here is another link to the documentation
All I've used is the simple example provide at the documentation page.
First of the gallery implementation this is simple

namespace PicasaGalleryModel
{
  public class PicasaGallery : IGallery
  {
    private PicasaService _service;
    private PicasaFeed _feed;


    private const string PICASA_SERVICE_NAME = "PicasaIGalleryModel";

    public PicasaGallery(string username)
    {
      Username = username;
    }

    public string Name
    {
      get
      {
        return Feed.Title.Text;
      }
    }

    public string Description
    {
      get { return ""; }
    }

    public int NumberOfAlbums
    {
      get { return Albums.Count(); }
    }

    public IEnumerable Albums
    {
      get
      {
        return InitializeGalleryAlbumFromPicasaFeed(); 
      }
    }

     public IGalleryAlbum GetAlbumFromId(string id)
     {
       return Albums.Where(g => g.Id == id).First();
     }

    private PicasaFeed Feed
    {
      get
      {
        if (_feed == null)
          _feed = RetrieveUserAlbumsFromPicasa();
        return _feed;
      }
    }

    private PicasaFeed RetrieveUserAlbumsFromPicasa()
    {
      AlbumQuery query = new AlbumQuery(PicasaQuery.CreatePicasaUri(Username));
      PicasaFeed feed = Service.Query(query);
      return feed;
    }

    private IEnumerable InitializeGalleryAlbumFromPicasaFeed()
    {
      foreach (PicasaEntry entry in Feed.Entries)
      {
        IGalleryAlbum album = new PicasaAlbum(entry,Service,Username);
        yield return album;
      }

    }

    private PicasaService Service
    {
      get
      {
        if(_service == null)
          _service = new PicasaService(PICASA_SERVICE_NAME);
        return _service;
      }
    }

    private void Logon()
    {
    }

    protected string Password
    {
      get;
      set;
    }

    protected string Username
    {
      get; set ;
    }
  }
}



DO note that the username is the logon name you use to logon to picasa.
The implementation have some unused and unfinished function, when I first the idea to this post it started as huge project where I wanted security from picasa as well to be covered, in this implementation.
Wow this class uses the IGalleryAlbum so let's move on to the implantation of this.

namespace PicasaGalleryModel
{
  public class PicasaAlbum : IGalleryAlbum
  {
    private PicasaEntry _albumFeed;
    private PicasaFeed _imageFeed;
    private AlbumAccessor _ac;

    public PicasaAlbum(PicasaEntry feed,PicasaService service,string username)
    {
      AlbumFeed = feed;
      Service = service;
      Username = username;
    }

    public string Name
    {
      get { return AlbumAccessor.AlbumTitle; }
    }

    public string Description
    {
      get { return AlbumAccessor.AlbumSummary; }
    }

    public string Category
    {
      get { return "CAT-SET_STATIC"; }
    }

    public IGalleryImage AlbumCoverImage
    {
      get
      {
        PhotoQuery query = PhotoQueryFromUri(AlbumFeed.Id.AbsoluteUri);
        return BuildIGalleryImageFromPicasa(Service.Query(query)).First();
      }
    }

    private AlbumAccessor AlbumAccessor
    {
      get
      {
        if (_ac == null)
          _ac = new AlbumAccessor(AlbumFeed);
        return _ac;
      }
    }

    public uint NumberOfImages
    {
      get
      {
        
        return AlbumAccessor.NumPhotos;
      }
    }

    public IEnumerable Images
    {
      get 
      {
        return BuildIGalleryImageFromPicasa(ImageFeed);          
      }
    }

    private IEnumerable BuildIGalleryImageFromPicasa(PicasaFeed feed)
    {
      foreach (PicasaEntry entry in feed.Entries)
      {
        IGalleryImage image = new PicasaImage(entry);
        yield return image;
      }
    }

    public PicasaEntry AlbumFeed
    {
      get { return _albumFeed; }
      set { _albumFeed = value; }
    }

    public PicasaFeed ImageFeed
    {
      get
      {
        if(_imageFeed == null)
        {

          _imageFeed = Service.Query(PhotoQueryFromUri(PicasaImageUri()));
        }
        return _imageFeed;
      }
    }

    private string PicasaImageUri()
    {
      return PicasaQuery.CreatePicasaUri(Username, Id);;
    }

    private PhotoQuery PhotoQueryFromUri(string uri)
    {
      return new PhotoQuery(uri);
    }

    private PicasaService Service
    {
      get; set;
    }

    public String Id
    {
      get
      {
        return AlbumAccessor.Id;
      }
    }

    private string Username
    {
      get;
      set;
    }
  }
}



And now to the final part the Image implementation

namespace PicasaGalleryModel
{
  public class PicasaImage : IGalleryImage
  {

    private PhotoAccessor _photoAccessor;

    private Dictionary _exifData;

    public PicasaImage(PicasaEntry entry)
    {
      Entry = entry;
    }

    private PicasaEntry Entry
    {
      get;
      set;
    }

    public string Name
    {
      get
      {
        return PhotoAccessor.PhotoTitle;
      }
    }

    public string Url
    {
      get
      {
        return Entry.Media.Content.Attributes["url"].ToString();
      }
    }

    public string Description
    {
      get { return PhotoAccessor.PhotoSummary; }
    }

    public string ThumbnailUrl
    {
      get
      {
        return Entry.Media.Thumbnails[0].Attributes["url"].ToString();
      }
    }

    public Dictionary ExifData
    {
      get
      {
        if (_exifData == null)
          InitializeExifDataToDictionary();
        return _exifData;
      }
    }

    private void InitializeExifDataToDictionary()
    {
      _exifData = new Dictionary();
      _exifData.Add("Camera model", Entry.Exif.Model.Value);
      _exifData.Add("ISO", Entry.Exif.ISO.Value);
      _exifData.Add("Focal Length", Entry.Exif.FocalLength.Value);
      //_exifData.Add("Exposure", Entry.Exif.Exposure.Value);
      _exifData.Add("F Stop", Entry.Exif.FStop.Value);
      _exifData.Add("Flash", Entry.Exif.Flash.Value);
    }

    

    public PhotoAccessor PhotoAccessor
    {
      get
      {
        if(_photoAccessor == null)
          _photoAccessor = new PhotoAccessor(Entry);
        return _photoAccessor;
      }
    }

  }
}



Now with the model in place we can start to render out the IGallery* stuff.
This is made so it should be easy to integrate into Umbraco hence the MasterPage file.
The frontend stuff consist of the main gallery which loads in two different controls depending on you are viewing a list of albums or a list images in an album. The styling is left for you do, since this is dependent on your website design. If you like a can In a later post do this.
The main gallery

The .ascx page

<%@ Page Language="C#" MasterPageFile="~/Gallery.master" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Presentation._Default" %>
<%@ Import Namespace="Interfaces" %>

<asp:Content ID="head" ContentPlaceHolderID="head" Runat="Server">
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/jquery.lightbox-0.5.min.js"></script>
<script type="text/javascript" src="js/InitLightbox.js"></script>
<link rel="stylesheet" type="text/css" href="/CSS/jquery.lightbox-0.5.css" media="screen" />
</asp:Content>

<asp:Content ID="GalleryContent" ContentPlaceHolderID="GalleryContent" Runat="Server">

<div>
My Gallery Test

<asp:PlaceHolder ID="galleryContent" runat="server" />

</div>
</asp:Content>

 


and the codepage

public partial class _Default : System.Web.UI.Page
  {
    private IGallery _gallery;
    public const string ALBUMID = "aid";

    protected void Page_Load(object sender, EventArgs e)
    {

      SetGalleryContent();
    }

   

    private void SetGalleryContent()
    {
      Control view;
      if (String.IsNullOrEmpty(AlbumId))
        view = LoadGalleryView;
      else
        view = LoadAlbumView;
      galleryContent.Controls.Add(view);
    }

    private IEnumerable GalleryAlbums
    {
      get
      {
        return Gallery.Albums;
      }
    }

    private Control LoadGalleryView
    {
      get
      {
        Control viewControl = LoadControl("~/GalleryView.ascx");
        GalleryView gallery = (GalleryView)viewControl;
        gallery.AlbumQueryString = ALBUMID;
        gallery.GalleryAlbums = GalleryAlbums;
        return viewControl;
      }
    }

    private Control LoadAlbumView
    {
      get
      {
        Control viewControl = LoadControl("~/AlbumView.ascx");
        AlbumView gallery = (AlbumView)viewControl;
        gallery.Images = CurrentAlbum.Images ;
        return viewControl;
      }
    }

    private IGalleryAlbum CurrentAlbum
    {
      get
      {
        return Gallery.GetAlbumFromId(AlbumId);
      }
    }
    private string AlbumId
    {
      get
      {
        return Request.QueryString[ALBUMID];
      }
    }

    private IGallery Gallery
    {
      get
      {
        if(_gallery == null)
          _gallery = new PicasaGallery("USERNAME_GOES_HERE");
        return _gallery;
      }
    }
  }


The List view of albums
Ascx page

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="GalleryView.ascx.cs" Inherits="Presentation.GalleryView" %>
<%@ Import Namespace="Interfaces"%>

<asp:Repeater runat="server" ID="AlbumeRepeater" DataSource="<%# GalleryAlbums %>">
<ItemTemplate>
<div class="AlbumCoverImage">
<a href="<%# AlbumLink(((IGalleryAlbum)Container.DataItem).Id) %>" >
<img src="<%# ((IGalleryAlbum)Container.DataItem).AlbumCoverImage.ThumbnailUrl %>" alt="" />
</a>
</div>
<div class="AlbumTitle">
<a href="<%# AlbumLink(((IGalleryAlbum)Container.DataItem).Id) %>" >
<%# ((IGalleryAlbum)Container.DataItem).Name %>
</a>
</div>
<div class="ImageCountInAlbum">
<%# ((IGalleryAlbum)Container.DataItem).NumberOfImages %>
</div>
<div class="AlbumDescription">
<%# ((IGalleryAlbum)Container.DataItem).Description%>
</div>

</ItemTemplate>
</asp:Repeater>

 

Codepage

public partial class GalleryView : System.Web.UI.UserControl
  {
    protected void Page_Load(object sender, EventArgs e)
    {
      AlbumeRepeater.DataBind();
    }

    public string AlbumLink(string albumId)
    {
      string url = String.Format("{0}{1}={2}", Request.RawUrl, AlbumQueryString, albumId);
      return url;
    }

    public string AlbumQueryString
    {
      get; set;
    }

    public IEnumerable GalleryAlbums
    {
      get;
      set;
    }

  }


And finally the list view of images in an album
Ascx page

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="AlbumView.ascx.cs" Inherits="Presentation.AlbumView" %>
<%@ Import Namespace="Interfaces"%>
<asp:Repeater ID="ImageRepeater" runat="server" DataSource="<%#Images %>">
<HeaderTemplate>
<ul id="Gallery">
</HeaderTemplate>
<ItemTemplate>
<li>
<a href="<%# ((IGalleryImage)Container.DataItem).Url %>" class="lightbox" title="<%# ExifData((IGalleryImage)Container.DataItem)%>"><img src="<%# ((IGalleryImage)Container.DataItem).ThumbnailUrl %>" /></a>

</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>

codefile

public partial class AlbumView : System.Web.UI.UserControl
  {
    protected void Page_Load(object sender, EventArgs e)
    {
      ImageRepeater.DataBind();
    }

    public string ExifData(IGalleryImage image)
    {
      string exifData = "";
      foreach(string key in image.ExifData.Keys)
      {
        exifData += string.Format("{0} : {1}
", key, image.ExifData[key]); } return exifData; } public IEnumerable Images { get; set; } }


The final part is using the jquery lightbox again you could switch this to you own favorite gallery viewing service the lightbox for jquery can be found here. I've extend this a bit so you can provide a max image height and max image width, both are found in the solution for this project.
And a service for my loyal readers, you can now download the entire solution which contains all the above models implementations, and all the other good stuff I've covered in this post. Now you can get HERE, Rememer to fill out your own Username

And now you can head over and see when this is integrated into umbraco, i've done this for my site here is a like to my gallery

 

Simple GoogleMap made easy

23. februar 2010 by Thomas Stern

I recently had to make a page showing different locations each with it own little googlemap.

To make it easy to use i used jQuery to hook in a create the googlemap from the html. The map generatet is a simple map with one marker and centered on this marker. with an width and height set to 400px

gmaps

So I used span tag html to set the settings need for generating each googlemap.
I then use jQuery to look for a container that have one or multple of theese maps inside and add a map to the specific "mapItem"

The html needed for one item is as shown below:

 

<div class="Map">
<span class="MapSettings">
<span class="longtitude">Na;</span>
<span class="longtitude">Na</span>
<span class="mapId">Map0</span>
<span class="address">Pentia store kongensgade 66 copenhagen denmark</span>
</span>
<div id="Map0" class="GoogleMap" style="width:400px; height:400px;">
</div>
</div>

 

To get it all to wok you of course need a googlemapApi key get from here

Here is the jquery file i used to hook in on document ready function note if the longtitude and/or latitude isn't set i'l try to and a marker to map from the address if that fails to you'll get an invalide map.

 

 

jQuery(document).ready(function()
{
  buildMapsFromHTML();
}
)

/*****************

*  MAP SEETINGS  *

*****************/
var map;
var geocoder;
var zoomLevel = 14;
var mapIndex = 0;
 

/*****************

*  MAP FUNCTIONS *

*****************/
function buildMapsFromHTML(){
  var googleMaps = jQuery(".Map");
  var i;
  for (i = 0; i < googleMaps.length; i++)
  {
    mapIndex = i;
    addMap(googleMaps[i],mapIndex);
  }
}

function addMap(mapSettings) {
  // GET SETTINGS FROM HTML
  var setting = jQuery(mapSettings).children(".MapSettings");
  var mapId = jQuery(setting).children(".mapId").html();
  var longtitude = jQuery(setting).children(".longtitude").html();
  var latitude = jQuery(setting).children(".latitude").html();
  var address = jQuery(setting).children(".address").html();

 
  if (latitude != "Na" || longtitude != "Na") {
    initializeMap(mapId);
    addMarkerToMap(longtitude, latitude);
    centerMap(longtitude, latitude);
  }
  else{
    generateMapFromAddress(address,mapId);
  }
}

function initializeMap(mapId) {
   map = new GMap2(document.getElementById(mapId));
}

function generateMapFromAddress(address,mapId) {
  var coordinate = cordinatesFromGoogle(address,mapId)
}

function cordinatesFromGoogle(address,mapId) {

  geocoder = new GClientGeocoder();

  geocoder.getLatLng(address, function(point) { 
  if(point) {
    var longtitude = point.lng().toFixed(5)
    var latitude = point.lat().toFixed(5)
    initializeMap(mapId);
    addMarkerToMap(longtitude, latitude);
    centerMap(longtitude, latitude);
  }
  })
}

function addMarkerToMap(longtitude, latitude) {
  var point = new GLatLng(latitude, longtitude);
  var marker = new GMarker(point);
  map.addOverlay(marker);
}

function centerMap(longtitude, latitude) {
   var point = new GLatLng(latitude, longtitude);
    map.setCenter(point, zoomLevel);
}

 

So an example html to bind it all together could look like this:

 

 

<html>
<head>
<script src="http://code.jquery.com/jquery-1.4.2.min.js" type="text/javascript"></script>
<script src="http://maps.google.com/maps?file=api&v=2&sensor=true_or_false&key=localhost type="text/javascript"></script>
<script type="text/javascript" src="googlMaps.js"></script>
</head>

<body>
<div class="Map">
  <span class="MapSettings">
    <span class="longtitude">Na</span>
    <span class="latitude">Na</span> 
    <span class="mapId">Map0</span> 
    <span class="address">store kongensgade 66 copenhagen denmark</span> 
  </span>
    <div id="Map0" class="GoogleMap" style="width:400px; height:400px;">
    </div>
 </div>
 
 <div class="Map">
  <span class="MapSettings">
    <span class="longtitude">Na</span>
    <span class="latitude">Na</span> 
    <span class="mapId">Map1</span> 
    <span class="address">store kongensgade 66 copenhagen denmark</span> 
  </span>
    <div id="Map1" class="GoogleMap" style="width:400px; height:400px;">
    </div>
 </div>
 
 </body>
</html>


If no longtitude or/and latitude is set i gets the marker from the address.
and Na should be used to say that you dont have those values. Offcourse it could be extended to specify which kind of map and so on. This could be set in the settings as well togehter with zoomlevel and so on.

remember to set the googlemap key to your domain