Total Selected Rows – Jscript to Javascript – H5 Revisited

IFL has been running 13.4 for some months now, and given what’s not being said about Smart Office in a couple of the conferences earlier this year, I felt it was time to investigate migrating scripts to H5.

Previously, I had been reluctant to pursue this because H5 really lacked maturity and for the majority of our users, Smart Office is just easier to use. The last time I did some serious work with it, it felt like whack-a-mole with bugs and functionality. But we’re now more than a year further down the track and it’s clear that there has been a lot of focus put on H5.

It also helps that the H5 SDK document (Infor M3 H5 Development Guide) in the Knowledge Base talks about debugging the Javascripts with Visual Studio and has some templates – something that piqued my interest 🙂

Though I’ve played around with the H5 scripts in the past, and the updated PDF is more than a copy/paste/tweak from the Smart Office SDK – it discusses new methods to work with the UI so I figured I’d start with a Smart Office script that was pretty simple but extracted data from the ListView, added controls, updated controls and had event subscriptions. Also the H5 Development Guide recommends using TypeScript which I’ve never worked with so I wanted to get a bit of a feel for it without dealing with too much change.

The script in question is on MWS060/B. It totals the on hand balance, allocated quantity and provides a count of the selected records in the ListView.

Pretty basic sort of stuff.

We’re going to be adding 6 controls, 3 labels, 3 TextBoxes. We are going to subscribe to the event on the ListView which fires when the selection changes, and we are going to extract data from the selected records on the listview. This example covers most of the fundamentals in my scripts.

First things first. I grabbed the Visual Studio template from KB 1909067 (it’s an attachment). Then I added my new Typescript file, MWS060_TotalSelectedRows to the project.

Note: The .ts file automatically generates a Javascript .js file which is what we need to upload and reference in H5.

I added a few lines of code so I could test the script and breakpoints, but this is where I encountered my first issue.

Before we go in to that, we probably need to do some explaining first – please refer to the documentation for more information as I am only going to touch on some key points.

In the H5ScriptsProjectTemplate we have a start URL, this points to H5 and we provide some additional arguments. We also have a location for a local webserver where our script will actually be loaded from.

My URL is

And where the script is loaded from is

When we run our project, IE will be launched and we log in to the H5 client, we then navigate to the program that has our script (MWS060_TotalSelectedRows.js) attached. The page will attempt to load the script from http://localhost:50167/ however, we are mixing https with http – IE, depending on your security settings will dutifully not load the http content.

What I had encountered was an issue with a mix of content secured from M3 and content that wasn’t secured off my machine. To allow this to work, you need to change a setting on your internet settings (or set the local IIS Express instance to use https – but I couldn’t quickly get that up and running).

I added M3/H5 server to my trusted sites zone, and I set the display mixed content to prompt. Yes, you could change this to enable, but even with trusted sites I’d prefer to know that we have potential issues.

Now I get this:

Click on Show all content -> Leave this page

You’ll be dropped back to the main H5 page, but I can just launch MWS060 again and my breakpoint gets triggered

The next thing to do was to subscribe to the SelectedRowsChanged event, the H5Developers Guide has an example of doing this,

    private run(): void

        const list = this.gController.GetGrid();
        const handler = (e, args) => { this.onSelectionChanged(e, args); };

    private onSelectionChanged(e: any, args: any): void
        let grid = args.grid;
        let selected = grid.getSelectedRows();

Interestingly on my MUA build, the event doesn’t trigger on the first click, you have to select a record and then select a second record after the fact before the event triggers. I’m waiting on a new build to be applied to the MUA before I log an issue.

We need to add our controls to the panel – nothing special there.

Next came the part that I was expecting to be straight forward, the actual updating of my controls with the values. With H5 Infor provide a number of common methods for you to extract and update UI data but this is where what was available departed from what the documentation said. They provide an example of using ScriptUtil to update values, but the arguments in the documentation is very different from what I could use. Eventually I noticed that the controller has an option to SetValue so I can do something like this:

this.gController.SetValue(“tbSelectedOhB”, totalOhB);

Ultimately we end up with this

It’s a little disappointing that I’m still hitting bugs with H5 that impact basic functionality but it’s likely something that I can work around by subscribing to a different event. Despite that, it is feeling a lot more mature and we have the GetMode() method which tells us whether we got to the panel through a create/change/copy/delete/display action – something I have been wanting for years.

There is definitely room for me to refine my development process which I’ll do as I spend more time with it.

Anyway, I’ve attached the Smart Office Jscript and the H5 TypeScript below.

Original JScript:

**	Name: 			MWS060_TotalSelectedRows
**	Program:		MWS060/B
**	Description:	Totals the allocated/on hand balance and number of rows selected
**	History
**		20150727	- added on-hand balance
**		20161110	- quantities display two decimals now
import System;
import System.Windows;
import System.Windows.Controls;
import MForms;

import Mango.UI;

package MForms.JScript
	class MWS060_TotalSelectedRows
		var gDebug = null;
		var gController = null;
		var gContent = null;

		var giAllocatableColumn : int = -1;
		var gstrAllocateableColumnName : String = "AVAL";
		var giOnHandBalanceColumn : int = -1;
		var gstrOnHandBalanceColumnName : String = "STQT";
		var glvListView = null;
		var gtbSelectedAllocated : TextBox = new TextBox();
		var glblSelectedAllocated : Label = new Label();
		var gtbSelectedPallets : TextBox = new TextBox();
		var glblSelectedPallets : Label = new Label();
		var gtbSelectedOhB : TextBox = new TextBox();
		var glblSelectedOhB : Label = new Label();
		var giStartRow : int = 5;
		var giColumn : int = 50;
		var gQuantityFormat : String = "d2";
		public function Init(element: Object, args: Object, controller : Object, debug : Object)
			gContent = controller.RenderEngine.Content;
			gController = controller;
			gDebug = debug;
			var lcListControl : MForms.ListControl = gController.RenderEngine.ListControl;
			glvListView = lcListControl.ListView;
			giAllocatableColumn = lcListControl.GetColumnIndexByName(gstrAllocateableColumnName);
			giOnHandBalanceColumn = lcListControl.GetColumnIndexByName(gstrOnHandBalanceColumnName);
			if(-1 != giAllocatableColumn)
				glblSelectedAllocated.Content = "Sel Alloc. Qty:";
				Grid.SetRow(glblSelectedAllocated, giStartRow);
				Grid.SetColumn(glblSelectedAllocated, giColumn);
				Grid.SetColumnSpan(glblSelectedAllocated, 10);
				Grid.SetRow(gtbSelectedAllocated, giStartRow);
				Grid.SetColumn(gtbSelectedAllocated, giColumn + 10);
				Grid.SetColumnSpan(gtbSelectedAllocated, 10);
				gtbSelectedAllocated.Height = Configuration.ControlHeight;
				gtbSelectedAllocated.Width = 100;
				gtbSelectedAllocated.TextAlignment = TextAlignment.Right;

			glblSelectedPallets.Content = "Sel Pallet. Qty:";
			Grid.SetRow(glblSelectedPallets, giStartRow+1);
			Grid.SetColumn(glblSelectedPallets, giColumn);
			Grid.SetColumnSpan(glblSelectedPallets, 10);
			Grid.SetRow(gtbSelectedPallets, giStartRow+1);
			Grid.SetColumn(gtbSelectedPallets, giColumn + 10);
			Grid.SetColumnSpan(gtbSelectedPallets, 10);
			gtbSelectedPallets.Height = Configuration.ControlHeight;
			gtbSelectedPallets.Width = 100;
			gtbSelectedPallets.TextAlignment = TextAlignment.Right;
			if(-1 != giOnHandBalanceColumn)
				glblSelectedOhB.Content = "Sel On hand Bal:";
				Grid.SetRow(glblSelectedOhB, giStartRow-1);
				Grid.SetColumn(glblSelectedOhB, giColumn);
				Grid.SetColumnSpan(glblSelectedOhB, 10);
				Grid.SetRow(gtbSelectedOhB, giStartRow-1);
				Grid.SetColumn(gtbSelectedOhB, giColumn + 10);
				Grid.SetColumnSpan(gtbSelectedOhB, 10);
				gtbSelectedOhB.Height = Configuration.ControlHeight;
				gtbSelectedOhB.Width = 100;
				gtbSelectedOhB.TextAlignment = TextAlignment.Right;		

		public function onSelectionChanged(sender : Object,  args : SelectionChangedEventArgs)
			if(-1 != giAllocatableColumn)
				gtbSelectedAllocated.Text = "";
			if(-1 != giOnHandBalanceColumn)
				gtbSelectedOhB.Text = "";
			gtbSelectedPallets.Text = "";
			if(null != glvListView.SelectedItem && glvListView.SelectedItems.Count > 0)
				var iAllocatedCount : decimal = 0;
				var iOnHandBalanceCount : decimal = 0;
				for(var row in glvListView.SelectedItems)
					if(-1 != giAllocatableColumn)
						iAllocatedCount += decimal(row[giAllocatableColumn]);
					if(-1 != giOnHandBalanceColumn)
						iOnHandBalanceCount += decimal(row[giOnHandBalanceColumn]);
				if(-1 != giAllocatableColumn)
					gtbSelectedAllocated.Text = iAllocatedCount.ToString(gQuantityFormat);
				if(-1 != giOnHandBalanceColumn)
					gtbSelectedOhB.Text = iOnHandBalanceCount.ToString(gQuantityFormat);
				gtbSelectedPallets.Text = glvListView.SelectedItems.Count.ToString();
		public function OnRequested(sender: Object, e: RequestEventArgs)
			if(MNEProtocol.CommandTypePage != e.CommandType)
				// remove our event subscriptions



TypeScript (note: this will not work if uploaded directly to M3, it needs to be converted to javascript)

**  Name:   MWS060_TotalSelectedRows
**  Panel:  MWS060/B
**  Description:
**      Totals the selected records On hand Balance, Allocatable Balance and counts the records selected
**  Written By:
**  History:
**      20170823    SAC * Ported to H5
class MWS060_TotalSelectedRows
    gDebug: IScriptLog = null;
    gController: IInstanceController = null;
    gContent: IContentElement = null;

    gAllocateableColumnName: String = "AVAL";
    gOnHandBalanceColumnName: String = "STQT";

    gListView = null;

    **  New controls that we will add to the panel
    tbSelectedAllocated: any = new TextBoxElement();
    lbSelectedAllocated: any = new LabelElement();
    tbSelectedPallets: any = new TextBoxElement();
    lbSelectedPallets: any = new LabelElement();
    tbSelectedOhB: any = new TextBoxElement();
    lbSelectedOhB: any = new LabelElement();

    gStartRow = 5;
    gColumn = 50;

    gQuantityFormat = "d2";

    constructor(scriptArgs: IScriptArgs)
        this.gDebug = scriptArgs.log;
        this.gController = scriptArgs.controller;
        this.gContent = scriptArgs.controller.GetContentElement();
        this.gListView = ListControl.ListView;

    private run(): void

        const list = this.gController.GetGrid();
        const handler = (e, args) => { this.onSelectionChanged(e, args); };

    private onSelectionChanged(e: any, args: any): void
        let grid = args.grid;
        let selected = grid.getSelectedRows();

        if (selected.length > 0)
            let totalOhB = 0;
            let totalAllocated = 0;
            let totalPallets = 0;

            let OhB = ListControl.ListView.GetValueByColumnName(this.gOnHandBalanceColumnName);
            let allocated = ListControl.ListView.GetValueByColumnName(this.gAllocateableColumnName);

            if (null != OhB)
                for (let currentOhB of OhB)
                    totalOhB += +currentOhB;
                for (let currentAllocated of allocated)
                    totalAllocated += +currentAllocated;

                this.gController.SetValue("tbSelectedPallets", allocated.length);
                this.gController.SetValue("tbSelectedOhB", totalOhB);
                this.gController.SetValue("tbSelectedAllocated", totalAllocated);

    private AddControls()
        if (null != this.gListView.GetValueByColumnName(this.gAllocateableColumnName))
            this.lbSelectedAllocated.Position = new PositionElement();
            this.lbSelectedAllocated.Position.Top = this.gStartRow;
            this.lbSelectedAllocated.Position.Left = this.gColumn;
            this.lbSelectedAllocated.Value = "Sel Alloc. Qty:";

            this.tbSelectedAllocated.Position = new PositionElement();
            this.tbSelectedAllocated.Position.Top = this.gStartRow;
            this.tbSelectedAllocated.Position.Left = (this.gColumn + 10);
            this.tbSelectedAllocated.Position.Width = 10;
            this.tbSelectedAllocated.IsRightJustified = true;
            this.tbSelectedAllocated.Name = "tbSelectedAllocated";


        this.lbSelectedPallets.Position = new PositionElement();
        this.lbSelectedPallets.Position.Top = this.gStartRow - 1;
        this.lbSelectedPallets.Position.Left = this.gColumn;
        this.lbSelectedPallets.Value = "Sel Pallet. Qty:";

        this.tbSelectedPallets.Position = new PositionElement();
        this.tbSelectedPallets.Position.Top = this.gStartRow - 1;
        this.tbSelectedPallets.Position.Left = (this.gColumn + 10);
        this.tbSelectedPallets.Position.Width = 10;
        this.tbSelectedPallets.IsRightJustified = true;
        this.tbSelectedPallets.Name = "tbSelectedPallets";


        if (null != this.gListView.GetValueByColumnName(this.gOnHandBalanceColumnName))
            this.lbSelectedOhB.Position = new PositionElement();
            this.lbSelectedOhB.Position.Top = this.gStartRow + 1;
            this.lbSelectedOhB.Position.Left = this.gColumn;
            this.lbSelectedOhB.Value = "Sel Pallet. Qty:";

            this.tbSelectedOhB.Position = new PositionElement();
            this.tbSelectedOhB.Position.Top = this.gStartRow + 1;
            this.tbSelectedOhB.Position.Left = (this.gColumn + 10);
            this.tbSelectedOhB.Position.Width = 10;
            this.tbSelectedOhB.Name = "tbSelectedOhB";
            this.tbSelectedOhB.IsRightJustified = true;


    public static Init(args: IScriptArgs): void
        new MWS060_TotalSelectedRows(args).run();
This entry was posted in Development, H5 Client, M3 / MoveX. Bookmark the permalink.

2 Responses to Total Selected Rows – Jscript to Javascript – H5 Revisited

  1. Lode Vlaeminck says:

    How do you feel about the Focus on the H5 client? Is the functionality of a java script the same as the functionality in a C# script?

    What about the SDK. We have implemented a few SDK apps already are we going to be able to migrate those to the H5 client? Same question for the mashups?

    If the rumours are true there will not be a Smart office client after the 13.4 release.

    Kind regards,

    Lode Vlaeminck

    • potatoit says:

      Infor are focusing heavily on the H5/Ming.le client – I spoke to one of the PMs in November and it sounds like they are really focused on improving performance and usability.

      I’m comfortable with the level of H5 Javascript functionality now – there are a few holes like how we interface with MWS which concern me. InforAPIs are obviously the way it will go but there needs to be a clean transparent authentication method for interfacing calls from scripts. I don’t think that Infor have really thought this one through enough and may be working on the philosophy of Mongoose being the path in those instances – hopefully not. In saying that, there are many instances where we can ditch MWS and SQL queries in favour of CMS100.

      SDK apps written in C# will need to be rewritten – how much work involved depends on how many .Net calls you make in your code and how you’ve structured it – so in some instances it may be a pretty significant rewrite.
      Mashups – you can convert mashups to H5 mashups – I haven’t really tried it myself. I suspect that if you have added a lot of extra custom XAML to make things work you may have some issues. And given how AppBuilder is being positioned, I don’t think Mashups will be getting much more love.

      Smart Office – the last I heard, Smart Office will not be offered with the multi-tenanted solution. It sounds like we are going to be finally transition to Grid 2, and there has been talk about optimising the communications between H5 and the M3BE my suspicion would be that Smart Office will not have this functionality added. Much of this should become clearer later in the year but I just can’t see Smart Office being an option for customers after 13.4.


Leave a Reply

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

You are commenting using your 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