Posted in Entity List, JavaScript/jQuery Plugins, Power Apps Portals

Power Apps Portals – JavaScript Tip #03 – Load Edit modal after record creation

Hi

A very common business requirement I see is to create a record as well as related (1:N) records within the same page. By design, in Power Apps Portals (as well as Dynamics 365 / CDS / Dataverse) we need first to create the main/parent record to be able to create any related records. There are a few different ways to implement this, for example:

  • Web Forms (or Entity Forms with redirect) – having the initial step to the main record creation, and then moving to another step/page with the subgrid
  • Web API – the new Web API allows us to create multiple records in the same request, but requires a good bit of custom development

The above might be enough for most scenarios, but I want to explore here a different one that is very common. Let’s say we have an Entity List (or subgrid) with both Create and Edit actions enabled using an Entity Form modal instead of opening a Web Page – how can we fix this problem then?

We can achieve this requirement by adding a JavaScript on the Entity List (or Entity Form containing the subgrid if that’s your case). The idea of the JavaScript code is to check if there is any created record on every time the list is loaded, and force the Edit Entity Form to open, this will allow the user to enter the related records without having to click on the record. Let’s take a look at how this will look to the end-user:

Technical Implementation

I will explain here, at a high-level, my implementation:

  • I have two global variables to help in my code
    • existingRecordList – this is an array that I will store the existing list items whenever loaded
    • firstLoad – this is a simple variable to control when the page is being loaded for the first time
  • Then I inject a function (CheckNewRecord) to the onload event of my Entity List
  • These are the steps of my main function (CheckNewRecord):
    • Initiate the existingRecordsList in case this is the first load
    • Compare the size between existingRecordsList and Entity List records
    • I am creating a new temporary array to avoid conflicting with my global array
    • For each item of my Entity List:
      • Add the record ID to the temporary array
      • Ignore if this is the first time loading or if we already found which one is the new record
      • Check if the ID doesn’t exist in the global array
      • If it doesn’t exist, it means we found the new record, so we can store that in a separate variable (newRecordId) as well as marking that we already found the new record
    • Reload the global array with the temporary array; this is necessary for the next loads of the Entity List
    • If we found a new record, we need then to open the Edit modal/Entity Form
      • Via jQuery I am finding the element with the ID of the new record, and triggering the click event for the “details-link” class
    • Finally set the firstLoad control variable to false
var existingRecordsList;
var firstLoad = true;

$(document).ready(function() {

    var list = $(".entitylist.entity-grid").eq(0);
    list.on("loaded", CheckNewRecord);
});

function CheckNewRecord() {

    var list = $(".entitylist.entity-grid").eq(0);
    var newRecordFound = false;
    var newRecordId;

    // first load
    if (firstLoad) {
        existingRecordsList = [];
    }

    if (existingRecordsList.length != $(list).find('table tbody tr').length) {

        var tempRecordList = [];

        $(list).find('table tbody tr').each(function() {
            var id = $(this).attr("data-id");
            tempRecordList.push(id);

            if (!firstLoad && !newRecordFound && existingRecordsList.indexOf(id) < 0) {
                newRecordId = id;
                newRecordFound = true;
            }
        });
        // reload global variable with items in temp list
        existingRecordsList = tempRecordList;

        if (newRecordFound && !!newRecordId) {
            // open edit modal
            var newRecordElement = $(list).find("[data-id='" + newRecordId + "']");
            $(newRecordElement).find("a.details-link").eq(0).trigger("click");
        }
    }
    firstLoad = false;
}

Conclusion

The idea of this post was more to show tricks that we can do with an Entity List/Subgrid by applying JavaScript code. This is not limited to opening the Edit Entity Form after the creation, but really anything you might need to perform via JavaScript once a record is created and you don’t want to re-load the page.

This works pretty well with an Entity List/Subgrid containing a small number of records. But be mindful that in case of a large list, this might not work as the new record might not be in the current page.

Posted in JavaScript/jQuery Plugins, Power Apps Portals

Power Apps Portals – JavaScript Tip #02 – Set Attributes Read-Only

Hello and welcome to another JavaScript tip for Power Apps Portals.

In this post I will show you how to set attributes read-only in your Entity Form / Web Form.

A generic way to set an attribute as read-only would be the following:

$("#<attribute name>").attr("readonly", true);

But depending on the datatype of your attribute, some additional changes to the HTML elements might be required. For example, a lookup field is normally rendered with a search button, or a datetime field has the datetime picker next to the input control:

Instead of re-writing the same code every time, let’s create some generic functions passing the field name as parameter, as well as a true/false flag to define if the field should be read-only. We will create a few different functions, according to the field datatype:

Set DateTime ReadOnly

SetDateTimeFieldReadOnly = function (fieldName, readOnly) {
    if (readOnly) {
        $('#' + fieldName).siblings("div.datetimepicker").find("input, .input-group-addon").attr("readonly", true);
        $('#' + fieldName).siblings("div.datetimepicker").find("input, .input-group-addon").css("cursor", "not-allowed");
        $('#' + fieldName).siblings("div.datetimepicker").find("input, .input-group-addon").on("mousedown", function (e) { e.preventDefault(); return false; });
    } else {
        $('#' + fieldName).siblings("div.datetimepicker").find("input, .input-group-addon").attr("readonly", false);
        $('#' + fieldName).siblings("div.datetimepicker").find("input, .input-group-addon").css("cursor", "default");
        $('#' + fieldName).siblings("div.datetimepicker").find("input, .input-group-addon").off("mousedown");
    }
};

Set Lookup ReadOnly

SetLookupFieldReadOnly = function (fieldName, readOnly) {
    if (readOnly) {
        $('#' + fieldName).siblings("div.input-group-btn").find("button").prop("disabled", true);
        $('#' + fieldName).siblings("div.input-group-btn").hide();
    } else {
        $('#' + fieldName).siblings("div.input-group-btn").find("button").prop("disabled", false);
        $('#' + fieldName).siblings("div.input-group-btn").show();
    }
};

Set Checkbox ReadOnly

SetCheckboxFieldReadOnly = function (fieldName, readOnly) {
    if (readOnly) {
        $('#' + fieldName).prop("disabled", true);
    } else {
        $('#' + fieldName).prop("disabled", false);
    }
};

Set Radio Button ReadOnly

SetRadioFieldReadOnly = function (fieldName, readOnly) {
    if (readOnly) {
        $('#' + fieldName).find("input[type='radio']").prop("disabled", true);
    } else {
        $('#' + fieldName).find("input[type='radio']").prop("disabled", false);
    }
};

Set Dropdown ReadOnly

SetDropdownFieldReadOnly = function (fieldName, readOnly) {
    if (readOnly) {
        $('#' + fieldName).attr("readonly", true);
        $('#' + fieldName).css("pointer-events", "none");
    } else {
        $('#' + fieldName).attr("readonly", false);
        $('#' + fieldName).css("pointer-events", "auto");
    }
};

We still need one more function to cater for any other datatype. This will be the main function that we will make the call and we need one additional parameter, representing the field datatype.

This function will validate the field type and call the appropriate function we created above (via switch case), finally having the generic read-only function in the default instruction:

Set Field ReadOnly Function

SetFieldReadOnly = function (fieldName, readOnly, type) {
    try {
        type = type.toLowerCase();

        switch (type) {
            case "date":
            case "time":
            case "datetime":
                SetDateTimeFieldReadOnly(fieldName, readOnly);
                break;
            case "lookup":
                SetLookupFieldReadOnly(fieldName, readOnly);
                break;
            case "checkbox":
                SetCheckboxFieldReadOnly(fieldName, readOnly);
                break;
            case "radio":
                SetRadioFieldReadOnly(fieldName, readOnly);
                break;
            case "dropdown":
                SetDropdownFieldReadOnly(fieldName, readOnly);
                break;
            default:
                if (!!readOnly) {
                    $("#" + fieldName).attr("readonly", true);
                    $("#" + fieldName).css("cursor", "not-allowed");
                    $("#" + fieldName).on("mousedown", function (e) { e.preventDefault(); return false; });
                } else {
                    $("#" + fieldName).attr("readonly", false);
                    $("#" + fieldName).css("cursor", "default");
                    $("#" + fieldName).off("mousedown");
                }
                break;
        }
    }
    catch (err) {
        console.error("Error SetFieldReadOnly: " + err.message);
        return;
    }
};

Now you can combine all JavaScript code above, saving as a .js file (and upload as a Web File), or a Web Template record, or a Content Snippet record, etc. Then refer to it in any Portal page, or even the Tracking Code Content Snippet (so it can be used in every page) – and that’s it, the functions are ready to be used in your Portals.

Posted in JavaScript/jQuery Plugins, Power Apps Portals

Power Apps Portals – Field Label Position

Hi,

In this post I will show you how to set the field label position within an Entity Form/Web Form in Power Apps Portals.

If you are familiar with Dynamics 365, you probably are aware that we can define which position to place a field label within a form. The positions available are:

  • Side: label and input control are positioned side-by-side
  • Top: label and input control are positioned as top-bottom

Unfortunately, Power Apps Portals ignores this definition and always renders your form as top-bottom positioning:

I will show you how to change this behavior to render your field label as side-by-side position. We can achieve this via JavaScript or CSS:

Via JavaScript/jQuery

If you need to set only for a specific form or perhaps specific section in your form, you can use the following jQuery function (just relace the sectionGeneral below for your section name):

$("table[data-name|='sectionGeneral']").find(".control").css("clear", "none");
$("table[data-name|='sectionGeneral']").find(".control").css("float", "right");

Via CSS

If you want to setup this behavior for every Entity Form/Web Form within your Portals, or perhaps for an entire Web Page, you can set it via CSS (you can place this in your .css file or the Web Page custom css field):

.section tbody tr td div.control{
    clear: none !important;
    float: right;
}

Now refresh your Portals Web Page and you will have the below rendered form (don’t forget to clear the cache):

Posted in JavaScript/jQuery Plugins, Power Apps Portals

Power Apps Portals – JavaScript Tip #01 – Hide & Show Elements

Power Apps Portals allows us to add custom JavaScript/jQuery code to manipulate behaviour within the Portals website.

I am starting a series of quick posts with snippets of JavaScript/jQuery code to help you with your Power Apps Portals implementation.

When talking about client-side customization, there is no single way of interacting with the page elements, there are several ways to achieve same results, in this post I will give an example on how to hide & show elements in your page, so here we go:

Hide & Show Fields

$("#<field name>").closest("td").find("div.control, div.info").hide(); // show();

Hide & Show Section

$("table[data-name='<section name>']").parent().hide(); // show();

Hide & Show Tab

$("div[data-name='<tab name>']").prev("h2.tab-title").hide();  // show();
$("div[data-name='<tab name>']").hide();  // show();

Hide & Show Entity List/Sub-grid column

var list = $(".entity-grid")
list.on("loaded", function () {
	list.find("th:contains('<column display name>')").hide(); // show();
	list.find("td[data-th='<column display name>']").hide(); // show();
}); 

Hide & Show Option Set value

$("#<field name> option[value='< option set value>']").hide(); // show();

Do you have any suggestions or requests for another JavaScript tip? Let me know in the comments below.

Posted in JavaScript/jQuery Plugins, Power Apps Portals

Power Apps Portals – Adding field Mask

Currently in Power Apps Portals, we can’t set a mask for a field, which usually improves a lot the UX. In this post I will show you how a simple example to achieve that using JavaScript/jQuery.

In my case, I am adding a whole number field to the Profile page/form.

Download jQuery Mask Plugin

For this example I am using the following jQuery Plugin: https://igorescobar.github.io/jQuery-Mask-Plugin/

  • Download the plugin and locate the jquery.mask.min.js file under the “dist” folder
  • Create a Web File in your CDS environment
    • Note that by default you can’t upload a .JS file, I am going to rename the file to .ES to bypass this validation (you can also change your environment settings but this is not recommended) – but keep the Partial URL as .JS
    • I am going to set the parent page as Home
    • I am going to set it to be hidden from site map and excluded from search
Mask01
Mask02
Mask03

Adding JS library reference

We need to reference the JS, open your Web Page (content page) and add the following to your HTML “copy” field:

Mask05

You can also add the above line to the “Tracking Code” Content Snippet, and this would be loaded in every page, in my example this JS would only be loaded in the Profile Web Page.

Adding the mask via JavaScript / jQuery 

Still in your content page, navigate to the Advanced tab and add the following code:

Mask04
$(document).ready(function () {
       AddWholeNumberMask();
});
AddWholeNumberMask = function()
{
         $("#ollie_wholenumber").mask("00000", { placeholder: "i.e.: 12345" });
};

Result

This is how you will see your mask in the Portal, and the mask will prevent the user from typing non-number fields or more than 5 numbers in my case.

Mask06

You can reference the below documentation for other masks (date field / money / e-mail / etc): https://igorescobar.github.io/jQuery-Mask-Plugin/docs.html

Above I am using a placeholder to indicate the user what the expected format is, but this is optional, I could have the mask just as: $(“#ollie_wholenumber”).mask(“00000”);