<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Portal on SNow adventures</title>
    <link>https://www.snow-adventures.com/tags/portal/</link>
    <description>Recent content in Portal on SNow adventures</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <copyright>© Samuel Meylan - 2020</copyright>
    <lastBuildDate>Thu, 20 Apr 2023 00:00:00 +0000</lastBuildDate><atom:link href="https://www.snow-adventures.com/tags/portal/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Dynamic countdown message in the portal</title>
      <link>https://www.snow-adventures.com/posts/dynamic-countdown-message-in-the-portal/</link>
      <pubDate>Thu, 20 Apr 2023 00:00:00 +0000</pubDate>
      
      <guid>https://www.snow-adventures.com/posts/dynamic-countdown-message-in-the-portal/</guid>
      <description>This article explains how to implement a dynamic countdown message in the portal, for example to update a timer in real time</description>
      <content:encoded><![CDATA[<h1 id="introduction">Introduction</h1>
<p>This article explains how to implement a dynamic countdown message in the portal. This can be used in various scenarios where a countdown or other variable, dynamic information is updated in the message, like session timeouts, limited-time offers, etc.</p>
<p>For example, it can be a message like &ldquo;You have MM:SS remaining to complete this action&rdquo;.</p>
<video autoplay loop muted playsinline style="width: auto;">
  <source src="/img/dynamiccountdownmessage/dynamic.webm" type="video/webm">
  Your browser does not support the video tag.
</video>
<h1 id="approach">Approach</h1>
<p>The idea is to get the base message with <code>gs.getMessage()</code> in the server script and replace the dynamically the timer using a client script.</p>
<h2 id="why-not-simply-use-a-static-angular-message">Why not simply use a static Angular message</h2>
<p>Angular offers a way to bind dynamic data to the HTML template, but it has limitations when the message itself is partially static with only one part that change dynamically. To my knowledge there isn&rsquo;t a way to easily replace a placeholder (like &ldquo;MM:SS&rdquo;) within the message itself every second.</p>
<h1 id="implementation">Implementation</h1>
<h2 id="server-script">Server Script</h2>
<p>In the Server Script, we basically retrieve the messages and the different options for the time options.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">// get the widget options
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">data</span><span class="p">.</span><span class="nx">timer</span> <span class="o">=</span> <span class="nx">options</span><span class="p">.</span><span class="nx">timeout</span><span class="p">;</span> <span class="c1">// initialize the timer, in milliseconds
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">data</span><span class="p">.</span><span class="nx">timeout</span> <span class="o">=</span> <span class="nx">options</span><span class="p">.</span><span class="nx">timeout</span><span class="p">;</span> <span class="c1">// keep the timeout value, used when resetting the time
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="c1">// get the count down message
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">data</span><span class="p">.</span><span class="nx">countDownMessage</span> <span class="o">=</span> <span class="nx">gs</span><span class="p">.</span><span class="nx">getMessage</span><span class="p">(</span><span class="nx">options</span><span class="p">.</span><span class="nx">countdownmessage</span><span class="p">);</span>
</span></span></code></pre></div><h2 id="client-script">Client script</h2>
<p>In the Client Script, we first need a function to format time in the &ldquo;MM:SS&rdquo; format and set the timer value used in the message:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">// Set the initial value of the time to display to the user
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">c</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">timeMMSS</span> <span class="o">=</span> <span class="nx">formatMillisecondsToMMSS</span><span class="p">(</span><span class="nx">c</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">timer</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Function to convert the millisecond in minutes:seconds (MM:SS) format
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">function</span> <span class="nx">formatMillisecondsToMMSS</span><span class="p">(</span><span class="nx">milliseconds</span><span class="p">){</span> 
</span></span><span class="line"><span class="cl">	<span class="kd">var</span> <span class="nx">seconds</span> <span class="o">=</span> <span class="nb">parseInt</span><span class="p">(</span><span class="nx">milliseconds</span> <span class="o">/</span> <span class="mi">1000</span><span class="p">,</span> <span class="mi">10</span><span class="p">);</span> 
</span></span><span class="line"><span class="cl">	<span class="kd">var</span> <span class="nx">minutes</span> <span class="o">=</span> <span class="nb">parseInt</span><span class="p">(</span><span class="nx">seconds</span> <span class="o">/</span> <span class="mi">60</span><span class="p">,</span> <span class="mi">10</span><span class="p">);</span>   
</span></span><span class="line"><span class="cl">	<span class="nx">seconds</span> <span class="o">=</span> <span class="nx">seconds</span> <span class="o">%</span> <span class="mi">60</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="nx">minutes</span> <span class="o">=</span> <span class="nx">minutes</span> <span class="o">%</span> <span class="mi">60</span><span class="p">;</span>   
</span></span><span class="line"><span class="cl">	<span class="kd">var</span> <span class="nx">formattedTime</span> <span class="o">=</span> <span class="nx">minutes</span><span class="p">.</span><span class="nx">toString</span><span class="p">().</span><span class="nx">padStart</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="s1">&#39;0&#39;</span><span class="p">)</span> <span class="o">+</span> <span class="s2">&#34;:&#34;</span> <span class="o">+</span> <span class="nx">seconds</span><span class="p">.</span><span class="nx">toString</span><span class="p">().</span><span class="nx">padStart</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="s1">&#39;0&#39;</span><span class="p">);</span>   
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="nx">formattedTime</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Then we need to set the timer for the countdown, that will update the time displayed to the user:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">// Start updating the timer every second
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">$timeout</span><span class="p">(</span><span class="nx">updateTime</span><span class="p">,</span> <span class="mi">1000</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">updateTime</span><span class="p">(){</span>
</span></span><span class="line"><span class="cl">	<span class="nx">c</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">timer</span> <span class="o">-=</span> <span class="mi">1000</span><span class="p">;</span> <span class="c1">// Decrease the timer by 1 second
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">	<span class="c1">// check if the time is over
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="k">if</span> <span class="p">(</span><span class="nx">c</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">timer</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="c1">// Set the displayed time to zero (so we never show negative time)
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>		<span class="nx">c</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">timeMMSS</span> <span class="o">=</span> <span class="nx">formatMillisecondsToMMSS</span><span class="p">(</span><span class="s2">&#34;0&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="c1">// perform the required action when time is over, for example redirect somewhere else
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>		<span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">href</span> <span class="o">=</span> <span class="s2">&#34;/somewhere.do&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span> <span class="k">else</span> <span class="cm">/*update counter*/</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">c</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">timeMMSS</span> <span class="o">=</span> <span class="nx">formatMillisecondsToMMSS</span><span class="p">(</span><span class="nx">c</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">timer</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="nx">$timeout</span><span class="p">(</span><span class="nx">updateTime</span><span class="p">,</span> <span class="mi">1000</span><span class="p">);</span> <span class="c1">// Update again after 1 second
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>In some cases, we want to reset the countdown, for example when the user is interacting with the page:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">// Reset the timer when navigating in the page 
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">$rootScope</span><span class="p">.</span><span class="nx">$on</span><span class="p">(</span><span class="s1">&#39;$locationChangeStart&#39;</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">event</span><span class="p">,</span> <span class="nx">newUrl</span><span class="p">,</span> <span class="nx">oldUrl</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">resetTimer</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>  
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Reset the timer on click and keydown events
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nb">document</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;click&#39;</span><span class="p">,</span> <span class="nx">resetTimer</span><span class="p">);</span> 
</span></span><span class="line"><span class="cl"><span class="nb">document</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;keydown&#39;</span><span class="p">,</span> <span class="nx">resetTimer</span><span class="p">);</span>   
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Function to reset the timer 
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">function</span> <span class="nx">resetTimer</span><span class="p">()</span> <span class="p">{</span> 
</span></span><span class="line"><span class="cl">	<span class="c1">// reset the timer 
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="nx">c</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">timer</span> <span class="o">=</span> <span class="nx">c</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">timeout</span><span class="p">;</span> 
</span></span><span class="line"><span class="cl">	<span class="nx">c</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">timeMMSS</span> <span class="o">=</span> <span class="nx">formatMillisecondsToMMSS</span><span class="p">(</span><span class="nx">c</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">timer</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="html-template">HTML template</h2>
<p>And the last, but not the least, here is the HTML template. To display the dynamic message, it call the function <code>formatCountDownMessage()</code> with the message and the time in the <em>MM:SS</em> format.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">span</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;countdownmessage&#34;</span><span class="p">&gt;</span>{{c.formatCountDownMessage(data.countDownMessage, data.timeMMSS)}}<span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>and the corresponding function (in the client script!):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">// Add the formatCountDownMessage
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">c</span><span class="p">.</span><span class="nx">formatCountDownMessage</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">message</span><span class="p">,</span> <span class="nx">timeMMSS</span><span class="p">)</span> <span class="p">{</span> 
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">message</span> <span class="o">||</span> <span class="o">!</span><span class="nx">timeMMSS</span><span class="p">)</span> <span class="k">return</span> <span class="nx">message</span><span class="p">;</span> 
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">message</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="s1">&#39;MM:SS&#39;</span><span class="p">,</span> <span class="nx">timeMMSS</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><h2 id="putting-all-together-in-the-client-script">Putting all together in the Client Script</h2>
<p>Here is the complete Client Script with all the part detailled above. You can also find the complete widget in the References section below.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">api</span><span class="p">.</span><span class="nx">controller</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">$timeout</span><span class="p">,</span> <span class="nx">$rootScope</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* widget controller */</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">c</span> <span class="o">=</span> <span class="k">this</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Set the initial value of the time to display to the user
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">c</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">timeMMSS</span> <span class="o">=</span> <span class="nx">formatMillisecondsToMMSS</span><span class="p">(</span><span class="nx">c</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">timer</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Start updating the timer every second
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">$timeout</span><span class="p">(</span><span class="nx">updateTime</span><span class="p">,</span> <span class="mi">1000</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Reset the timer when navigating in the page 
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">$rootScope</span><span class="p">.</span><span class="nx">$on</span><span class="p">(</span><span class="s1">&#39;$locationChangeStart&#39;</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">event</span><span class="p">,</span> <span class="nx">newUrl</span><span class="p">,</span> <span class="nx">oldUrl</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">resetTimer</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Reset the timer on click and keydown events
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nb">document</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;click&#39;</span><span class="p">,</span> <span class="nx">resetTimer</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nb">document</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;keydown&#39;</span><span class="p">,</span> <span class="nx">resetTimer</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Function to update the timer 
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kd">function</span> <span class="nx">updateTime</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">c</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">timer</span> <span class="o">-=</span> <span class="mi">1000</span> <span class="c1">// Decrease the timer by 1 second
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>			
</span></span><span class="line"><span class="cl">        <span class="c1">// check if the time is over
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">if</span> <span class="p">(</span><span class="nx">c</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">timer</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// Set the displayed time to zero (so we never show negative time)
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="nx">c</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">timeMMSS</span> <span class="o">=</span> <span class="nx">formatMillisecondsToMMSS</span><span class="p">(</span><span class="s2">&#34;0&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// perform the required action when time is over, for example redirect somewhere else
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">href</span> <span class="o">=</span> <span class="s2">&#34;/somewhere.do&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span> <span class="k">else</span> <span class="cm">/*update counter*/</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">c</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">timeMMSS</span> <span class="o">=</span> <span class="nx">formatMillisecondsToMMSS</span><span class="p">(</span><span class="nx">c</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">timer</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="nx">$timeout</span><span class="p">(</span><span class="nx">updateTime</span><span class="p">,</span> <span class="mi">1000</span><span class="p">);</span><span class="c1">//  Update again after 1 second
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Function to reset the timer 
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kd">function</span> <span class="nx">resetTimer</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// reset the timer 
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nx">c</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">timer</span> <span class="o">=</span> <span class="nx">c</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">timeout</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="nx">c</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">timeMMSS</span> <span class="o">=</span> <span class="nx">formatMillisecondsToMMSS</span><span class="p">(</span><span class="nx">c</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">timer</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Function to convert the millisecond in minutes:seconds (MM:SS) format
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kd">function</span> <span class="nx">formatMillisecondsToMMSS</span><span class="p">(</span><span class="nx">milliseconds</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kd">var</span> <span class="nx">seconds</span> <span class="o">=</span> <span class="nb">parseInt</span><span class="p">(</span><span class="nx">milliseconds</span> <span class="o">/</span> <span class="mi">1000</span><span class="p">,</span> <span class="mi">10</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="kd">var</span> <span class="nx">minutes</span> <span class="o">=</span> <span class="nb">parseInt</span><span class="p">(</span><span class="nx">seconds</span> <span class="o">/</span> <span class="mi">60</span><span class="p">,</span> <span class="mi">10</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nx">seconds</span> <span class="o">=</span> <span class="nx">seconds</span> <span class="o">%</span> <span class="mi">60</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="nx">minutes</span> <span class="o">=</span> <span class="nx">minutes</span> <span class="o">%</span> <span class="mi">60</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kd">var</span> <span class="nx">formattedTime</span> <span class="o">=</span> <span class="nx">minutes</span><span class="p">.</span><span class="nx">toString</span><span class="p">().</span><span class="nx">padStart</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="s1">&#39;0&#39;</span><span class="p">)</span> <span class="o">+</span> <span class="s2">&#34;:&#34;</span> <span class="o">+</span> <span class="nx">seconds</span><span class="p">.</span><span class="nx">toString</span><span class="p">().</span><span class="nx">padStart</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="s1">&#39;0&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nx">formattedTime</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Add the formatCountDownMessage
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">c</span><span class="p">.</span><span class="nx">formatCountDownMessage</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">message</span><span class="p">,</span> <span class="nx">timeMMSS</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">message</span> <span class="o">||</span> <span class="o">!</span><span class="nx">timeMMSS</span><span class="p">)</span> <span class="k">return</span> <span class="nx">message</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">			
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nx">message</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="s1">&#39;MM:SS&#39;</span><span class="p">,</span> <span class="nx">timeMMSS</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><h1 id="references">References</h1>
<p>1.<a href="https://gitlab.com/samuelmeylan/snowadventuresnippets/-/snippets/4784822">A complete example widget is availlable in github</a></p>]]></content:encoded>
    </item>
    
    <item>
      <title>Interactive map in the portal</title>
      <link>https://www.snow-adventures.com/posts/interactive-map-in-the-portal/</link>
      <pubDate>Sat, 12 Dec 2020 00:00:00 +0000</pubDate>
      
      <guid>https://www.snow-adventures.com/posts/interactive-map-in-the-portal/</guid>
      <description>Integrate OpenStreetMap with ServiceNow records in the Portal</description>
      <content:encoded><![CDATA[<p>This article shows how to display records from SericeNow that are related to a location on a map in the ServiceNow portal, using OpenStreetMap (OSM).</p>
<p>For this purpose, we will create a portal Widget and make it interact with a list to show the records related to the choosen location.</p>
<p>Instead of relying on the google map services, it use instead OpenStreetMap (OSM) data <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>To achieve this, it relies on the open-source Javascript library Leafletjs to render interactive maps<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>.</p>
<p>While there are a number of other libraries to render maps, like OpenLayers<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>, Leaflet has the advantage of being lighweight and easier to use.</p>
<p>So as we don&rsquo;t need to cope with more complexe stuff like geo projections and handling various geo data sources, Leaflet offer all the functionnalities we need for a simple map and is enough for our needs.</p>
<h3 id="a-simple-use-case">A simple use case&hellip;</h3>
<p>The use case in scope for of this article is quite simple: we want to show the users on a map, grouped by location if there are more than one, and then filter a list of users according the location the we click on the map.</p>
<figure>
    <img loading="lazy" src="/img/interactivemap_1/portaldemo.gif"/> <figcaption>
            Map demo in the portal
        </figcaption>
</figure>

<p>There are mainly 3 parts:</p>
<ol>
<li>Create a Widget to show the map</li>
<li>Query the data for ServiceNow and show them on the map</li>
<li>Enable interaction with the ServiceNow Portal when a location is selected on the map</li>
</ol>
<p>We will now go through all the steps and build incrementally the map widget.</p>
<h3 id="how-leaflet-works">How Leaflet works</h3>
<p>As said before, we are using the Leaflet.js API for all our mapping needs.</p>
<p>Before jumping in ServiceNow to build the widget, I suggest you to take a few minutes to read at least the Leaftet&rsquo;s Quick Start Guide<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>.</p>
<p>The key points are mainly:</p>
<ul>
<li>We need the library and css in the page</li>
<li>The HTML part is very simple, just a <code>&lt;DIV&gt;</code> tag</li>
<li>Then in the code we need:
<ul>
<li>to create the <em>map</em> with <code>L.map</code> and make the link with the <code>&lt;DIV&gt;</code>tag</li>
<li>Use <code>setView</code> to indicate where to center the map and the scale to use</li>
<li>build the <em>tileLayer</em> with <code>L.tileLayer</code>, and that where we incidcate the OpenStreetMap tiles server</li>
<li>Add the <em>features</em> on the map with <code>L.marker</code></li>
<li>We need then to react on click to interract with the portal and also center the map on the clicked feature</li>
</ul>
</li>
</ul>
<h2 id="building-the-map-widget">Building the map widget</h2>
<p>To start buidling the map widget, I recommand using a custom scoped application in ServiceNow.</p>
<p>When your application is created, create a new Portal Widget:</p>
<ul>
<li>Name : Leaflet Map</li>
<li>ID: leaflet_map</li>
</ul>
<h3 id="store-the-leaflet-api-scripts-as-widget-dependencies">store the Leaflet API scripts as widget dependencies</h3>
<p>When creating the dependencies, you can either use a link to the Leaflet script online or store a copy in the ServiceNow instance.</p>
<p>I recommand storing a copy, so we don&rsquo;t depedant on other servers availlability for this part.</p>
<p>To do so, start by downloading the Libraries, either from the UNPKG repository <sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> or from the Leaflet web site <sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup>.</p>
<p>We need the two following files:</p>
<ul>
<li>leaflet.js</li>
<li>leaflet.css</li>
</ul>
<p>Then create a new Widget Dependency called &ldquo;Leaflet&rdquo; and add a new JS include  and a new CSS include containing repectively the content of file <code>leaflet.js</code> and <code>leaflet.css</code>.</p>
<figure>
    <img loading="lazy" src="/img/interactivemap_1/add_dependencies.gif"/> <figcaption>
            Add Leaflet dependencies to the widget
        </figcaption>
</figure>

<h3 id="set-the-html-css-and-client-script-for-a-first-test">Set the HTML, CSS and client script for a first test</h3>
<p>Set the HTML part as follow:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;map&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>Then the CSS part:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="p">#</span><span class="nn">map</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">position</span><span class="p">:</span> <span class="kc">absolute</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">top</span><span class="p">:</span> <span class="mi">0</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">left</span><span class="p">:</span> <span class="mi">0</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">width</span><span class="p">:</span> <span class="mi">500</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">height</span><span class="p">:</span> <span class="mi">500</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>And finally the client script:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">api</span><span class="p">.</span><span class="nx">controller</span><span class="o">=</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="cm">/* widget controller */</span>
</span></span><span class="line"><span class="cl">	<span class="kd">var</span> <span class="nx">c</span> <span class="o">=</span> <span class="k">this</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// build the map
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="kd">var</span> <span class="nx">map</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">L</span><span class="p">.</span><span class="nx">Map</span><span class="p">(</span><span class="s1">&#39;map&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// get the tile layer
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="kd">var</span> <span class="nx">url</span> <span class="o">=</span> <span class="s1">&#39;https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kd">var</span> <span class="nx">tilelayer</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">L</span><span class="p">.</span><span class="nx">tileLayer</span><span class="p">(</span><span class="nx">url</span><span class="p">,{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">attribution</span><span class="o">:</span> <span class="s1">&#39;&amp;copy; &lt;a href=&#34;https://www.openstreetmap.org/copyright&#34;&gt;OpenStreetMap&lt;/a&gt; contributors&#39;</span> <span class="cm">/*required as per OSM license - https://www.openstreetmap.org/copyright/en */</span>
</span></span><span class="line"><span class="cl">	<span class="p">});</span>
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	<span class="c1">// add layer to the map
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="nx">map</span><span class="p">.</span><span class="nx">addLayer</span><span class="p">(</span><span class="nx">tilelayer</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// set the view
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="kd">var</span> <span class="nx">latitude</span> <span class="o">=</span> <span class="nx">c</span><span class="p">.</span><span class="nx">options</span><span class="p">.</span><span class="nx">start_position_latitude</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kd">var</span> <span class="nx">longitude</span> <span class="o">=</span> <span class="nx">c</span><span class="p">.</span><span class="nx">options</span><span class="p">.</span><span class="nx">start_position_longitude</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kd">var</span> <span class="nx">scale</span> <span class="o">=</span> <span class="nx">c</span><span class="p">.</span><span class="nx">options</span><span class="p">.</span><span class="nx">start_scale</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="nx">map</span><span class="p">.</span><span class="nx">setView</span><span class="p">(</span><span class="nx">L</span><span class="p">.</span><span class="nx">latLng</span><span class="p">(</span><span class="nx">latitude</span><span class="p">,</span> <span class="nx">longitude</span><span class="p">),</span> <span class="nx">scale</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>As a good practice, we should avoid to hardcode the values in the script and use options instead. So we still need to add this JSON in the <em>Option schema</em> of the widget. You can of course change the default value to suit your needs or change them later in the widget instances.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-JSON" data-lang="JSON"><span class="line"><span class="cl"><span class="p">[</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;start_position_longitude&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;section&#34;</span><span class="p">:</span><span class="s2">&#34;other&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;default_value&#34;</span><span class="p">:</span><span class="s2">&#34;6.6327&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;label&#34;</span><span class="p">:</span><span class="s2">&#34;Start position longitude&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;string&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">},</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;hint&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;start_position_latitude&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;section&#34;</span><span class="p">:</span><span class="s2">&#34;other&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;default_value&#34;</span><span class="p">:</span><span class="s2">&#34;46.5218&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;label&#34;</span><span class="p">:</span><span class="s2">&#34;Start position latitude&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;string&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">},</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;start_scale&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;section&#34;</span><span class="p">:</span><span class="s2">&#34;other&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;default_value&#34;</span><span class="p">:</span><span class="s2">&#34;13&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;label&#34;</span><span class="p">:</span><span class="s2">&#34;Start scale&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;string&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></div><p>Make sure the widget option <em>Has preview</em> is checked and you can now vizualize the result with the Widget Editor:</p>
<figure>
    <img loading="lazy" src="/img/interactivemap_1/step1_vizualization.png"/> <figcaption>
            Simply showing a map in the widget
        </figcaption>
</figure>

<h2 id="query-the-data-from-servicenow">Query the data from ServiceNow</h2>
<p>Now that we have a map displayed, we need to add informations coming from the ServiceNow tables.</p>
<p>In this example we will show Users from the table <em>sys_user</em>. The position of each user will be determined by the <em>location</em> field.</p>
<p>Then we will use the <em>longitute</em> and <em>latitude</em> from the referenced Location (table <em>cmn_location</em>).</p>
<h3 id="geojson-format">geoJSON format</h3>
<p>To indicate Leaflet what to display and where, we need to provide the API with a <em>geoJSON</em> string <sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup> <sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">8</a></sup>.</p>
<p>Each &ldquo;object&rdquo; to put on the map is called a <em>feature</em>.</p>
<p>The GeoJSON standard specify various type of data that can be represented, but the one that suits our needs the best is the <em>FeatureCollection</em>, where multiple <em>Features</em> can be represented (but it work also if there is only one feature).</p>
<p>The format is as follow:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-JSON" data-lang="JSON"><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;FeatureCollection&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&#34;features&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">		<span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;geometry&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;Point&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">				<span class="nt">&#34;coordinates&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">					<span class="err">x</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">					<span class="err">y</span>
</span></span><span class="line"><span class="cl">				<span class="p">]</span>
</span></span><span class="line"><span class="cl">			<span class="p">},</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;Feature&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;properties&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="nt">&#34;popupContent&#34;</span><span class="p">:</span> <span class="s2">&#34;text&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">				<span class="nt">&#34;iconText&#34;</span> <span class="p">:</span> <span class="s2">&#34;iconText&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">				<span class="nt">&#34;locationId&#34;</span> <span class="p">:</span> <span class="s2">&#34;sys_id of location&#34;</span>
</span></span><span class="line"><span class="cl">			<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The <em>properties</em> part is free, so we can set everything we need. For the example, there is</p>
<ul>
<li><em>popupContent</em>, that contains the text to show on a pop-up when clikcing the location,</li>
<li><em>iconText</em> that is shown on the map (will be used for the count of record on each location,</li>
<li><em>locationId</em> that contains the sys_id of the location&rsquo;s record.</li>
</ul>
<h3 id="retrieving-the-data">Retrieving the data</h3>
<p>To retreive the data, we will use a GlideAggregate on the table and group by the location. Note that the table, the name of the field that refernece the location table and the query on the table are coming from the Widget options:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl">	<span class="c1">// get the options values
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="kd">var</span> <span class="nx">sourceTableName</span>  <span class="o">=</span> <span class="nx">options</span><span class="p">.</span><span class="nx">features_source_table</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kd">var</span> <span class="nx">locationFieldName</span> <span class="o">=</span> <span class="nx">options</span><span class="p">.</span><span class="nx">feature_location_field</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kd">var</span> <span class="nx">sourceTableQueryStr</span> <span class="o">=</span> <span class="nx">options</span><span class="p">.</span><span class="nx">feature_table_query</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// get the data from the table to shown as features
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="kd">var</span> <span class="nx">featureGa</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">GlideAggregate</span> <span class="p">(</span><span class="nx">sourceTableName</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="nx">sourceTableQueryStr</span><span class="p">){</span>
</span></span><span class="line"><span class="cl">		<span class="nx">featureGa</span><span class="p">.</span><span class="nx">addEncodedQuery</span><span class="p">(</span><span class="nx">sourceTableQueryStr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="nx">featureGa</span><span class="p">.</span><span class="nx">addNotNullQuery</span><span class="p">(</span><span class="nx">locationFieldName</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="nx">featureGa</span><span class="p">.</span><span class="nx">addAggregate</span><span class="p">(</span><span class="s1">&#39;COUNT&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="nx">featureGa</span><span class="p">.</span><span class="nx">groupBy</span><span class="p">(</span><span class="nx">locationFieldName</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="nx">featureGa</span><span class="p">.</span><span class="nx">query</span><span class="p">();</span>
</span></span></code></pre></div><h3 id="building-the-geojson">Building the GeoJSON</h3>
<p>To build the GeoJSON, we will use two functions: one to give the main GeoJSON object and another to build each Feature that will be added to the main GeoJSON object.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm">	@name getGeoJSONFeaturesCollectionObject
</span></span></span><span class="line"><span class="cl"><span class="cm">	@description get the main GeoJSON Feature Collection Object
</span></span></span><span class="line"><span class="cl"><span class="cm">	@returns {object} GeoJSON Feature collection Object
</span></span></span><span class="line"><span class="cl"><span class="cm">**/</span>
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">getGeoJSONFeaturesCollectionObject</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="s2">&#34;type&#34;</span><span class="o">:</span> <span class="s2">&#34;FeatureCollection&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			<span class="s2">&#34;features&#34;</span><span class="o">:</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">		<span class="p">};</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm">	@name getFeatureObject
</span></span></span><span class="line"><span class="cl"><span class="cm">	@description Get the Feature part of the GeoJSON
</span></span></span><span class="line"><span class="cl"><span class="cm">	@param {GlideRecord} [loationGr] - GlideRecord from the Location table (cmn_location)
</span></span></span><span class="line"><span class="cl"><span class="cm">	@param {String} [popUpContent=] - Text for the feature&#39;s popup. If not provided, will default to the location&#39;s display value. Can contains &#34;{0}&#34;, that will be replaced by the location&#39;s Display Value
</span></span></span><span class="line"><span class="cl"><span class="cm">	@param {String} [iconText=] - Text to show on the feature&#39;s icon. If not provided, will default to the location&#39;s display value.
</span></span></span><span class="line"><span class="cl"><span class="cm">	@returns {object} GeoJSON Feature collection Object
</span></span></span><span class="line"><span class="cl"><span class="cm">**/</span>
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">getFeatureObject</span> <span class="p">(</span><span class="nx">locationGr</span><span class="p">,</span> <span class="nx">popUpContent</span><span class="p">,</span> <span class="nx">iconText</span><span class="p">){</span>
</span></span><span class="line"><span class="cl">		<span class="nx">popUpContent</span> <span class="o">=</span> <span class="nx">popUpContent</span> <span class="o">||</span> <span class="nx">locationGr</span><span class="p">.</span><span class="nx">getDisplayValue</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">		<span class="nx">iconText</span> <span class="o">=</span> <span class="nx">iconText</span> <span class="o">||</span> <span class="nx">locationGr</span><span class="p">.</span><span class="nx">getDisplayValue</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="s2">&#34;geometry&#34;</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="s2">&#34;type&#34;</span><span class="o">:</span> <span class="s2">&#34;Point&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">				<span class="s2">&#34;coordinates&#34;</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">					<span class="nb">parseFloat</span><span class="p">(</span><span class="nx">locationGr</span><span class="p">.</span><span class="nx">longitude</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">					<span class="nb">parseFloat</span><span class="p">(</span><span class="nx">locationGr</span><span class="p">.</span><span class="nx">latitude</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">				<span class="p">]</span>
</span></span><span class="line"><span class="cl">			<span class="p">},</span>
</span></span><span class="line"><span class="cl">			<span class="s2">&#34;type&#34;</span><span class="o">:</span> <span class="s2">&#34;Feature&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			<span class="s2">&#34;properties&#34;</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="s2">&#34;popupContent&#34;</span><span class="o">:</span> <span class="nx">gs</span><span class="p">.</span><span class="nx">getMessage</span><span class="p">(</span><span class="nx">popupContent</span><span class="p">,</span> <span class="p">[</span><span class="nx">locationGr</span><span class="p">.</span><span class="nx">getDisplayValue</span><span class="p">()]),</span>
</span></span><span class="line"><span class="cl">				<span class="s2">&#34;iconText&#34;</span> <span class="o">:</span> <span class="nx">iconText</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">				<span class="s2">&#34;locationId&#34;</span> <span class="o">:</span> <span class="nx">locationGr</span><span class="p">.</span><span class="nx">getUniqueValue</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">			<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="p">};</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span></code></pre></div><p>Then we use those functions when looping on the <code>featureGa</code> records to generate the GeoJSON:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl">	<span class="c1">// get the GeoJSON object
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="kd">var</span> <span class="nx">geoJSONobj</span> <span class="o">=</span> <span class="nx">getGeoJSONFeaturesCollectionObject</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// for each feature
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="k">while</span> <span class="p">(</span><span class="nx">featureGa</span><span class="p">.</span><span class="nx">next</span><span class="p">()){</span>
</span></span><span class="line"><span class="cl">			<span class="c1">// set the popup content from the options, ot use the displayValue if not set
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>			<span class="kd">var</span> <span class="nx">popupContent</span> <span class="o">=</span> <span class="nx">options</span><span class="p">.</span><span class="nx">feature_popup_content</span> <span class="o">||</span> <span class="nx">featureGa</span><span class="p">.</span><span class="nx">getDisplayValue</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">			
</span></span><span class="line"><span class="cl">			<span class="c1">// get the feature object
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>			<span class="kd">var</span> <span class="nx">featureObj</span> <span class="o">=</span> <span class="nx">getFeatureObject</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">				<span class="nx">featureGa</span><span class="p">[</span><span class="nx">locationFieldName</span><span class="p">].</span><span class="nx">getRefRecord</span><span class="p">(),</span> <span class="cm">/*get the referenced location record*/</span>
</span></span><span class="line"><span class="cl">				<span class="nx">popupContent</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">				<span class="nx">featureGa</span><span class="p">.</span><span class="nx">getAggregate</span><span class="p">(</span><span class="s1">&#39;COUNT&#39;</span><span class="p">)</span> <span class="cm">/*use the count as the iconText*/</span>
</span></span><span class="line"><span class="cl">			<span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">			<span class="c1">// add in the GeoJSON features
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>			<span class="nx">geoJSONobj</span><span class="p">.</span><span class="nx">features</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">featureObj</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="c1">// set the geoJSON in the data, so it can be used on the client script	
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="nx">data</span><span class="p">.</span><span class="nx">geoJSONobj</span><span class="o">=</span> <span class="nx">geoJSONobj</span><span class="p">;</span>
</span></span></code></pre></div><h3 id="dont-forget-to-add-the-options">Don&rsquo;t forget to add the options&hellip;</h3>
<p>As you may have notice, we use the widet&rsquo;s options to set the table, the fields, and so in the server script. You need to add the following options in the <em>Option Schema</em> of the widget:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-JSON" data-lang="JSON"><span class="line"><span class="cl">   <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;hint&#34;</span><span class="p">:</span><span class="s2">&#34;Name of the table to show as features on the map&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;features_source_table&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;section&#34;</span><span class="p">:</span><span class="s2">&#34;other&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;default_value&#34;</span><span class="p">:</span><span class="s2">&#34;sys_user&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;label&#34;</span><span class="p">:</span><span class="s2">&#34;Features Source Table&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;string&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">}</span><span class="err">,</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;hint&#34;</span><span class="p">:</span><span class="s2">&#34;Name of the field that reference the location on the table shown as features on the map&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;feature_location_field&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;section&#34;</span><span class="p">:</span><span class="s2">&#34;other&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;default_value&#34;</span><span class="p">:</span><span class="s2">&#34;location&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;label&#34;</span><span class="p">:</span><span class="s2">&#34;Feature location field&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;string&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">}</span><span class="err">,</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;hint&#34;</span><span class="p">:</span><span class="s2">&#34;Query string to apply on the table shown as features on the map&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;feature_table_query&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;section&#34;</span><span class="p">:</span><span class="s2">&#34;other&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;default_value&#34;</span><span class="p">:</span><span class="s2">&#34;location.nameSTARTSWITHLausanne&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;label&#34;</span><span class="p">:</span><span class="s2">&#34;Feature table query&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;string&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">}</span><span class="err">,</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;hint&#34;</span><span class="p">:</span><span class="s2">&#34;Supports {0}, that will be replaced by the location display value. Supports HTML.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;feature_popup_content&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;section&#34;</span><span class="p">:</span><span class="s2">&#34;other&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;default_value&#34;</span><span class="p">:</span><span class="s2">&#34;This is the feature &#39;{0}&#39;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;label&#34;</span><span class="p">:</span><span class="s2">&#34;Feature popup content&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;string&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">}</span>
</span></span></code></pre></div><h2 id="display-the-features-on-the-map">Display the features on the map</h2>
<p>Now that we have a geoJSON availlable, we need to handle it in the client script. For this we will use the API <code>geoJSON</code> function and we will define <code>onEachFeature</code>function that will set up the popup content and trigger the function <code>clickAction</code>when the feature is clicked. We will also define <code>pointToLayer</code> to draw an icon and display the <em>iconText</em> on the map.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl">	<span class="kd">var</span> <span class="nx">geoJSONobj</span> <span class="o">=</span> <span class="nx">c</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">geoJSONobj</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="nx">L</span><span class="p">.</span><span class="nx">geoJSON</span><span class="p">([</span><span class="nx">geoJSONobj</span><span class="p">],</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="nx">onEachFeature</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">feature</span><span class="p">,</span> <span class="nx">layer</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="c1">// prepare the popup content with the feature&#39;s properties
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>			<span class="kd">var</span> <span class="nx">popupContent</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">			<span class="k">if</span> <span class="p">(</span><span class="nx">feature</span><span class="p">.</span><span class="nx">properties</span> <span class="o">&amp;&amp;</span> <span class="nx">feature</span><span class="p">.</span><span class="nx">properties</span><span class="p">.</span><span class="nx">popupContent</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="nx">popupContent</span> <span class="o">+=</span> <span class="nx">feature</span><span class="p">.</span><span class="nx">properties</span><span class="p">.</span><span class="nx">popupContent</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">			<span class="p">}</span>
</span></span><span class="line"><span class="cl">			<span class="c1">// set the popup content and call clickAction on click
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>			<span class="nx">layer</span><span class="p">.</span><span class="nx">bindPopup</span><span class="p">(</span><span class="nx">popupContent</span><span class="p">).</span><span class="nx">on</span><span class="p">(</span><span class="s1">&#39;click&#39;</span><span class="p">,</span> <span class="nx">clickAction</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="p">},</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="nx">pointToLayer</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">feature</span><span class="p">,</span> <span class="nx">latlng</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="c1">// prepare the icon text
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>			<span class="kd">var</span> <span class="nx">iconText</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">			<span class="k">if</span> <span class="p">(</span><span class="nx">feature</span><span class="p">.</span><span class="nx">properties</span> <span class="o">&amp;&amp;</span> <span class="nx">feature</span><span class="p">.</span><span class="nx">properties</span><span class="p">.</span><span class="nx">iconText</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="nx">iconText</span> <span class="o">=</span> <span class="nx">feature</span><span class="p">.</span><span class="nx">properties</span><span class="p">.</span><span class="nx">iconText</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">			<span class="p">}</span>
</span></span><span class="line"><span class="cl">			
</span></span><span class="line"><span class="cl">			<span class="c1">// create a DIV icon.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>			<span class="kd">var</span> <span class="nx">myIcon</span> <span class="o">=</span> <span class="nx">L</span><span class="p">.</span><span class="nx">divIcon</span><span class="p">({</span> 
</span></span><span class="line"><span class="cl">				<span class="nx">className</span><span class="o">:</span> <span class="s1">&#39;map-label-content&#39;</span><span class="p">,</span> <span class="cm">/*important to style it with CSS*/</span>
</span></span><span class="line"><span class="cl">				<span class="nx">iconSize</span><span class="o">:</span> <span class="p">[</span><span class="mi">29</span><span class="p">,</span><span class="mi">29</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">				<span class="nx">html</span><span class="o">:</span><span class="nx">iconText</span> <span class="cm">/*this is the text shown on the map*/</span>
</span></span><span class="line"><span class="cl">			<span class="p">});</span>
</span></span><span class="line"><span class="cl">			
</span></span><span class="line"><span class="cl">			<span class="c1">// return a marker with the icon
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>			<span class="k">return</span> <span class="nx">L</span><span class="p">.</span><span class="nx">marker</span><span class="p">(</span><span class="nx">latlng</span><span class="p">,</span> <span class="p">{</span><span class="nx">icon</span><span class="o">:</span> <span class="nx">myIcon</span><span class="p">});</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">})</span>
</span></span><span class="line"><span class="cl">		<span class="p">.</span><span class="nx">addTo</span><span class="p">(</span><span class="nx">map</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	<span class="kd">function</span> <span class="nx">clickAction</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="c1">// center on the clicked feature
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>		<span class="nx">map</span><span class="p">.</span><span class="nx">setView</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">getLatLng</span><span class="p">(),</span><span class="nx">map</span><span class="p">.</span><span class="nx">getZoom</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span></code></pre></div><p>Then (and this is important, otherwise it doesn&rsquo;t work), we need to define the CSS code for our icon (the one with the class name <code>map-label-content</code>). You can drop this code in the CSS section of the Widget:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-CSS" data-lang="CSS"><span class="line"><span class="cl"><span class="p">.</span><span class="nc">map-label-content</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">background</span><span class="p">:</span><span class="kc">red</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">   <span class="k">border</span><span class="p">:</span><span class="mi">5</span><span class="kt">px</span> <span class="kc">solid</span> <span class="nb">rgba</span><span class="p">(</span><span class="mi">255</span><span class="p">,</span><span class="mi">255</span><span class="p">,</span><span class="mi">255</span><span class="p">,</span><span class="mf">0.5</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="k">text-align</span><span class="p">:</span> <span class="kc">center</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">position</span><span class="p">:</span> <span class="kc">absolute</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">overflow</span><span class="p">:</span> <span class="kc">hidden</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">background-repeat</span><span class="p">:</span> <span class="kc">no-repeat</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">background-position</span><span class="p">:</span> <span class="kc">center</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">color</span><span class="p">:</span> <span class="mh">#fff</span><span class="p">;</span>                    <span class="err">//</span> <span class="err">icon</span> <span class="err">color</span>
</span></span><span class="line"><span class="cl">  <span class="k">width</span><span class="p">:</span> <span class="mi">29</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">height</span><span class="p">:</span> <span class="mi">29</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">border-radius</span><span class="p">:</span> <span class="mi">50</span><span class="kt">%</span><span class="p">;</span>             <span class="err">//</span> <span class="err">make</span> <span class="err">a</span> <span class="err">circle</span>
</span></span><span class="line"><span class="cl"> <span class="c">/* line-height: 22pt;*/</span>
</span></span><span class="line"><span class="cl">  <span class="k">font-size</span><span class="p">:</span> <span class="mi">15</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">font-family</span><span class="p">:</span> <span class="s1">&#39;FontAwesome&#39;</span><span class="p">;</span>     <span class="err">//</span> <span class="err">anything</span> <span class="err">goes</span>
</span></span><span class="line"><span class="cl">  <span class="k">filter</span><span class="p">:</span> <span class="nb">drop-shadow</span><span class="p">(</span><span class="mi">2</span><span class="kt">px</span> <span class="mi">2</span><span class="kt">px</span> <span class="mi">2</span><span class="kt">px</span> <span class="nb">rgba</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mf">0.5</span><span class="p">));</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The CSS is probably not perfect, but it does the job. If there is any CSS freaky around that have hints for improvements, please feel free to contact me 🙂.</p>
<figure>
    <img loading="lazy" src="/img/interactivemap_1/features_on_map.png"/> <figcaption>
            The features on the map, with the counter
        </figcaption>
</figure>

<h3 id="a-simpler-alternative">A simpler alternative&hellip;.</h3>
<p>If you want something simpler, without displaying the number of records on each location, you can replace the content of the function <code>pointToLayer</code> with this code:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">pointToLayer</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">feature</span><span class="p">,</span> <span class="nx">latlng</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="cm">/* The simpler alternative*/</span>
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="nx">L</span><span class="p">.</span><span class="nx">circleMarker</span><span class="p">(</span><span class="nx">latlng</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">radius</span><span class="o">:</span> <span class="mi">8</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">fillColor</span><span class="o">:</span> <span class="s2">&#34;#ff7800&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">color</span><span class="o">:</span> <span class="s2">&#34;#000&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">weight</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">opacity</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">fillOpacity</span><span class="o">:</span> <span class="mf">0.8</span>
</span></span><span class="line"><span class="cl">	<span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>			
</span></span></code></pre></div><p>You can then also drop the CSS part for <code>map-label-content</code></p>
<figure>
    <img loading="lazy" src="/img/interactivemap_1/simple_features_on_map.png"/> <figcaption>
            A sober version of the features
        </figcaption>
</figure>

<h2 id="enable-interaction-with-the-servicenow-portal">Enable interaction with the ServiceNow Portal</h2>
<h3 id="trigger-an-event-from-the-map-when-cliking-a-feature">Trigger an event from the map, when cliking a feature</h3>
<p>The last addition to our map widget is to broadcast an event when a feature is clicked.</p>
<p>To do so, you can simply add this line of code in the <code>clickAction</code> function in the client script of the widget:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl">	<span class="c1">// trigger event
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="nx">$rootScope</span><span class="p">.</span><span class="nx">$broadcast</span><span class="p">(</span><span class="s1">&#39;mapFeatureClick&#39;</span><span class="p">,</span> <span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">feature</span><span class="p">.</span><span class="nx">properties</span><span class="p">.</span><span class="nx">locationId</span><span class="p">);</span>
</span></span></code></pre></div><p>This will send an event <code>mapFeatureClick</code> with the sys_id of the location.</p>
<p>Now the map widget is ready, it is time to start preparing the widget responsible for the list and then put them together on a page</p>
<h2 id="a-quick-and-dirty-list-widget">A quick and dirty list widget</h2>
<p>For our list widget, we will clone the existing widget &ldquo;Simple List&rdquo; (widget-simple-list).</p>
<blockquote>
<p>⚠ To avoid the handling the script parts that are using ServiceNow functions that are not availlable in scoped application, I strongly suggest to set your scope to <strong>global</strong>. Otherwise prepare yourself to adapt the Simple List widget clone to make it work in a scoped application&hellip;</p>
</blockquote>
<p>So, make sure to be in the global scope and clone the &ldquo;Simple List&rdquo; widget to &ldquo;List linked to map&rdquo;.</p>
<p>Then add a listener in the client script of you new widget, to react when <code>mapFeatureClick</code> is broadcasted:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl">	<span class="nx">$rootScope</span><span class="p">.</span><span class="nx">$on</span><span class="p">(</span><span class="s1">&#39;mapFeatureClick&#39;</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">event</span><span class="p">,</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">c</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">locationFilter</span> <span class="o">=</span>  <span class="nx">data</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="nx">c</span><span class="p">.</span><span class="nx">server</span><span class="p">.</span><span class="nx">update</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">	<span class="p">});</span>
</span></span></code></pre></div><p>This will set a new variable <code>locationFilter</code> in data.</p>
<p>Then we need to modify the server script to use this  <code>locationFilter</code> to filter the records that are shown on the list:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="cm">/* to insert just after line 30:
</span></span></span><span class="line"><span class="cl"><span class="cm">...
</span></span></span><span class="line"><span class="cl"><span class="cm">if (input &amp;&amp; input.filterText)
</span></span></span><span class="line"><span class="cl"><span class="cm">	gr.addEncodedQuery(options.display_field + &#34;LIKE&#34; + input.filterText)
</span></span></span><span class="line"><span class="cl"><span class="cm">*/</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="nx">input</span> <span class="o">&amp;&amp;</span> <span class="nx">input</span><span class="p">.</span><span class="nx">locationFilter</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="nx">gr</span><span class="p">.</span><span class="nx">addQuery</span><span class="p">(</span><span class="s1">&#39;location&#39;</span><span class="p">,</span> <span class="nx">input</span><span class="p">.</span><span class="nx">locationFilter</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/*
</span></span></span><span class="line"><span class="cl"><span class="cm">options.title = options.title || gr.getPlural();
</span></span></span><span class="line"><span class="cl"><span class="cm">...
</span></span></span><span class="line"><span class="cl"><span class="cm">*/</span>
</span></span></code></pre></div><p>You can then save the widget.</p>
<h2 id="creating-the-portal-page-and-test-it">Creating the portal page and test it</h2>
<p>The last step is now to create a simple portal page and add the two widgets to it.</p>
<p>Set the list widget options to query the user table (sys_user) and select the primary and secondary fields.</p>
<p>Then make you have users in the correct location and test you page. The list will be filtered according the location you select on the map.</p>
<figure>
    <img loading="lazy" src="/img/interactivemap_1/portaldemo.gif"/> <figcaption>
            Map demo in the portal
        </figcaption>
</figure>

<h2 id="conclusion">Conclusion</h2>
<p>In this article we created a basic map widget, that can be used to interact with the service portal. There is plenty of room for improvements and for adding extra features, but I believe it is a good starting point.</p>
<p>I enjoyed digging in the geo data and map rendering world and I will probably follow up this article with other implementations, like positionning the map when a record is selected in a list, or displaying a custom floor plan and so on. So stay tuned 😉</p>
<p>Please leave you comments or questions below.</p>
<blockquote>
<p>ℹ All the code for the map widget is availlable in <a href="https://gitlab.com/samuelmeylan/www.snow-adventures.com/-/snippets/2049614">GitLab</a>.</p>
</blockquote>
<h2 id="-important-remarks-about-openstreetmap-server-usage">⚠ IMPORTANT Remarks about OpenStreetMap server usage</h2>
<p>As you probaly noticed, we are using the OpenStreetMap server to get the Tiles for our map. This is perfectly fine for some tests and for playing around, but there are some points that need to be considered if this is going to be used &ldquo;in real life&rdquo;.</p>
<p>The OSM foundation policy <sup id="fnref:9"><a href="#fn:9" class="footnote-ref" role="doc-noteref">9</a></sup> states:</p>
<blockquote>
<p>We are in principle happy for our map tiles to be used by external users for creative and unexpected uses – in contrast to most web mapping providers, which insist that you use only their supplied API.</p>
</blockquote>
<blockquote>
<p>However, OpenStreetMap’s own servers are run entirely on donated resources. They have strictly limited capacity. Heavy use of OSM tiles adversely affects people’s ability to edit the map, and is an abuse of the individual donations and sponsorship which provide hardware and bandwidth. As a result, we require that users of the tiles abide by this tile usage policy.</p>
</blockquote>
<p>Therefore I would recommand to use a commercial Tile server or even to setup your own Tile server for your maps. You can find a list of Tile servers here <sup id="fnref:10"><a href="#fn:10" class="footnote-ref" role="doc-noteref">10</a></sup> and some information about setting up a Tile server here <sup id="fnref:11"><a href="#fn:11" class="footnote-ref" role="doc-noteref">11</a></sup>.</p>
<hr>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://wiki.openstreetmap.org/wiki/Main_Page">OpenStreetMap wiki</a> - accessed 2020 12 12&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Leaflet web site, <a href="https://leafletjs.com/">Leafletjs.com</a> - accessed 2020 12 12&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>OpenLayers web site, <a href="https://openlayers.org/">OpenLayers.org</a> - accessed 2020 12 12&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Leaflet&rsquo;s Quick Start Guide, <a href="https://leafletjs.com/examples/quick-start/">Quick Start Guide</a> - accessed 2020 12 12&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>UNPKG leaflet repository, <a href="https://unpkg.com/browse/leaflet@1.7.1/dist/">unpkg.com</a> - version 1.7.1&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>Leaflet&rsquo;s download page, [<a href="https://leafletjs.com/download.html">Leafletjs.com</a> - version 1.7.1&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:7">
<p>GeoJSON web site, <a href="https://geojson.org/">geojson.org</a> - accessed 2020 12 12&#160;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:8">
<p>IETF RFC7946, Butler, et al., <a href="https://tools.ietf.org/html/rfc7946">rfc7946</a> - 1996&#160;<a href="#fnref:8" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:9">
<p>OSM foundation, <a href="https://operations.osmfoundation.org/policies/tiles/">Tile Usage Policy</a> - accessed 2020 12 12&#160;<a href="#fnref:9" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:10">
<p>OpenStreetMap wiki, <a href="https://wiki.openstreetmap.org/wiki/Tile_servers">Tile servers</a> - accessed 2020 12 12&#160;<a href="#fnref:10" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:11">
<p><a href="https://openmaptiles.org/">OpenMapTiles</a>  - accessed 2020 12 12&#160;<a href="#fnref:11" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>]]></content:encoded>
    </item>
    
  </channel>
</rss>
