Using JScript and a Mashup to browse for values on M3 panels

The goal of this post is to show how JScript and Mashups can be used to extend the browse functionality in M3. The idea is simple, instead of using the M3 Browse dialog to find a field value on a panel a custom built Mashup is used instead. The Mashup can use M3 panels with Enterprise Search, data services and all the other Mashup controls to improve the browse experience for the user.

The solution consists of two parts, a generic JScript and one or more browse Mashups. The browse Mashup is started using a button or a keyboard shortcut, the user selects something in the Mashup and then one or more values from the Mashup are used to update fields on the panel. The rest of the post will cover a simple browse Mashup and the important parts of the JScript.

Screenshot of the example Mashup

Browse Mashup example on the MMS001 E-panel

Browse Mashup
The browse Mashup can be simple or very complicated but there are some requirements that must be fulfilled for the script to work. There must be a named control in the Mashup that implements the IMashupCurrentItem interface. Most of the Mashup controls will do fine such as the M3 ListPanel and the M3 MIListPanel etc. The named control will be used to get the selected values and in most cases it will be some kind of list panel. In our example the control is a M3 ListPanel named UserList.

The next step is to make it possible for the user to select a value and the most obvious solution is to have a select button. The JScript can be configured with the name of a button in the Mashup that is to be used as the select button. The button in the example is named SelectButton. The script also adds an event handler for the Open event to the named Mashup control so if the control supports the Open event this can also be used to trigger the select. In the M3 ListPanel the user can double-click on a list row or select a list row and press the enter key to trigger the Open event.

A browse Mashup can be very specific so that it only works for one field or more generic so that it supports more fields and different scenarios. The Mashup can be initialized by parameters from the script and also by field values from the panel to provide required input values or to control the behavior of the Mashup.

The example Mashup below uses a M3 ListPanel with the MNS150 program to select users. There is also a search field that is powered by Enterprise search.

Mashup XAML

<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ui="clr-namespace:Mango.UI.Controls;assembly=Mango.UI" xmlns:mashup="clr-namespace:Mango.UI.Services.Mashup;assembly=Mango.UI" xmlns:m3="clr-namespace:MForms.Mashup;assembly=MForms">
   <Grid.ColumnDefinitions>
      <ColumnDefinition Width="*" />
   </Grid.ColumnDefinitions>
   <Grid.RowDefinitions>
      <RowDefinition Height="Auto" />
      <RowDefinition Height="*" />
      <RowDefinition Height="Auto" />
   </Grid.RowDefinitions>

   <StackPanel Grid.Row="0" Margin="8,8,0,0" Orientation="Horizontal">
      <TextBlock Text="Search query:" />
      <TextBox Name="SearchQuery" Width="200" Height="20" Margin="8,0,0,0" />
      <Button Content="Search" IsDefault="True" Margin="8,0,0,0">
         <Button.CommandParameter>
            <mashup:Events>
               <mashup:Event TargetName="UserList" SourceEventName="Click" TargetEventName="Search">
                  <mashup:Parameter SourceKey="Query" Value="{Binding Path=Text, ElementName=SearchQuery}" />
               </mashup:Event>
            </mashup:Events>
         </Button.CommandParameter>
      </Button>
   </StackPanel>

   <m3:ListPanel Grid.Row="1" Margin="8,24,8,0" Name="UserList" Program="MNS150" EnableRelatedOptions="False" EnableBasicOptions="False">
      <m3:ListPanel.Events>
         <mashup:Events>
            <mashup:Event SourceEventName="Startup">
            </mashup:Event>
         </mashup:Events>
      </m3:ListPanel.Events>
   </m3:ListPanel>

   <Button Margin="8,16,8,8" Grid.Row="2" Name="SelectButton" Content="Select" HorizontalAlignment="Right" VerticalAlignment="Bottom" />

</Grid>

Mashup Manifest

<?xml version="1.0" encoding="UTF-8"?>
<Manifest>
  <Name>M3UserSearch</Name>
  <Author />
  <Description>M3 User Search</Description>
  <Version>1.0.0</Version>
  <Files>
    <File Path="M3UserSearch.xaml" MimeType="application/xaml+xml" VisibleName="M3 User Search" MainClass="True" SingleInstance="True" />
  </Files>
</Manifest>

Script overview
The script has a lot of parameters where some are mandatory and others are optional for controlling the behavior of the script. The parameters are documented in the script header so most of them won’t be covered here. The rest is a walkthrough of important parts of the script to explain how it works and perhaps give some ideas for how to solve similar issues in other scripts.

Init
The Init method starts by loading the script parameters from the args string and initializing mandatory and optional parameters using the LoadParameters and SetParameters methods. The script will exit if any mandatory parameters are missing. The syntax of the args string is a semi colon (;) separated list of name value pairs, where the name and value are separated by an equals sign (=).

All scripts that attach any event handlers to controls on a M3 panel should listen to the Requested event of the controller and this script is no exception. When the Requested event fires the script should remove all event handlers to prevent memory leaks and other unexpected behaviors.

If the script is configured to use a button that button will be created and added to the M3 panel. A keyboard shortcut is also hooked up if configured.

The script has the option to reuse a Mashup so the last check is to see if the script can attach to a Mashup that is already started. The reuse option should at least be considered for Mashups that takes a long time to start up. Browse Mashups that are used very frequently should probably also be configured to be reusable.

LaunchMashup
The LaunchMashup function is called from the event handlers for the button (OnClickBrowse) and the keyboard shortcut (OnKeyDown). If the Mashup is reusable the function will try to find an existing Mashup. If the Mashup is not reusable or if no existing Mashup can be found a new Mashup will be launched using an overloaded version of the LaunchTask method in the DashboardTaskService. The LaunchTask method is called with a RunnerStatusChangedEventHandler so that the script will know when the Mashup is up and running.

OnRunnerStatusChanged
This event handler will be called when the status of the Runner for the Mashup changes. When the status changes to RunnerStatus.Running the script will attach to the started Mashup. When the Mashup is closed the status will change to RunnerStatus.Closed and the script will detach from the Mashup by removing all the event handles that had been added.

AttachToMashup
The script will try to find a select button in the Mashup and if a button is found it will add a click handler to it. A handler for the Open event is then added to the Mashup window to catch the bubbling Open events.

There is a limitation with this solution if there are more than one control in the Mashup that fires the Open event. Further revisions of the script should probably check that the source of the Open event actually is the control that is defined to be the source of the browse values.

DetachFromMashup
This function simply removes any event handlers added by the AttachToMashup function.

ProcessSelect
This function is called from the event handler for the select button in the Mashup (OnClickSelect) and also from the event handler for the Open event (OnOpen). The SetSelectedValues function will be called to get values from the Mashup and set them on fields on the panel. The script can be configured to set a single value or multiple values.

If the Mashup is configured to be modal or if it is not reusable the Mashup will be closed. Closing the Mashup will cause the Runner to change its status to Closed and the OnRunnerStatusChanged function will be called where the scripts detaches all events from the Mashup.

In the other cases the DetachFromMashup will be called to remove event handlers from the reusable Mashup. Since the Mashup window had input focus the M3 window must be activated to restore keyboard focus to the panel. This is done asynchronously by adding a callback on the dispatcher queue using the Application.Current.Dispatcher.BeginInvoke method. By doing this we ensure that the Mashup window gets time to close before we restore focus to the M3 panel.

Test instructions
To test the script you need to create a Browse Mashup or use the example in this post. The Mashup must be deployed, either privately from the Mashup Designer or on a LSO server using the LCM. The script can be tested from the Script Tool (mforms://jscript) by entering an argument string in the Args field. The example arguments below are intended to be used on the MMS001 E-panel with the example Mashup. If you create your own Mashup or changes any names you must updated the argument string accordingly.

Example argument string

MashupUri=mashup:///?BaseUri=M3UserSearch.mashup&RelativeUri=M3UserSearch.xaml;MashupControlName=UserList;MashupButtonName=SelectButton;SourceFieldNames=USID;TargetFieldNames=MMRESP;ButtonText=Browse;ButtonPosition=14,8,11,1;IsShortcutKeyEnabled=true;IsModal=true;IsReusable=false

M3MashupBrowse_1_0 script code

import System;
import System.Collections;
import System.ComponentModel;
import System.Web;
import System.Windows;
import System.Windows.Controls;
import System.Windows.Input;
import System.Windows.Threading;
import Mango.Services;
import Mango.UI.Core;
import Mango.UI.Services;
import Mango.UI.Services.Mashup;
import MForms;

//
// M3MashupBrowse_1_0
// 2011-12-21
//
// This script can be used to browse for values on a M3 panel using a Mashup.
//
// Overview
// --------
// The script is generic and must be configured using an argument string with parameters.
// The browse Mashup can be opened using a browse button added by the script or by using the Ctrl+F keyboard shortcut in the target field.
// It is possible to browse for one or more values in a single select.
// The script can use a button in the Mashup to trigger the select but it also supports the Open Mashup event (mouse double-click or enter for the M3 ListPanel).
// The Mashup can be displayed in a modal window (the default) or in a non-modal window.
// The Mashup instance can be reused for non-modal windows.
//
// Prerequisites
// -------------
// A deployed Mashup that contains a named Mashup control that implements the IMashupCurrentItem interface (such as the M3 ListPanel, M3 MIListPanel, DataListPanel etc).
// If a M3 ListPanel is used make sure that the IsDefaultActionEnabled property is set to false on the ListPanel to prevent the default behavior for Enter and double-click in the list.
//
// Argument syntax
// ---------------
// The argument string should consist of name/value pairs for each parameter on the form Name=Value.
// The name/value pairs should be separated by semi-colon on the form Name1=Value1;Name2=Value2;
//
// Mandatory parameters
// --------------------
// MashupUri - The URI to the Mashup that should be used for browsing a value.
// MashupControlName - The name of the Mashup control that is used for selecting a value.
// SourceFieldNames - A single field name or a comma separated list of source field names. The field name is used to get a value from the Mashup control.
//
// Optional parameters
// -------------------
// TargetFieldNames - A single field name or a comma separated list of target field names. The field name is used to set a value on the panel. If this parameter is not set the script must be connected to the target element.
// MashupButtonName - The name of a button in the Mashup used to select a value.
// ButtonText - The text on the panel browse button.
// ButtonPosition - The position for the panel browse button. Column,Row,Width,Height in grid coordinates. Ex: 14,8,11,1
// ButtonTabIndex - The tab index for the panel browse button.
// IsModal - Indicates if the Mashup should be shown in a modal window or not. The default value is true.
// IsReusable - Indicates if the Mashup can be reused. The default value is false. If this parameter is set to true the IsModal parameter will have no effect.
// IsShortcutKeyEnabled - Indicates if the Ctrl+F shortcut key is enabled. The default value is true.
// DefaultFieldNames - A single field name or a comma separated list of target field names. The field name should be a name of a control on the panel.
//    Values for these controls will be sent to the Mashup at startup using the DefaultValues parameters.
// DefaultTargetFieldNames - A single field name or a comma separated list of target field names.
//    Can be used in combination with the DefaultFieldNames parameter if the names of the fields on the panel are different from the ones in the Mashup.
//    Ex: MMITNO on the panel should be sent to the Mashup as ITNO or ItemNumber.
//    Note that this parameter must contain the same number of names as the DefaultFieldNames parameter.
//
// Example argument string
// -----------------------
// MashupUri=mashup:///?BaseUri=M3UserSearch.mashup&RelativeUri=M3UserSearch.xaml;MashupControlName=UserList;MashupButtonName=SelectButton;SourceFieldNames=USID;TargetFieldNames=MMRESP;ButtonText=Browse;ButtonPosition=14,8,11,1;IsShortcutKeyEnabled=true;IsModal=true;IsReusable=false

package MForms.JScript {
	class M3MashupBrowse_1_0 {

		var ParamMashupUri = "MashupUri";
		var ParamMashupControlName = "MashupControlName";
		var ParamMashupButtonName = "MashupButtonName";
		var ParamIsModal = "IsModal";
		var ParamIsReusable = "IsReusable";
		var ParamButtonText = "ButtonText";
		var ParamButtonTabIndex = "ButtonTabIndex";
		var ParamButtonPosition = "ButtonPosition";
		var ParamSourceFieldNames = "SourceFieldNames";
		var ParamTargetFieldNames = "TargetFieldNames";
		var ParamDefaultFieldNames = "DefaultFieldNames";
		var ParamDefaultTargetFieldNames = "DefaultTargetFieldNames";
		var ParamIsShortcutKeyEnabled = "IsShortcutKeyEnabled";

		var cacheKey : String;
		var isMandatoryMissing = false;
		var mashupUri;
		var sourceCacheKey;
		var sourceFieldNames;
		var targetFieldNames;
		var mashupControlName;
		var mashupButtonName;
		var isModal;
		var isReusable;
		var isShortcutKeyEnabled;
		var parameters;
		var runner;
		var content;
		var debug;
		var buttonText;
		var buttonIndex;
		var buttonPosition;
		var browseButton;
		var controller;
		var mashupButton;
		var mashupHost;
		var mashupContent;
		var targetControl;
		var isAttached;

		public function Init(element : Object, args : Object, controller : Object, debug : Object) {
			this.debug = debug;
			if (String.IsNullOrWhiteSpace(args)) {
				debug.WriteLine("Script arguments missing");
				return;
			}

			this.controller = controller;

			LoadParameters(args);
			SetParameters(element);
			if (isMandatoryMissing) {
				debug.WriteLine("One or more mandatory parameters missing, script will exit.");
				return;
			}

			this.content = controller.RenderEngine.Content;
			controller.add_Requested(OnRequested);

			if (buttonText != null && buttonPosition != null) {
				AddButton(buttonText, buttonPosition);
			}

			if (isShortcutKeyEnabled && targetFieldNames != null) {
				AddKeyHandler();
			}

			if (isReusable) {
				AttachToExistingMashup(cacheKey);
			}
		}

		private function AddKeyHandler() {
			var control = ScriptUtil.FindChild(content, targetFieldNames[0]);
			if (control != null && control.Text != null) {
				control.add_KeyDown(OnKeyDown);
				targetControl = control;
			}
		}

		public function OnKeyDown(sender : Object, e : KeyEventArgs) {
			try {
				if (e.Key == Key.F && e.KeyboardDevice.Modifiers == ModifierKeys.Control) {
					e.Handled = true;
					LaunchMashup();
				}
			} catch (ex) {
				debug.WriteLine(ex.ToString());
			}
		}

		private function SetParameters(element) {
			// Mandatory parameters
			mashupUri = GetMandatoryParameter(ParamMashupUri);
			mashupControlName = GetMandatoryParameter(ParamMashupControlName);
			var p = GetMandatoryParameter(ParamSourceFieldNames);
			if (p != null) {
				sourceFieldNames = p.Split(',');
				sourceCacheKey = p;
			}

			// Optional parameters
			mashupButtonName = GetParameter(ParamMashupButtonName);
			buttonText = GetParameter(ParamButtonText);
			buttonPosition = GetParameter(ParamButtonPosition);
			isShortcutKeyEnabled = GetBoolParameter(ParamIsShortcutKeyEnabled, true);
			isReusable = GetBoolParameter(ParamIsReusable, false);
			isModal = !isReusable && GetBoolParameter(ParamIsModal, true);

			p = GetParameter(ParamTargetFieldNames);
			if (p != null) {
				targetFieldNames = p.Split(',');
			} else if (element != null) {
				targetFieldNames = new String[1];
				targetFieldNames[0] = element.Name;
			}

			cacheKey = GetCacheKey();
		}

		private function AddButton(text, position) {
			browseButton = new Button();
			browseButton.Content = text;
			browseButton.MinHeight = 0;
			browseButton.Height = Double.NaN;
			browseButton.Width = Double.NaN;
			browseButton.Margin = Configuration.ControlMargin;
			browseButton.HorizontalAlignment = HorizontalAlignment.Stretch;
			SetPositionFromString(browseButton, position);
			content.Children.Add(browseButton);
			var p = GetParameter(ParamButtonTabIndex);
			if (p != null) {
				browseButton.TabIndex = Int32.Parse(p);
			}

			browseButton.add_Click(OnClickBrowse);
		}

		private function GetCacheKey() {
			var b = new System.Text.StringBuilder(mashupUri);
			if (controller != null) {
				b.Append(controller.PanelState.JobId);
			}
			if (sourceCacheKey != null) {
				b.Append(sourceCacheKey);
			}
			return b.ToString();
		}

		private function GetExistingMashup(key) {
			var runner = SessionCache.Get(key);
			if (runner != null) {
				if (runner.Status == RunnerStatus.Running && runner.Host != null) {
					return runner;
				}
				SessionCache.Remove(key);
			}
			return null;
		}

		private function AttachToExistingMashup(key) {
			var runner = GetExistingMashup(key);
			if (runner != null) {
				AttachToMashup(runner.Host);
				return true;
			}
			return false;
		}

		private function LaunchExistingMashup(key) {
			var runner = GetExistingMashup(key);
			if (runner != null) {
				AttachToMashup(runner.Host);
				runner.Host.ActivateWindow(true);
				return true;
			}
			return false;
		}

		private function GetPanelValue(name) {
			if (content == null) {
				return null;
			}
			var element = ScriptUtil.FindChild(content, name);
			return element != null ? MFormsUtil.GetControlValue(element) : null;
		}

		private function GetMashupUri() {
			var defaultValues = "";
			var p = GetParameter(ParamDefaultFieldNames);
			if (p != null) {
				var names = p.Split(',');
				var targets = null;
				p = GetParameter(ParamDefaultTargetFieldNames);
				if (p != null) {
					targets = p.Split(',');
				}

				for (var i = 0; i < names.Length; i++) { 					var name = names[i]; 					var value = GetPanelValue(name); 					if (value != null) { 						if (targets != null && targets.Length > i) {
							name = targets[i];
						}
						defaultValues += name + ":" + value + ";"
					}
				}
			}

			var uri = mashupUri;
			if (!String.IsNullOrEmpty(defaultValues)) {
				uri += "&DefaultValues=" + HttpUtility.UrlEncode(defaultValues);
			}

			if (isModal) {
				uri += "&lso-modal";
			}
			return uri;
		}

		private function LaunchMashup() {
			if (isReusable && LaunchExistingMashup(cacheKey)) {
				return;
			}

			var uri = GetMashupUri();
			var task = new Task(uri);
			var handler : RunnerStatusChangedEventHandler = OnRunnerStatusChanged;
			DashboardTaskService.Current.LaunchTask(task, null, handler);
		}

		private function SetSelectedValue(targetName, value, focus) {
			var textBox = ScriptUtil.FindChild(content, targetName);
			if (textBox == null) {
				debug.WriteLine("Target field " + targetName + " not found");
				return;
			}
			textBox.Text = value;
			if (focus) {
				MFormsUtil.HandleFocus(textBox);
			}
		}

		private function GetSelectedValue(name) {
			var list = mashupHost.HostContent.FindName(mashupControlName);
			if (list == null) {
				debug.WriteLine("ListPanel " + mashupControlName + " not found");
				return null;
			}
			return list[name];
		}

		private function AttachToMashup(host : IInstanceHost) {
			if (isReusable && isAttached) {
				return;
			}

			mashupHost = host;
			mashupButton = host.HostContent.FindName(mashupButtonName);
			if (mashupButton != null) {
				mashupButton.add_Click(OnClickSelect);
			} else {
				debug.WriteLine("Button " + mashupButtonName + " not found");
			}

			var handler : CurrentItemChangedEventHandler = OnOpen;
			mashupContent = host.HostContent;
			mashupContent.AddHandler(Events.OpenEvent, handler, true);
			isAttached = true;
		}

		private function DetachFromMashup() {
			if (mashupButton != null) {
				mashupButton.remove_Click(OnClickSelect);
				mashupButton = null;
			}
			if (mashupContent != null) {
				var handler : CurrentItemChangedEventHandler = OnOpen;
				mashupContent.RemoveHandler(Events.OpenEvent, handler);
				mashupContent = null;
				mashupHost = null;
			}
			isAttached = false;
		}

		private function DetachEvents() {
			if (browseButton != null) {
				browseButton.remove_Click(OnClickBrowse);
			}
			if (controller != null) {
				controller.remove_Requested(OnRequested);
				controller = null;
			}
			if (targetControl != null) {
				targetControl.remove_KeyDown(OnKeyDown);
				targetControl = null;
			}
		}

		private function SetSelectedValues(currentItem : IMashupCurrentItem) {
			for (var i = 0; i < targetFieldNames.Length; i++) {
				var source = sourceFieldNames[i];
				var target = targetFieldNames[i];
				var value = currentItem.GetValue(source);
				if (value != null) {
					SetSelectedValue(target, value, i == 0);
				} else {
					debug.WriteLine("No selected value found for source " + source + " and target " + target);
				}
			}
		}

		private function ProcessSelect(currentItem : IMashupCurrentItem) {
			if (controller == null) {
				return;
			}

			SetSelectedValues(currentItem);

			if (isModal || !isReusable) {
				mashupHost.Close();
				mashupHost = null;
				runner = null;
				return;
			}

			DetachFromMashup();

			var action : Action = ActivateAsync;
			Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Input, action);
		}

		private function ActivateAsync() {
			try {
				if (controller != null && controller.Host != null) {
					controller.Host.ActivateWindow(true);
				}
			} catch (e) {}
		}

		public function OnOpen(sender : Object, e : CurrentItemChangedEventArgs) {
			try {
				ProcessSelect(e.CurrentItem);
			} catch (e) {}
		}

		public function OnClickSelect(sender : Object, e : RoutedEventArgs) {
			try {
				var currentItem = GetMashupControl(mashupHost, mashupControlName);
				if (currentItem != null) {
					ProcessSelect(currentItem);
				}
			} catch (e) {}
		}

		public function OnRunnerStatusChanged(sender : Object, e : RunnerStatusChangedEventArgs) {
			try {
				if (e.NewStatus == RunnerStatus.Running) {
					runner = e.Runner;
					AttachToMashup(runner.Host);
					if (isReusable) {
						SessionCache.Add(cacheKey, runner);
					}
				} else if (e.NewStatus == RunnerStatus.Closed) {
					DetachFromMashup();
				}
			} catch (e) {}
		}

		public function OnClickBrowse(sender : Object, e : RoutedEventArgs) {
			try {
				LaunchMashup();
			} catch (e) {}
		}

		public function OnRequested(sender : Object, e : RequestEventArgs) {
			try {
				DetachEvents();
				DetachFromMashup();
			} catch (e) {}
		}

		private function GetBoolParameter(name, defaultValue) {
			var value = GetParameter(name);
			if (value != null) {
				return value.ToLower().StartsWith("t");
			}
			return defaultValue;
		}

		private function GetParameter(name) {
			return parameters.ContainsKey(name) ? parameters[name] : null;
		}

		private function GetMandatoryParameter(name) {
			var value = GetParameter(name);
			if (value == null) {
				isMandatoryMissing = true;
				debug.WriteLine("Mandatory parameter " + name + " not found");
			}
			return value;
		}

		private function LoadParameters(args) {
			parameters = new Hashtable();
			var kvps = args.Split(';');
			for (var i = 0; i < kvps.Length; i++) { 				var kvp = kvps[i]; 				var index = kvp.indexOf("="); 				if (index > 0) {
					var key = kvp.substring(0, index);
					var value = kvp.substring(index + 1);
					parameters[key] = value;
				} else {
					debug.WriteLine("Invalid parameter: " + kvp);
				}
			}
		}

		private function GetMashupControl(host : IInstanceHost, name : String) {
			var control = host.HostContent.FindName(name);
			if (control == null) {
				debug.WriteLine("Mashup control " + name + " not found");
				return null;
			}
			return control;
		}

		private function SetPositionFromString(element, position) {
			var a = position.Split(',');
			var height = a.Length > 3 ? a[3] : 1;
			SetPosition(element, Int32.Parse(a[0]), Int32.Parse(a[1]), Int32.Parse(a[2]), height);
		}

		private function SetPosition(element, left, top, width, height) {
			Grid.SetColumn(element, left);
			Grid.SetRow(element, top);
			Grid.SetColumnSpan(element, width);
			Grid.SetRowSpan(element, height);
		}
	}
}

52 thoughts on “Using JScript and a Mashup to browse for values on M3 panels

  1. Daniel

    Hi,
    i have a question:
    I want to use the script to create a browse mashupt for items (MMS001).
    I created my own search mashup and deployed that with the LC Manager.
    If i use the mashupuri in this script i get a error message in the script tool that the mashup cannot be found. But i can use the Uri for a shortcut on the canvas and that works:

    var ParamMashupUri = “mashup:///?BaseUri=Mashups\Item_search.mashup&RelativeUri=itemsearch.xaml”;

    Is that the correct Uri?
    Is the mashup controller name the name of my M3List in the mashup?

    Thank you in advance for your help!

    Reply
    1. thibaudatwork

      Hi Daniel, you must escape the backslashes with double-backslashes in the script, like this: var uri = “mashup:///?BaseUri=Mashups\\CustomerService.mashup&RelativeUri=CustomerService.xaml”;

      Reply
  2. karinpb

    Hi, this is not my post and not my script but I think that the Uri looks correct. It is strange that Peter’s example does not have Mashups\ in the Uri but I know there is a different in the path if the Mashup is locally deployed and perhaps his example is for a locally deployed Mashup.

    I would try (the same as you):
    mashup:///?BaseUri=Mashups\Item_search.mashup&RelativeUri=itemsearch.xaml

    But you should NOT modify anything in the script – just use the configuration argument when setting up the personalization.

    So don’t change this line
    var ParamMashupUri….

    And yes, the MashupControl name is the name of your m3:ListPanel control in the mashup.

    What exactly is your error message?
    Mashup control xxx not found? In that case the MashupControlName does not have the correct name of the list.

    Reply
  3. Daniel

    It works now! The only problem that i have is, that we are using server mashups only. Is there a possibility to change the script to use a server mashup?

    Reply
    1. karinpb

      Of couse it works with server (deployed) mashups. As long as the arguments to the JScript has the correct uri. The mashup does not have to be a main application – so it doesn’t have to be in the Navigator.

      Reply
  4. Daniel

    Ok it works now also with a server deployed mashup.
    So now the last question 🙂 (hopefully)
    With the script tool everything works fine! I have the correct arrgument and if i run the script i see the button in OIS101/B and i can browse with my mashup.

    I copied now the script on the server: (\applications\LSO_M3_Adapter\webapps\mne\jscript\Itemsearch.js).
    And i added in OIS101/B -> Scripts “Itemsearch” with the correct arrgument.
    But nothing happen if i open OIS101/B again, also no error message in the log file….

    Is there a step inbetween i forgot?

    Reply
  5. Gaston

    Karin,

    I am calling a mahsup as a data lookup with this script and all works well. I would like to use this same approach calling MMS200 as a data lookup but it is missing the bookmarks, MMS200 is key because allows searching by many keys that we like. We can develop a web app to work like MMS200 an attach it to a mashup but I wonder if Java scripting could reach to the web app and retrieve the seletected data back to the LSO screen, The wep app can be done in AS400 NetApps.

    Thank you,
    Gaston

    Reply
    1. karinpb

      Hi Gaston,
      As for your question it is only possible to have a web application and populate data back if you have the Smart Office SDK (only available to our consultants and partners – not customers) and can write C# code that handles the new window with the web application as well as the integration. It is possible to communicate via COM with javascript between c# and a web app but that requires you to be able to add specific javascript to your web page as well and I’m not sure if you can do that with AS400 NetApps (becuase I don’t know AS400 NetApps).
      I’ve helped out with a similar assignment for a large important customer and it is quite advanced, it’s doable if you have the right skillset but it isn’t easy.

      I would recommend that you investigare the investment to add bookmark support to MMS200 using the MAK. It seems from a list that I have that MMS200 has bookmark support in M3 10.1.

      There is a programming standrard for adding bookmarks and in the Knowledge Base you can search for “Bookmarks in M3 10.1″.

      The bookmark code must be managed as M3 modifications at customers today, but they will be part of the next release of M3 (so if added as modification today, customers will not have to worry about upgrading them with the next M3 release, as they will be part of standard).

      Doing a MOD to enable booksmarks in MMS200 is much better than building the Web integration in Smart Office for a number of reasons.

      Reply
      1. Gaston

        Karin,

        I understand now.
        We are using M3BE1412 but I am having issues with the MMS200/B used in a mashup. I will followup through the Lawson case.

        Thank you Karin for your help,
        Gaston

  6. Chris Kalathas

    Hi,

    This is fantastic, thank you for the cod!.

    I am in the process of applying this concept to PDS002 to allow my engineers to search for alias’ numbers from MMS025 when creating product structures. Is it possible to call the mashup via a button on MMS002 then populate the Comp Nr / WC field (WWMTPL)?

    I know they could have MMS025 open at the same time, but it’s not ideal. I am in the middle of integrating this business into M3 and my preference is to make the transition as smooth as possible.

    Any help would be appreciated.

    Thank you

    Reply
    1. norpe Post author

      If you can create a Mashup for MMS025 that can show a list that contains the values you want to use in PDS002 it should be possible to use this script.

      Reply
      1. Chris Kalathas

        Thansk for the reply.

        I got to this point with no issues. I have the mashup with MMS025, the button is in place on PDS002 … what I can’t achieve is returning the value to the WWMTPL field (it’s on the browse panel). jScript returns an error ‘Target field WWMTPL not found’

        I have tried different TargetFieldNames based on the references in mForms/SES200 (WWMTPL), Server View (WBMTNO, XXMTNO, X1MTNO, X3MTNO) but to no avail. I’m not sure if populating a browse panel field requires different syntax in the jScript ….

  7. SENSER

    Hi all,
    Is it possible to run/launch a jscript from a Mashup (for example, into a specific Mashup, run the M3MashupBrowse_1_0.js or M3RelatedOption.js) ?
    I don’t find any way to perform this operation…

    Reply
      1. Jsenser

        Hi Karin,
        Thank for your response…
        I have another idea (i will test it this afternoon) : declared the script into the standard program and hope to benefit this script into the Mashup about the standard program…
        Do you mean what i want to say ?

      2. karinpb

        You can run a script in a standard List program and benefit from it from within your mashup. We even have a blog post on how you can check that the standard program is running within a Mashup. Please note that scripts will not run on a detail panel in a Mashup.

      3. Jsenser

        Hi Karin,
        Thank you for your response. I take notes about your comment.
        Another request : i had setup the script into the OIS106 program and display a Mashup with “order number”.
        When i select an order number into the mashup, OAORNO into the panel is updated but not others fields (customer, order lines …).
        So i need to simulate an ‘F5’ (upated action) into the script to refresh standard program… i try it since last Thursday but it do not work.
        I add the following script at the end of DetachFromMashup() function
        try
        {
        debug.WriteLine(“Jordane — Avant action : ” + actio);
        controller.PressKey(actio);
        debug.WriteLine(“Jordane — —–Apres action—–“);
        }
        catch (e)
        {
        // Handle error
        debug.WriteLine(“Request could not be completed, Please check your action parameters setup!”);
        }
        The obtained behavior : is OAORNO is set, OIS106 screen is updated… but the button added by script (“Select order number”) disapear into to standard panelafter refresh the script…
        Do you know why ?
        Jsenser

  8. Jsenser

    It’s ok now… sorry !
    The problem is that i tested my script with Scritp Tool…
    With script tool i had a specific behavior (scritp is only executed when i clicked “Run”…)
    With deployed my script into the server… but i renamed my script, not the class name of the scritp… and in this case, nothing happens…
    So, sorry for last post,
    Jsenser

    Reply
    1. karinpb

      Ok. You should always change the log level to debug and search for the name of your script. If it is added corrected you will find it in the log. (internal://log)

      Reply
  9. Pingback: Command & Control a Mashup from an M3 program | M3 ideas

  10. Marcus

    Hi. I am looking for a way to enter data in the field transaction code depending on the ordertype. For example, in OIS101/ for a creditorder we would like the transactioncode to be hardcoded per ordertype (instead of on the ordertype). I have been trying some with getfield OAORTP==”CRE” setfield “WBRSCD” == “999” but I am not able to solve this. Is this due to messed up parsefloat or what am I doing wrong? 🙂

    I know that the transactioncode could be set in OIS014 but in our case different users always use the different codes (RSCD) (but user AA always uses code XX and user BB always uses code ZZ) but we all use the same ordertype.

    is this a hard one?

    Reply
    1. karinpb

      Hi Marcus,
      I don’t know on what panel the RSCD field is. But as long as it is an open input field you should be able to set and get the value with script. Use the util methods as you can find the om this blog. But it has to be visible and editable on the screen you have created the script for. The datatype should not matter in this case. You need to use log messages to debug your code.

      Reply
  11. jonathansanjuan

    Hi,

    Has anyone ever encountered that on the mashup->jscript sequence, on this code:

    public function OnClickSelect(sender : Object, e : RoutedEventArgs) {
    try {
    var currentItem = GetMashupControl(mashupHost, mashupControlName);
    if (currentItem != null) {
    ProcessSelect(currentItem);
    }
    } catch (e) {}
    }

    But anything within “ProcessSelect(currentItem)” doesn’t seem to run. I’ve put some debug.Writeline within and it doesn’t run.

    I’ve also tried:

    debug.WriteLine(“OnClickSelect 1:”+mashupHost+”:”+mashupControlName+”:”+currentItem);
    ProcessSelect(currentItem);
    debug.WriteLine(“OnClickSelect 2”);

    And “OnClickSelect 2” doesn’t display either.

    Reply
  12. Jesper

    Hi
    I know this is an old post, but I give it a shot anyway 🙂
    Maybe it’s me can’t figure out how to do this, but i want to search for customer adresse in OIS002 when creating customer orders in OIS100/A.
    I have created the mashup, modified the Jscript argument and it works like a charme, I can search for an adress and return the adress number to OIS100/A…
    My issue is that Mashup should only show customer selected in OIS100/A, so I need to parce the CUNO from OACUNO to the Mashup. How do I do that?

    Thanks in advance
    Jesper

    Reply
    1. karinpb

      Hi Jesper,
      Just because it is old doesn’t mean that it isn’t still a very good post – and valid. Of course some details might change but I still use this blog myself when I get questions just because it is so easy to send a link with some mode information.

      You need to modify your mashup so that in the URL you add default parameters. The Mashup will then use the passed in parameter to load the addresses. This means that the Mashup will only work with an URL that has the default parameters added to the URL (as you only want to show addresses for that customer)

      There is an example of passing default parameters in the Mashup Designer called “Window”. Not so easy to spot what the URL must look like but it adds some values to the URL that it launches. But note that the URL is special since it goes to the samples – and back to the same Widget.

      The sample could be better explained with information text blocks so you need to play close attentions to details. But I’ll try and explain the part you are looking for below:

      The mashup is launched with an URL. Eg:

      var url = “mashup:///?BaseUri=Mashups\\CustomerService.mashup&RelativeUri=CustomerService.xaml”

      To that URL you can add a parameter called DefaultValues. Please note that if you have values that contains characters that needs to be escaped you MUST URL encode the entire value of the Default values parameters. You can pass multiple values with the following semicolon separated list : “parameter1:value1;parameter2:value2” etc.

      The URL would en with something like this:
      ...RelativeUri=yourmashup.xaml&DefaultValues=CUNO:YourValue;OtheParam:Value

      Now in your startup event you use CUNO and it would be retrieved from the DefaultValues – if there is any. The values passed like default values will just be “there” ready to be used in an Event.

      Good luck 🙂

      Reply
      1. SM@hd!k

        Hi karinpb,

        Thanks for the blog and details on how to pass parameters from M3 to Mashup.
        I have prepared the uri with in same format and i can open the mashup successfully but values are not appearing in Mashup text box.
        eg. If user selecting any line from M3 >COS115 and clicking Jscript button,it should open Mashup to populate the ORNO on mashup screen.
        Below is the uri :-
        new Uri(“mashup:///?BaseUri=MCO_Document_Connect.mashup&RelativeUri=MCO_DocumentConnect.xaml&DefaultValues=ORNO:0000000001;MashupControlName:MCO_Document_Connect;”)

        Mashup Properties for Text box:-
        Text:-Text=”{Binding Converter={StaticResource CurrentItemValue}, ConverterParameter=ORNO, Mode=OneTime}”

        Please help!!

  13. Jesper

    Hi Karin
    I do agree that the blog provides value even though some article are older, I have great pleasure of this blog, which have provided me with several idea/knowledge for Mashup, so thanks you for that 🙂

    Instead of limiting the Mashup to the selected customer, I now use Mashup to find customer, facility and delivery address and return values to OIS100/A with Jscript.

    I only have one issue left, which i believe is “Work by design”, but maybe you have a good solution for this as well 🙂
    After customer, facility and adress id have been send back to OIS100/A and i continue by pressing Next to get to the Confirm screen, The facility change from the selected to my default facility (from MNS150).

    Is it possible to force OIS100/A to keep the value from the Jscript?

    /Jesper

    Reply
    1. karinpb

      Hi,
      If this is the standard behavior in M3 when navigating back to the A panel again there is nothing you can do. Just try the same scenario without the script to see how it behaves.

      Reply
  14. Eric

    Hello Karin – here is a use case I am trying to solve. I currently have a properties file loaded on each user’s PC, for Zebra printer and port information, where a JScript file is instantiated to derive the information from the properties file. Recently I’ve been tasked to create a mashup where I now need the properties information off the clients PC to perform various tasks. Based on this article I have been able to create a different JScript and call it from within the mashup; however, the only way I see the data from the properties file is through a ConfirmDialog I’ve put in the JScript. Is it possible for the JScript to return a string to the Mashup?

    Reply
    1. Eric

      It is possible to send the text field object in the mashup to the JScript and then have the JScript in turn update the values I need in the mashup?

      Reply
      1. karinpb

        Hi,
        When you say that you have “a different JScript that you call from within the Mashup” – I guess you have a M3 detail in the Mashup where you run JScript?
        With JScript you can do many crazy tricks. You can read the properties file and show a dialog.

        The JScript is running within the mashup so you can locate the IInstanceHost and use the visual tree to locate any element that has a unique name within your mashup. You can set the value of a hidden textbox and use that value in a Mashup event. But you can’t really call one Jscript to another so what would you like to do with the information that you have in the dialog. Do you want to put that information in another M3 panel in the Mashup? What would you like the information for? Check Mango.UI.Helpers for some nice util functions like:

        This approach is a bit chunky and will only work on fields that you create on the mashup – it will not work on fields in M3 panels. So context is everything. There might be other options that I don’t think about as well.

        var parent = Helpers.FindParent(menu, typeof(EmbeddedHostWindow)) as EmbeddedHostWindow;
        var element=Helpers.FindVisualElement(Visual parent, String elementName)
        

        I’ll check if I have a more complete example of navigating the visual tree. Perhaps it was EmbeddedHostWindow you should search for…I need to think about this and discuss with norpe.

      2. Eric

        Hi Karin,

        I will investigate your latest response. Thank you.

        In Summary:
        The mashup is calling a JScript on the server called ReadLocalPrinterPropertyFile.js via a button at this point. From the button’s clicked source I use the LinkUri (mforms://_runscript?name=ReadLocalPrinterPropertyFile.js. When the JScript is invoked I read the properties file with File.ReadAllLines(“c:\\M3PrinterProperty\\printer.properties); and put value into variable I call lineArray. I then iterate for a substring of the PrinterIP and PrinterPort value in the file. Once I get the values I show it with a ConfirmDialog.ShowAttentionDialog from JScript. The JScript variable used to get the printer values is what I would like returned to the mashup’s text boxes.

  15. Ester

    Hi Karin,
    I’m working on a solution to retrieve a value from a Mashup and set it in OIS300 on W1OBKV, but
    I get the message “Target field W1OBKV not found” from function SetSelectedValue. How can I set a value from a mashup to a filter field?
    I found also this solution https://potatoit.kiwi/2013/06/12/ois300-sorting-order-8-remember-the-responsible/ , that sets values in OIS300, it works to set the status but not to set a value in W1OBKV.

    Any idea?
    Many thanks in advance

    Ester

    Reply
    1. norpe Post author

      The W1OBKV field is probably a list position field in this scenario and the ScriptUtil.FindChild method only finds children on the top level on the panel. There is no utility method for setting position field values but an array of list position fields (PositionFields) is available on the ListControl class. The script example below shows one way to set a list position field value.

      import System;
      import System.Windows;
      import System.Windows.Controls;
      import MForms;
      
      package MForms.JScript {
         class SetPositionFieldExample {
            public function Init(element : Object, args : Object, controller : Object, debug : Object) {
      
               var listControl = controller.RenderEngine.ListControl;
               if (listControl != null) {
                  setPositionFieldValue(listControl, "W1OBKV", "MANGO");
               }
            }
      
            public function setPositionFieldValue(listControl, name, value) {
               var fields = listControl.PositionFields;
               if (fields != null) {
                  for (var field in fields) {
                     if (field.Name == name) {
                        field.Text = value;
                        return;
                     }
                  }
               }
            }
         }
      }
      
      Reply
  16. Ester

    Hi Norpe,
    Thank you very much for your help. It works. I also managed to refresh OIS300 with controller.PressKey(“F5”); on DetachFromMashup() function as Jenser suggested but I need now to find out how to close the mashup once the record is selected and moved to OIS300.
    Ester

    Reply
  17. pyracell

    Hi there
    Very nice blog.
    I was wondering whether it is possible to use the existing M3 Browse dialogs?
    Ex. the one from MMS001 J-Panel (MMFCCM-field).
    No need to fabricate my own lookalike if your dialog is accessible.
    If this is possible, could you please pass an example, and a list of possible browse dialogs to open, or where to find these?

    I am looking forward to hear from you.
    Best regards, Henrik

    Reply
    1. karinpb

      Hi Henrik,
      It’s not possible to reuse the M3 Browse dialogs. It’s totally dependent on the M3 business logic and not a generic component. It would have been great to be able to reuse it.

      Reply
      1. pyracell

        Hi Karin

        Thanks for your answer. I’ll try to make my own browse dialogs as general as possible then, for reusing purpose.

        I have a few extra questions. Not directly related to this blog though, but I didn’t know where else to put them. I will first describe my issues and then pose the question in a short form.

        Q1)
        I am using Jscript to add a couple of textboxes to an existing window panel. Using an MIWorker I am fetching data I want to show in these input elements. If the user edit these fields, I use an MIWorker to update the data.
        My issue occurs, if a standard validation fails or if the user then return to this very panel after an edit. When the user then try to go to the next panel again the window log will inform the user that (in danish) “Posten er ændret af brugeren XXX” (something like “The item is changed by user XXX”) with an error code XUP0001-40. I havn’t been able to find anything about this error code.
        I can’t figure out why I get this message. Data is stored as it was meant to.
        – What is the reason for this window log information, and how can I prevent it, letting the user continue to the next panelstate?

        Q2)
        I am listening to the requested and requesting event, to figure out when the “next” button is pressed and the panelstate changes, but the “next” button and the enter key both turn out as a “KEY” Command with the CommandValue “ENTER”. I want some of my logic to fire when the panelstate changes, but for some windows, a validation happens first. When this happen, my logic is fired even though the panelstate stays the same.
        – Is there a way to hook up to a panelstatechange event?

        I look forward to hear from you

        Best regards, Henrik

      2. karinpb

        Hi Henrik,
        About Q1) we had a support case yesterday regarding CUSEXMI that would incorrectly cause locks. We just forwarded a reported issue to M3 and they have identified and corrected the bug, it was related to releasing a lock. If CUSEXMI is the program you are having issues with I suggest that you contact support to get the correction. In this case there was a timeout in the MIWS log indicating a grid timeout between mi ws and the mi program. YOu can always check the MIWS and the M3 logs to see if there are any suspicious entries.

  18. karinpb

    Hi Henrik,

    Q1) This is a generic message that indicates that the data that you are viewing is obsolete and someone else has changed this record. You would need to refresh the panel to get the latest data to be able to proceed if you are in edit mode in the program. The reason is as I understand it that M3 has different checks to know that the record has not been changed while you were editing it. There is no way around this as the user needs to refresh and check the data. The user that changed the data – is that possible the same user that is running the interactive program. The MI programs that you call are they related directly to the same program that you are running? Because that would mean trouble as the MI program would update and there can potentially be a conflict with the running interactive program. I don’t have access to the development environment for M3 but if you do have access to MAK (for modifications) then I think that you can get the error code’s description from there.
    Answer: Prevent it by not editing the same record from different sources (eg interactive program and MI)

    Q2) There is no panelstatechange event. I’m not sure what you mean with validation. It is possible that the requesting event gets cancelled before you get it so you can’t relay on it. If you need to know if the panelstate has change then you can compare the panel in requested with the requestCompleted event.

    Reply
  19. pyracell

    Hi Karin

    Thank you for the additional information.
    The MI program you refer to, isn’t the one I am using though.
    I am having issues with “MMS200MI -> UpdItmTech” and “PPS200MI -> UpdLine”. I have search wide and far to find a good example using MIWorker to store data, but so far I’ve only found examples fetching lines or item data.
    Do you happen to have a good example I could take a look at?
    I am curious what I am doing wrong.

    Is there a way to “freeze” the window/panel progress, while handling the storage, to make sure I don’t obstruct myself, nor the native process.
    Or to add the MIWorker to a thread and queue it to run after the native code has finished?

    I look forward hearing from you

    Best regards, Henrik

    Reply
  20. Eric Wayker

    Hello

    I am attempting to pass an MIListPanel through a button click event which will pass the object to a JScript file through a LinkUrl. I would like to be able to consume the object in JScript and iterate over the list of rows which is the Mashup’s list view. The value binding I was sending is {Binding miListPanel_tabItemPicking_CUG, Path=Items}. The miListPanel_tabItemPicking_CUG makes a call to MBDREADMI.SelCUGEX1U1 which is a new custom MDBREADMI going to CUGEX1 table. The result set will give me a list in the mashup.
    The button clicked event reads the new JScript through an mforms://_runscript?name=Bastian_ManualShippingUtility.js JScript. I am trying to set the element value on the JScript which I like to cast into a ListView object and then iterate over the rows.

    Is this possible?

    Regards,

    Eric

    Reply
    1. karinpb

      Hi,
      Sorry for the late reply. I must have missed this comment. As you probably have found out this is not possible in the way that you describe. Objects are lost when passed over as parameters in an URL. If you have access to the Homepages SDK (I don’t know the current license terms for this component but I could find out) then you could build your own mashup controls and pass full objects via a launch of a TASK as the TASK can take a parameter object and you could have a feature that would contain the ShippingUtil code. Eg. You could have that code in c# and re-use it from the feature code to scripts and so on. But passing a full list of object like that requires code within the mashups itself and possibly your own button that would accept a binding with a list of MIRecords for example.

      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