Adding new pages using the Loginapp Design Kit

To provide maximal flexibility, the Loginapp Design Kit allows adding of entire pages written completely in JavaScript. This mechanism should be chosen with care since it also comes with additional costs and complexity.

  • However, there are reasons why it might be necessary to write a page using JavaScript:
  • For custom server-side flow steps that require a front-end that cannot be done using the Configurable Step UI Config plugin UI in IAM's server-side configuration.
  • For existing product pages that have to be altered in a way such that the mechanism provided for Customizing an existing product page using the Loginapp Design Kit is insufficient.

On top of all API methods defined for Customizing an existing product page using the Loginapp Design Kit, this method provides additional API methods for custom pages.

While writing custom pages in JavaScript is very powerful, it also comes with additional responsibilities: rendering the page's HTML, handling form input and validation, dealing with button clicks and interacting with the back-end must be implemented individually. Therefore, choose this option only if needed and ensure a good understanding of JavaScript.

To avoid problems with caching, it is recommended to use SASS whenever possible.

Rendering a page using JavaScript

When writing a page in JavaScript, one of the key tasks is to render the content of the page itself. For that, the Design Kit provides the following methods:

Show moreShow less
iam.api.customizing.initialize = function () { 
    class MyCustomJavaScriptPage extends iam.api.types.CustomJavaScriptPage { 
  
        // Defines the page's HTML content 
        contentHtml() { 
            return '...'; 
        } 
  
        // Invoked when the above provided HTML has been rendered into the page 
        afterRender() { 
            // Whatever logic required, e.g. putting focus on a field 
        } 
    } 
  
    // Register the page: 
    iam.api.customizing.registerPage(new MyCustomJavaScriptPage()); 
}

To be available in the Loginapp Design Kit's menu, add custom pages to the Loginapp Design Kit navigation menu.

For the remainder of this section, the surrounding function iam.api.customizing.initialize is not shown in the code examples. However, remember that all JavaScript customizations must happen within this function as shown above.

Handing control back to the Loginapp UI

  • With custom pages, the Loginapp UI hands control over to the custom JavaScript to:
  • Render the page.
  • Handle the input.
  • Perform calls to Flow API REST endpoints.

The REST responses are either successful or failed flow-responses.

  • A custom page has two options to handle them:
  • Deal with them autonomously.
  • Hand control back to the Loginapp UI to handle them.

Example:

iam.api.customizing.initialize = function () { 
    class MyCustomJavaScriptPage extends iam.api.types.CustomJavaScriptPage { 
  
        // Defines the page's HTML content 
        contentHtml() { 
            return '...'; 
        } 
  
        // Invoked when the above provided HTML has been rendered into the page 
        afterRender() { 
            // Whatever logic required, e.g. putting focus on a field 
        } 
    } 
  
    // Register the page: 
    iam.api.customizing.registerPage(new MyCustomJavaScriptPage()); 
}

In this basic example, when the POST to the back-end was successful, handleSuccessfulFlowResponse is called.
The UI will then automatically determine what to do, e.g. navigate to the next page. On the other hand, if interaction fails, handleFailedFlowResponse is called.

Note that the error handling in this example might be oversimplified for the general case. I.e. a failed interaction might require the Loginapp UI to render validation errors or alerts and only status codes like 401 or 403 might lead to the invocation of handleFailedFlowResponse.

To use the Design Kit without a back-end may require mocking the REST endpoints called by JavaScript. For more details, refer to Loginapp Design Kit REST response mocking configuration.

Full page render example

The following example illustrates how to render a page that looks like the standard mTAN authentication page and how to add back-end invocations in afterRender. Note that this is a simple example that could be further improved, e.g. by dealing with other status codes or by handling error responses that contain field validation errors.

Show moreShow lesscopy
iam.api.customizing.initialize = function () { 
    class MyCustomPage extends iam.api.types.CustomJavaScriptPage { 
  
        isApplicable({pageId, flowId, stepId}) { 
            return pageId === 'my-custom-page'; 
        } 
  
        afterRender() { 
            document.getElementById('otp').focus(); 
            const me = this; 
  
            // Example using XHR: 
            document.getElementById('myCustomMtanForm').addEventListener('submit', function (event) { 
                event.preventDefault(); 
                const data = me.formToJson(document.getElementById('myCustomMtanForm')); 
                const xhr = new XMLHttpRequest(); 
                xhr.onreadystatechange = function () { 
                    if (this.readyState !== 4) { 
                        return; 
                    } 
                    if (this.status === 200) { 
                        me.handleSuccessfulFlowResponse(xhr); 
                    } else { 
                        me.handleFailedFlowResponse(xhr); 
                    } 
                }; 
                xhr.open('POST', me.getRestBaseUrl() + '/public/authentication/mtan/otp/check'); 
                xhr.setRequestHeader('Content-Type', 'application/json'); 
                xhr.setRequestHeader('X-Same-Domain', 1); 
                xhr.send(JSON.stringify(data)); 
            }); 
  
            // Same example using 'fetch': 
            // document.getElementById('myCustomMtanForm').addEventListener('submit', function (event) { 
            //    event.preventDefault(); 
            //    const data = me.formToJson(document.getElementById('myCustomMtanForm')); 
            //    fetch(me.getRestBaseUrl() + '/public/authentication/mtan/otp/check', { 
            //        method: 'POST', 
            //        headers: { 
            //            'Content-Type': 'application/json', 
            //            'X-Same-Domain': 1 
            //        }, 
            //        body: JSON.stringify(data) 
            //    }).then(response => { 
            //        if (response.status === 200) { 
            //            me.handleSuccessfulFlowResponse(response); 
            //        } else { 
            //            me.handleFailedFlowResponse(response); 
            //        } 
            //    }); 
            // }); 
  
            document.getElementById('otp').addEventListener('keyup', function () { 
                const hasNoInput = document.getElementById('otp').value.length === 0; 
                document.getElementById('checkOtp').disabled = hasNoInput; 
            }); 
        } 
  
        getRestBaseUrl() { 
            const base = document.getElementsByTagName('base')[0].href; 
            const regex = new RegExp('/ui/?$'); 
            return base.replace(regex, '') + '/rest'; 
        } 
  
        formToJson(form) { 
            const formData = {}; 
            const elements = form.querySelectorAll("input, select, textarea"); 
            for (let i = 0; i < elements.length; ++i) { 
                const element = elements[i]; 
                if (element.name) { 
                    formData[element.name] = element.value; 
                } 
            } 
            return formData; 
        } 
  
        onPageEnter() { 
            console.log('entering: my-custom-page'); // for the sake of illustration 
        } 
  
        onPageExit() { 
            console.log('exiting: my-custom-page'); // for the sake of illustration 
        } 
  
        contentHtml() { 
            return `<form id="myCustomMtanForm" novalidate="" autocomplete="off"> 
                    <div class="iam-row-group"> 
                        <div class="iam-form-group iam-row"> 
                            <label class="iam-col-12 iam-col-md-5 iam-col-form-label" for="otp"> 
                                {{#translate}}authentication.mtan.otp{{/translate}} 
                            </label> 
                            <div class="iam-col-12 iam-col-md-7"> 
                                <input class="iam-form-control" type="text" id="otp" name="otp" placeholder=""> 
                            </div> 
                        </div> 
                    </div> 
                    <div class="iam-form-group iam-row"> 
                        <div class="iam-col-12 iam-button-group"> 
                                <button class="iam-btn iam-btn-block-level-sm-down iam-float-right iam-btn-outline-primary" 
                                        type="submit" id="checkOtp" title="" disabled> 
                                    {{#translate}}authentication.mtan.actions.check-otp{{/translate}} 
                                </button> 
                        </div> 
                    </div> 
                </form>`; 
        } 
    } 
  
    // Register the page: 
    iam.api.customizing.registerPage(new MyCustomPage()); 
};