Google Maps

The site I am working on moving over uses Google Maps on one of the pages. I have some ideas for implimenting this, but I wanted to see if anyone else has done it, and might be willing to share some tips.

Currently we have one or two google maps but they are outside Rx as we do not know how to implement them either. as this type of interactive content is likely to become more popular, any guidance on how best to get these managed by Rx would be very helpful.

Cara

The way that I see it, there are four distinct problems:
[ol]
[li]Calling the API in the head[/li][li]Loading specific data[/li][li]Calling the load and unload functions in the body tag[/li][li]The Google Maps div itself[/li][/ol]

My initial answer is some conditional tags in the global template based on specific content IDs, which works right now since we only have one page that needs it, but isn’t exactly scalable.

I have been thinking about creating a specific content type for items 2 and 4 and loading them into pages via slots in the global. Item 1 could be part of the slot, and you could then run a conditional statement that checks to see if those slots are filled and, if so, add in item 3.

I am new enough to Rx that I am not sure if this solution is the cleanest, but right now it is all I have got.

You could set up a page template that does not use a global template, hence can have its own head and body tags. Then a dispatch template to decide, based on a field in the content type, e.g. a checkbox labelled “Include Google Map?”, to decide whether to use that page template. It would mean having to keep this page template up-to-date with changes to the look-and-feel of the global template(s).

Andrew.

Sean,

I think you’re on the right track. However, there is no reason to have multiple slots.

If you have multiple templates (1 to render the head script tag, 1 to render the body onload, etc), you can call them all from the same slot by overriding the template name in the parameters you pass to the #slot macro. (in 5.x, we had to do this with an extension named “MutateSlotVariants”, but in 6.x, this is just a matter of passing the template name. You’re better off not knowing about the old technique).

It may also be possible to pass binding variables. Variables you set in the bindings tab will be visible to the global template. So, if you have a binding named “$bodyunload” that you set in the page template, your global template would simply need to check if $bodyunload is empty, and include it in the body tag if it isn’t.

Note that this DOES NOT work with variables you set in the template body (with #set), the variables have to be set in the bindings tab.

This same technique works with snippets that you include in a page as well as global templates.

I would not recommend using a page without a global template: that will lead to double maintenance (as Andrew pointed out). I think it’s possible to do this with templates and binding variables in a reasonably scalable way.

If you get this working, I’d love to see you post a description of how you did it. I’m sure that there are others with a similar desire. If you get stuck, please let me know, and I’ll help where I can.

I hope this helps.

Dave

Thanks Andrew and Dave, those are both good suggestions. Sounds like Dave’s ideas will result in a clean implimentation. I’ll try it later today and see how it goes.

Is there somewhere in the forums or documentation I can look to see an example of how to change the template in the slot? That would actually help me in other areas of this design as well.

Sean,

to set the template in a slot, you have to pass a parameter named “template” to the last argument in the #slot macro. There are 2 ways to do this:

use a String, and pass the parameters “query string” style:

#slot(“myslot” $beforeslot $beforeitem $afteritem $afterslot “template=foo&max_results=10”)

or build a map

#set($slotParams.template=“foo”)
#set($slotParams.max_results=10)
#slot(“myslot” $beforeslot $beforeitem $afteritem $afterslot $slotParams )

Note that there are NO QUOTES on the last parameter (this is the most common mistake I see people make when using a map)

Back to the <script> tags in the head: I had an even better idea, which is to use the #nodeslot macro. I will build a simple example of this and post it, it’s too hard to explain without it.

Dave

As promised, here’s a simple example of how to embed a script in the global header. This is rather long, so bear with me.

It turns out that the #node_slot macro is not the way to do this, you need to write a new macro to call the assembly service. I just happened to have one on hand (written by one of our consultants). You can add this to your template, or add it to /Rhythmyx/rx_resources/vm/rx_assembly.vm. It does require the PSOToolkit, unfortunately, since Velocity’s handling of Lists is rather lame, we had to write a Java function to get around it.

#macro(render_item $assemblyItem $snippetName)
#set($calloutTemplate = $sys.asm.findTemplateByName($snippetName))
#set($calloutItem = $assemblyItem.clone())
$calloutItem.setTemplate($calloutTemplate)
#set($resultList = $sys.asm.assemble($user.psoListTools.asList($calloutItem)))
$rx.doc.extractBody($resultList.get(0).toResultString())
#end

This macro will return the body of a snippet template for the current item (or any other item you can get your hands on).

I created a content type (I called it gmPage), which has 2 fields a “header_script” field and a body field. I gave this type a simple snippet that renders the script in a script tag: (this template is named gmScript)

<html>
   <head>
	<!-- note: this only shows in preview --> 
     <meta content="Percussion Rhythmyx" name="generator"/>
      <title>GM Header Script</title>
   </head>
   <body>
	  <script type="text/javascript">#displayfield("header_script")</script>
   </body>
</html>

I then created a simple page template that renders the body (template gmPage)


<html>
   <head>
     <meta content="Percussion Rhythmyx" name="generator"/>
      <title>local title</title>
   </head>
   <body>
      <h2>Page title</h2>
	  #field("body")
   </body>
</html>

The next step is a global template that will include the script tag in the head:

<head>
     <meta content="Percussion Rhythmyx" name="generator"/>
      <title>Global Page Title</title>
	  #if($hasHeaderScript)##
      #render_item($sys.assemblyItem "gmScript")
	  #end##
   </head>
   <body
#if($bodyOnLoad)##
	onLoad="$bodyOnLoad"
#end##
	>	
	#inner()
</body>
</html>

Note that there are 2 variables referenced in the global template. These are in the bindings tab of the gmPage template:

$hasHeaderScript="true"
$bodyOnLoad="alertmenow()"

I created a simple content item where the “header script” field contains the alertmenow() function:


function alertmenow() { alert("alert me now!!!") };

When I preview the item, the script will run. Here’s the rendered source (with some of the whitespace removed)

<html>
<head>
     <meta content="Percussion Rhythmyx" name="generator"/>
      <title>Global Page Title</title>
	  <script type="text/javascript">function alertmenow() { alert("alert me now!!!") };</script>
	     </head>
   <body
	onLoad="alertmenow()"
	>	
      <h2>Page title</h2>
	  <p>This is the body field. </p>   </body>
</html>

You should be able to extend this technique to add scripts in the global header, and the body on load and on unload events.

The exported content types and templates are in the attached zip file.

BTW, I checked with our presales guys, they show Google maps in some demos. They are just including the <script> tags and the <div> tag directly in the body of the page.

They get around the unload by calling:

  
    if (!document.body.getAttribute("onunload")){
       document.body.onunload = 'GUnload();';
    } else {
       document.body.onunload = document.body.onunload + '; GUnload();';
    }

They are also including the call to the Google script the same way:

<script idtype="text/javascript" src="http://www.google.com/jsapi?key=AB....">;</script>

and they have a callback function which they declare in the script:

function maps_initialize() {

      if (GBrowserIsCompatible()) {
        var map = new GMap2(document.getElementById(maptag));
        map.addControl(new GLargeMapControl());
        map.addControl(new GMapTypeControl());
        geocoder = new GClientGeocoder();
  if (maplocation != '') {
      maps_showAddress(map, maplocation);
  } else {
        maps_showAddress(map, "Boston, MA");
  }
      }
    }
    function maps_showAddress(map, address) {
      if (geocoder) {
   geocoder.getLatLng(
              address,
              function(point) {
                if (!point) {
                  alert(address + " not found");
                } else {
      map.setCenter(point, zoom);
                  var marker = new GMarker(point);
      map.addOverlay(marker);
      marker.openInfoWindowHtml(mapbubble);
                }
              }
            );
      }
    }
 google.setOnLoadCallback(maps_initialize);

I do think your method (putting the scripts in the head) is better, but it’s clear that you don’t really need to make Google maps work.

Dave

Thanks for all of that - very informative. I am actually going to sit down and start hammering on this now.

I had thought about putting the tags in the body. I know they can live there, but I prefer not to do that when I don’t have to. I still ended up with a lot of the same issues anyway, so it seemed better to hammer out a solution the way I wanted it instead of the more obvious way.

Making progress, but have run into a roadblock.

It seems that the global template doesn’t see the binding I did for the body tag stuff.

The bindings are in the templates that are being loaded in via the slot, NOT in the content template. I did this so I could include a gmap in any content template that had the gmap slot available.

I have also tried loading the gmap template via an inline template, and seeing if the binding would be visible that way, but it isn’t. The only time the binding loads properly is when I put it in the primary content template.

Not sure if this is supposed to be this way, or a bug, or something I am doing wrong. Hopefully one of the latter two, since the former would kill being able to put the load/unload functions in the body tag.

I also ran into another problem with trying to put the gmap div in via the inline template. For some reason, the editor doesn’t acknowledge that there is content in the gmap template, and removes the whole thing during save. The only way I could make it work was by putting a word in the inline template tags it drops in, which forced it to save it.

Sean,

The Page Template is the “main routine”. It creates a binding map into which the bindings are inserted.

Each snippet template is called from the main page template. Each snippet template receives a COPY of the binding map. All of the values are present, but any modifications to the map are local to the snippet. (This is also true of inline snippets, it makes no difference).

After all of the snippets have been called and the page assembly is complete, the Global Template is called. Again, the global template receives a copy of the binding map, and any modifications made in the global template are not propagated back to the binding map of the page template (although by then, the page assembly is complete).

There’s actually one additional step: the Velocity Assembler (unlike, for example, the Dispatch Assembler) actually has 2 copies of the map: the main instance is created in the bindings, and then a COPY of this map is passed to the Velocity engine. This is why variables you set with #set don’t appear in the snippets or the global template.

This explains why you are seeing the behavior you describe.

This was done intentionally: if there was only one binding map shared amongst all templates, there would be endless opportunities for templates to “step on” each other. Something you set in one binding could be changed based on whether certain snippets where included in the page. We chose a call-by-value approach explicitly, especially since we know that a good number of our users are not programmers, and trying to explain call-by-reference vs call-by-value was more than we felt necessary.

There may, in fact, be ways for you to set “Session Variables” within Rhythmyx, but I want to have some discussions with people in our development team to be make sure we’re not going to get in trouble by doing this.

Dave

I have been pursing a non body tag solution. It seems that the body tags onload and onunload have their fair share of detractors and supporters (as it is with many web things).

So depending on who you listen to, using the body attributes could be bad form, even though it is how Google’s example is set up.

I would like to keep my Rx setup as clean as possible, at least until I reach a point in which there isn’t a legitimate option for me to follow without bending the “rules.” So I am going to go with an option I dug up in searching for alternatives.


function addLoadEvent(func) {
  var oldonload = window.onload;
  if (typeof window.onload != 'function') {
    window.onload = func;
  } else {
    window.onload = function() {
      if (oldonload) {
        oldonload();
      }
      func();
    }
  }
}

addLoadEvent(load);

// arrange for our onunload handler to 'listen' for onunload events
if (window.attachEvent) {
        window.attachEvent("onunload", function() {
                GUnload();      // Internet Explorer
        });
} else {

        window.addEventListener("unload", function() {
                GUnload(); // Firefox and standard browsers
        }, false);

}