Dynamics 365 portals: Liquid with a JavaScript framework – vue.js

If you are a new developer to Dynamics 365 portals but you are familiar with modern web development techniques then you probably know a JavaScript framework like react.js, angular or vue.js.  With the Dynamics 365 portal and liquid templates you can utilize the development approaches with those frameworks within your liquid templates to create Single Page Applications (SPAs).  Let’s take a look at a simple example to understand how you can start implementing these yourself with more complex requirements.

There are a multitude of frameworks out there, they have a lot of similarities to each of them and the concepts very similar so you should be able to apply these techniques to any of the frameworks.  For this example I will be using the popular new vue.js framework.

The benefits of a lot of JavaScript frameworks is the ability to separate the view and model so you can easily create data bound templates.  At this your point you might be like but liquid/web templates already do this for me.  They do, but they do not provide a solution beyond that, like creating events, transitions, routing, security or other convince functions/features that are within a lot of the JavaScript frameworks.  Liquid does a great job of the templates but leaves the rest for you to figure out with most likely a library like jQuery or other JavaScript libraries out there, which can get a little messy, and unstructured.

With the ability to create a REST based GET service in liquid you have the basis for creating a template with all the frameworks functionality using the development knowledge you already have.  You don’t even need to know Dynamics, or the portals, or liquid, just that you are consuming a JSON based model and rendering it with your framework of choice.  If you are not familiar with creating REST based GET service that returns JSON or XML then checkout the previous post, Dynamics 365 portals: Use liquid to return JSON or XML.

For this example we are re-using the JSON based service in FullCalendar liquid post we did a couple of months ago with a couple of additional fields included in the JSON return.  The new JSON includes the course schedule ID so we can use it to uniquely identify the records in our presentation so they can easily have events wired up to them.

[
  {% assign urlMarker = sitemarker['Event Details'] %}
  {% for item in feed.results.entities %}
    {
      "id": "{{ item.dpx_coursescheduleid }}",
      "courseid": "{{ item.dpx_courseid.id }}",
      "title": "{{ item.dpx_courseid.name }} - {{ item['instructorlink.dpx_contactid'].name }}",
      "start": "{{ item.dpx_starttime | date_to_iso8601 }}",
      "end": "{{ item.dpx_endtime | date_to_iso8601 }}",
      "color": "{{ item['instructorlink.new_colorcode'] }}",
      "textColor": "{{ item['instructorlink.new_textcolor'] }}",
      "url": "/{{ urlMarker.Url }}?id={{ item.id }}"
    }{% unless forloop.last %},{% endunless %}
  {% endfor -%}
]

Now we can build of our vue.js templates and logic in a new web template or various templates that consume this service. If you are not familiar with vue.js then you can read more about its functionality on the documentation site – https://vuejs.org/v2/guide/.

Add a new web template, named whatever you like, mine is called “Liquid – vuejs”. Within it the first item you will need to include is a reference to vue.js. For this example I am using the CDN version of vue.js which will return the latest version automatically.

<script src="https://cdn.jsdelivr.net/npm/vue"></script>

Next I am going to open the raw tag, {% raw %} which tells the liquid parser to basically ignore the tags it would normally look for when parsing and rendering liquid. This is necessary because liquid uses the same mustache brackets that the JavaScript frameworks would use as well. You can start and end the raw segments as much as you like so that you can mix your liquid functionality with the framework at the same time. The first element we put in is the script template which makes up the vue.js view.

{% raw %}
<script type="text/x-template" id="cs-default-template">
    <div v-bind:id="'cs-data'">
        <div class="alert alert-danger" v-for="error in errors" v-bind:key="error.id">
            {{ error.description }}
        </div>
        <transition name="fade">
          <div v-if="itemsLoaded && hasItems">
            <div class="col-md-4" v-for="item in items" v-bind:key="item.id">
              <div v-bind:style="{ background: item.color, color: item.textColor }">
                <p style="background: #ccc; padding: 5px; color:#000;margin:0;">
                  <span class="fa fa-calendar"></span>
                  <a href="#" v-bind:id="item.id" v-on:click.prevent="details">
                      {{ item.title }}
                  </a>
                </p>
                <p style="padding: 5px;">
                  Start: <abbr class="timeago" v-bind:title="item.start"></abbr></br>
                  End: <abbr class="timeago" v-bind:title="item.end"></abbr>
                </p>
                <div class="download-indicator pull-right">
                    <i class="fa fa-circle-o-notch fa-spin fa-fw"></i>
                    <span class="sr-only">Loading...</span>
                </div>
              </div>
            </div>
          </div>
        </transition>
        <transition name="fade">
            <div v-if="itemsLoaded && !hasItems" class="alert alert-info">
                <span class="fa fa-info-circle"></span>
                There are no course schedules available for the current record.
            </div>
        </transition>
        <transition name="fade">
            <div v-if="!itemsLoaded && !isError" class="loading text-center">
                Loading items...<br />
                <i class="fa fa-circle-o-notch fa-spin fa-fw"></i>
            </div>
        </transition>
    </div>
</script>
{% endraw %}

Within this code you will notice a lot of HTML and binding of data elements from a array collection called items. Items as you will see from our vue component will be the results of the REST JSON service. Other items to note are the v-for, v-if, and v-bind, these are all vue.js directives. Any vue.js directive will be prefixed with v-, and can apply a reactive behaviour to the DOM. There are a lot of different directives in vue.js so be sure to checkout the documentation for more.

Next we need to create some component logic. We have our template that has its view display but now we need to provide it with function and events. To do this we create a vue component. You can compose your own components with view so you can then inject them anywhere within your application, repeatedly without re-writing logic.

<script>
  Vue.component('cs-items', {
    props: {
    },
    data: function () {
        return {
            items: [],
            errors: [],
            itemsLoaded: false
        }
    },
    computed: {
        hasItems: function () {
            return this.items.length > 0;
        },
        isError: function () {
            return this.errors.length > 0;
        }
    },
    template: '#cs-default-template',
    created: function () {
        var elId = this.$root.$el.id;
        console.log('cs container -- created : ' + elId);

        $.ajax({
          method: "GET",
          url: "/liquid/calendar-json/"
        }).done(function (responseData) {
            console.log(responseData);
            this.items = responseData;
            this.itemsLoaded = true;
        }.bind(this)).fail(function (jqXHR, textStatus, errorThrown) {
            console.log("Ajax error: " + errorThrown);
            if (jqXHR.xhr.status == 401) {
                this.errors.push({ id: "401", description: "User is not authorized for the requested CS." });
            } else {
                this.errors.push({ id: "500", description: "Error retrieving CS.  Details: " + errorThrown });
            }                
        }.bind(this));
    },
    updated: function () {
        var elId = this.$root.$el.id;
        console.log('cs container -- updated : ' + elId);
        $('#' + elId + ' abbr.timeago').timeago();
    },
    methods: {
        details: function (event) {
            var elId = this.$root.$el.id;
            console.log('cs container -- details : ' + event.target.id);
        }
      }
  });
</script>

With this code we are basically registering the tag cs-items so that when we include it in HTML (<cs-items></cs-items>) the vue component engine will execute its logic against it. This vue component is figured with the template property to use our previously created template with the ID: #cs-default-template. Basically where ever you have the custom component HTML it will replace it with the template code while processing it with the vue.js engine. That engine includes events like created, updated and several more. For our purposes on create we want to populate the template with the data from our REST JSON service. The beauty of vue.js is you can determine how you want to make this call and the library you want to accomplish that with. For these purposes I have used jQuery but you could use our favorite library here.

Finally we have our component HTML that we can include anywhere in our page now and have the vue engine render it.

<div id="cs-items-01" class="cs-container">
  <cs-items></cs-items>
</div>

Vue has a number of functions that you can explore as part of creating components, props (parameters to your component), computed props, events (created, updated, etc.), methods that you can then include in your vue template and much more. I won’t go through all the vue component functionality here as it can all be found on their documentation site.

The end result of this example is we display a listing of course schedules using the REST JSON service and have an event wired up to click the details of an item. The onclick here for this sample will just get the ID of the item clicked and log it to the console but here you could execute your own functionality and call additional data services that render more content or render the view differently.

Here is the final result and the complete code of the example.

<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://use.fontawesome.com/f90e38839e.js"></script>

<div id="cs-items-01" class="cs-container">
  <cs-items></cs-items>
</div>

{% raw %}
<script type="text/x-template" id="cs-default-template">
    <div v-bind:id="'cs-data'">
        <div class="alert alert-danger" v-for="error in errors" v-bind:key="error.id">
            {{ error.description }}
        </div>
        <transition name="fade">
          <div v-if="itemsLoaded && hasItems">
            <div class="col-md-4" v-for="item in items" v-bind:key="item.id">
              <div v-bind:style="{ background: item.color, color: item.textColor }">
                <p style="background: #ccc; padding: 5px; color:#000;margin:0;">
                  <span class="fa fa-calendar"></span>
                  <a href="#" v-bind:id="item.id" v-on:click.prevent="details">
                      {{ item.title }}
                  </a>
                </p>
                <p style="padding: 5px;">
                  Start: <abbr class="timeago" v-bind:title="item.start"></abbr></br>
                  End: <abbr class="timeago" v-bind:title="item.end"></abbr>
                </p>
                <div class="download-indicator pull-right">
                    <i class="fa fa-circle-o-notch fa-spin fa-fw"></i>
                    <span class="sr-only">Loading...</span>
                </div>
              </div>
            </div>
          </div>
        </transition>
        <transition name="fade">
            <div v-if="itemsLoaded && !hasItems" class="alert alert-info">
                <span class="fa fa-info-circle"></span>
                There are no course schedules available for the current record.
            </div>
        </transition>
        <transition name="fade">
            <div v-if="!itemsLoaded && !isError" class="loading text-center">
                Loading items...<br />
                <i class="fa fa-circle-o-notch fa-spin fa-fw"></i>
            </div>
        </transition>
    </div>
</script>

<script>
  Vue.component('cs-items', {
    props: {
    },
    data: function () {
        return {
            items: [],
            errors: [],
            itemsLoaded: false
        }
    },
    computed: {
        hasItems: function () {
            return this.items.length > 0;
        },
        isError: function () {
            return this.errors.length > 0;
        }
    },
    template: '#cs-default-template',
    created: function () {
        var elId = this.$root.$el.id;
        console.log('cs container -- created : ' + elId);

        $.ajax({
          method: "GET",
          url: "/liquid/calendar-json/"
        }).done(function (responseData) {
            console.log(responseData);
            this.items = responseData;
            this.itemsLoaded = true;
        }.bind(this)).fail(function (jqXHR, textStatus, errorThrown) {
            console.log("Ajax error: " + errorThrown);
            if (jqXHR.xhr.status == 401) {
                this.errors.push({ id: "401", description: "User is not authorized for the requested CS." });
            } else {
                this.errors.push({ id: "500", description: "Error retrieving CS.  Details: " + errorThrown });
            }                
        }.bind(this));
    },
    updated: function () {
        var elId = this.$root.$el.id;
        console.log('cs container -- updated : ' + elId);
        $('#' + elId + ' abbr.timeago').timeago();
    },
    methods: {
        deleteFile: function (event) {
        },
        details: function (event) {
            var elId = this.$root.$el.id;
            console.log('cs container -- details : ' + event.target.id);

            var item = $('#' + event.target.id);
            
        }
      }
  });
  
  var vm = new Vue({
      el: '#cs-items-01'
  });

</script>
{% endraw %}

This was really meant as an introduction to using a JavaScript framework within the Dynamics 365 portal. There is a lot more you can do and future examples I will document and post including methods you can use to actually post back data with a companion app to the Dynamics 365 instance creating a complete application with the framework of your choice.

Happy Halloween! 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *