Controlling the user’s canvas with stand-alone Jscripts

The user’s canvas is where the user has his shortcuts and widgets monitoring business data. The first time the user starts Smart Office she will get the default Canvas with the content that an administrator has configured. In this post I’ll show you how to share a canvas file on a fileshare and create a Jscript that will load the Canvas everytime Smart Office starts.


This means that you can update the Canvas file and the changes are propageted to all users from a central location. Think of this as a standard Canvas that will be loaded with the latest data over and over again.

The solution builds on the following concepts in Smart Office:
– Adding a start-up task to the installation link.
– Launching Smart Office via a web link or shortcut that contains a link with parameters.
– Running scripts stand-alone (currently for M3 only)
– Knowing what Smart Office API to use to load a Canvas.jade file
– Using the save canvas option on the canvas menu to create a canvas file

Adding a start-up task to the installation link
The installation link to Smart Office looks something like this: http://yourserver/mango/MangoClient.application?server=https://server.infor.com. The exact URL can be found on the installation page after installing Smart Office. It is possible to add client parameters to this link. For an overview of the parameters read this post. One parameter is the TASK parameter.

Adding a task to run a JScript after start up will look like this:


http://yourserver/mango/MangoClient.application?server=https://server.infor.com&task=mforms://_runscript?name=ScriptName.js&args=YourParametersInURIFormat

//no parameters in this example
http://yourserver/mango/MangoClient.application?server=https://server.infor.com&task=mforms://_runscript?name=OpenJadeFile.js

// if you have parameters the task parameter needs to be encoded. Perhaps the 'YourParametersInURIFormat' needs to be encoded before encoding the mforms://_runscript link as well.
http://yourserver/mango/MangoClient.application?server=https://server.infor.com&task=mforms%3A%2F%2F_runscript%3Fname%3DScriptName.js%26args%3DYourParametersInURIFormat

An online encoding tool always come in handy when an URL (or parameter) needs encoding. I encode the parameter parts seperately.

For this link to work there has to be a JScript deployed on your mne / M3 User Adapter server, in the same way that you would deploy any standard M3 script.

If you would like this link on the Desktop you can follow the instructions in How to put the Smart Office icon on your desktop post.

The users need to open Smart Office with this specific link for the script to run.

The canvas script

The script will load the Canvas file via an API that will load the Canvas file on the UI thread. This is generally a big no no. You should not load stuff on the UI thread. I have therefore added a new API method that will load in the background and then replace the Canvas. It will be available in 10.1.2. But until then, since this is part of the start-up, it might be OK to load the Canvas file on the UI-thread. I do recommend you to change the script as soon as the new API is available.

Here is the Jscript:

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

package MForms.JScript {
   class OpenJadeFile {
      public function Init(element: Object, args: Object, controller : Object, debug : Object) {
         debug.WriteLine("Script Initializing.");
         
         // uncomment the line you would like to test
         
        //This line works in current releases but will load the canvas on the UI thread
     	FileFormatService.Current.Launch("\\\\server\\Canvas\\KarinCanvas.jade", FileContent.Canvas, false);
     	
     	//This line will work in 10.1.2 and download on background thread or load directly if the file exists locally
     	//FileFormatService.Current.LaunchNoWarning(new Uri("\\\\server\\Canvas\\KarinCanvas.jade"));
     	
     	// Use launch task with parameter to hide warning will work in 10.1.2
     	//var task = new Task("\\\\server\\Canvas\\KarinCanvas.jade");
     	//task.Parameter = false;
     	//DashboardTaskService.Manager.LaunchTask(task);    
     	
      }
   }
}

The link can be to a local file, an URL or a file share. Using a local file if possible is good and in case you as a user would like to use this approach then you should simply create the desktop Icon with a task that points to a local file. It will look like this:


http://yourserver/mango/MangoClient.application?server=https://server.infor.com&task=file://E:ExampleFilesKarinCanvas.jade

The only “problem” with this link is that you will get a warning dialog before opening the Canvas. But this is a way for the end-user to have a set of links that can be opened directly so that she does not have to go in Smart Office and select switch canvas – it can be done directly. This approach does not require a script to be deployed, but has the drawback of the confirm dialog.

QuestionDialogCanvas

Disclaimer
You have to consider resolution when you create the canvas. Either you need to make sure the content fits in different resolutions or you create different links.
Consider passing in the path to the jade file as a parameter in the script. That way you can use the same script for a bunch of different files. If you have issues with the path as a parameter you will need to URI encode it. If you use a name standard for you canvas files the script itself can check the resolution and load different files. Since you can write code you can do pretty much anything. Perhaps something for a follow-up post.

User’s will be able to update the canvas but the central canvas file will be loaded as they re-start Smart Office with the URL.

15 thoughts on “Controlling the user’s canvas with stand-alone Jscripts

  1. Heiko

    Hi Karin,

    the “runscript” command does not look for the local script path. Is there any way to achieve this? It would be great for development/test purposes.

    I was already thinking about a generic script which can be called with the script name as a parameter; but is there any object which exposes the “LocalScriptPath” setting to the script? I found the “ScriptManager” class, but it is abstract.

    /Heiko

  2. Jonas

    Is there any way to initialize a script when user login to ISO without altering the link to ISO?

    To give you the background, we are performing an upgrade from M3 7.1 to 13 and we would like to present users that login to the old environment with information about the closing down of the old environment and refer them to the new environment..

    Of course we inform all users via the other possible channels but usually there are a few people that open the old environment by routine or mistake..

    1. karinpb Post author

      Hej Jonas,
      It is a valid scenario but we don’t have support for it.
      If you have the Smart Office SDK it would be very easy to write a feature that shows a message at startup.
      It would also be pretty easy to add support for script as well (at least without parameters).
      I can perhaps create this feature but I’m on vacation this week.

      The trick is to subscribe to ClientState changes and wait until the client is running. The actual message could be a configuration in the profile.
      So it would be a showStartup message feature or startup script feature and then the profile needs to have the URL to the script.

  3. Pingback: Adding Items to the Navigator Menu | Potato IT

  4. thibaudatwork

    Tjena Karin, I have a Hyperlink personalization in a column of the CRS610/B list. The Hyperlink launches a script with mforms://_runscript/?name=Test . The script needs the controller of CRS610/B (as well as the content and the selected row which I can get from the controller). But because the way the script was launched, it gets null. How can I get the controller? Tack. /Thibaud

      1. thibaudatwork

        This is what I have so far, but I am hoping for a better way:
        public function Init(element: Object, args: Object, controller : Object, debug : Object) {
        if (controller == null) {
        var host: IInstanceHost = Mango.UI.Services.WindowManager.Current.ActiveWindow;
        var instances = MainController.Current.GetInstances();
        for (var controller_: IInstanceController in instances) {
        if (controller_.Host.Task == host.Task) {
        this.controller = controller = controller_;
        break;
        }
        }
        }
        }

      2. thibaudatwork

        Sorry for the spam. I followed your instructions for Debugging script assemblies in Visual Studio, and How to check if a JScript is running in a Mashup. Here is the solution to find the controller (of a Hyperlink to a script in a Conditional Style), even if the m3:ListPanel is in a Mashup. This works whether in an MForms instance, in a Mashup instance, and in the Mashup Designer. Let me know if you have a shorter solution. Tack så mycket!

        public void Init(object element, object args, IInstanceController controller, ScriptDebugConsole debug)
        {
        	if (controller == null)
        	{
        		IInstanceHost activeWindow = Mango.UI.Services.WindowManager.Current.ActiveWindow;
        		System.Collections.Generic.IList<IInstanceController> instances = MainController.Current.GetInstances();
        		foreach (IInstanceController controller_ in instances)
        		{
        			EmbeddedHostWindow parentWindow = (EmbeddedHostWindow) Mango.UI.Utils.Helpers.FindParent(controller_.RenderEngine.ListControl.ListView, Type.GetType("Mango.UI.Services.EmbeddedHostWindow,Mango.UI"));
        			if (parentWindow == activeWindow)
        			{
        				controller = controller_; // found
        				break;
        			}
        		}
        		if (controller == null) {
        			// not found
        		}
        	}
        }
      3. mactol

        Hi thibaudatwork,

        Did you resolve the issue regarding about the script not working when run from Mashup?
        I am having the same problem and i would like to know how you’ve solve this.

        Thank you!

    1. karinpb Post author

      Hi,
      I’m afraid there is no supported way to get the controller. What is the overall scenario that you are trying to solve? Does it have to be a Hyperlink personalization? Why not have the script create the hyperlinks and attach an event handler? I do understand that launching a script would be easier but how would you even know from which progarm the link was clicked? There might be multiple CRS610/B panels? Let me know the full scenario and I’ll ask Norpe.

      1. thibaudatwork

        Tjena Karin,

        Tack för svaret.

        My goal is to have a general solution to launch a script from a hyperlink in a list, and get the corresponding controller. This should work from an M3 program, and from a Mashup that has one or more instances of that M3 program, each hyperlink knowing which M3 controller it was launched from (as you say, a Mashup can have multiple CRS610/B panels).

        My specific scenario is to launch a script that calls an M3 API to get some values based on the selected row (for that I need the controller), that constructs a URL, and that starts that URL in a browser with ScriptUtil.Launch(url). That’s my specific scenario, but it could be anything else.

        So far I was only able to launch the script in a conditional style with mforms://_runscript/?name=MyScript.js . The problem is that the controller is not carried over, it is lost along the way, MForms.MainController.HandleRunScriptCommand passes null to ScriptManagerM3.Instance.RunScripts, and hence the script gets null for controller.

        As you suggest, the alternative is to create the hyperlinks and the event handler manually by script, but instead of zero lines of code, now we are talking about 50 lines of code or more.

        Can I make this a request for feature? Please enhance Smart Office such that it carries over the controller, if any, for any link mforms://_runscript .

        Tack,

        –Thibaud

  5. thibaudatwork

    Mactol, here is my limping code that attemps to retrieve the controller even from a Mashup (in C#, to be run from a hyperlink in CRS618/B1), hoping that karinpb and norpe will be able to help:

    public void Init(object element, string args, IInstanceController controller, ScriptDebugConsole debug)
    {
    	// ensure we have a controller; the controller is null if the script is run with mforms://_runscript/
    	if (controller == null)
    	{
    		// get the window that ran the script with mforms://_runscript/ , we suppose it was run from a Hyperlink in Conditional Style; PROBLEM: if mforms://_runscript/ was run from somewhere else, then ActiveWindow may return an incorrect window
    		IInstanceHost activeWindow = Mango.UI.Services.WindowManager.Current.ActiveWindow;
    		// get all the running MForms instances (it could be a list panel or detail panel, inside a Mashup or not
    		System.Collections.Generic.IList<IInstanceController> instances = MainController.Current.GetInstances();
    		foreach (IInstanceController controller_ in instances)
    		{
    			if (controller_.RenderEngine.ListViewControl != null) { // the ListViewControl is null if this MForms instance is a DetailPanel; process only the instances that have a list
    				EmbeddedHostWindow parentWindow = (EmbeddedHostWindow) Mango.UI.Utils.Helpers.FindParent(controller_.RenderEngine.ListViewControl, Type.GetType("Mango.UI.Services.EmbeddedHostWindow,Mango.UI"));
    				if (parentWindow == (activeWindow as EmbeddedHostWindow))
    				{
    					if (controller_.PanelState.PanelHeader.Equals("CRS618/B1")) { // the parentWindow could be a Mashup with multiple M3 programs and CRS618/B1; process only the instances of CRS618/B1
    						// found the controller
    						// PROBLEM1: this may be the wrong controller if there are more than one CRS618/B1 in a Mashup (it should be a rare scenario)
    						// PROBLEM2: this may be the wrong controller if there are zombie instances leftover by the Mashup Designer (there is a bug in Mashup Designer that it doesn't cleanup its sessions)
    						this.controller = controller = controller_;
    						break;
    					}
    				}
    			}
    		}
    		if (controller == null)
    		{
    			// your error handler here, controller not found
    		}
    	}
    	// the rest of your code here
    }
    

Comments are closed.