Power Pages with Gen 2 questionnaires

From Resco's Wiki
Jump to navigation Jump to search
Warning Work in progress! We are in the process of updating the information on this page. Subject to change.

Gen 2 questionnaires can be played on Power Pages. Compared to legacy integration, the second-generation player operates as a lightweight, native component with minimal background processes and a simplified data model. To get you started faster, we’ve included a preconfigured demo page that lets you deploy a working example almost instantly.

Demo page

We have prepared a sample page that you can add to your organization. Use it to quickly test or showcase questionnaires on Power Pages.

  1. Start the Resco Inspections app.
  2. On the Get Started page, scroll down to the section about Power Pages and download the solution file.
    download the sample Power Pages solution from the Resco Inspections model driven app
  3. Go to make.powerapps.com and import the solution. Wait for installation, then publish all customizations.
  4. Go to make.powerpages.microsoft.com and make sure you are in the same environment.
  5. Go to the Inactive sites tab and Reactivate the rescoQuestionnaire site.
    Resco-questionnaire-inactive-site
  6. Find the demo among your active sites. It contains the questionnaire player component on the home page and has the required tables and Web API permissions set.

To display an actual questionnaire:

  1. Open the source code in Visual Studio Code.
  2. Find <resco-questionnaire> component in the Home page.
  3. Instead of the placeholder, enter the ID of a questionnaire/answer that exists in your environment.
    enter a valid questionnaire id
  4. Save, Sync, and Preview. Sometimes, it might be necessary to fully reload the page to bypass the browser cache.
Warning The demo page uses benevolent table permissions and content security policy to simplify testing. For production use, review and adjust the settings.

Power Pages advanced

The previous section explains how to quickly import and deploy a sample Power Page with questionnaires. If you want to build a Power Page from scratch, here are the details. We cover table permissions, web API permissions, and the procedure for adding questionnaires to a page.

Set up table permissions

Add the necessary permissions for the following tables:

  • resco_questionnaire
  • resco_questionnaireanswer
  • annotation
  • systemuser
  • webresource
  • organization – Read
  • Any table accessible from your questionnaires (e.g., account, contact…)

You can decide what kind of access you want to grant users. Your web can be accessed by anonymous users or authenticated users. The access type might be global or parent, account, or contact. You can learn more about the security model of Power Pages in the official Microsoft documentation. In this document, we describe how to grant anonymous users access to the mentioned tables.

While editing your site, go to Security > Table permissions. For each table, repeat the following procedure:

  1. Click +New.
  2. Enter the Name of your permission, Website, and the Table Name.
  3. Set Access Type to "Global".
  4. Check the required privileges, for example, Read.
  5. Click Add roles and check role(s) for which the permission is created. In our case, it is Anonymous Users.
  6. Save all changes.

Set up web API access

Questionnaire Player uses the Microsoft Web API service to communicate with the underlying Dataverse organization. You must set the Web API access to the same tables as in the previous step.

  1. While editing your site, click the ellipsis button in the left pane (just under Set Up) and select Power Pages Management.
  2. Select Website > Site Settings from the menu.
  3. For each table, create two new site settings:
    • Enable the web API for this table.
    • Set up fields that should be available via web API (or use asterisk for all).
    • For the organization table, add more restricted web API permissions:
      • WebApi/organization/enabled Value: true
      • WebApi/organization/fields Value: organizationid

Inspections on Power Platform: Set web API access in site settings

Content security policy

If you want to use questionnaires with qp-bridge on Power Pages, you need to enable inline scripts in your page's content security policy settings.

content security policy - script source

Adding the player to Power Pages

  1. Include an external JavaScript in your page:
    <script type="text/javascript" src="/_webresource/resco_questionnairePlayer/WebComponent/rescoQuestionnairePowerPageComponent.js"></script>
  2. Use the web component in your page:
<resco-questionnaire 
  id="q1"
  questionnaire-id="3ca3a54d-f2b3-f011-bbd3-7ced8d769a67" 
  regarding='{
    "entityName":"account",
    "name":"test",
    "id":"16b373ee-b0af-f011-bbd3-7c1e52365f30"
  }'
  user-id={{user.id}}
/>
<resco-questionnaire> attributes explained
  • id is the unique identifier of the HTML element
  • questionnaire-id is the GUID of the questionnaire (template or answer) to display
  • regarding: if you supply this optional attribute, it fills the fields regardingIdName, regardingId and regardingIdLabel on questionnaire answer
  • user-id: optional attribute, it can be initialized in Power Pages using Liquid user-id={{user.id}} if user is logged

Functions and handlers

You can use JavaScript to work with the questionnaire player. It exposes some basic functions:

## Exposed Public Methods

The `resco-questionnaire` web component exposes the following public methods that can be called from JavaScript:

### `initializeExternalHandlers(handlers)`

Initializes external handlers for various questionnaire events.

**Parameters:**
- `handlers` (PowerPageHandlers | undefined): An object containing optional handler functions

**PowerPageHandlers Interface:**
```typescript
{
  openEntityForm?: (entityName: string, id?: string) => Promise<Reference | undefined>;
  onSave?: () => Promise<void>;
  onLoaded?: () => Promise<void>;
  onCommandExecuted?: (commandName: string) => Promise<void>;
}

Example:

const questionnaire = document.getElementById("q1");
questionnaire.initializeExternalHandlers({
    openEntityForm: async function(entityName, id) {
        console.log(`Opening form for ${entityName} with id: ${id}`);
        // Custom implementation to open entity form
        return { id: id, name: entityName };
    },
    onSave: async function() {
        console.log("Questionnaire is being saved");
        // Validation logic - throw error to prevent save
        // This is called BEFORE the save operation
    },
    onLoaded: async function() {
        console.log("Questionnaire has loaded");
        // Custom logic after questionnaire loads
    },
    onCommandExecuted: async function(commandName) {
        console.log(`Command executed: ${commandName}`);
        // Custom logic after command execution
    }
});

save()

Saves the current state of the questionnaire.

Returns

  • Promise<boolean>: Resolves to true if save was successful, false otherwise

Example

const questionnaire = document.getElementById("q1");
const success = await questionnaire.save();
if (success) {
    console.log("Questionnaire saved successfully");
} else {
    console.error("Failed to save questionnaire");
}

Notes

  • If an onSave handler is registered, it will be called during the save operation.
  • If the onSave handler throws an error, the save will be prevented.

complete()

Marks the questionnaire as completed and closes it.

Returns

  • Promise<boolean>: Resolves to true if complete was successful, false otherwise

Example

const questionnaire = document.getElementById("q1");
const success = await questionnaire.complete();
if (success) {
    console.log("Questionnaire completed successfully");
} else {
    console.error("Failed to complete questionnaire");
}

executeCommand(cmdName)

Executes a named custom command in the questionnaire.

Parameters

  • cmdName (string): The name of the command to execute

Returns

  • Promise<boolean>: Resolves to true if command execution was successful, false otherwise

Example

const questionnaire = document.getElementById("q1");
const success = await questionnaire.executeCommand("SendEmail");
if (success) {
    console.log("Command executed successfully");
} else {
    console.error("Failed to execute command");
}

Notes

  • If an onCommandExecuted handler is registered, it will be called after successful command execution.
  • The command must exist in the questionnaire configuration.

withBusyIndicator(action)

Wraps an asynchronous action with a busy indicator (loading spinner) in the questionnaire UI.

Parameters

  • action (() => Promise<T>): An async function to execute while showing the busy indicator

Returns

  • Promise<T>: Resolves to the value returned by the action

Example

const questionnaire = document.getElementById("q1");

// Wrap multiple operations with a single busy indicator
const result = await questionnaire.withBusyIndicator(async () => {
    await questionnaire.executeCommand("ValidateData");
    await questionnaire.save();
    return "Operations completed";
});

console.log(result); // "Operations completed"

Complete usage example

document.addEventListener('DOMContentLoaded', async function() {
    // Wait for the custom element to be defined
    await customElements.whenDefined('resco-questionnaire');
    
    const questionnaire = document.getElementById("q1");
    
    // Initialize handlers
    questionnaire.initializeExternalHandlers({
        onSave: async function() {
            console.log("About to save questionnaire");
            // Perform validation
            // throw new Error("Validation failed") to prevent save
        },
        onLoaded: async function() {
            console.log("Questionnaire loaded successfully");
        },
        onCommandExecuted: async function(commandName) {
            console.log(`Command ${commandName} was executed`);
        },
        openEntityForm: async function(entityName, id) {
            console.log(`Opening ${entityName} form with id: ${id}`);
            return { id: id, name: entityName };
        }
    });
});

// Save questionnaire with busy indicator
async function saveWithIndicator() {
    const questionnaire = document.getElementById("q1");
    const success = await questionnaire.withBusyIndicator(async () => {
        return await questionnaire.save();
    });
    
    if (success) {
        alert("Saved successfully!");
    }
}

// Complete questionnaire
async function completeQuestionnaire() {
    const questionnaire = document.getElementById("q1");
    const success = await questionnaire.complete();
    
    if (success) {
        console.log("Questionnaire completed and closed");
    }
}

// Execute custom command
async function sendNotification() {
    const questionnaire = document.getElementById("q1");
    const success = await questionnaire.executeCommand("SendNotification");
    
    if (success) {
        console.log("Notification sent");
    }
}

Error handling

All methods return Promise<boolean> (except withBusyIndicator) to indicate success or failure. Always check the return value and handle errors appropriately:

try {
    const success = await questionnaire.save();
    if (!success) {
        console.error("Save operation failed");
        // Handle failure case
    }
} catch (error) {
    console.error("Unexpected error:", error);
    // Handle exception
}

Errors are also logged to the browser console with descriptive messages for debugging purposes.

Handler execution flow

onSave handler
  • When: Called BEFORE save operation (validator)
  • Purpose: Validate data, perform pre-save checks
  • Can prevent save: Yes (by resolving promise with false
  • Triggered by:
    • Calling questionnaire.save()
    • Clicking the save button inside the questionnaire UI
    • Executing custom script

Limitations

  • Data mapping is not available for Power Pages
  • Rules that use metadata are not supported
  • Questionnaire reports are not available