Dynamics 365 portals v9 solutions now available and liquid editor change

If you are running on Dynamics 365 v9.x the solutions that accompany the latest portal update which moves the portal version up a major version to v9 are now available in all geos. Solutions for portal updates needs to be installed manually or are customer driven by an administrator instead of like the portal code which is applied by Microsoft. Administrators can access this update in the Dynamics 365 Administration Center and use the solution editor for the instance.

The new solutions with the v9.x are only available for v9.x instances though, you will not have them installed if you are still on v8.2 of Dynamics 365. So be aware there are v9 only changes in the future of portals. You can read more about the documented changes on the Microsoft Support site, instructions for new features like Reset Portal and Change Base URL can be found on the Microsoft Docs site.

But not everything is documented. The big change in these solution updates first pointed out by @readyxrm (Nick Doelman) privately is the inclusion of intellisense for liquid within the Web Template editor. But that is actually just the highly visible change. The major change is the editor for Web Templates and Liquid Templating to the Microsoft Monaco Editor which is the same editor that VS Code uses. This moves away from the ACE Editor that ADXStudio had originally implemented in the product.

You can see some interesting methods in the initialization of the editor that point to a liquid parser being built for the Monaco editor and the autocomplete registration.

registerLiquidLanguage(monaco);
registerAutocompleteProvider(monaco);

Within the registerAutocompleteProvider method they have the starts of the intellisense definition. It is very basic at this point and there is no dynamic build of intellisense based on your existing liquid code and you can’t yet access standard liquid objects (user, page, sitemap, etc.) and their attributes or attributes of queried entities. It is the first implementation though and I would imagine there is more to come with later releases.

function registerAutocompleteProvider(monaco) {
	monaco.languages.registerCompletionItemProvider('liquid', {
		provideCompletionItems: () => {
			var autocompleteProviderItems = [];
			var keywords = ['assign', 'capture', 'endcapture', 'increment', 'decrement',
						'if', 'else', 'elsif', 'endif', 'for', 'endfor', 'break',
						'continue', 'limit', 'offset', 'range', 'reversed', 'cols',
						'case', 'endcase', 'when', 'block', 'endblock', 'true', 'false',
						'in', 'unless', 'endunless', 'cycle', 'tablerow', 'endtablerow',
						'contains', 'startswith', 'endswith', 'comment', 'endcomment',
						'raw', 'endraw', 'editable', 'endentitylist', 'endentityview', 'endinclude',
						'endmarker', 'entitylist', 'entityview', 'forloop', 'image', 'include',
						'marker', 'outputcache', 'plugin', 'style', 'text', 'widget',
						'abs', 'append', 'at_least', 'at_most', 'capitalize', 'ceil', 'compact',
						'concat', 'date', 'default', 'divided_by', 'downcase', 'escape',
						'escape_once', 'first', 'floor', 'join', 'last', 'lstrip', 'map',
						'minus', 'modulo', 'newline_to_br', 'plus', 'prepend', 'remove',
						'remove_first', 'replace', 'replace_first', 'reverse', 'round',
						'rstrip', 'size', 'slice', 'sort', 'sort_natural', 'split', 'strip',
						'strip_html', 'strip_newlines', 'times', 'truncate', 'truncatewords',
						'uniq', 'upcase', 'url_decode', 'url_encode'];

			for (var i = 0; i < keywords.length; i++) {
				autocompleteProviderItems.push({ 'label': keywords[i], kind: monaco.languages.CompletionItemKind.Keyword });
			}

			return autocompleteProviderItems;
		}
	});
}

They have a good list of tags and filters but there is no relationships or understanding of where they work with operators so don’t expect a lot out of this autocomplete.

This is though a big change and hopefully a very prosperous one for Dynamics 365 portals developers. Using the same editor as VS Code and the multitude of features it has with it could really open up the possibilities, both for Microsoft and for customizers to enhance it. Monaco is open source and has a well documented API that can be used to extend it.

Sadly they weren’t able to yet addressed the much loved developer shortcut to save a web template often of CTRL + S and make the code window bigger or full screen function. Maybe soon though with the investment in this new editor :).

Dynamics 365 portal: Use liquid fetchxml with paging cookie

You might be familiar already with a previous post on Use Liquid to Return JSON or XML but what if you want efficient paging included in your scenario. I have had a couple of queries of how to do this with large data sets so that the fetchxml limit of 5000 results can be exceeded or results returned in an efficient manner as possible. Fetchxml has a solution with the paging cookie and the portal natively uses this in all its entity view type queries, but you can use it as well in your custom liquid fetchxml!

Using the same method in the previous post Use Liquid to Return JSON or XML we will setup a web template that makes the fetchxml query and instead of returning HTML we will set the MIME type to application/json.

This is the stubbed in liquid code we are starting with in our web template:

{% fetchxml feed %}
  <fetch version="1.0" mapping="logical">
    <entity name="contact">
      <attribute name="firstname" />
      <attribute name="lastname" />
      <attribute name="contactid" />
      <order attribute="lastname" descending="false" />
    </entity>
  </fetch>
{% endfetchxml %}{
  "results": [
    {% for item in feed.results.entities %}
      {
        "firstname": "{{ item.firstname }}",
        "lastname": "{{ item.lastname }}",
        "contactid": "{{ item.contactid }}"
      }{% unless forloop.last %},{% endunless %}
    {% endfor %}
  ]
}

Here we are just making a simple query using the liquid fetchxml tag and returning a list of all contacts (up to 5000 with the fetchxml limit).

The problem using this just like this is that it is not getting back a limited number of results and there is no paging of records involved. To make paging efficient on large date sets Microsoft has included what is called a paging cookie in fetchxml so that you can get faster application performance. Read more about the fetchxml paging cookie on the Microsoft Docs site – Page Large Result Sets with FetchXML.

The paging_cookie property should be used with the more_results boolean property both available the results object of a fetchxml query. The code below now has updated to include both of those properties in the highlighted lines in JSON returned by the endpoint.

{% fetchxml feed %}
  <fetch version="1.0" mapping="logical">
    <entity name="contact">
      <attribute name="firstname" />
      <attribute name="lastname" />
      <attribute name="contactid" />
      <order attribute="lastname" descending="false" />
    </entity>
  </fetch>
{% endfetchxml %}{
  "morerecords": {{ feed.results.more_records }},
  "paging-cookie": "{{ feed.results.paging_cookie }}",
  "results": [
    {% for item in feed.results.entities %}
      {
        "firstname": "{{ item.firstname }}",
        "lastname": "{{ item.lastname }}",
        "contactid": "{{ item.contactid }}"
      }{% unless forloop.last %},{% endunless %}
    {% endfor %}
  ]
}

Now you can make logic decisions if to get more records based on the value of more_records and use the value of paging_cookie to provide to the fetchxml.

At this point we are going to want to include a page size or returned record count so that we aren’t getting all the records at once (to a max of 5000). To do this you want to add the count attribute to the opening fetch with a integer value as to the number of records in the page of results.

<fetch version="1.0" mapping="logical" count="10">

Now we have setup the returned JSON with all the necessary details for the UI to make choices to get more data. Now we need further enhance the liquid logic to allow the UI to pass the endpoint parameters for the page and paging cookie so you can include those in the liquid fetchxml query. To do this we need to collect both of those as query string parameters and then add them to the fetchxml query if they exist.

For the paging cookie we want some logic so that the cookie is only included when it is passed as a parameter to the endpoint. Adding the following code to the top of your web template will check the request parameters for the key 'paging-cookie' and if it has a value then setup the XML statement attribute with the value of the query string parameter.

{% assign pagingCookie = request.params['paging-cookie'] %}
{% if pagingCookie %}
  {% assign pagingCookie = ' paging-cookie="{{ pagingCookie }}"' | liquid %}
{% endif %}

Note we also have a filter at the end of the paging cookie variable assignment, the liquid filter so that liquid is executed in the assignment of the variable.

With the paging cookie and the page parameter we want to add those to the opening fetch xml tag.

<fetch version="1.0" mapping="logical"{{ pagingCookie }} page="{{ request.params['page'] | default:1 }}" count="10">

We have also applied the default filter on the request.params['page'] so that when it isn’t included as a query string parameter that it assumes you want the first page. The first page won’t also require a paging cookie.

Everything looks pretty good at this point with the exception of one gotcha. The paging cookie value is going to be XML. XML is not going to go well into a query string parameter because it includes illegal URL characters. We could solve this with the UI layer itself and translate or encode the XML as URL safe but that would require logic at the UI. We can actually encode the XML on the liquid end so that the data we pass to whatever the UI is doesn’t need to worry about any translating, just passing that same data back.

We are going to add another property to the return JSON that is the encoded version of the paging cookie. With this we need to use a liquid filter called url_escape to encode all the XML to URL friendly characters.

"paging-cookie-encoded": "{{ feed.results.paging_cookie | escape | url_escape }}",

For example the original XML paging cookie of:

<cookie page="1"><lastname last="Vermander" first="Administrator" /><contactid last="{D77E163F-4B77-E811-A960-000D3A1CA7D6}" first="{7469FD95-C0BD-4236-90BF-1D1100291DF5}" /></cookie>

Becomes:

%26lt%3Bcookie+page%3D%26quot%3B1%26quot%3B%26gt%3B%26lt%3Blastname+last%3D%26quot%3BVermander%26quot%3B+first%3D%26quot%3BAdministrator%26quot%3B+%2F%26gt%3B%26lt%3Bcontactid+last%3D%26quot%3B%7BD77E163F-4B77-E811-A960-000D3A1CA7D6%7D%26quot%3B+first%3D%26quot%3B%7B7469FD95-C0BD-4236-90BF-1D1100291DF5%7D%26quot%3B+%2F%26gt%3B%26lt%3B%2Fcookie%26gt%3B

With this you now have a JSON endpoint that supports paging with the fetchxml paging cookie and can now efficiently return any number of records with various page sizes in your portal implementations. I do always recommend you keep your page sizes reasonable for performance considerations.

Below is the completed web template example with paging cookie included in the input of the endpoint and output of JSON.

{% assign pagingCookie = request.params['paging-cookie'] %}
{% if pagingCookie %}
  {% assign pagingCookie = ' paging-cookie="{{ pagingCookie }}"' | liquid %}
{% endif %}
{% fetchxml feed %}
  <fetch version="1.0" mapping="logical"{{ pagingCookie }} page="{{ request.params['page'] | default:1 }}" count="10">
    <entity name="contact">
      <attribute name="firstname" />
      <attribute name="lastname" />
      <attribute name="contactid" />
      <order attribute="lastname" descending="false" />
    </entity>
  </fetch>
{% endfetchxml %}{
  "morerecords": {{ feed.results.more_records }},
  "paging-cookie": "{{ feed.results.paging_cookie }}",
  "paging-cookie-encoded": "{{ feed.results.paging_cookie | escape | url_escape }}",
  "page": {{ request.params['page'] | default: 0 }},
  "results": [
    {% for item in feed.results.entities %}
      {
        "firstname": "{{ item.firstname }}",
        "lastname": "{{ item.lastname }}",
        "contactid": "{{ item.contactid }}"
      }{% unless forloop.last %},{% endunless %}
    {% endfor %}
  ]
}

You can test your new service now without parameters and with parameters. Your query for the first page would follow this format, the page parameter being optional.

https://[portalname].microsoftcrmportals.com/[json-endpoint]/?page=1

Queries for next or previous pages should include the encoded version of the paging cookie.

https://[portalname].microsoftcrmportals.com/[json-endpoint]/?page=2&paging-cookie=%26lt%3Bcookie+page%3D%26quot%3B1%26quot%3B%26gt%3B%26lt%3Blastname+last%3D%26quot%3BVermander%26quot%3B+first%3D%26quot%3BAdministrator%26quot%3B+%2F%26gt%3B%26lt%3Bcontactid+last%3D%26quot%3B%7BD77E163F-4B77-E811-A960-000D3A1CA7D6%7D%26quot%3B+first%3D%26quot%3B%7B7469FD95-C0BD-4236-90BF-1D1100291DF5%7D%26quot%3B+%2F%26gt%3B%26lt%3B%2Fcookie%26gt%3B

Upgrade Adxstudio Portals v7.x to xRM Portals Community Edition

With Adxstudio Portals having an end of support date announced as August 2018, many existing Adxstudio customers are looking to see what their upgrade options are. One of the options to upgrade to the new Dynamics 365 portals from Microsoft is to utilize the Portals Source Code version or xRM Portals Community Edition as part of the upgrade path. Hidden within the PowerShell scripts for importing the Portal Source Code version is logic to actually upgrade your portal data and make it compatible with the new data formats in the release of the Portals Source Code. This allows you to maintain your existing implementation instead of having to completely re-implement it on the new version. Once you are at Portal Source Code (v8.3 portals) you can then move to online portals maintaining the implementation as well. To trigger this you need to be aware of 2 site settings that will need to be created for each web site. In this post we will look at going the first step of the upgrade process, from Adxstudio Portals v7 to Portal Source Code/xRM Portals Community Edition.

Firstly, run a backup of your instance and read over some caveats to be aware of.

  • This example will use the Basic/Start/Custom Portal template with a completely out of the box portal.
  • The upgrade process will not perfectly upgrade all your components. The upgrade process is focused around transforming data to work within the new data model. You will need to review your configurations to ensure they all function properly within the new version.
  • Custom branding will likely need to be redone
  • New portal data from the latest version will not be imported. So you will not get any new templates or configurations from the new version, but your existing data will be maintained. If you want new portal data and configurations you can install a fresh portal in a new instance and manually copy over data.
  • If multiple web sites are present all web sites must be upgraded at once and therefore all must have the 2 site settings
  • Code customizations need to be transported to the updated code base and tested for compatibility OR implemented using new out of box configuration based method. NOTE: all custom code must be removed or abstracted to a Dynamics Portal Companion App approach before upgrading to the online Dynamics 365 portals
  • If you are not using ASP.NET Identity in Adxstudio Portals this is a required change prior to removing the Adxstudio solutions as the schema for the old forms based authentication will be removed with them.

For this example I have a new Dynamics 365 instance, version 8.2.x with Adxstudio Portals v7.0.0025 installed using the Basic Portal starter template. Here are the solutions installed in the system currently:

To do the upgrade we are doing to use the package deployer packages that come with the solutions components (MicrosoftDynamics365PortalsSolutions.exe). You can download that item from the Microsoft Download Center.

Within these packages there is logic that looks at the following 2 site settings to determine if it will run the data transformation to resolve the data model changes, UpgradeWebsiteData and WebsiteLCIDforUpgrade to determine the language to apply to the site. Below is an example of the site setting values:

Name Value Example Value Description
UpgradeWebsiteData true Boolean value, default of false
WebsiteLCIDforUpgrade 1033 LCID Code for web site. Use one of 43 supported portal languages.

Once you have these site settings in, and remember if you have multiple sites you will want to have the settings in for all sites. You cannot upgrade one site and then upgrade another site later. The sites all share the same solutions which will be upgraded on the first site being done. We can then proceed with running the PowerShell script which will run one of the packages.

Open your PowerShell and navigate to the .\PackageDeployerPackages\ folder. Within that folder execute the import script, .\Import.ps1.

This will prompt you to select your connection type, on premises or online. Enter your full organization URL, ie. https://orgname.crm.dynamics.com, then enter your language LCID code. Then you will be prompted to select your package. You will want to select the starter portal you already have installed. Remember this package list and names differs a bit from the Adxstudio Installer website gallery list. Below is a map Adxstudio to Dynamics 365 portals.

Adxstudio Portals Website Gallery Templates Microsoft Dynamics 365 portals Templates
Basic Portal Starter/Custom Portal
Community Portal Community Portal
Company Portal Not Supported
Conference Portal Not Supported
Customer Portal Customer Self-Service Portal
Government Portal Not Supported
Partner Portal Partner Portal
Retail Portal Not Supported
Not Supported Employee Self-Service Portal

It is important to note that features in the portals have also changed. Community Portal with xRM Portals Community Edition and Dynamics 365 portals online no longer contains event management functionality. There will be other gaps such as this in other portals. Please ensure you validate using a portals comparison like available on adoxio.com or installing the latest portals in a clean instance.

Once you have selected your package the package deployer will start its process of importing solutions and then running the data transformation against the websites that return the site settings above.

When it is completed running you will be left with a combination of the Adxstudio solutions and the new Dynamics 365 portal ones from the Portals Source Code release. Here is an example of the combined solutions for the Basic/Starter/Custom Portal template.

You can now test the portal using the xRM Portals Community Edition code base against your instance and start to validate some of the custom configurations you put in place.

Once you have completed a quick validation the next step is to remove all the Adxstudio solutions based on their dependency tree usually last in is first out, so work your way down the list and delete each of them. Because the Dynamics 365 portals solutions are there the entities will remain in the system so none of your data should be removed either. Always best practice to be taking your backups at logical points of this process as well. With Dynamics 365 online this is a super easy process.

Note that if you have published web notification steps then you will need to unregister the SDK message steps where the event handler is “Adxstudio.Xrm.Plugins.Webnotifications”, otherwise you may run into issues removing some solutions.

Once you have removed all the Adxstudio solutions you should be left with just the Dynamics 365 ones. You can retest your functionality and move on to addressing any configuration and branding issues.

This process will have upgraded you to the Portal Source Code version solutions and you can utilize xRM Portals Community Edition has the code base for your portal and now deploy that to your web servers or to an Azure App Service. If you had any custom code that you need to continue to maintain then you should have moved it into this code base and made any necessary changes to continue its existing function.

Upgrading to Portal Source Code/xRM Portals Community Edition could be where you stop for now. When you are ready to go fully online you can continue with an upgrade to the online version of Dynamics 365 portals. In an upcoming post we will look at how you can do that upgrade from Portal Source Code version or xRM Portals Community Edition to the latest Microsoft Dynamics 365 portals online service.

If you have questions related to the upgrade please post a comments and I will try to include answers in future blog posts about the topic.

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! 🙂

xRM Portals Community Edition Now Available!

With the news on last Friday of the Microsoft Open Source Portals for Dynamics 365 becoming available, Adoxio has now launched their fork of that code as xRM Portals Community Edition. There is a lot to explore with this release of the open source portals and the xRM Portals Community Edition, what does it include, who is it best suited for, what are the long term goals, what are the gaps and how you can get your hands on it.

Let’s start with what the open source release includes and what is in xRM Portals Community Edition.

Open Source Portals, available on the Microsoft Download Center:

  • Microsoft Dynamics 365 Portals Solutions
  • Microsoft Dynamics 365 Portals Source
  • Self-hosted Installation Guide for Portals

xRM Portals Community Edition takes the components of the open source portals that are licensed under MIT, which is only the portals source, and made it available as a project on GitHub for the community, be it partners or individual developers to contribute, extend and fix issues in an easy and open fashion. Adoxio has decided to help facilitate this as the original portal experts for Dynamics 365/CRM, with the major goal of the project being to help provide a way for existing customers of legacy versions of Adxstudio a way to upgrade and move to online portals from Microsoft.

Note that the solutions still need to be downloaded directly from Microsoft on the download center as they are not licensed under MIT and are only provided as managed solutions. This does have an impact in limiting certain aspects of the project in terms of the communities ability to make fixes beyond the portals source code. With the history and experience of being a developer implementing portals I can tell you this isn’t a blocker that is going to prevent changes that maybe necessary, it will just involve some creative problem solving and possible re-engineering of certain components. Again this might not always be necessary to re-engineer components until a layering approach is not a support scenario for the necessary modifications or fixes required.

It is important to realize that with this release of open source portals there are some major caveats and gaps that certain use cases of the portals may cause issues for upgrades.

  • Problem: This is a one time only release by Microsoft
    Solution: xRM Portals Community Edition is maintained by partners like Adoxio as well other community members allowing for bug fixes and a maintained version of the open source release.
  • Problem: Not supported directly by Microsoft.
    Solution: xRM Portals Community Edition is supported by partners like Adoxio and updates will be provided for free by the community. Adoxio offers a number of different support options as well as the project is open to other Dynamics partners to contribute and provide their own support options.
  • Problem: Does not include all Adxstudio Portals solutions/portals like Event Management, Retail and 311/Government Service Requests
    Potential Solution: Partners will be offering comparable solutions that are compatible with the online version of portals or even compatible with open source portals for on premise.

The gap in the CRM solutions cannot be understated. If you are an Adxstudio Portals customer currently using Event Management, Retail Portal components like shopping cart or payment, as well as any component of the 311/Government Portal service request functionality then you will need to consider your future upgrade path. These solutions or starter portals are not available as part of Dynamics 365 portals or the open source portals release so there is currently no supported path forward for these scenarios without looking at additional partner based solutions.

With xRM Portals Community Edition everyone now has the capability to build this functionality right into their own custom version of the portal as well or whatever other customizations they deem necessary for their requirements. Even old Adxstudio functionality that isn’t actively turned on in the online product still remains in the code base and likely could be activated once properly validated and updated as necessary. You will notice that the commerce, and areas like Service311 remain in the code base and do compile but are missing their dependent schema due to not all legacy solutions being maintained.

Any change or investigation into functionality that is not already activated though should be met with caution as it is an investment and something that will need to be self maintained by the customer and/or partner as a result. xRM Portals Community Edition will be kept in a supportable fashion so extensions that cause significant modifications will be done external from the core project as to not disrupt the supportability of using the code base as an upgrade path. This does not prevent an extension library type approach that so many of Adxstudio Portals partners have taken over the years and hugely familiar to them.

Who is it for?

Now aware of the caveats and gaps we can more closely understand who this release and xRM Portals Community Edition is targeted at. First and foremost it is for those legacy Adxstudio customers that need an upgrade path to online portals from Microsoft. Up until now the only approach has been to re-implement all of the functionality manually. If you used a legacy Adxstudio portals starter portal that is still included in the Microsoft Dynamics 365 portal (Community, Partner, Customer, Basic/Starter) then the scripts and solutions included in the release will allow you to start testing that upgrade process. Further documentation on this upgrade process should be forthcoming in the next few weeks as well.

The other most popular use case of xRM Portals Community Edition is the use for those customers staying with on premises Dynamics instances. xRM Portals Community Edition contains the capability to support both Server-to-Server authentication (suggested for online instances) and the old connection string with a service account based authentication (used for on premises instances). Also because you have access to the source code, you can deploy it anywhere. This allows on premises customers to build and host their own portal while connecting to their on premises servers.

Finally those looking to pull out certain functionality or are interested in continuing to build a custom developed portal for specific business use can utilize a fork of xRM Portals Community Edition as a starting point for their development. It is a great way to kick start your project and just requires you to follow the MIT license requirements. This is also a way for previous Adxstudio Portals ISVs to continue maintaining their components as closely as possible to how they operate today with legacy Adxstudio Portals and their customers to get new enhanced functionality.

A word of caution to all. Online portals are the future of portals from Microsoft. While xRM Portals Community Edition will be maintained by Adoxio and has the potential to be community driven it will not have the backing or insights of the Microsoft Dynamics product group. If you are considering a long term portal solution and want to have the support of Microsoft for issues/fixes as well as new features and enhancements then you strongly need to look at online solutions. xRM Portals Community Edition is for those upgrading and those understanding of the investments they are making in an open source solution. This not to say xRM Portals Community Edition is not a viable solution just to be aware of the gaps, caveats and limitations of it.

What does the future hold for open source portals?

Many ask why Microsoft has made this release, how in earth could they give away the source for something they purchased and are continuing to iterate on. There are a number of reasons in terms of providing an upgrade path, providing support and an option to on premises customers, allowing legacy ADX ISVs a way to maintain their customizations, and provide a custom code solution for those that demand the capability to implement functionality that requires server code access.

There are major changes ahead for the online portals product after this release. If you look at the code base (MasterPortal) as well as the core Adxstudio library (Adxstudio.Xrm) there are major dependencies to libraries that Microsoft has since deprecated. Both Microsoft.Xrm.Portals as well as Microsoft.Xrm.Client are foundation libraries that the portal relies on for all of its operations. With the deprecated libraries it is very likely that the first modification Microsoft is making to their new branch of the portals code will be to remove the dependencies on these libraries and potentially others. There are major additions as well as improvements in areas like entity permissions and caching the team at Microsoft probably also wants to make to help bring new experiences and a more reliable product to end users. This release marks a point in time where it still follows the old Adxstudio structure and model which may significantly be changing for future releases. Perhaps even a move to ASP.NET Core with .NET Standard lies in the future if you are looking far out (Dynamics/CRM SDK is only around 75% compatible currently with .NET Standard so a little ways to go, and has major dependencies to non .NET Standard based libraries).

The same changes can be made to xRM Portals Community Edition or a future branch of it, to keep it in step with the online Microsoft version of the product. This is no small undertaking though in terms of effort, and will need the backing of the community to do so. The release of the source leaves the power in the hands of the community to make those choices or completely different ones that serve their own use cases the best. Microsoft has really put the power of portals for Dynamics in our hands, shared all the knowledge, and functionality that was developed over many years and experiences at Adxstudio.

Looking to learn more or participate in the community edition?