Handling navigation events in a modal browser window using JScript

This post will cover a very specific scenario that relates to the areas of modal tasks, the WebBrowser control and navigation events. We got a question on how to close a modal browser window in Smart Office when the browser control navigated to a specific URL. The answer is not obvious so this post will show one way to solve this using JScript.

The requirements could be summarized like this:

  • Launch a HTTP URL from JScript in a modal window.
  • Close the modal window when the user navigates to a specific URL.

The reason for the second requirement was that an Internet Explorer dialog was displayed when the web application tried to close the window from JavaScript. To get around this the JScript could close the window when the web application navigated to a URL that indicated that it was done. This would be a nicer experience for the user but the problem was how to achieve this in Smart Office with JScript.

This post will be a simplified version of the original scenario. I will not use the real web application and I will not add any UI from the script. The important things are how to find the browser control and how to respond to the different events.

Launching the modal task

The first problem is how to launch a task in a modal window and still be able to execute code that is dependent on the content in the modal window. When a modal window is shown the calling code blocks until the modal window is closed. There are ways to get around this using the dispatcher but there is also a method in IDashboardTaskService called LaunchTaskModal that solves the issue.

The LaunchTaskModal accepts two optional callback delegates called LaunchEventHandler and RunnerStatusChangedEventHandler. The LaunchEventHandler can be used to determine if a task could be launched at all. The RunnerStatusChangedEventHandler can used to check the status of the runner, usually when it changes status from Pending to Running.

In the Init method of the script an Uri instance is created for the web page to load, in this example Google. The LaunchTaskModal method is called with a new task and callback delegates for both the launch and the runner status changes.

public function Init(element : Object, args : Object, controller : Object, debug : Object) {
    this.logger = debug;
    this.controller = controller;
    controller.add_Requested(OnRequested);

    var uri = new Uri("http://www.google.com");
    DashboardTaskService.Current.LaunchTaskModal(new Task(uri), OnLaunch, OnRunnerStatusChanged);
}

The OnLaunch function doesn’t do anything useful in this example other than writing the launch status to the output pane. In a real script you would probably have more error handling here. If the task could be launched the launch status will be “OK”. If the launch status is “Failed” it could mean that there was no application registered for the scheme or that the application threw an exception. The application that handles the http/https schemes is always available so launch errors should be rare for web pages / applications.

public function OnLaunch(sender : Object, e : LaunchEventArgs) {
    try {
        logger.Debug("Launch Status: " + e.LaunchStatus);

        if (e.LaunchStatus == LaunchStatus.Failed) {
            logger.Error("Failed to launch task");
        }
    } catch (ex) {
        logger.Error("Failed to handle the launch event", ex);
    }
}

The OnRunnerStatusChanged function checks if the runner for the modal task has changed to Running. In this case the Running status means that the modal window and the WebBrowser control has been created. The window is accessed using the Host property on the runner and it is then sent to the AttachToBrowser function.

public function OnRunnerStatusChanged(sender : Object, e : RunnerStatusChangedEventArgs) {
    try {
        logger.Debug("Runner Status: " + e.NewStatus);

        if (e.NewStatus == RunnerStatus.Running) {
            AttachToBrowser(e.Runner.Host);
        }
    } catch (ex) {
        logger.Error("Failed to handle runner status", ex);
    }
}

Attaching events to the web browser control

When a web page is displayed in a Smart Office window the WebBrowser control from Windows Forms is used. The reason for using the Windows Forms version of the WebBrowser control is that it allows greater options for customization using COM Interop compared to the WebBrowser control that is part of WPF. To be able to use control from Windows Forms in WPF the browser control is wrapped in a WindowsFormsHost. Knowing this we can get a reference to the WebBrowser and attach event handlers.

The AttachToBrowser function first stores the host so that the modal window can be closed later on and then gets a reference to the WindowsFormsHost using the HostContent property on the host window. The WindowsFormsHost class has a property called Child that contains the actual WebBrowser control. Finally an event handler is attached to the Navigating event on the WebBrowser control.

public function AttachToBrowser(host) {
    this.host = host;
    var formsHost : WindowsFormsHost = host.HostContent;
    browser = formsHost.Child;
    browser.add_Navigated(OnNavigated);
}

Handling the navigation events

The Navigated event on the WebBrowser control is raised each time the URL changes. The OnNavigated function checks if it is the special URL that should close the window. In this example I simply check if the URL contains the text “www.youtube.com” and if that is true the host window is closed. The function also writes some debug logs so that it is easier to see what happens.

public function OnNavigated(sender : Object, e : WebBrowserNavigatedEventArgs) {
    try {
        var url : String = e.Url.ToString();
        logger.Debug("Browser navigated to: " + url);

        if (url.Contains("www.youtube.com")) {
            if (host != null) {
                logger.Debug("Closing host");
                host.Close();
            }
        }
    } catch (e) {
        logger.Error("Failed to handle navigated event", e);
    }
}

Testing the script

To test the script you first need to start any M3 program such as MMS001. Then start the Script Tool using mforms://jscript and copy the full script code found in the end of the post.

When the script is started a modal Smart Office window should be opened with the Google web page. The output pane in the Script Tool should show the URL that the browser navigates to. If you click on the Youtube link in the Google page the browser window should close. You can modify the script to test using other URLs.

MNEMN_Canvas

Conclusion

The example scenario in this post is quite specific so it might not be directly applicable to something you are working on. Even if that is the case I hope that parts of the solution could still be usable such as how to handle events from modal task launches or how to access the WebBrowser control in a Smart Office window.

Complete script

import System;
import System.Windows;
import System.Windows.Controls;
import MForms;
import Mango.Services;
import Mango.UI.Services;
import Mango.UI.Core;
import System.Windows.Forms;
import System.Windows.Forms.Integration;

package MForms.JScript {
    class ModalWebBrowserTest {
        var logger;
        var controller;
        var browser : System.Windows.Forms.WebBrowser;
        var host : IInstanceHost;

        public function Init(element : Object, args : Object, controller : Object, debug : Object) {
            this.logger = debug;
            this.controller = controller;
            controller.add_Requested(OnRequested);

            var uri = new Uri("http://www.google.com");
            DashboardTaskService.Current.LaunchTaskModal(new Task(uri), OnLaunch, OnRunnerStatusChanged);
        }

        public function OnLaunch(sender : Object, e : LaunchEventArgs) {
            try {
                logger.Debug("Launch Status: " + e.LaunchStatus);

                if (e.LaunchStatus == LaunchStatus.Failed) {
                    logger.Error("Failed to launch task");
                }
            } catch (ex) {
                logger.Error("Failed to handle the launch event", ex);
            }
        }

        public function OnRunnerStatusChanged(sender : Object, e : RunnerStatusChangedEventArgs) {
            try {
                logger.Debug("Runner Status: " + e.NewStatus);

                if (e.NewStatus == RunnerStatus.Running) {
                    AttachToBrowser(e.Runner.Host);
                }
            } catch (ex) {
                logger.Error("Failed to handle runner status", ex);
            }
        }

        public function AttachToBrowser(host) {
            this.host = host;
            var formsHost : WindowsFormsHost = host.HostContent;
            browser = formsHost.Child;
            browser.add_Navigated(OnNavigated);
        }

        public function OnNavigated(sender : Object, e : WebBrowserNavigatedEventArgs) {
            try {
                var url : String = e.Url.ToString();
                logger.Debug("Browser navigated to: " + url);

                if (url.Contains("www.youtube.com")) {
                    if (host != null) {
                        logger.Debug("Closing host");
                        host.Close();
                    }
                }
            } catch (e) {
                logger.Error("Failed to handle navigated event", e);
            }
        }

        public function OnRequested(sender : Object, e : RequestEventArgs) {
            try {
                if (controller != null) {
                    controller.remove_Requested(OnRequested);
                    controller = null;
                }
                if (browser != null) {
                    browser = null;
                    browser.remove_Navigated(OnNavigated);
                    browser = null;
                }
            } catch (ex) {
                logger.Error("Failed to handle the requested event", ex);
            }
        }
    }
}

5 thoughts on “Handling navigation events in a modal browser window using JScript

  1. Alessio

    Hello. This script was very helpful for what I had to do.
    Speaking about ways to embed web pages, is there a way to let the M3 form recognize the PDF format? I am trying to open a web list of documents and when I click on a PDF file, it doesn’t show up, but instead a little cross appears.
    Thank you.

    Reply
    1. norpe Post author

      Depending on the version of Smart Office and the bitness of the operating system (32 or 64) it could be an issue with the PDF plugin used. Do you have some more information?

      Reply
  2. Alessio

    I am launching it on a 64-bit machine with Windows 8. The PDF plugin works fine if I open the link with IE, but it doesn’t when I embed the web page in M3. Smart Office is in version 10.0.2.0.4.

    Reply
    1. norpe Post author

      The problem is most likely that the WebBrowser control is running the 64-bit version of Internet Explorer and that the PDF plugin do not have 64-bit support.

      Up to Smart Office version 10.0.5.x the application is built using the AnyCPU setting which means it will be a 64-bit process on 64-bit operating systems. This is a problem for web pages that requires plugins that do not support 64 bits. For Smart Office 10.1.x we changed so that the application is built with the x86 setting which means it will always be a 32 bit process on all operating systems. The benefit of this is that web pages with plugins should work in almost all cases. One downside of this is that the memory limit for the Smart Office process is 3 GB but we hope that won’t be an issue for some time.

      I’d guess that your scenario would work on Smart Office 10.1.x.

      Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s