QR Codes have been around for quite a long time, but since the pandemic, they have become common place. They are used at restaurants for contactless menus and even the Superbowl ads. QR codes give your users an easy way to get to your website using their phones without having to type the url into their mobile browser. Wouldn't it be nice to have a way to create those QR codes on the fly and even let your staff to create them and then be able to see how the interactions on those perform.

The Details

This system uses a workflow to create a shortlink and attach tracking information to the shortlink and has changes to the short link page and a special shortlink page used by your staff to view their content and interactions.

Page Short Link Entity

For tracking purposes we will add 2 attributes to the Page Short Link Entity. A Ministry Attribute, a defined value holding the names of your ministries who can request short links/qr codes and Requester Attribute, a Person Entity holding who made the request.

Note: We use the Ministry Defined Type throughout our website so we can be consistent with all aspects of our website in regards to content.

  1. Visit System Settings...Entity Attributes
  2. Change the Entity Type Filter to: Page Short Link
  3. Click the + to add an attribute to the Page Short Link Entity
  4. Add Ministry Attribute:
    1. Name: Ministry
    2. Active: checked
    3. Field Type: Defined Value
    4. Defined Type: Ministries
  5. Add Requester Attribute:
    1. Name: Requester
    2. Active: checked
    3. Field Type: Person
Entity

Workflow

The workflow is used to create short links and/or connet ministries and requesters to existing short links.

  1. Import the Workflow attached into your Rock Instance.
    1. Visit Power Tools...Workflow Import/Export
    2. On the right, select the downloaded file, choose the category and select Test.
      This will verify that everything will import.
    3. After running the test and you determine it is safe, uncheck the test and import the workflow.
  2. Edit the imported Workflow
    1. Change the Attribute Ministry with your Defined Type
    2. On the action: Create Short Link in Activity: Create Short Link, change the Site to your Public Site
      We only allow short link creation to our public website.
    3. On the action: Get created Entity in Activity: Create Short Link, change the SiteId == 9 to your public SiteId
    4. On the action: Set URL in Activity: Redirect-Close, change the last line to be the Page "Page Short Link Detail" you create below
      /page/1905?ShortLinkId={{- slid -}}
Workflow

Pages to View Short Links and Details

Create 2 pages to help manage your Shortlinks that have the new attributes attached to them. The first page uses a table formatting plugin named "Datatables". Find more information at datatables.net on usage.

  1. Add Page: Short Link Creation:
    1. Page Basic Settings: Layout: Left Sidebar
    2. Page Advanced Settings: Header Content:
      <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.24/css/jquery.dataTables.css">
      <script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/1.10.24/js/jquery.dataTables.js"></script>
    3. add Block to Main: Page Parameter Filter
      1. Block Title Text: Ministry
      2. Show Block Title: checked
      3. Add Filters :
        1. Name: Ministry
        2. Field Type: Defined Value
        3. Defined Type: Ministries or whatever Defined Type you use
    4. add Block to Main: Dynamic Data
      1. Block Properties...Basic Settings: WebRequest checked
      2. Query:
        select psl.Id
            ,  psl.Token
            , psl.Url
            , p.NickName + ' ' + p.LastName as Requester
            , p.Id as RequesterId
            , dv.[Value] as Ministry
        from PageShortLink psl
        left join AttributeValue req
            on req.EntityId = psl.Id and req.AttributeId = 25576 -- Attribute Id of Requester Attribute on Page Short Link Attribute
        LEFT JOIN PersonAlias pa
            ON pa.Guid = TRY_CAST(req.[Value] AS uniqueidentifier)
        LEFT JOIN Person p 
            ON p.Id = pa.PersonId
        JOIN AttributeValue min 
            ON min.EntityId = psl.Id and min.AttributeId = 25575 -- Attribute Id of Ministry Attribute on Page Short Link Attribute
        JOIN DefinedValue dv
            ON min.Value = cast(dv.Guid as varchar(50))
        WHERE (@Ministry = '' or min.Value = @Ministry)
        ORDER BY psl.Token
      3. Parameters: Ministry=;
      4. Customize Results with Lava: checked
      5. Formatted Output:
      6. <table id="ShortLink" class="display">
            <thead>
                <tr>
                <th>Token</th><th>Ministry</th><th>Requester</th><th>Url</th><th>Details</th><th>Download QR</th>
                </tr>
            </thead>
            <tbody>
                {% for row in rows %}
                    <tr>
                        <td>{{ row.Token }}</td>
                        <td>{{ row.Ministry }}</td>
                        <td><a href="/Person/{{ row.RequesterId}}">{{ row.Requester }}</a></td>
                        <td>{{ row.Url }}</td>
                        <td class="text-center"><a href="/page/1905?ShortLinkId={{ row.Id }}"><i class="fa fa-file-alt"></i></a></td>
                        
                        {% capture qrUrl %}https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=www.oneandall.church/{{ row.Token | EscapeDataString }}{% endcapture %}
                        <td class="text-center"><a href="{{ qrUrl }}" download="{{ row.Token}}"><i class="fa fa-download"></i></a></td>
                    </tr>
                {% endfor %}
            </tbody>
        </table>
        <script>
        $(document).ready( function () {
            $('#ShortLink').DataTable({"paging": false});
        } );
        </script>
    5. Add Block to Sidebar: HTML Content
      1. Code: <a href="/WorkflowEntry/398" class="btn btn-primary">Create Short Link</a>
        Change the Workflow Id to the Workflow ID created above
  2. Add Page: Page Short Link Detail
    1. Page Basic Settings: Layout: Left Sidebar
    2. Add Block to Main: Dynamic Chart
      1. Chart Height: 200
      2. Query Params: ShortLinkId=
      3. SQL:
        SELECT asd.DATE AS DATETIME
            , count(i.Id) AS YValue
            , 'Interactions' AS SeriesName
        FROM Interaction i
        INNER JOIN InteractionComponent ic
            ON ic.Id = i.InteractionComponentId
        INNER JOIN AnalyticsSourceDate asd
            ON asd.DateKey = i.InteractionDateKey
        INNER JOIN InteractionSession sess
            ON sess.id = i.InteractionSessionId
        INNER JOIN InteractionDeviceType idt
            ON idt.Id = sess.DeviceTypeId
        WHERE ic.EntityId = @ShortLinkId
            AND ic.InteractionChannelId = 10
            AND NOT (idt.Name LIKE '%bot%')
        GROUP BY asd.DATE
      4. Column Width: 12
      5. Chart Style: Rock
      6. Show Legend: No
      7. Chart Type: Line
    3. Add Block to Main: Short Link Click List
    4. Add Block to Sidebar: HTML Content
      1. HTML:
        {% pageshortlink id:'{{ PageParameter.ShortlinkId }}' iterator:'items' securityenabled:'false' %} 
            {% for item in items %} 
                <div class="text-center"> 
                    {% capture qrUrl %}https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=https://www.oneandall.church/{{ item.Token | EscapeDataString }}{% endcapture %} 
                    <img src="qrUrl">
                    <a class="btn btn-primary mt-3" href="{{ qrUrl }}" download="{{ item.Token}}" target="_blank"><i class="fa fa-download"></i> Download QR Code</a> 
                    <h3>www.oneandall.church/{{ item.Token }}</h3> 
                    <p>Redirects to: {{ item.Url }}</p> 
                </div> 
            {% endfor %} 
        {% endpageshortlink %}
PageDetail

Pulling it all together

To give our staff the ability to create their own short links and qr codes, we have created a custom footer on our website. This is only available to a select group of people who are given access to the Block itself.

  1. Add Block to footer of site: HTML Content
    1. Change view permissions of block to allow any security group you would like to see the block and deny "All Users"
    2. Edit HTML, NOTE: You will need to edit the code to fit the style of your website:
      {% assign PageId = 'Global' | Page:'Id' %}
      {% assign EditAccess = PageId | HasRightsTo:'Edit','Rock.Model.Page' %}
      <div class="container">
          {% assign url = 'Global' | Page:'Url' | EscapeDataString %}
          <a class="oa-btn oa-btn-primary" style="border-color:#000;" href="{{ 'Global' | Attribute:'InternalApplicationRoot' }}WorkflowEntry/###?Url={{ url }}" target="_blank">Short Link</a>
          
          {% if EditAccess == true %}
              &nbsp;&nbsp;<a accesskey="e" class="oa-btn oa-btn-primary" style="border-color:#000;" href="{{ 'Global' | Attribute:'InternalApplicationRoot' }}pages?Page={{ PageId }}" target="_blank">Edit Page</a>
          {% endif %}
      </div>
      NOTE: There are 2 buttons in this block.
      1. Button to Create the Short Link/QR Codes (starts Workflow created in this recipe Replace ### with the Workflow Type Id)
      2. Button for those who have edit rights to the page so they can easily get to the page if they need to update it with the Page editing layout
  2. Update the Link Detail page found in CMS Configuration...Short Links and can be found at (/admin/cms/short-links/1)
      1. Add to Header Content of Page:
        <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js" integrity="sha512-TW5s0IT/IppJtu76UbysrBH9Hy/5X41OTAbQuffZFU6lQ1rdcLHzpU5BzVvr/YFykoiMYZVWlr/PX1mDcfM9Qg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-moment"></script>
        <script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-trendline"></script>
        <script> 
            Chart.defaults.elements.line.tension = 0.4;
            Chart.defaults.plugins.legend.position = 'right';
            Chart.defaults.plugins.legend.align = 'start';
        Chart.defaults.interaction.mode = 'index';
        </script>
      2. Add 3 Blocks to the Main Zone between Short Link Detail and Short Link Click List:
        1. Entity Attribute Values
        2. Dynamic Data, Rock v13.4 has a fix and allows for a Dynamic Chart block instead and you can ignore step 1 above.
        3. HTML Content
      3. Configure Entity Attribute Values Block:
        1. Name: Attributes
        2. Entity Type: Page Short Link
      4. Configure Dynamic Data Block:
        1. Query:
          select asd.Date as DateTime
              , count(i.Id) as YValue
              , 'Interactions' as SeriesName
          from Interaction i
          inner join InteractionComponent ic
              on ic.Id = i.InteractionComponentId
          inner join AnalyticsSourceDate asd
              on asd.DateKey = i.InteractionDateKey
          inner join InteractionSession sess
              ON sess.id = i.InteractionSessionId
          inner join InteractionDeviceType idt
              ON idt.Id = sess.DeviceTypeId
          where ic.EntityId = @ShortLinkId 
              and ic.InteractionChannelId = 10 
              and not (idt.Name like '%bot%')
          group by asd.Date
          order by asd.[Date]
        2. Parameters: ShortLinkId=
        3. Formatted Output:
          <div class="col-md-8">
              <div class="chart-container" style="position:relative; width:100%; height:300px; margin-bottom:50px;">
                  <h3>Interactions</h3>
                  <canvas id="newGiverChart"></canvas>
              </div>
          </div>
          
          <script>
              var ctx = document.getElementById("newGiverChart").getContext('2d');
              var config = {
                  type: 'line',
                  data: {
                      labels: [{%-for row in rows -%}"{{row.DateTime | Date:'MM/dd/yy' }}"{%- if forloop.last-%}{%-else-%},{%-endif-%}{%-endfor-%}],
                      datasets: [
                          {%- for row in rows -%}
                              {%- if forloop.first -%}
                                  {
                                      label: 'Interactions', 
                                      {%- capture color -%}{%- cycle 'bgc':'#FFC905','#4054D9','#8DC63F','#652D90','#00ADEE','#F15A29','#FF6675','#FFB1BB' -%}{%- endcapture -%}
                                      borderColor: "{{color | UnescapeDataString}}",
                                      backgroundColor: "{{color | UnescapeDataString}}",
                                      borderWidth: 1,
                                      data: [
                              {%- endif -%}
                              {x:'{{row.DateTime | Date:'MM/dd/yy'}}', y:{{row.YValue}} },
                              {%- if forloop.last -%}
                                  ]
                                  },
                              {%- endif -%}
                          {%- endfor-%}
                      ]
                  },
                  options: {
                      responsive: true,
                      maintainAspectRatio: false,
                          animation: {
                              duration: 2500,
                          },
                      plugins: {
                          tooltip: {
                              mode: 'index'
                          },
                          legend: {
                              display: false
                          }
                      },
                      scales: {
                          x: {
                              type: 'time',
                              time: {
                                  unit:'day',
                                  displayFormats: {
                                      month: 'MM/YY',
                                      week: 'MM/DD/YY'
                                  },
                                  tooltipFormat: 'MM/DD/YY'
                              }
                          },
                          y: {
                              beginAtZero: true
                          }
                      }
                  }
              }
              var newgiverLine = new Chart(ctx, config);
          </script>
        4. Configure HTML Content:
          {% pageshortlink id:'{{ PageParameter.ShortlinkId }}' iterator:'items' securityenabled:'false' %}
              {% for item in items %}
                  {% capture qrUrl %}https://api.qrserver.com/v1/create-qr-code/?size=300x300&data={{'Global' | Attribute:'PublicApplicationRoot'}}{{ item.Token | EscapeDataString }}{% endcapture %}
                  <div class="text-center">
                      <h3>{{ item.Token }}</h3>
                      <p>{{ item.Url }}</p>
                      <img src="{{ qrUrl }}"><br>
            <a class="btn btn-primary mt-3" href="{{ qrUrl }}" download="{{ item.Token}}" target="_blank"><i class="fa fa-download"></i> Download QR Code</a>
              </div>
            {% endfor %}
          {% endpageshortlink %}

If you are on version 13.4, you can replace the Dynamic Data block with a Dynamic Chart block similar to the one created on the Page Short Link Detail Page earlier. A bug prevents the Dynamic Chart from using a pageparameter in the page route.