Albert Jan Schot
 

Virtual Earth 'nearest' location

22

Jan

Ever wonder what happens when you put your team from work in a locked room for 24 hours and ‘force’ them to make a site based on a certain ‘issue’. If you do take a look at Les 24 heures de Tam Tam (In Dutch though). Last Friday we had this ‘event’ for the first time and as you can on the pictures / movies we had a lot of fun.

It was for this event that I had brush up my Virtual Earth skills, since as a team we decided to make a small ‘route planner’ based on Virtual Earth. The idea was to create a new map, set up a layer containing a set of pushpins with the locations of all Snack corners, and based on the current location of the user show a route to the nearest Snack Corner. Pretty plain and simple we thought.  It happens to be a bit complicated though; the Virtual Earth API doesn’t have an option to get nearest pushpin, and getting the route for each pushpin and then deciding what the nearest location is would probably fail when there are more than 5 pushpins.

Then the old school math struck me: why not use Pythagoras do decide what the nearest pushpin is (though it might be not as precise as getting the Route and determine the exact distance,  but getting the overall distance it is a great improvement on performance).

So assuming you have a map, containing a set of pushpins (tip: check the veLayerSpec with GeoRSS), you can loop trough each pushpin and set the latitude and longitude to an array, and define two vars, one for the ID of the pushpin and one of the value of the Pushpin ( I used 999).  Then the next simple function  will be sufficient (comments inline):  

    function GetNearest(latlon)

    // latlon should be the location searchin from

    {  

        var temp = new String(latlon);

        var mySplitResult = temp.split(",")

       

        var searchLat = mySplitResult[0];

        var searchLon = mySplitResult[1];

       

        // latlon is striped in to two values

        // pushPinsLat is the lenght of the array that we will check our current lat/lon against

        // So we loop trough the array with pushpin locations

       for (var i=0, len=pushPinsLat.length; i<len; ++i )

       {

         

         var ppLat = pushPinsLat[i];

         var ppLon = pushPinsLon[i];

        

         // Math.abs returns an absolute value;

         var latcount = Math.abs(ppLat - searchLat);

         var loncount = Math.abs(ppLon - searchLon);

        

         //Math.pows gets our lat/lon raised to the power of 2

         var temp = Math.pow(latcount, 2);

         var temp2 = Math.pow(loncount, 2);

        

         // When we have the lat / lon we can do a square root

         // the dst will resemble the 'distance',

         var dst = Math.sqrt(temp+temp2);

        

         // Then only set the the pushpinVar containing the ID when the current ID is smaller

         // When were done looping trough the set ppID contains the pushpin ID nearest to current location

         if (dst < ppValue)

         {     

             ppValue = dst;

             ppID = latlon;

         }

       }

    }

 

So the math saved the day, since the actual getting of the route is easy; the API allows you to get a route with map.GetDirections(locations, options).  

Albert-Jan Schot schreef

Comments (0)

Albert-Jan Schot

XSLT Tricks

14

Jan

Doing the XSLT for SharePoint can be frustrating, especially getting custom site columns or values you’re not sure about if they actually exists in a certain context. For the standard item styles most of you might know the trick:

<xsl:for-each select="@*">

      <xsl:value-of select="name()"/> -

      <xsl:value-of select="."/>  <br/>

</xsl:for-each>

 

That let you display all the variables you can access with name and value.  Now that makes sense, it allows you to pick the values you want, and put them easily in your xHTML template creating very nice looking views.

Since you can style the search almost the same way as standard XSLT results one would assume you can use the same template to display all the properties accessible from search, too bad that assumption is wrong.  The trick would be the following code:

<?xml version="1.0" encoding="UTF-8"?>

      <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

            <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

                  <xsl:template match="/">

                        <xmp><xsl:copy-of select="*"/></xmp>

                  </xsl:template>

</xsl:stylesheet>

 

It will allow you to get all indexed values that you can use, combining that with adding additional columns you can easily check whether the columns are filled for a specific result set. (By adding columns to the search results you can see if a certain column is filled as you expected).  

Albert-Jan Schot schreef

Comments (0)

Albert-Jan Schot

jQuery, XSL en SharePoint

09

Jan

In a World where everything moves, even static information needs changes. And since jQuery appeared a lot of information on the web became more interactive. (At least for developers it is way easier to implement nice graphical add-ons). For the most of us jQuery is a way to give some more user experience in the things you develop.  In this first post on this blog there will be a step by step guide of how to apply jQuery to a standard SharePoint content query (in the example we will  make a accordion style to a list). Examples of this in plain HTML can be found on http://jquery.bassistance.de/accordion/demo/

And a few screenshots how it will look in MOSS:

To understand the working of the ContentQuery Webpart we first have to take a look at a standard CQWP with grouping activated:  

<div class="groupheader item medium">Header</div>

<div id="linkitem" class="item link-item">

      <a href="#" target="" title="">Link</a>

</div>

However to use jQuery and especially the accordion function we need to tidy up the HTML a bit, and apply some minor fixes so the final result will look like:

<ul class="groupheader item medium">

      <li>

            Header

            <ul class="item link-item">

                  <li>Link</li>

            <ul>

      <li>

<ul>

This will allow the jQuery engine to automatically hide or unhide the items within the First <ul>.

To fix this there are several changes on the itemstyle.xsl, the headerstyle.xsl and the contentquerymain.xsl, and to do so some basic experience of xHTML / CSS and xsl(t) can come in handy.

The First step is to create a new header template that displays the header as a link (obviously that should be done in the headerstyle.xsl).

<xsl:template name="HeaderStyle" match="*" mode="header">

            <xsl:param name="CurPosition" />

            <a href="#" class="head">

                  <xsl:call-template name="OuterTemplate.GetGroupName">

                        <xsl:with-param name="GroupName" select="@*[name()=$Group]"/>

                        <xsl:with-param name="GroupType" select="$GroupType"/>

                  </xsl:call-template>

            </a>

      </xsl:template>

 

And for the itemstyle we need to create another itemstyle template that displays each item as a <li>.

<xsl:template name="ListLink" match="Row[@Style='ListLink']" mode="itemstyle">

            <xsl:param name="CurPos" />

            <xsl:variable name="SafeLinkUrl">

                  <xsl:call-template name="OuterTemplate.GetSafeLink">

                        <xsl:with-param name="UrlColumnName" select="'LinkUrl'"/>

                  </xsl:call-template>

            </xsl:variable>

            <xsl:variable name="DisplayTitle">

                  <xsl:call-template name="OuterTemplate.GetTitle">

                        <xsl:with-param name="Title" select="@Title"/>

                        <xsl:with-param name="UrlColumnName" select="'LinkUrl'"/>

                  </xsl:call-template>

            </xsl:variable>

            <xsl:variable name="LinkTarget">

                  <xsl:if test="@OpenInNewWindow = 'True'" >_blank</xsl:if>

            </xsl:variable>

            <li>

                  <a href="{$SafeLinkUrl}" target="{$LinkTarget}" title="{@LinkToolTip}" >

                        <xsl:value-of select="$DisplayTitle" />

                  </a>

            </li>

      </xsl:template>

Again we make a new template, this time in the ContentQueryMain.xsl where all the action will take place:

      <xsl:template name="OuterTemplate.BodyWoonbronContact">

            <xsl:param name="Rows" />

            <xsl:param name="FirstRow" />

            <xsl:param name="LastRow" />

 

      </xsl:template>

First thing to fix in this template is a reference to the javascript we are going to use. These scripts can be found on http://docs.jquery.com/Downloading_jQuery and http://bassistance.de/jquery-plugins/jquery-plugin-accordion/ . Besides these scripts there is a minor ‘hardcoded’ javascript that should be done, but it isn’t much.  

            <script type="text/javascript" src="/_layouts/1043/jquery.js"></script>

            <script type="text/javascript" src="/_layouts/1043/jquery.accordion.js"></script>

 

            <script type="text/javascript">

                  $(document).ready(function(){

                  $('.contactgegevens').accordion({

                  autoheight: false,

                  alwaysOpen:false,

                  active: false,

                  header: '.head'

 

                  });

                  });

            </script>

Make sure the references to jquery and jquery.accordion are correct, and the scripts can be found from wherever  your webpart might be.

Then we can write the headers and elements for each item and its header. They will of course start with a <ul> class. A little note here is that the first <ul> class should correspond to the described class in the little piece of JavaScript we wrote, so that jQuery knows what to do with the list.

The next step is a matter of 4 checks to make sure each header starts with an <ul>, and ends with it whenever the last item in it is written. First thing is a check to close any headers (if there is one). Next thing is a new list item (in this case a header), then a corresponding <ul> that will contain all the items that belongs to the header.  And finally if there are no more items everything will be closed properly.

      <ul class="contactgegevens">

            <xsl:for-each select="$Rows">

                  <xsl:variable name="CurPosition" select="position()" />

                        <xsl:if test="($CurPosition &gt;= $FirstRow and $CurPosition &lt;= $LastRow)">

                        <xsl:variable name="StartNewGroup" select="@__begingroup = 'True'" />

                        <xsl:variable name="StartNewColumn" select="@__begincolumn = 'True'" />

                        <xsl:choose>

                                    <xsl:when test="$StartNewGroup">

                                    <xsl:if test="$CurPosition != $FirstRow">

                                    <xsl:text disable-output-escaping="yes">

                                          <![CDATA[

                                                </ul>

                                                </li>

                                          ]]>

                                    </xsl:text>

                                    </xsl:if>

                                    <xsl:text disable-output-escaping="yes">

                                          <![CDATA[

                                                <li>

                                          ]]>  

                                    </xsl:text>

 

                                    <xsl:call-template name="OuterTemplate.CallHeaderTemplate">

                                          <xsl:with-param name="CurPosition" select="$CurPosition" />

                                          </xsl:call-template>

                                    <xsl:text disable-output-escaping="yes">

                                          <![CDATA[

                                                <ul>

                                          ]]>

                                    </xsl:text>

                              </xsl:when>

                        </xsl:choose>

                              <xsl:call-template name="OuterTemplate.CallItemTemplate">

                              <xsl:with-param name="CurPosition" select="position()" />

                                    <xsl:with-param name="LastRow" select="$LastRow" />

                        </xsl:call-template>

                        </xsl:if>

                  <xsl:if test="$CurPosition = $LastRow">

                        <xsl:text disable-output-escaping="yes">

                              <![CDATA[

                                    </ul>

                                    </li>

                              ]]>

                        </xsl:text>

                  </xsl:if>

            </xsl:for-each>

      </ul>

After all these changes (make sure you publish a major version of the files in order to let others see your changes), are done you can use the corresponding Header- and itemstyle in any ContentQuery Webpart, and choose for Group.

 

Albert-Jan Schot schreef

Comments (0)

Albert-Jan Schot