How to add filters to a ListView in a Mashup

What is the XAML code needed to add filters to a ListView in a Mashup in Lawson Smart Office? The solution is to put a StackPanel with a Label and a TextBox inside the GridViewColumn:

<GridViewColumn>
    <StackPanel>
        <Label Content="Location" Foreground="White" />
        <TextBox Name="WHSL" />
    </StackPanel>
</GridViewColumn>

Here is a screenshot of a desired M3 program (for example: MWS060/B) with filters in the list (in this case: Receiving number, Location, and Lot number); we want to reproduce that list and its filters as a Mashup:

And here is the resulting Mashup with the filters in the list; the result is surprisingly similar to the actual M3 panel:

The particular Mashup shown in the screenshot above calls a REST Web Service made with JSP and which is not provided here. The Mashup passes the values of the filters as parameters of the Web Service in the URL. The Web Service executes SQL against M3 using JDBC and returns the result set as XML. Finally, the Mashup displays the resulting XML in the ListView as data in columns and rows. Here’s the full XAML code of that Mashup:

<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" Margin="10">
	<Grid.Resources>
	</Grid.Resources>

	<Grid.ColumnDefinitions>
		<ColumnDefinition Width="*" />
	</Grid.ColumnDefinitions>
	<Grid.RowDefinitions>
		<RowDefinition Height="Auto" />
		<RowDefinition Height="*" />
	</Grid.RowDefinitions>

	<StackPanel Orientation="Horizontal" Grid.Row="0">
		<Label Content="Warehouse" VerticalAlignment="Center" Margin="3" />
		<TextBox Name="WHLO" VerticalAlignment="Center" Margin="3" />
		<Label Content="Item number" VerticalAlignment="Center" Margin="3" />
		<TextBox Name="ITNO" VerticalAlignment="Center" Margin="3" />
		<Label Content="Container" VerticalAlignment="Center" Margin="3" />
		<TextBox Name="CAMU" VerticalAlignment="Center" Margin="3" />

		<Button Content="Apply" IsDefault="True" VerticalAlignment="Center" Margin="3" Width="55">
			<Button.CommandParameter>
				<mashup:Event TargetName="WebService" TargetEventName="List">
					<mashup:Parameter SourceKey="WHLO" Value="{Binding ElementName=WHLO, Path=Text}" />
					<mashup:Parameter SourceKey="ITNO" Value="{Binding ElementName=ITNO, Path=Text}" />
					<mashup:Parameter SourceKey="CAMU" Value="{Binding ElementName=CAMU, Path=Text}" />
					<mashup:Parameter SourceKey="REPN" Value="{Binding ElementName=REPN, Path=Text}" />
					<mashup:Parameter SourceKey="WHSL" Value="{Binding ElementName=WHSL, Path=Text}" />
					<mashup:Parameter SourceKey="BANO" Value="{Binding ElementName=BANO, Path=Text}" />
				</mashup:Event>
			</Button.CommandParameter>
		</Button>
	</StackPanel>

	<mashup:DataListPanel Name="WebService" Grid.Row="1" Margin="3">
		<mashup:DataListPanel.Events>
			<mashup:Events>
				<mashup:Event SourceEventName="Startup" />
			</mashup:Events>
		</mashup:DataListPanel.Events>
		<mashup:DataListPanel.DataService>
			<mashup:DataService Type="REST">
				<mashup:DataService.Operations>
					<mashup:DataOperation Name="List">
						<mashup:DataParameter Key="REST.BaseAddress" Value="http://hostname/ItemSearchMashup.jsp?WHLO={WHLO}&amp;ITNO={ITNO}&amp;CAMU={CAMU}&amp;REPN={REPN}&amp;WHSL={WHSL}&amp;BANO={BANO}" />
						<mashup:DataParameter Key="REST.RemoveNamespace" Value="True" />
						<mashup:DataParameter Key="REST.XPath" Value="root/r" />
					</mashup:DataOperation>
				</mashup:DataService.Operations>
			</mashup:DataService>
		</mashup:DataListPanel.DataService>
		<ListView Name="Customers" ItemsSource="{Binding Items}" Style="{DynamicResource styleListView}" ItemContainerStyle="{DynamicResource styleListViewItem}">
			<ListView.View>
				<GridView ColumnHeaderContainerStyle="{DynamicResource styleGridViewColumnHeader}">
					<GridView.Columns>
						<GridViewColumn Header="Whs" DisplayMemberBinding="{Binding XPath=MLWHLO}" />
						<GridViewColumn DisplayMemberBinding="{Binding XPath=MLREPN}">
							<StackPanel>
								<Label Content="Recvng no" Margin="0" Foreground="White" />
								<TextBox Name="REPN" Margin="0" />
							</StackPanel>
						</GridViewColumn>

						<GridViewColumn Header="Item number" DisplayMemberBinding="{Binding XPath=MLITNO}" />

						<GridViewColumn Header="Name" DisplayMemberBinding="{Binding XPath=MMITDS}" />
						<GridViewColumn Header="Supplier" DisplayMemberBinding="{Binding XPath=LMSUNO}" />
						<GridViewColumn DisplayMemberBinding="{Binding XPath=MLWHSL}">
							<StackPanel>
								<Label Content="Location" Margin="0" Foreground="White" />
								<TextBox Name="WHSL" Margin="0" />
							</StackPanel>
						</GridViewColumn>

						<GridViewColumn DisplayMemberBinding="{Binding XPath=MLBANO}">
							<StackPanel>
								<Label Content="Lot number" Margin="0" Foreground="White" />
								<TextBox Name="BANO" Margin="0" />
							</StackPanel>
						</GridViewColumn>

						<GridViewColumn Header="On-hand" DisplayMemberBinding="{Binding XPath=MLSTQT}" />
						<GridViewColumn Header="Allocatble" DisplayMemberBinding="{Binding XPath=Allocatble}" />
						<GridViewColumn Header="Sts" DisplayMemberBinding="{Binding XPath=MLSTAS}" />
						<GridViewColumn Header="M dt" DisplayMemberBinding="{Binding XPath=LMMFDT}" />
						<GridViewColumn Header="Exp dt" DisplayMemberBinding="{Binding XPath=LMEXPI}" />
						<GridViewColumn Header="Sls dt" DisplayMemberBinding="{Binding XPath=LMSEDT}" />
					</GridView.Columns>
				</GridView>
			</ListView.View>
		</ListView>
	</mashup:DataListPanel>

</Grid>

That’s it!

Web Service pretty print

Here is a technique that uses XSLT to pretty print the XML metadata of a Lawson Web Service. The output shows each operation’s details (name, input/output parameters, type, length, constraint, SQL statement, etc.) in a human readable HTML format. This technique is useful for creating template spreadsheets in Excel where we input data without having to manually enter the headers in the spreadsheet which is error prone. The result is similar to the template spreadsheets generated by Smart Data Tool.

  1. Suppose we have the following Thibaudweb service with three operations, one of each type, API, MDP, and SQL:

  2. Save the XML metadata into a file somewhere in your computer. For that, go to the Lawson Web Service server view (for example: http://hostname/LWS_DEV/), select List Services, expand your web service (in my case Thibaud), right-click the Meta Data link, select Save Target As, and save the XML file somewhere in your computer:
  3. Then, open the XML file in a text editor, insert the following processing instruction at the top of the file, and save the file:
    <?xml-stylesheet type="text/xsl" href="WebServicePrettyPrint.xslt"?>
  4. Then, save a copy of the following XSLT file to somewhere in your computer, in the same folder as the XML file: http://ibrix.info/lws/WebServicePrettyPrint.xslt
  5. Then, open the XML file in Microsoft Internet Explorer. The XSLT processor of Internet Explorer’s MSXML will convert the XML metadata into HTML using the XSLT file above. The HTML output shows each operation’s details (name, input/output parameters, type, length, constraint, SQL statement, etc.) in a human readable HTML format. The result looks like this:
  6. Internet Explorer will show a security warning asking if you want to run the script. Click Allow blocked content. The blocked content is a small piece of JavaScript code that transposes the HTML tables.
  7. The little button at the top right transposes the HTML tables. Click the button. Copy/paste the transposed table in an Excel spreadsheet. That will serve as the header. Now just enter the data. That’s useful to create the template spreadsheets in Excel similar to Smart Data Tool.

That’s it!

 

UPDATE 2012-08-14: Added support for MDP Output fields and Related Programs.

Print M3 programs locally

Did you ever want to quickly print a screen capture of the current M3 program using any of your local Windows printers and printer preferences?

Print Screen

One known technique is to press the Print Screen button on the keyboard, and to paste the resulting bitmap image into Microsoft Paint or Microsoft Word, and to print from there. The result is a bitmap print. The advantages are: the truthfulness of the result which is a replica of what the user sees on the screen (WYSIWYG), the ability to print locally using the locally configured Windows printers, as well as the last minute control of the printer preferences. The disadvantage is the poor non vectorial quality, and the waste of printer ink used in printing background colors.

MOM, StreamServe

The other known technique is to print using the standard M3 functionality via MOM and streamfiles to StreamServe. The result is a vectorial print. The advantages are: the high quality of the vectorial print, the ability to customize the resulting documents via StreamServe Design Center, and the pre-configuration of the printer preferences in MOM. Also, StreamServe can print more complex content like barcodes. The disadvantage is that pretty much each M3 program needs to be configured via MOM and StreamServe, which requires custom implementation, as well as the inability to print locally using the locally configured Windows printers, and the inability to have last minute control of the printer preferences.

Script

It’s actually possible to programmatically print a screen capture of the current M3 program with a simple Personalized Script for Lawson Smart Office in JScript.NET. The result is similar to using the Print Screen button, while avoiding the extra steps of pasting the bitmap image into Microsoft Paint or Microsoft Word. One click print, locally.

For that, the script must get a reference to the current M3 window, convert the content from vectorial to bitmap using RenderTargetBitmap and BmpBitmapEncoder, and to print it using PrintDocument.

import System;
import System.IO;
import System.Drawing;
import System.Drawing.Printing;
import System.Windows.Media;
import System.Windows.Media.Imaging;

package MForms.JScript {
	class PrintMe {
		var img;
		public function Init(element: Object, args: Object, controller : Object, debug : Object) {
			try {

				// get the current window
				var h = controller.RenderEngine.Host;
				var e = h.VisualElement;

				// convert vectorial to bitmap
				var RTbmap: RenderTargetBitmap = new RenderTargetBitmap(h.Width, h.Height, 96, 96, PixelFormats.Default);
				RTbmap.Render(e);
				var encoder = new BmpBitmapEncoder();
				encoder.Frames.Add(BitmapFrame.Create(RTbmap));
				var stream = new MemoryStream();
				encoder.Save(stream);
				var gdiBitmap = new Bitmap(stream);
				stream.Close();
				stream.Dispose();
				this.img = gdiBitmap;

				// print
				var pd: PrintDocument = new PrintDocument();
				pd.add_PrintPage(OnPrintPage);
				pd.Print();

			} catch (ex: Exception) {
				debug.WriteLine(ex);
			}
		}
		function OnPrintPage(sender: Object, e: PrintPageEventArgs) {
			e.Graphics.DrawImage(this.img, 0, 0);
		}
	}
}

There are also options to preview the document and to open the printer preferences but I haven’t yet succeeded in using them correctly:

(new PrintPreviewDialog()).ShowDialog();
(new PrintDialog()).ShowDialog();

Suppose you have an M3 program like this:

The result of the print would look like this (using printer PDF995):

The next step is to find a solution to add a margin, polish the print, and show the print preview and printer preferences.

The last step would be to place the script in a new Print button in the M3 panel, or to inject a new Print option in the File menu, to deploy the script on the server, and to attach it to the desired M3 programs with the XML Customization files. With that, the user would be able to quickly and locally print the M3 programs.

SQL to XML in a Script

Here’s an example of how to read data from M3’s database using SQL, and how to convert the result into XML, in a Personalized Script for Lawson Smart Office. This example is for Microsoft SQL Server.

import System.Data;
import System.Data.SqlClient;

package MForms.JScript {
     class Test {
         public function Init(element: Object, args: Object, controller : Object, debug : Object) {
             var connection = new SqlConnection('server=sqlserver;database=M3EDBTST;uid=userid;pwd=password');
             connection.Open();
             var cmd: SqlCommand = new SqlCommand('SELECT DISTINCT OKCONO, OKCUNO FROM MVXJDTA.OCUSMA', connection);
             var da: SqlDataAdapter = new SqlDataAdapter(cmd);
             var ds: DataSet = new DataSet('result');
             da.Fill(ds);
             debug.WriteLine(ds.GetXml());
         }
     }
}

It produces the following XML:

<result>
  <Table>
    <OKCONO>1</OKCONO>
    <OKCUNO>Y60000    </OKCUNO>
  </Table>
  <Table>
    <OKCONO>1</OKCONO>
    <OKCUNO>Y60001    </OKCUNO>
  </Table>
  ...
</result>

The result looks like:

Create XML in a Script

There are several techniques to create an XML document in a Personalized Script for Lawson Smart Office. The programming language is JScript.NET.

1) XElement

Here’s an example with LINQ’s XElement :

import System;
import System.Xml.Linq;

package MForms.JScript {
     class Test {
         public function Init(element: Object, args: Object, controller : Object, debug : Object) {
             var x: XElement = new XElement('hello',
                 new XAttribute('id', 'message'),
                 new XElement('world', 'Hello World!'));
             debug.WriteLine(x.ToString());
         }
     }
 }

It produces the following XML:

<hello id="message">
  <world>Hello World!</world>
</hello>

Here’s a screenshot of the result:

2) XmlDocument

Here’s an example with XmlDocument:

import System;
import System.Xml;

package MForms.JScript {
     class Test {
         public function Init(element: Object, args: Object, controller : Object, debug : Object) {
             var doc: XmlDocument = new XmlDocument();
             var dec: XmlDeclaration = doc.CreateXmlDeclaration('1.0', null, null);
             doc.AppendChild(dec);
             var root: XmlElement = doc.CreateElement('hello');
             doc.AppendChild(root);
             var child: XmlElement = doc.CreateElement('world');
             child.SetAttribute('id', 'message');
             child.InnerText = 'Hello World!';
             root.AppendChild(child);
             debug.WriteLine(doc.OuterXml);
         }
     }
}

It produces the following XML:

<?xml version="1.0"?><hello><world id="message">Hello World!</world></hello>

Here’s a screenshot of the result:

3) XmlWriter

And here’s an example with XmlWriter:

import System;
import System.Xml;
import System.Text;

package MForms.JScript {
     class Test {
         public function Init(element: Object, args: Object, controller : Object, debug : Object) {
             var s: StringBuilder = new StringBuilder('');
             var writer: XmlWriter = XmlWriter.Create(s);
             writer.WriteStartDocument();
             writer.WriteStartElement('hello');
             writer.WriteAttributeString('id', 'message');
             writer.WriteElementString('world', 'Hello World');
             writer.WriteEndElement();
             writer.WriteEndDocument();
             writer.Flush();
             debug.WriteLine(s);
         }
     }
}

It produces the following XML:

<?xml version="1.0" encoding="utf-16"?><hello id="message"><world>Hello World</world></hello>

Here’s a screenshot of the result:

4) XmlSerializer

Here’s an example with XmlSerializer:

import System.Text;
import System.Xml;
import System.Xml.Serialization;

package MForms.JScript {
     class Test {
         public function Init(element: Object, args: Object, controller : Object, debug : Object) {
             var o = new Hello();
             var s = new XmlSerializer(o.GetType(), 'thibaudns');
             var b: StringBuilder = new StringBuilder('');
              var writer: XmlWriter = XmlWriter.Create(b);
             s.Serialize(writer, o);
             debug.WriteLine(b);
         }
     }
     class Hello {
         var World: String = 'Hello World!';
     }
}

It produces the following XML:

<?xml version="1.0" encoding="utf-16"?>
<Hello xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="thibaudns">
<World>Hello World!</World>
</Hello>

Here’s a screenshot of the result:

Discussion

Here is a discussion on when to use which solution.

Note: I tested these examples with Lawson Smart Office 9.1.3.1.7.

Send SMS from Smart Office with Skype

To send an SMS text message to a mobile phone from a Personalized Script in Lawson Smart Office using Skype do:

skype.SendSms("+18472874945", "Hello World", null)

Script

A simple script that sends an SMS would be:

import System;
import System.Reflection;

package MForms.JScript {
    class SendSms {
         public function Init(element: Object, args: Object, controller : Object, debug : Object) {
             try {
                 var assembly: Object = Assembly.LoadFrom('C:\\Program Files\\Skype\\SEHE\\Interop.SKYPE4COMLib.dll');
                 var skype = assembly.CreateInstance('SKYPE4COMLib.SkypeClass');
                 skype.Attach(8, false);
                 skype.SendSms('+18472874945', 'Hello World!', null);
             } catch (ex: Exception) {
                 debug.WriteLine(ex);
             }
         }
    }
}

Note: The programming language for scripts in Smart Office is JScript.NET.

Installation

Follow these steps to run the script above:

  1. Download and install Skype on the computer that is running Smart Office (the script must communicate with Skype locally). Then sign in to Skype (the script will not work if you are not signed in). Also, your Skype account must have credit (USD, EUR, etc.) to be able to send SMS.
  2. Download and unzip Skype4COMsomewhere in your computer, for example C:\Program Files\Skype\skype4com-1.0.36\ . Skype4COM is the API used to send/receive Skype commands. Then register the DLL Skype4COM.dll with the following command:
    regsvr32 Skype4COM.dll

  3. Download and install SEHE and place the file Interop.SKYPE4COMLib.dll somewhere in your computer or somewhere on the network so that it is accessible by the Smart Office computer, for example C:\Program Files\Skype\SEHE\Interop.SKYPE4COMLib.dll or http://host/path/Interop.SKYPE4COMLib.dll . That DLL contains the Interop code to be able to call Skype4COM from the .NET framework.
  4. Launch Smart Office, and log in.
  5. For the Script Tool it is necessary to have an M3 program open, so open for example Customer. Open – CRS610.
  6. Open the Script Tool with the following command:
    mforms://jscript
  7. Copy/paste the sample script above into the Script Tool
  8. Change the path to the DLL. In my example I used C:\\Program Files\\Skype\\SEHE\\Interop.SKYPE4COMLib.dll . Make sure to escape the backslashes in the String with double backslashes.
  9. Change the phone number. It must be in international notation. In my example I used +18472874945.
  10. Click Compile
  11. Click Run
  12. Skype will show the message “LawsonClient.exe wants to use Skype”. Click Allow access; Skype will only ask once.
  13. Skype will now send the SMS. Check in your mobile phone that you received it. That’s it!

Note: I tested this on Windows XP and on Windows 7 32-bit with success. It doesn’t seem to work on Windows 64-bit. Also, I tested this in Smart Office 9.x.

More advanced script

A more elaborated script with an editable SMS text message in a pop-up looks like: