Missing columns? Restore columns

Somehow I am doing a lot of maintenance and troubleshooting of Infor Smart Office Mashups these days.

Missing columns

Today I had the unusual case of a user that was missing many columns in a list. The list is an <m3:ListPanel> of STS300/B1 in a Mashup. The problem did not occur when running STS300 directly, it did not occur for other users, and it did not occur with his userid on another computer. The problem occurred only with the specific combination of his userid, on his computer, on that M3 program, in that Mashup. Good luck troubleshooting that.

Here is an illustration of missing columns, shown here simply with CRS046/B:

Troubleshooting in vain

We wasted time troubleshooting in vain: the XAML source code, the sorting order, the view, personal views. the filters, the personalizations, the Bookmark in MTS043, the M3 program Java source code, the interactive subsystem, and we uninstalled/re-installed Smart Office, without progress.

Duh, restore the columns

Then, the user selected right-click > Restore Columns in the list, and that fixed the problem [FACEPALM] It is one of those little things that are easy to forget. We do not know how 32 columns accidentally went missing in the first place, it is not something one does by mistake without remembering. That is still a mystery.

Anyway, here is the reminder for next time:

User specific data storage

Where are those settings stored you ask? They are stored in the file MFormsColumnDefinitions.xml in the user specific data storage path, and are persisted to disk when the user logs out of Smart Office:
b5

And those settings are loaded and saved in MForms.List.ListColumnManager:
b6

That’s it.

Please comment, like, subscribe, share, author. Thanks for your support.

Mashup quality control #2

Today I had to troubleshoot why a refresh icon in a Smart Office Mashup was not refreshing, and after correcting it, I included the correction as a new predicate rule in my Mashup quality control tool to automatically spot similar errors in other Mashups.

IconButtons and CommandBarButtons

Here is a screenshot of the icon bar and its XAML code:

You can find more information about the Smart Office Design System’s Icons, IconButtons and CommandBarButons on norpe’s blog post.

The error

After some manual troubleshooting, I found that the icon was refreshing the wrong list, i.e. the Click event had the TargetName property set to the wrong <m3:ListPanel>, it was set to PurchaseOrderList (incorrect) instead of RequisitionList (correct). Probably the developer copy/pasted the code from another tab and forgot to change the TargetName. That’s hard to find, quick to fix.

<ds:CommandBarButton IconName="Refresh" ToolTip="{mashup:Constant Refresh, File=Mango.UI}">
    <ds:CommandBarButton.CommandParameter>
        <mashup:Event TargetName="PurchaseOrderList" TargetEventName="Refresh" />
   </ds:CommandBarButton.CommandParameter>
</ds:CommandBarButton>

Finding other errors

Assuming the icons are grouped in a <StackPanel> followed by their <m3:ListPanel>, I can quickly find similar errors with the following XPath expression that lists all the icons TargetName:

//*[name()='ds:IconButton.CommandParameter' or name()='ds:CommandBarButton.CommandParameter']/mashup:Event/@TargetName

The Python code would be:

import os
import glob
import lxml.etree as etree

for f in glob.glob(os.path.join(r'C:\BuyerPortal', '*.xaml')):
    tree = etree.parse(f)
    r = tree.xpath("//*[name()='ds:IconButton.CommandParameter' or name()='ds:CommandBarButton.CommandParameter']/mashup:Event/@TargetName", namespaces={'mashup': 'clr-namespace:Mango.UI.Services.Mashup;assembly=Mango.UI', 'ds': 'clr-namespace:Mango.DesignSystem;assembly=DesignSystem'})
    for element in r:
        print(f, element)

Result

Then I visually inspect the result for outliers. In my case, I see the error I found earlier, and a new error I did not know about:
3

This is a quick way to help identify errors before users have to.

Future work

This still requires a visual inspection of the result. A better solution would be to calculate the distance between the icon and its target m3:ListPanel in the XAML tree, where a minimum distance would indicate a low probability of error, and a maximum distance would indicate a high probability of error.

That’s it!

Please comment, like, subscribe, share, author. Thanks for your support.

Related posts

Mashup quality control #1

It has become difficult for me to manually maintain the Smart Office Mashups of my customer – there are about 50 files, 1,000 controls, and 10,000 lines of XAML – so I am developing a software verification tool that does automatic quality control for me.

How?

I defined a set of predicate rules, and I use XPath to validate the Mashup against those rules. I am using Python for now because of its expressiveness and interactivity, but I will port it to JScript.NET or C# soon to benefit from the Smart Office API.

Sample rule

As a sample rule, I want all the <m3:ListPanel> controls to have the property IsListHeaderVisible=”True” such that users have the ability to expand the list header and change the sorting order, view, and apply filters. If one of the list panels does not have that property I want to know about it and correct it. Note this is my own preference, and other developers may have the opposite preference.

Here is the property in Mashup Designer:
3

The following XPath expression will return the list panels not validating the rule:

//m3:ListPanel[not(@IsListHeaderVisible="True")]

Here is a Python code to validate that rule:

import os
import glob
import lxml.etree as etree

for f in glob.glob(os.path.join(r'C:\RentalCounterMashup', '*.xaml')):
    tree = etree.parse(f)
    r = tree.xpath('//m3:ListPanel[not(@IsListHeaderVisible="True")]', namespaces={'m3': 'clr-namespace:MForms.Mashup;assembly=MForms'})
    for element in r:
        print(f, element.attrib['Name'])

The result is the following, a list of XAML filenames and <m3:ListPanel> names that fail the rule:
4

Result

In my example, out of 63 list panels, 46 had the property, and 17 were missing the property, that’s 27% of list panels not passing the quality control. In other words, I was able to quickly identify a third of the list panels to correct.

Future work

I have many more ideas to implement, for example:

  • Ensure there are no hard-coded values in the MForms Bookmarks, Links, and MForms Automation, such as hard-coded CONO or DIVI
  • Automatically correct the Mashup, e.g. set IsListHeaderVisible=”True” if missing, and save

That’s it!

Let me know in the comments below if you have other rules to control the quality of Mashups.

Please click Like, share this post with your colleagues, click Follow to subscribe, come write the next blog post with us, and send some love to the other M3 blogs as well. This is a volunteer-based community, and your participation keeps the community alive and growing. Thank you.

Related posts

Inspect tool for Mashups

How great the Inspect tool is for developing Mashups in Infor Smart Office! I mentioned it a long time ago in another post about developing scripts, and I want to mention it here again.

I am currently maintaining a Mashup that other developers created. That Mashup has 30 XAML files, 10,000 lines of code, 500 controls, 80 tabs in three levels, etc. Any time I need to modify the Mashup, I have to follow a thread to find the relevant line of source code.

With the Inspect tool, I can point at a control in the Mashup (watch cursor) to find any of its parts, find its name, its parent tab, its XAML file, etc. That saves me valuable time.

Here are some screenshots:
1 2 3

I wish the Mashup Designer had the same watch cursor feature. Maybe it is easy to implement with the Smart Office SDK. To be explored.

There is also the Snoop tool I mentioned in the other post. Try that too.

That’s it!

Let me know in the comments below what other tools you use. Share this post. Click Like. Follow this blog. And come write the new blog post with us. This is a volunteer-based community, and your participation keeps it going. Thank you.

Default country in Mashup ComboBox

Quick illustration of how to set the user’s country as the default selection in a Mashup ComboBox in Infor Smart Office.

Suppose you have a <m3:MIComboBox>, and you want it to be a list of countries (e.g. FR-France, SE-Sweden, US-United States, etc.), populated from the M3 API CRS045MI.LstByCode, displaying CSCD and TX40, and you want the default selection to be the user’s country (e.g. US).

For that, I will use the System.Globalization.RegionInfo.CurrentRegion’s property TwoLetterISORegionName and assume that CRS045 uses the same two-letter ISO codes.

Here is the code with the relevant lines:

<StackPanel
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:mashup="clr-namespace:Mango.UI.Services.Mashup;assembly=Mango.UI"
   xmlns:glob="clr-namespace:System.Globalization;assembly=mscorlib"
   xmlns:m3="clr-namespace:MForms.Mashup;assembly=MForms">
    <TextBlock Name="MyCountry" Text="{Binding Source={x:Static glob:RegionInfo.CurrentRegion}, Path=TwoLetterISORegionName}" Visibility="Hidden" />
    <m3:MIComboBox Name="Countries" SortField="CSCD" SelectedValuePath="[CSCD]" SelectedValue="{Binding ElementName=MyCountry, Path=Text}" Width="200">
       <m3:MIComboBox.Events>
          <mashup:Events>
             <mashup:Event SourceEventName="Startup" />
          </mashup:Events>
       </m3:MIComboBox.Events>
       <m3:MIComboBox.DataSource>
          <m3:MIDataSource Program="CRS045MI" Transaction="LstByCode" OutputFields="CSCD,TX40" IsCacheable="True" />
       </m3:MIComboBox.DataSource>
       <m3:MIComboBox.ItemTemplate>
          <DataTemplate>
             <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Path=[CSCD]}" />
                <TextBlock Text=" - " />
                <TextBlock Text="{Binding Path=[TX40]}" />
             </StackPanel>
          </DataTemplate>
       </m3:MIComboBox.ItemTemplate>
    </m3:MIComboBox>
 </StackPanel>

Here is the result:
blog

blog_

That’s it. Please like, share, subscribe, author.

Application messages in Infor Smart Office

Here is an illustration of application messaging in Infor Smart Office to send, broadcast, and receive messages, and process responses between applications in Smart Office, whether in scripts, Mashups, or other entities.

Documentation

The Infor Smart Office SDK Developer’s Guide has Chapter 19 Application messages, and the Smart Office SDK help file has the API reference for Mango.UI.Services.Messages.ApplicationMessageService:
3

Note: For more information on the Smart Office SDK refer to my previous post.

Scripts

Here are some examples of sending, broadcasting and receiving messages, and processing responses in Smart Office scripts; the other party in the communication can be another script, a Mashup or another entity in Smart Office.

To send a message and process the response:

import Mango.UI.Services.Messages;

package MForms.JScript {
	class ApplicationA {
		public function Init(element: Object, args: Object, controller : Object, debug : Object) {
			// send message
			var message: ApplicationMessage = new ApplicationMessage();
			message.Sender = "ApplicationA";
			message.Recipient = "ApplicationB";
			message.Parameter = "Hello World";
			var response: ApplicationMessageResponse = ApplicationMessageService.Current.SendMessage(message);
			// process response
			response.MessageStatus;
			response.Result;
		}
	}
}

To receive a message and return a response:

import Mango.UI.Services.Messages;

package MForms.JScript {
	class ApplicationB {
		public function Init(element: Object, args: Object, controller : Object, debug : Object) {
			ApplicationMessageService.Current.AddRecipient("ApplicationB", OnMessage);
		}
		function OnMessage(message: ApplicationMessage): ApplicationMessageResponse {
			message.Sender;
			message.Recipient;
			message.Parameter;
			var response: ApplicationMessageResponse = new ApplicationMessageResponse();
			response.MessageStatus = MessageStatus.OK;
			response.Result = "Bonjour";
			return response;
		}
	}
}

To broadcast a message (there is no response):

import Mango.UI.Services.Messages;

package MForms.JScript {
	class ApplicationC {
		public function Init(element: Object, args: Object, controller : Object, debug : Object) {
			var broadcastMessage: ApplicationMessage = new ApplicationMessage();
			broadcastMessage.Sender = "ApplicationC";
			broadcastMessage.Recipient = "GroupX";
			broadcastMessage.Parameter = "HELLO WRRRLD!!!!!!";
			ApplicationMessageService.Current.SendBroadcastMessage(broadcastMessage);
		}
	}
}

To receive a broadcasted message (there is no response):

import Mango.UI.Services.Messages;

package MForms.JScript {
	class ApplicationD {
		public function Init(element: Object, args: Object, controller : Object, debug : Object) {
			ApplicationMessageService.Current.AddBroadcastRecipient("GroupX", OnBroadcastMessage);
		}
		function OnBroadcastMessage(message: ApplicationMessage) {
			message.Sender;
			message.Recipient;
			message.Parameter;
		}
	}
}

Mashups

Here are some examples of sending, broadcasting and receiving messages in Smart Office Mashups (there are no responses); the other party in the communication can be another Mashup, a script or another entity in Smart Office.

To send a message (there is no response):

<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">
	<Button Name="BtnMessage" Content="Send" Width="150" />
	<mashup:ApplicationMessageControl Name="test">
		<mashup:ApplicationMessageControl.Events>
			<mashup:Events>
				<mashup:Event SourceName="BtnMessage" SourceEventName="Click" TargetEventName="Send" Debug="True">
					<mashup:Parameter TargetKey="Sender" Value="MashupE" />
					<mashup:Parameter TargetKey="Recipient" Value="MashupF" />
					<mashup:Parameter TargetKey="Parameter" Value="Hello World" />
				</mashup:Event>
			</mashup:Events>
		</mashup:ApplicationMessageControl.Events>
	</mashup:ApplicationMessageControl>
</Grid>

To receive a message (there is no response):

 <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">
	<mashup:ApplicationMessageControl Name="test" Recipient="MashupF">
		<mashup:ApplicationMessageControl.Events>
			<mashup:Events>
				<mashup:Event SourceEventName="Received" Debug="True">
					<mashup:Parameter SourceKey="Sender" />
					<mashup:Parameter SourceKey="Recipient" />
					<mashup:Parameter SourceKey="Parameter" />
				</mashup:Event>
			</mashup:Events>
		</mashup:ApplicationMessageControl.Events>
	</mashup:ApplicationMessageControl>
</Grid>

To broadcast a message:

<mashup:Event...TargetEventName="Broadcast">

To receive a broadcasted message:

<mashup:ApplicationMessageControl...BroadcastRecipient="Everyone">

Illustration

Here is an illustration of messages going in all directions between scripts and Mashups:
r

More

The ApplicationMessageService API has more methods and properties available:

  • You can send multiple parameters and multiple result values with Dictionary<string, Object>.
  • You can return different MessageStatus values depending on your needs, for example OK, Failed or InvalidMessage.
  • You can send a message asynchronously to not block the UI; but there is no response.
  • You should verify if the recipient already exists before adding a message handler; otherwise you will get the exception “A recipient with the key has already been added.”
  • You can remove recipients.

Refer to the SDK documentation for more information.

That’s it. Please like, comment, share, follow, contribute.

Workaround to have Google Maps back in Mashups

I found a quick workaround to have Google Maps back into Infor Smart Office Mashups. There are currently three problems with Google Maps in Mashups. I’m using Smart Office 10.1.1.1.5, on Windows 7 Professional 64 bits, with Internet Explorer 11.0.9600.17207.

Problem 1

The first problem is that some time ago Google Maps changed their service and removed the parameter output=embed from the allowed parameters of the URL. The embed output was great as it used to hide the header, footer, and sidebar making it ideal for limited spaces like Mashups; the default output having been classic. With that parameter now disallowed, it causes Google Maps to display the error “The Google Maps Embed API must be used in an iframe” and that happens whether in a browser or in a Mashup, and it even broke the built-in Mashup sample of Mashup Designer:
9
8

I haven’t investigated all the details of the problem but I found a workaround. Replace the value embed of the parameter with either of these values: svembed, embedmfe, or svembedmfe. sv seems to be the prefix for Street View. I haven’t yet figured out what the suffix mfe means nor if it will remain long lived.

Here’s a sample result of Google Maps embedded in my browser:
10

Problem 2

Instead of using the output parameter we could simply use the Google Maps new look which is lean and sexy and also hides the header, footer and sidebar. But unfortunately, the WebBrowser control of Smart Office Mashups sends the HTTP request header Accept: */* instead of Accept: text/html, application/xhtml+xml, */* and that causes Google Maps to respond with HTTP 302 Found redirecting to output=classic and we’re back at problem 1:
12

Problem 3

The third problem is that Google Maps in a Mashup now throws a JavaScript error popup:
11

I haven’t yet investigated what causes it. It seems to be caused by the old render mode IE7 that Smart Office uses (although in the past the same render mode wasn’t causing the script error). The workaround is to add a registry key to your Windows to force the render mode to IE11 (and you need to install Internet Explorer 11). You can read more about this in Karin’s post. Microsoft has tools to push registry keys to users computers. Here’s the Windows Registry key I just added on my computer:
4

Result

After changing to output=svembed, and after adding the Windows Registry key, here’s the result in my Smart Office: the output is correctly embedded, there is no script error, the map works correctly (zoom/pan/etc.), and the Mashup events still work correctly:
7

That was a quick workaround to get Google Maps back into Mashups. If you know of a simpler solution let me know.

Calling M3 Web Services with SQL adapter in Smart Office Mashup

Once in a while I receive this question of how to call an M3 Web Service (MWS) with SQL adapter in a Mashup for Infor Smart Office. As a reminder, MWS has three adapters: M3 API, M3 Display Program (MDP), and SQL (JDBC). Each will return a different SOAP response, thus each will need a slightly specific XAML. The trick with the SQL response is the new0Collection.

Here is my sample SQL:

SELECT OKCUNO, OKCUNM
FROM MVXJDTA.OCUSMA
WHERE OKCONO=? AND OKSTAT=?

1b

Here is the web service in MWS Designer (note the new0 result set in the output):
4_
6b

Here is the Web service test tool in Smart Office (note the new0Collection):
8_

Here is the resulting Mashup:
10b

And here is the final XAML source code with the new0Collection binding highlighted:

<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">
	<Grid.Resources></Grid.Resources>
	<Grid.ColumnDefinitions>
		<ColumnDefinition Width="*" />
	</Grid.ColumnDefinitions>
	<Grid.RowDefinitions>
		<RowDefinition Height="*" />
		<RowDefinition Height="Auto" />
	</Grid.RowDefinitions>
	<mashup:DataListPanel Name="CustomerList" Grid.Row="0">
		<mashup:DataListPanel.Events>
			<mashup:Events>
				<mashup:Event SourceEventName="Startup" TargetEventName="list" />
			</mashup:Events>
		</mashup:DataListPanel.Events>
		<mashup:DataListPanel.DataService>
			<mashup:DataService Type="WS">
				<mashup:DataService.Operations>
					<mashup:DataOperation Name="list">
						<mashup:DataParameter Key="WS.CredentialSource" Value="Current" />
						<mashup:DataParameter Key="WS.Wsdl" Value="https://host:26108/mws-ws/SIT/TestSQL?wsdl" />
						<mashup:DataParameter Key="WS.Address" Value="https://host:26108/mws-ws/SIT/TestSQL" />
						<mashup:DataParameter Key="WS.Operation" Value="LstCustomers" />
						<mashup:DataParameter Key="WS.Contract" Value="TestSQL" />
						<mashup:DataParameter Key="LstCustomers1.CONO" Value="750" />
						<mashup:DataParameter Key="LstCustomers1.STAT" Value="20" />
					</mashup:DataOperation>
				</mashup:DataService.Operations>
			</mashup:DataService>
		</mashup:DataListPanel.DataService>
		<ListView Name="Customers" ItemsSource="{Binding new0Collection}" Style="{DynamicResource styleListView}" ItemContainerStyle="{DynamicResource styleListViewItem}">
			<ListView.View>
				<GridView ColumnHeaderContainerStyle="{DynamicResource styleGridViewColumnHeader}">
					<GridView.Columns>
						<GridViewColumn Header="Customer" DisplayMemberBinding="{Binding Path=OKCUNO}" />
						<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Path=OKCUNM}" />
					</GridView.Columns>
				</GridView>
			</ListView.View>
		</ListView>
	</mashup:DataListPanel>
	<ui:StatusBar Name="StatusBar" Grid.Row="1" />
</Grid>

That’s it!

H5 Client and M3 API with jQuery DataTables revisited

Today I will revisit my previous articles on H5 Client and M3 API with jQuery DataTables: I will correct a few mistakes in my code, I will update the code to the latest versions of jQuery and DataTables, and I will introduce new features.

Motivation: IBrix conversion

I’m helping a customer convert their old IBrix to HTML5/JavaScript. The IBrix we’re starting with uses a lot of M3 API and M3 Web Services (MWS), as well as IPM <c:table> components to render resulting data in selectable lists of rows and columns. To replace that, we could write Infor Smart Office Mashups that use the controls m3:MIListPanel, m3:MIPanel, and mashup:DataService, but the customer won’t use Smart Office as their main user interface; they’ll use Infor M3 H5 Client instead. Alternatively, we could convert the Smart Office Mashups to Web Mashups for H5 Client, but Web Mashups currently don’t support the MI controls. Infor says they are adding support for more and more controls, but they haven’t released any specific information on which controls they’ll add nor on what timeline. The customer couldn’t wait for Web Mashups to support those controls, so we decided to rewrite the IBrix from scratch using HTML5/JavaScript, and to re-evaluate Web Mashups later when Infor releases something new in the future. We opted for jQuery as the JavaScript library – as opposed to another JavaScript library like Dojo – because jQuery is already used by H5 Client and Web Mashups, so if we have to learn something new we might as well just learn one. As part of this learning process, I will share with you what I learn.

Files & folders

I haven’t yet found a good place to put my HTML5/JavaScript code. So for now I will continue to use the mne folder in Infor LifeCycle Manager where I have my own sub-folder:
folder

It’s probably not the best idea, but we already trust Smart Office Script files in the jscript sister folder, so I don’t yet see a problem with putting other files in a sibling folder. The only problem could be an upgrade or a migration that wouldn’t account for our piggyback folder and our folder could be lost (yikes!).

URI relative references

Most resources on the Infor Grid can be accessed over an insecure channel with HTTP, or over a secure channel with HTTPS, like many resources on the web. If our code requests resources over a mix of secure and insecure channels, and the user requests the page over a secure channel, the browser (for example Internet Explorer and Google Chrome) will protect the user: it will show a security icon in the address bar, it will show a security popup, and it will not load the resources that are over the unsecure channel unless the user confirms to do so:

MixedContent
MixedContentChrome2
MixedContent

The page behaves seemingly normal in one case and broken in another. It’s tricky to troubleshoot for the developer, users, support, etc.

The solution is to use URI relative reference for scheme abstraction, i.e. remove the scheme part of the URI. The browser will then load the resources using the scheme the user chose in the first place, HTTP or HTTPS.

For example, replace this:

http://code.jquery.com/jquery-1.10.2.min.js
https://code.jquery.com/jquery-1.10.2.min.js

with this:

//code.jquery.com/jquery-1.10.2.min.js

The resulting URI looks strange, yet it’s valid. It’s not a well-known technique, yet it has been in the URI specification for a long time. And it’s important in our case to prevent a potentially broken page that’s hard to troubleshoot.

URI relative references cont’d.

Also, the Infor Grid is a distributed system where the same application can run on any of the deployed nodes and any of the binding ports. Thus the variations in the URI can be on the scheme HTTP/HTTPS, the host A or B, and the port number X or Y. For example the user could legitimately request any of these and should get the same Grid application:

So our code must account for this possibility. If we used absolute URI in our code, H5 Client and our code wouldn’t be within the same document origin, and we could run into same-origin policy restrictions.

The solution is to use relative references again, this time just with the path and query parts of the URI, without the scheme://host:port parts.

For example, use:

/m3api-rest/execute/CRS610MI/GetBasicData;returncols=CUNM?CONO=910&CUNO=ACME

instead of:

https://host:48494/m3api-rest/execute/CRS610MI/GetBasicData;returncols=CUNM?CONO=910&CUNO=ACME

Caching

As we’re developing for the web, we have to be cognizant of caching and cookies. In my case I’m developing static HTML pages that I’m updating frequently (within seconds). I’m using a browser to execute the page. The browser caches the page, and there could be proxies along the way that cache the page. I’m using Fiddler to intercept M3 API requests/responses. MNE uses cookies to maintain the session across requests. To prevent caching, I use Fiddler > Rules > Performance > Disable caching, and I switch with the anonymous mode of the browser, InPrivate Browsing in Internet Explorer, and Incognito window in Chrome to start fresh without cookies. And I check my page versions and cookies in the HTTP Responses in Fiddler.

Security vulnerability

H5 Client is one of the Grid applications that will work over both HTTP-only, an insecure channel, and HTTPS, a secure channel. Unfortunately, when using HTTP, the login phase is done over HTTP-only as well, it’s not redirected over HTTPS and back like M3 Workplace did, and as all sites should do. So the user/password is sent Base64-encoded over the network, i.e. in clear text:
Password

I validated this with H5 Client 10.2.1.2 (Enterprise):
H5v

That’s a security vulnerability. So I’m looking into if this was an undetected installation/configuration mishap, and how to force HTTPS-only and prevent HTTP, at least for the login phase, or at default for the entire MNE. I reported this to Infor. To be further investigated.

H5 Client is usually confined to the corporate network behind firewall and NAT, so this is not as big a risk as with a vulnerability that would be exposed on the Internet.

UPDATE 2014-05-07: It appears this was a misconfiguration of the particular Grid I tested. Check your Grid in case you too have an undetected misconfiguration. Go to: Grid Management > M3_H5_Client > Web Components > Web Routers > Configure. The WWW Authentication Methods should only be checked for HTTPS, and unchecked for HTTP. I don’t do installations/configurations of the Grid, so this is to be confirmed by a certified installer.

 H5 Client lifetime

Let’s learn more about the H5 Client lifetime: login, session handling, keep alive, and logout.

  1. When the user requests H5 Client at /mne, /mne/, or /mne/index.jsp, the J2EE Jetty server sets a new cookie JSESSIONID for the session. I don’t yet know where that cookie is used.
  2. At /mne/index.jsp the server will challenge the client for authentication, and the browser will prompt the user for id/password in a popup.
  3. The user enters the M3 userid/password, and the browser sends that Base64-encoded along with the JSESSIONID cookie. The Authorization header will be transmitted throughout the session.
  4. The server authenticates the credentials and responds with a new cookie JUZUSR2SKRJVIOR2. This cookie will be used by the browser to maintain the session across requests, and it’s even validated by M3 API and MWS which will be very useful later.
  5. At this point we can add our HTML5/JavaScript code into an H5 Client Page. The browser will pass along the JUZUSR2SKRJVIOR2 cookie and authenticate our M3 API and MWS requests.
  6. Also, H5 Client will POST a CMDTP=LOGON to the MNE server with the userid UID and the two cookies. The server seems to ignore the cookies here.
  7. The server will respond with the MNE session id SID.
  8. Then the user optionally opens an M3 Program like CRS610.
  9. When the user closes the browser, H5 Client will issue CMDTP=QUIT with the SID, and the server will logout that user from M3 and invalidate that SID. However, the cookies are not invalidated. Is that another potential security vulnerability?
  10. Also, H5 Client does ping with CMDTP=FNC&CMDVAL=PING at about a 25mn frequency to maintain the session alive and not let it timeout.

We can now draw some useful conclusions:

  1. For H5 Client, we need the SID parameter, and either the HTTP Basic Authorization header or the cookie JUZUSR2SKRJVIOR2.
  2. For M3 API and M3 Web Services, we can use either the HTTP Basic Authorization header or the cookie JUZUSR2SKRJVIOR2. This will prove very useful later.

The JSESSIONID cookie doesn’t seem to be used anywhere.

In my observations, I saw both a HTTP Basic Authorization header and the cookie JUZUSR2SKRJVIOR2 throughout the session, and that seems redundant to me as we only need one, not both. I don’t see an impact yet.

I did my tests by analyzing the HTTP Requests/Responses with Fiddler, and with forged HTTP Requests in Fiddler Composer to try the various combinations. And I did my tests in a rush so not everything might be accurate. Please comment if you find a discrepancy. All I wanted to know was how to authenticate the M3 API and MWS requests.

M3 API authentication

We now know we don’t need to authenticate our M3 API requests, i.e. we don’t need to hard-code any userid/password in our code nor prompt the user to login, provided we conform to the following:

  1. Our code must run within the same browser session as H5 Client, i.e. same origin, for example in an H5 Page.
  2. Remove any authentication from our code, i.e. remove any user/password, and don’t authenticate HTTP Requests to M3 API or MWS.
  3. Use only the path & query parts of the URL without the scheme://host:port, so for example use just /m3api-rest, in order to be within the same origin, so the browser will pass the cookie along, regardless of scheme HTTP/HTTPS, host/FQDN, port

Alternatively, we could build our own login page, but we would have to deal with password management (password expired, forgot password), session keep-alive, and logout.

M3 API with jQuery.ajax()

In my previous article, I had use the native XMLHttpRequest object to call the M3 API. This time I will use jQuery.ajax():

	$(document).ready(function () {
		$.ajax({
			url : "/m3api-rest/execute/CRS610MI/GetBasicData;returncols=CUNM,TOWN,ECAR,PONO,CSCD?CONO=910&CUNO=ACME",
			"dataType": "json"
		})
	});

jQuery DataTables

Now to render the M3 API into a jQuery DataTable, I won’t do a time and memory consuming row and cell creation anymore. I’ll simply use the jQuery DataTables Custom data source property dataSrc:

	var program = 'CRS610MI';
	var transaction = 'LstByNumber';
	var maxrecs = 100;
	var returncols = 'CUNO,CUNM,CUA1,TFNO,STAT';
	var inputFields = 'CONO=910&CUNO=ACME';
	// construct the URL
	var url = '/m3api-rest/execute/' + program + '/' + transaction + ';maxrecs=' + maxrecs + ';returncols=' + returncols + '?' + inputFields;
	// prepare the columns for dataTable
	var arr = returncols.split(',');
	var columns = [];
	for (var i in arr) {
		columns[i] = { "data": arr[i] };
	}
	$(document).ready(function () {
		var table = $('#CustomerList').dataTable( {
			"ajax": {
				"url": url,
				"dataSrc": function (json) {
					var result = [];
					for (var i in json.MIRecord) {
						var record = {};
						json.MIRecord[i].NameValue.map(function(o){ record[o.Name] = o.Value; });
						result[i] = record;
					}
					return result;
				}
			},
			"columns": columns
		});
	});

And the HTML fragment with the column headers:

	<table id="CustomerList" class="display" cellspacing="0" width="100%">
		<thead>
			<tr>
				<th>Customer</th>
				<th>Name</th>
				<th>Address line 1</th>
				<th>Telephone</th>
				<th>Status</th>
			</tr>
		</thead>
	</table>

Row selection

To enable row selection:

	$('#CustomerList tbody').on('click', 'tr', function () {
		if ($(this).hasClass('selected')) {
			$(this).removeClass('selected');
		} else {
			table.$('tr.selected').removeClass('selected');
			$(this).addClass('selected');
		}
	});

Context menu

To get a context menu (right-click), I use jQuery contextMenu, and it looks like this (unfinished code):

	$(function(){
		$.contextMenu({
			selector: '#CustomerList tbody',
			callback: function(key, options) {
				// PENDING
			},
			items: {
				"Select": {name: "select", icon: "select"},
				"Copy": {name: "copy", icon: "copy"},
				"Change": {name: "change", icon: "edit"},
				"Display": {name: "display", icon: "display"},
				"Delete": {name: "delete", icon: "delete"}
			}
		});
	});

I’m still working on completing this code.

Result

Here’s a screenshot of the result:
contextmenu

Also, jQuery DataTables supports client-side search, pagination, and sorting. It took about 6s to load and render 3,000 rows. That’s about 2ms per row, not bad. But I will look into making this server-side anyway as client-side is not suitable for production.

 

Conclusion

In this post, we re-visited how to call M3 API with jQuery, how to take care of authentication, how to maintain the session across requests, how to do session keep alive, and logout (in fact H5 Client takes care of it, and the browser passes along the session to our code provide we conform to certain conditions), how to render the result with jQuery DataTables, how to enable row selection, and how to add a context menu.

As future work, I will follow-up on the security vulnerabilities, complete the code for the context menu, I will implement the equivalent of the SelectionChanged event of Mashups, I will render the M3 API Get with jQuery, and I will implement the F4-Browse dialogs of Ken Eric.

With all those simple engineering problems tackled one after the other, I will have a good basis to start converting IBrix to HTML5/JavaScript.

A more tricky engineering problem will be to implement the server-side search, pagination, and sorting.

Related articles

 

That’s it! This was a sloppy article put together hastily to get it out of my system and anchor it at once. It nonetheless contains useful information. I’ll be posting more. Stay tuned. Subscribe. Comment. Share. Enjoy.

Introduction to Web Mashups

Here’s a quick introduction to Web Mashups for Infor M3 H5 Client; Web Mashups are the cousins of Infor Smart Office Mashups. To do Web Mashups, you’ll need Infor Smart Office, the Mashup Designer, and Infor M3 H5 Client. I’ll show lots of screenshots.

What are Web Mashups

Historically, Smart Office Mashups were just called Mashups and would run in Smart Office. Smart Office is currently the main user interface for M3, built using C# and Microsoft WPF. With the launch of Infor M3 H5 Client released about 2013, M3 can now run in HTML5/JavaScript in any major web browser (for example Microsoft Internet Explorer, Google Chrome, Apple Safari, Mozilla Firefox), in any major operating system (for example Microsoft Windows, Mac OS,  Linux, etc.), in any major device (Mac, PC, iPhone, iPad, Android, etc.). As part of that web enablement, Mashups are now automatically converted from Smart Office Mashups to Web Mashups, and they can run inside H5 Client or standalone in a browser.

To do Web Mashups, the developer creates Mashups as usual in XAML with the Smart Office Mashup Designer, and the server converts the XAML the best it can into HTML5/JavaScript, jQuery, REST/SOAP, and JSON/XML. Currently Web Mashups don’t support all the controls that Smart Office Mashups supports. I successfully tested m3:ListPanel, m3:DetailPanel, as well as Grid, StackPanel, Label, TextBox, and Button. It seems MIListPanel and MIDetailPanel are not supported. It seems Document Archive is supported. And it seems XAML’s ListView may already be supported, to be confirmed. According to the Product Manager and component owner at Infor, they are adding support for more and more controls. I expect loss along the conversion as I don’t think it’s possible to automatically convert all of XAML, Binding, Converters, and other advanced WPF tricks. But it should work fine if we stick to a specific subset of Mashups.

Documentation

You can read more about Web Mashups on the Infor InfoCenter:
doc

Web Mashup demo

I’m working with Ryan, an IBrix and J2EE developer at a long-time customer, to help him convert IBrix from the obsolete Movex Workplace to the new M3 13.1 as part of their company’s upgrade. We’re evaluating the capabilities of Web Mashups, and the best strategy to do IBrix conversion. Here I’ll illustrate the basics.

First, I’ll create a simple Mashup using the built-in example at Mashup Designer > Help > M3 Transactions > Item list & details:
1b

That simple Mashup is good for illustration purposes as it shows records from M3 Customer. Open – CRS610, and it is made of a m3:ListPanel to show the list CRS610/B, and a m3:DetailPanel to show fields from CRS610 panels EFG:
2b

Then, I’ll put the XAML inside a Mashup Project (*.manifest), and I’ll deploy the Mashup privately as a Web Mashup deployment target:
3b

Then, Smart Office opens a Deployment Result popup confirming the Web Mashup is deployed privately, with two buttons Open and Debug:
4b

Then, I click Open, and Smart Office launches the Web Mashup in my browser at /mashup/web/MyMashup, and the server prompts me for M3 authentication with User Name and Password:
5b

Finally, I can use the Web Mashup, it works great:
6b

I tried the Debug to simulate a Search event with a Query parameter, but it didn’t work for me, nothing happened when I clicked Execute event, and I don’t yet know why:
7b

You can also check the version of your Mashup grid application at /mashup/about/version, in my case it’s 10.1.0.3.23:
1b

You can also go to the Administration UI and see the deployed Mashups at /mashup/admin:
10

You can also generate a *.webmashup package to deploy the Web Mashup globally:
g

You can also see the supported controls, parameters, and events:
7

You can also check some of the files in LifeCycle Manager:
2b

That’s it!

Also, check out the post on Web Parts for H5 Client.

And don’t forget to subscribe by clicking the Follow button below, leave us your comments, share, and contribute.