How to add a column to a list

In this article I illustrate a technique to dynamically add a column to the list of a B panel in M3 using a Personalized Script for Lawson Smart Office.

For example, suppose we want to add the column Country (CSCD) in CRS610/B1. None of the available Sorting orders (QTTP) displays the Country. How do we do?

This is the desired result:

1. Add a column by changing the View in CRS020

The first technique would be to add a column by simple configuration of the View in CRS020. But sometimes the specified M3 program is not configured for it. For instance, CRS610 is not configurable, whereas MMS200 is configurable as seen in this screenshot:

2. M3 modification

The second technique would be to add a column with a modification to the M3 Java source code and to the View Definition with MAK. But modifications may not be an option in certain M3 implementations.

3. Add a column programmatically with a script

The third technique would be to add a column dynamically with a Personalized Script for Lawson Smart Office.

Append the column

First, we get a reference to the list’s controls:

var listControl = controller.RenderEngine.ListControl; // MForms.ListControl
var listView = controller.RenderEngine.ListViewControl; // System.Windows.Controls.ListView
var columns = listView.View.Columns; // System.Windows.Controls.GridViewColumnCollection

Second, we append a new GridViewColumn to the ListView:

var gvch = new GridViewColumnHeader();
gvch.Content = "New Column";
var gvc = new GridViewColumn();
gvc.Header = gvch;
gvc.CellTemplateSelector = new ListCellTemplateSelector(columns.Count, listControl.Columns);
columns.Add(gvc);

Third, we increase each row’s array by one additional element:

var rows = listView.Items;
for (var i = 0; i < rows.Count; i++) {
	var row = rows[i];
	var oldArray = row.Items;
	var newArray = new String[columns.Count];
	oldArray.CopyTo(newArray, 0);
	row.Items = newArray;
	rows.RemoveAt(i);
	rows.Insert(i, row);
}

Finally, we can set our values in the new column:

listView.Items[0].Items[columns.Count - 1] = "Hello world 0";
listView.Items[1].Items[columns.Count - 1] = "Hello world 1";
listView.Items[2].Items[columns.Count - 1] = "Hello world 2";
listView.Items[3].Items[columns.Count - 1] = "Hello world 3";
listView.Items[4].Items[columns.Count - 1] = "Hello world 4";

The result looks like this, with the Personalizations like Hyperlinks and Conditional Styles preserved:

The complete script looks like this:

Populate with data

Now we have to populate the column with actual data from M3. For that we can call an M3 API, execute SQL, or consume a Lawson Web Service. We can even use the API MDBREADMI to read an M3 table instead of using SQL.

In this article, I will just hard-code “Hello World” and I will let the reader choose the technique that best suits its needs because getting the data off M3 is not the point of this post.

Load more data on scroll view

Finally, we have to load data in increments as the user scrolls the view. Indeed, as the user scrolls down the list Smart Office loads the data in increments, without re-rendering the whole panel, for efficiency.

For that, we need the VisualTreeHelper to get a reference to the ScrollViewer:

var border = VisualTreeHelper.GetChild(listView, 0);
var grid = VisualTreeHelper.GetChild(border, 0);
var scrollViewer: ScrollViewer = VisualTreeHelper.GetChild(grid, 3); // System.Windows.Controls.ScrollViewer

Then, we have to attach to the ScrollViewer’s OnScrollChanged event:

scrollViewer.add_ScrollChanged(OnScrollChanged);
function OnScrollChanged(sender: Object, e: ScrollChangedEventArgs) {}

That event is fired either once, either consecutively twice depending on if new rows were added to the list or not. Only when e.VerticalChange==0 it means that new rows were added.

var oldCount, newCount;
function OnScrollChanged(sender: Object, e: ScrollChangedEventArgs) {
     if (e.VerticalChange != 0) {
         oldCount = listView.Items.Count;
     } else {
         newCount = listView.Items.Count;
         var diff = newCount - oldCount; // that many rows were just added to the list 
     }
}

Here’s an illustration:

Now we know exactly which rows are new:

var fromRowIndex = oldCount;
var toRowIndex = newCount - 1;
for (var i = fromRowIndex; i <= toRowIndex; i++) {
     listView.Items[i] // new row
}

Now we can load and show some data, like Hello + customer number:

var lastColumnIndex = columns.Count - 1;
for (var i = fromRowIndex; i <= toRowIndex; i++) {
     var row = listView.Items[i];
     var data = "Hello " + row.Item[0]; // Hello CUNO
     row.Items[lastColumnIndex] = data;
}

Here is a screenshot of the final result:

Here’s the complete source code:

 import System;
 import System.Windows;
 import System.Windows.Controls;
 import System.Windows.Media;
 import Mango.UI.Services.Lists;

 package MForms.JScript {
     class Test {

         var listView, oldCount;
         /*
             Main entry point.
         */
         public function Init(element: Object, args: Object, controller : Object, debug : Object) {
             try {
                 var listControl = controller.RenderEngine.ListControl; // MForms.ListControl
                 this.listView = controller.RenderEngine.ListViewControl; // System.Windows.Controls.ListView
                 var columns = listView.View.Columns; // System.Windows.Controls.GridViewColumnCollection

                 // append a new GridViewColumn to the ListView
                 var gvch = new GridViewColumnHeader();
                 gvch.Content = "New Column";
                 var gvc = new GridViewColumn();
                 gvc.Header = gvch;
                 gvc.CellTemplateSelector = new ListCellTemplateSelector(columns.Count, listControl.Columns);
                 columns.Add(gvc);

                 var fromRow = 0;
                 var toRow = listView.Items.Count - 1;
                 var newNbColumns = columns.Count;
                 var lastColumnIndex = columns.Count - 1;

                 // increase each row's array by one additional element
                 increaseRowsArray(fromRow, toRow, newNbColumns);

                 // load data in the new column of each row
                 loadData(fromRow, toRow, lastColumnIndex);

                 // find the ScrollViewer
                 var border = VisualTreeHelper.GetChild(listView, 0);
                 var grid = VisualTreeHelper.GetChild(border, 0);
                 var scrollViewer: ScrollViewer = VisualTreeHelper.GetChild(grid, 3); // System.Windows.Controls.ScrollViewer

                 // attach to the OnScrollChanged event
                 scrollViewer.add_ScrollChanged(OnScrollChanged);
                 scrollViewer.add_Unloaded(OnUnloaded);            

             } catch (ex: Exception) {
                 debug.WriteLine(ex);
             }
         }

         /*
             That event is fired either once, either consecutively twice depending on if new rows were added to the list or not.
             Only when e.VerticalChange==0 it means that new rows were added.
         */
         function OnScrollChanged(sender: Object, e: ScrollChangedEventArgs) {
             try {
                 if (e.VerticalChange != 0) {
                     oldCount = listView.Items.Count;
                 } else {
                     var fromRow = oldCount;
                     var toRow = listView.Items.Count - 1;
                     var newNbColumns = listView.View.Columns.Count;
                     var lastColumnIndex = listView.View.Columns.Count - 1;
                     increaseRowsArray(fromRow, toRow, newNbColumns);
                     loadData(fromRow, toRow, lastColumnIndex);
                 }
             } catch (ex: Exception) {
                 MessageBox.Show(ex);
             }
         }

         /*
             Increase each row's array by one additional element.
         */
         function increaseRowsArray(fromRow, toRow, newNbColumns) {
             var rows = listView.Items;
             for (var i = fromRow; i <= toRow; i++) {
                 var row = rows[i];
                 var oldArray = row.Items;
                 var newArray = new String[newNbColumns];
                 oldArray.CopyTo(newArray, 0);
                 row.Items = newArray;
                 rows.RemoveAt(i);
                 rows.Insert(i, row);
             }
         }

         /*
             Loads data in the list, from the specified row's index, to the specified row's index, at the specified column index.
         */
         function loadData(fromRow, toRow, columnIndex) {
             for (var i = fromRow; i <= toRow; i++) {
                 var data = "Hello " + listView.Items[i].Item[0]; // Hello CUNO
                 listView.Items[i].Items[columnIndex] = data;
             }
         }

         /*
             Cleanup
         */
         function OnUnloaded(sender: Object, e: RoutedEventArgs) {
             sender.remove_Unloaded(OnUnloaded);
             sender.remove_ScrollChanged(OnScrollChanged);
         }

     }
 }

That’s it! Special thanks to Peder W for the original solution.

UPDATE 2012-09-27

This script is deprecated. Refer to the latest script here.

Related articles

How to consume a Lawson Web Service from a Personalized Script in Smart Office

Calling Lawson Web Service (LWS) from a Personalized Script in Lawson Smart Office is very useful as LWS has three adapters: M3 API, SQL, and M3 Display Program (MDP).

As of today, there are three known solutions to call LWS from a script: 1) the “Big string”, 2) the XML writer, and 3) the C# proxy written with Microsoft Visual Studio. Each of these solutions has its advantages and disadvantages.

In the paper How to call LWS from a JScript I illustrate a new solution which complements the other three known solutions. This new solution is interesting as it minimizes code source surface while still ensuring SOAP validation. And the solution does not involve any C# coding, nor does it require Microsoft Visual Studio. For this new solution, we will use the free Microsoft Web Services Description Language Tool (wsdl.exe) to generate a proxy class in C# that we’ll use from JScript.NET.

As an addendum to the paper, I write two additions. First, it’s possible to make the C# proxy re-usable for multiple environments (ex: DEV, PRD, TST) by setting the variable Url of the proxy instance. Second, I have successfully tested this technique with all three LWS adapters: M3 API, SQL, and MDP.

Related articles

How to pass settings to a script

In this post I discuss several techniques to pass settings to a Personalized Script for Lawson Smart Office.

Suppose we have three settings: fieldX, and Y that we want to pass to a script with the respective values WRPHNO, 33, and 17. The question is how do we pass those settings and their values to the script? I can think of five techniques: 1) hard-code the settings in the source code, 2) use comma separated values and String.Split, 3) use JSON, 4) use XML, or 5) use Java Properties or .NET Resource files.

1. Hard-code the settings

The first technique is to hard-code the settings as Fields in the class declaration of the script:

var field = "WRPHNO";
var X = 33;
var Y = 17;

Here is an example:

Or we can use an Array:

var settings = ["WRPHNO", 33, 17];
settings[0] // field
settings[1] // X
settings[2] // Y

Here is an example with the Array:

2. Use comma separated values and String.Split

The second technique is to pass a comma separated list of values like WRPHNO,33,17 as the argument of the script, and to access the values as an array after a String.Split:

var settings = args.Split(",");
settings[0] // field
settings[1] // X
settings[2] // Y

Here is an example:

We can also read the values from a file:

var settings = System.IO.File.ReadAllText("C:\\path\\settings.txt").Split(",");

3. Use JSON

The third technique is to use JSON. With JSON we write our settings in object literal notation like this:

{ "field": "WRPHNO",
 "X": 33,
 "Y": 17 }

We set the JSON text as the argument of the script, surrounded by parenthesis, and we call eval(JSON) from the script. The settings become a JScript object that we can access with settings.field, settings.X, and settings.Y. Here is an example:

We can also read the JSON text from a file:

var JSON = System.IO.File.ReadAllText("C:\\path\\settings.txt");
var settings = eval("("+JSON+")");

4. Use XML

The fourth technique is to pass XML as the argument of the script, and to use XPath in SelectSingleNode to access the values:

var doc = new XmlDocument();
doc.LoadXml(args);
doc.SelectSingleNode("/settings/field").InnerText
doc.SelectSingleNode("/settings/X").InnerText
doc.SelectSingleNode("/settings/Y").InnerText

Here is an example:

We can also load the XML from a file:

doc.Load("file://hostname/settings.xml");

5. Use Properties or Resource files

The fifth technique is to use Java Properties files or .NET Resource files, but I couldn’t find a concise solution that fits in only a couple of lines of code; it seems to require many lines of code.

UPDATE 2012-12-14: I would copy/paste my properties file to the Smart Office installation point folder on the web server, and refer to it from the script as http://smartoffice/LSO/thibaud.properties using standard .NET classes to read files. If you have an example let me know and I can post it here.

Discussion

The hard-coded values are simple to implement for the developer, but it makes the script non reusable by nature, and it invites a risk of corruption when the administrator has to manually edit the source code to change the settings.

The comma separated list of values and String.Split is also simple to implement for the developer, but maintenance is inversely proportional to scalability: the more settings we add the harder it becomes to identify which value is located where in the string. Also, if by mistake a comma goes missing the whole settings are compromised.

Using JSON is great because of the possibilities to validate the JSON object with JSlint and JSONLint. Also, it’s scalable: we can store a large number of settings in complex structures like in sub-settings or in a tree of settings and still maintain readability.

Using XML is great for all the advantages of XML, like editing tools and semantic validation.

Settings page

I encourage implementing a settings page as an HTML form for the administrators to easily configure the script and save the settings in the XML Customization file in Smart Office.

In our example, and with JSON, we would have the following HTML form:

<input id="field"/>

We create a JSON string in JavaScript like this:

var settings = {};
settings.field = document.getElementById("field").value;
settings.X = document.getElementById("X").value;
settings.Y = document.getElementById("Y").value;
var JSONtext = JSON.stringify(settings);

The JSON object is provided as a JS file by Douglas Crockford here, and it is also natively supported in modern web browsers like Microsoft Internet Explorer 8 and in Google Chrome.

Our settings page would look like this:

The source code for that simple settings page is:

// <![CDATA[
javascript" src="https://raw.github.com/douglascrockford/JSON-js/master/json2.js">
// ]]>// Field  X  Y  

Here are two sample web pages that use JSON for passing plenty of settings to a script to be stored in the XML Customization file:

Sample 1: http://ibrix.info/AddressValidation/settings/

Sample 2: http://ibrix.info/skype/settings/

Customization file

Once the settings are generated, either as a comma separated list of values, or as JSON text, or as XML, we can save the result in the argument attribute of the Customization file in Smart Office. The value must be XML escaped with for example this online tool.

Here is an example of an XML Customization file for a script AddressValidationM3 for CRS610/E with plenty of XML-escaped JSON as the argument:

For more information on XML Customization files in Smart Office, refer to the Chapter Managing M3 MForms Personalizations of the Lawson Smart Office Administration Guide:

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!

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.