Calling M3 APIs in JScript on a Background Thread

In 9.1.3 we introduced a generic support for running any M3 API without having to create WS Stubs. If you are on a previous version you can still call a web service but it requires much more work including the use of Smart Office SDK. All calls to M3 APIs have to be done on a background thread!

There is simply no excuse for doing this the incorrect way. If you call the web service on the UI thread the entire UI will freeze for the time period that the call takes. In normal cases this will not be noticable but that does not matter. No calls to other servers on the UI thread.

The render time in the UI might be percevied as a freeeze in the UI if there are many UI objects that are being added to the screen. This is the kind of delay that we cannot do anothing about and this is the delay that you somtimes will notice in a tool such as the Web Service test tool (tool://wstest) when the input panel is generated dynamically based on the input structure of the WSDL. There will always be delays related to UI rendering but we can never have delays such as a 4 minute time-out delay simply becuase a server is down.

This takes the JScript programming, any programming for that matter, to another level. In Lawson Smart Office there is only one UI thread. You can make calls on a background thread using different methods, for example:

1. BackgroundWorker

2. Use the new classes for Parallel Programming in the .NET Framework, for example Task.Factory.StartNew()

3. Asynchronous Programming Using Delegates

Now my personal choice is nr 2 but I recommend nr 1 for you. When you are comfitable with the model you can start to look at the other alternatives. There are a few things you need to consider in your design.

1. The user interface is responsive and therefor the user might have navigated away. Once your call completes you might be on another panel or the program might be closed or whatever. In some sencarios you will need to look your UI for inputs and in other you will simply have to check the state of the application.

2. Objects created on the UI thread cannot be changed by a background thread. You notice this when you get an Exception with some cryptig message the “The calling thread cannot access this object because a different thread owns it”. If you want to access and do stuff with the UIObjects say a TextBox in you code you have to be on the UIThread. Therefor you cannot change properties of UI objects from you background thread. You need to dispatch to the UI thread and change the properties on the UIThread.

The UI thread queues work items inside an object called Dispatcher. The Dispatcher selects work items on a priority basis and runs each one to completion. Every UI thread must have at least one Dispatcher, and each Dispatcher can execute work items in exactly one thread. You have access to the Dispatcher on all objects that derive from UIElement. We also have a static reference that you can use: Mango.UI.Services.DashboardService.Current.Dispatcher.

If you have a method and you want to check if you are on the UIthread or not use:

if(Mango.UI.Services.DashboardService.Current.Dispatcher.CheckAccess()){
  // we are on the UI thread
}else{
  // dispatch to the UI thread - void method
  DashboardService.Internal.Dispatcher.Invoke(new VoidDelegate(DoStuffOnUIThreadMethod), DispatcherPriority.Input, null);
 // dispatch to UI Thread to method with signature MyMethod(object parameter)
  DashboardService.Current.Dispatcher.BeginInvoke(new ObjParameterDelegate(MyMethod), DispatcherPriority.Input, parameter);
}

You can read more on the threading model for WPF on MSDN. Now I’m going a bit out of trach covering dispatching. What I was planning to cover was the BackgroundWorker. With the BackgroundWorker you don’t need to do dispatching becuase the bacground worker will handle that for you. But it is good to now some dispatching techniques. Becuase one of the standrad tricks I use when there are issues with stuff as focus and selections is simply do dispatch it to the UI thread queque.

Using the BackgroundWorker
I’ll show you a script that calls an API to get the kredit limit for a customer or OIS100A as soon as the customer number has been entered. This way you can see if the customer has reached it’s credit limit before entering an order.

We use the BackgroundWorker and set two methods. Once that will DoWork on the Background thread and one that will be called once the Backgraound operation is completed. The code that is called on the BackgroundThread is wrapped in a try-catch by the framework and you will receive an error flag if ther was en exception. If you write and dispatch yourself you must always, always have a try- catch all in the background methods. Un unhandled exception in a background thread will cause Lawson Smart Office to crash. Make it a custom to always handle exceptions in your code!

OIS100/A
The script will show the Sales Person and the invoice overdue amount on the A-panel. Thanks to Peter for giving me a good starting point with the script.

Below is the full script. Note that this is an example of what you can do and not production code. There are lots of documentation on the MIAccess in the code documentation and also the MIWorker which is a wrapped worker that will handle all the dispaching for you.

The MIWorker can be used like this (example gets user data from MNS150MI):

private void GetName(string user)
{
   var record = new MIRecord();
   record["USID"] = user;
   MIWorker.Run("MNS150MI", "GetBasicData", record, OnRunCompleted);
}

private void OnRunCompleted(MIResponse response)
{
   if (!response.HasError)
   {
      var name = response.Item.GetString("NAME");
      Console.WriteLine(name);
   }
}

Here is the JScript that calls two APIs on the OIS100A panel.

import System;
import System.Windows;
import System.Windows.Controls;
import System.ComponentModel;
import MForms;
import Lawson.M3.MI;

package MForms.JScript {
   class OIS100A_SalesAndOverdue {
      var controller;
      var debug;
      var contentPanel;
      var textBoxSalesPerson;
      var textBoxOverdue;
      var customer;
      var responseOrderInfo;
      var responseFinancial;
      var lastException;
      var isReloaded;
      var textBoxCustomer;

      public function Init(element: Object, args: Object, controller : Object, debug : Object) {
         try {
            this.controller = controller;
            this.debug = debug;
            contentPanel = controller.RenderEngine.Content;

            textBoxCustomer = ScriptUtil.FindChild(contentPanel, "OACUNO");
            if(textBoxCustomer == null) {
               debug.WriteLine("TextBox OACUNO not found.");
               return;
            }

            textBoxCustomer.add_LostKeyboardFocus(OnLostKeyboardFocus);
            debug.WriteLine("Attach focus event handler");

            customer = textBoxCustomer.Text;

            AddLabel("Sales person:", 40, 1, 20);
            textBoxSalesPerson = AddTextBox("", 56, 1, 10);

            AddLabel("Overdue invoice amount:", 40, 2, 20);
            textBoxOverdue = AddTextBox("", 56, 2, 10);
            textBoxOverdue.HorizontalContentAlignment = HorizontalAlignment.Right;

            // Check for panel reloading to avoid updating fields that are not on the current panel when the transactions completes.
            controller.add_Requested(OnRequested);

             if(String.IsNullOrWhiteSpace(customer)) {
               debug.WriteLine("No value for OACUNO");
            }else{
               LoadAdditionalData();
            }

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

      public function OnRequested(sender: Object, e: RequestEventArgs) {
        // We always have to remove all event handlers
         debug.WriteLine("Removing handlers");
         controller.remove_Requested(OnRequested);

         if(textBoxCustomer!=null){
            textBoxCustomer.remove_LostKeyboardFocus(OnLostKeyboardFocus);
         }
         isReloaded = true;
      }

      public function LoadAdditionalData(){

      if(!String.IsNullOrWhiteSpace(customer)){
            // Use BackgroundWorker + MIAccess two run multiple transaction.
            // The MIWorker class could have been used for a single transaction.
            debug.WriteLine("Loading data for customer: " + customer);
            var worker = new BackgroundWorker();
            worker.add_DoWork(OnDoWork);
            worker.add_RunWorkerCompleted(OnRunWorkerCompleted);
            worker.RunWorkerAsync();
        }
      }

      public function OnLostKeyboardFocus(sender:Object, e: System.Windows.Input.KeyboardFocusChangedEventArgs){
      try{
        debug.WriteLine("Lost focus "+textBoxCustomer.Text);
          if(textBoxCustomer.Text!=customer){
          customer=textBoxCustomer.Text;
           if(!String.IsNullOrEmpty(customer)){
               LoadAdditionalData();
           }else {
              ClearValues();
           }
            }
         }catch(error){
         debug.writeLine(error);
         }
      }

      public function ClearValues(){
         textBoxOverdue.Text=String.Empty;
         textBoxSalesPerson.Text=String.Empty;

      }

      public function OnDoWork(sender : Object, e : DoWorkEventArgs) {
         try {
            if(isReloaded || customer == null) { return; }

            var program = "CRS610MI";
            var record = new MIRecord();
            record["CUNO"] = customer;

            responseOrderInfo = MIAccess.Execute(program, "GetOrderInfo", record);
            if(responseOrderInfo.HasError) { return; }

            responseFinancial = MIAccess.Execute(program, "GetFinancial", record);
         } catch(ex) {
            lastException = ex;
         }
      }

      public function OnRunWorkerCompleted(sender : Object, e : RunWorkerCompletedEventArgs) {
         try {
            debug.WriteLine("OnRunWorkerCompleted");
            if(isReloaded) { return; }

            if(lastException != null) {
               debug.WriteLine(lastException);
               lastException = null;

               return;
            }

            if(responseOrderInfo != null && !responseOrderInfo.HasError) {
               textBoxSalesPerson.Text = responseOrderInfo.Item["SMCD"];
            }
            if(responseFinancial != null && !responseFinancial.HasError) {
               textBoxOverdue.Text = responseFinancial.Item.GetString("TDIN");
            }
         } catch(ex) {
            debug.WriteLine(ex);
         }
      }

      public function AddLabel(content, left, top, width) {
         var label = new Label();
         label.Content = content;
         label.Padding = new Thickness(1);
         SetPosition(label, left, top, width);
         contentPanel.Children.Add(label);
         return label;
      }

      public function AddTextBox(text, left, top, width) {
         var textBox = new TextBox();
         textBox.MinHeight = Configuration.ControlHeight;
         textBox.Height = Configuration.ControlHeight;
         textBox.Padding = new Thickness(0);
         textBox.Text = text;
         SetPosition(textBox, left, top, width);
         contentPanel.Children.Add(textBox);
         return textBox;
      }

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

I can make my example even better by specifying what output fields I’m interested in. That way only the data requested will be sent. Notice that the Execure method take an aditional MIParameters object. Check the documentation for more info.

 var program = "CRS610MI";
 var record = new MIRecord();
 record["CUNO"] = customer;

 var miparameter = new MIParameters();
 var fields : String[] = new String[1];
 fields[0]="SMCD";

responseOrderInfo = MIAccess.Execute(program, "GetOrderInfo", record, miparameter);

Here is a view of the MIAcess in the documentation.

Note that in my example I could have use the MIMultiWorker or the MIWorker since they have built in support for threading. But I chose to show you an example with the BackgroundWorker since that approach is usable for accessing any kind of service. Also note that you will not be able to use MIAccess.Execute directly on the UI thread since it will throw an InvalidOperationException.

Read more on threading in this msdn article Build More Responsive Apps With The Dispatcher.

Happy hacking!

34 thoughts on “Calling M3 APIs in JScript on a Background Thread

  1. Paul Grooby

    Nice – Like the potential use of the parraellel processing options using the new .Net framework 4. Its always helpfull to look through other peoples code to see how they’ve done stuff . great stuff, Paul

  2. Scott campbell

    Couldn’t have been better timing!

    On the crossthreading; I’d just like highlight is that it isn’t safe to manipulate controls directly in the BackgroundWorker.DoWork event, you need to use the likes of a dispatcher.
    But the events, BackgroundWorker.ProgressChanged and BackgroundWorker.RunWorkerCompleted are safe.

    And thank you very much for the info on Mango.UI.Services.DashboardService.Current.Dispatcher, I have spent many hours trying to find a way to implement the dispatcher in jscripts but got stuck trying to work around delegates.

    Thank you.

    Cheers,
    Scott

  3. Pingback: Translate M3 with Google Translate API « M3 ideas

  4. Pingback: Modification Removal – Freecap In OIS101 – BackgroundWorker and Adding Columns | Potato IT

  5. Pingback: BackgroundWorkers in Smart Office Scripts – Part 1 « M3 ideas

  6. Ahmed Taha

    while trying to use the API call it gives me this error could you help me please

    C:\Users\ahtaha\AppData\Local\Temp\rjjeqxnm.0.js(97,31) : error JS1135: Variable ‘MIRequest’ has not been declared
    C:\Users\ahtaha\AppData\Local\Temp\rjjeqxnm.0.js(101,30) : error JS1135: Variable ‘MIRecord’ has not been declared
    C:\Users\ahtaha\AppData\Local\Temp\rjjeqxnm.0.js(104,13) : error JS1135: Variable ‘MIWorker’ has not been declared
    C:\Users\ahtaha\AppData\Local\Temp\rjjeqxnm.0.js(110,45) : error JS1135: Variable ‘MIResponse’ has not been declared

      1. Jose Ramon

        Hi!, first of all thanks for the blog, it’s being a really good source of information and inspiration, I’m currently discovering the magic of the scripts.
        I was wondering if there is a way to call API from older versions of Smart Office.

      2. karinpb Post author

        Yes and no. There is no easy way to call the APIs. Before we got the generic M3 API WS you had to deploy web services in Lawson Web Services using the Web Service Designer. Then in order to call the web service you need to generate C# stub code. So you can create a dll with some own calls to access API and then load that dll in the script. But it’s a lot of work. If this is a version that supports the Smart Office SDK you can write the code as part of a feature and deploy it to all clients and then just call methods from your script. The way to modify the generated stubs are described in the first version of the SDK Developers guide but this is something I can only recommend if you already know how to generate and use WS – stibs in .net.

  7. Pingback: Progress indicator adorner | M3 ideas

  8. thibaudatwork

    Hola Jose Ramón. Another technique is to call the API from the good old JSP IBrix and the good old IDSP in IPM IBrix (if you know how to program them) and make a simple HTTP Request to it. You don’t even need to hard-code the user and password in the script as you can get them from the script (see my post on it). I never tested this technique but I believe it’s possible. However I believe those IBrix frameworks are not supported by Infor anymore. Hope it helps. /Thibaud

    1. karinpb Post author

      Hi,
      No I don’t have an example but it’s pretty much like the MIWorker, it’s just that the signature is different. Instead of passing one MIRequest and one callback you pass in a list of requests and the callback is a MIMultiResult.
      public void RunWorkerAsync(IList requests, Action callback)

      The callback has a ResponseList with all of the replies. This is for running a list of APIs but you can always use your own BackgroundWorker or use the new Tasks library instead.

      We don’t use this class internally if we did I could have sent you that as an example. It was created as a util functionality for other projects.

  9. Mokhtar Kalmoush

    I am trying to run it on older version but does not work…What alternatives? Here are Errors that i got :
    C:\Users\Mokhtar\AppData\Local\Temp\4_bbkfw7.0.js(266,23) : error JS1135: Variable ‘MIRecord’ has not been declared
    C:\Users\Mokhtar\AppData\Local\Temp\4_bbkfw7.0.js(274,17) : error JS1135: Variable ‘MIAccess’ has not been declared

    1. karinpb Post author

      These classes has been around since 9.1.3 or something like that. Your errors indicate that there is an error in the script. Have you inlcued ithe namespace Lawson.M3.MI? If you are on a lower version the classes does not exist. If your M3 still has the M3-WS-API component then you can make REST calls directly to it intead. But if you are on an old verion of M3 you probably don’t have that and then you need to use the Lawson Web Services studio – create the API – generate a stub dll – and then load that dll with jscript and call the stubs. With stubs I mean the .Net class stubs that you can generate from the WSDL. And you create the WSDL with the web services studio. There might also be a solution where you can use the M3 API dlls directly, they are part of the M3 API Client like the MITest.

      But that is a lot of work and it was so many years since I used those techologies so I would not be able to help out with questions.

  10. ZaherElShamy

    thanks a lot for that helpful Post ,, but is there a way to Call API in the Current Thread ?? not the Background … i want to do something then Continue only after getting the Result
    thanks

    1. karinpb Post author

      Hi,
      You can call MIAccess.Execute(string program, string transaction, MIRecord record) and then continue after getting the result. But you cannot call the method on the UIThread since that is not allowed since it can potentially freeze the entire UI. I’m not sure what your scenario is since you did not take the time to give us much info. But you cannot call it on the UIThread directly.

      If you would like to prevent a navigation step in a form then there is a way which involves cancelling the request and it is described in this post: Validating M3 panels using JScript and MI programs before a request

      Server calls hsa to be done on a background thread and UIElements can only be updated on the UIThread. So in many cases you need to pass data around.

      1. ZaherElShamy

        thanks a lot ,,, that was very helpful for me .. i also got a new knowledge from the Post “Validating M3 panels using JScript and MI programs before a request” ,,, thanks again

  11. ZaherElShamy

    i don’t know if it is the suitable place for my question ,,, but you can kindly give me the target link to ask ,,, i want to encrypt my files *.js to be DLL files instead ,, as we have many customers and we have to encrypt our code as JS files are Readable ,, so i hope there exists some way to Encrypt or Convert these JS files to something like DLLs ,,, thanks

  12. kentast

    Hi,

    I try to create an script who retrieve multiple rows (MWS420MI – LstPLViaPckLst) for each row want to retrieve net weight via item number (API call MMS200MI – GetItmBasic).
    Show error dialog – items without net weigth.

    I using “BackgroundWorker” to retrieve all rows before I show error dialog.
    I works fine to retrieve multiple rows, but i have problem to run MMS200MI – GetItmBasic synchronous after MWS420MI – LstPLViaPckLst row.

    Any suggestions to solve this problem

    1. karinpb Post author

      Hi,
      I’m not sure what the issue is. Can you share the code? It would be easier? Is it an issue with accessing UI from a worker thread or do you need to know how to make a synchronous call? Are you using MIAccess.Execute()? If you look and call that method it will be synchronous. But as soon as you need to show a UI that needs to be but on a queue. I’m thinking that you need to split your scenario depending on when you show the UI. Do you show one message per row with missing net weight? You need to store a list of all items with missing weight and then show a single message. Perhaps you can explain more or share some code?

      1. Kent Åström

        Hi,
        The purpose of the script is to check if any items have no net weight in MWS420 row (Delivery no / picklist suffix).
        If the script find items with no weight, an error message dialog with one or more items will be shown to the user.
        I’m using MIAccess.Execute.
        It work fine with first API call MWS420MI LstPLViaPckLst, bur I have problem with MMS200MI GetItmBasic that I want to call synchronous after every MWS420MI LstPLViaPckLst row.
        I have make some comment i function OnRunWorkerCompleted.

        import System;
        import System.Windows;
        import System.Windows.Controls;
        import System.Windows.Input;
        import System.Windows.Media;
        import System.ComponentModel;
        import System.Collections;
        import Mango.UI.Services.Lists;
        import Mango.UI.Controls;
        import MForms;
        import Mango.UI;
        import Mango.UI.Core;
        import Mango.UI.Core.Util;
        import Mango.UI.Services;
        import Lawson.M3.MI;

        package MForms.JScript {

        class CheckItemsWeight {

        // Declare log function. Both “Script Tool Console” and “Smart Office log file”
        static var logger : log4net.ILog;
        var debug, message;
        function log(message : String) {
        if (debug.Debug) {
        debug.Debug(message);
        } else {
        if(logger == null) {
        logger = Mango.Core.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        }
        }
        }

        // Declare script global variables
        public var listControl;
        var controller, button, content;
        public var fieldName;
        public var cono, dlix, plsx, itno, itds, newe, errorMessage;
        public var itemNbr, itemDesc;
        public var response = MIResponse;
        public var responsePickListRows, responseItemInformation;
        public var itemListArray : Array = new Array();

        // Init function
        public function Init(element: Object, args: Object, controller : Object, debug : Object) {
        content = controller.RenderEngine.Content;
        listControl = controller.RenderEngine.ListControl;
        this.debug = debug;
        // Add button – “Check Weight”
        addButton(“check”, 20, 2, 60, “Check Weight”);
        }

        // Action – click on new button
        // Execute MWS420MI Transaction
        public function OnClick(sender: Object, e: RoutedEventArgs) {
        cono = UserContext.Company;
        fieldName = “S1DLIX”;
        dlix = listControl.GetColumnValue(fieldName);
        fieldName = “S1PLSX”;
        plsx = listControl.GetColumnValue(fieldName);
        if (debug.IsDebugEnabled && dlix == null) {
        message = “Delivery Indexnumber is not on view”;
        debug.Debug(message)
        }
        if (debug.IsDebugEnabled && plsx == null) {
        message = “Picklist Suffixnumber is not on view”;
        debug.Debug(message)
        }
        errorMessage = ” “;
        if(dlix != null && plsx != null) {
        //listPickListLines();
        var worker = new BackgroundWorker();
        worker.add_DoWork(OnDoWork);
        worker.add_RunWorkerCompleted(OnRunWorkerCompleted);
        worker.RunWorkerAsync();
        }
        }

        // Execute API “MWS420MI”, transaction “LstPLViaPckLst”
        //private function listPickListLines() {
        private function OnDoWork(sender : Object, e : DoWorkEventArgs) {
        try {
        var api = “MWS420MI”;
        var trans =”LstPLViaPckLst”;
        var record = new MIRecord();
        record[“CONO”] = cono;
        record[“RIDI”] = dlix;
        record[“PLSX”] = plsx;
        var parameters = new MIParameters();
        parameters.OutputFields = [‘ITNO’];
        parameters.MaxReturnedRecords = 0;
        responsePickListRows = MIAccess.Execute(api, trans, record, parameters);
        if (responsePickListRows.HasError) {
        if (debug.IsErrorEnabled) {
        message = “MWS420MI API call response error with Delivery Indexnumber: ” + dlix + ” and Picklist Suffixnumber ” + plsx;
        debug.Error(message);
        }
        }
        } catch(ex) {
        if (debug.IsErrorEnabled) {
        message = “MWS420MI API call error: ” + ex + ” with Delivery Indexnumber: ” + dlix + ” and Picklist Suffixnumber ” + plsx;
        debug.Error(message);
        }
        }
        }

        // API response – MWS420MI
        //private function listPickListLinesResponse(response : MIResponse){
        private function OnRunWorkerCompleted(sender : Object, e : RunWorkerCompletedEventArgs){
        if (debug.IsInfoEnabled) {
        message = “API MWS420MI transaction LstPLViaPckLst executed with Delivery Indexnumber: ” + dlix + ” and Picklist Suffixnumber: ” + plsx;
        debug.Info(message);
        }
        if(responsePickListRows != null && !responsePickListRows.HasError) {
        for (var row in responsePickListRows.Items) {
        itno = row.GetString(“ITNO”);
        if (debug.IsDebugEnabled) {
        message = “Item number: ” + itno;
        errorMessage = errorMessage + ” ” + “Item number: ” + itno;
        debug.Debug(message)
        }

        // Problem “The Execute method must be called on background thread”
        OnDoWorkDescription();

        // Problem “M3 program, script, SmartOffice ended”, because two BackgoundWorder ???
        //var workerDescription = new BackgroundWorker();
        //workerDescription.add_DoWork(OnDoWorkDescription);
        //workerDescription.add_RunWorkerCompleted(OnRunWorkerCompletedDescription);
        //workerDescription.RunWorkerAsync();

        }
        }
        ConfirmDialog.ShowErrorDialog(errorMessage);
        }

        // Execute API “MMS200MI”, transaction “Get”
        //private function getItemInformation(itno) {
        //private function OnDoWorkDescription(sender : Object, e : DoWorkEventArgs) {
        private function OnDoWorkDescription() {
        try {
        var api = “MMS200MI”;
        var trans =”GetItmBasic”;
        var record = new MIRecord();
        record[“CONO”] = cono;
        record[“ITNO”] = itno;
        var parameters = new MIParameters();
        parameters.MaxReturnedRecords = 0;
        responseItemInformation = MIAccess.Execute(api, trans, record, parameters);
        if (responseItemInformation.HasError) {
        if (debug.IsErrorEnabled) {
        message = “MWS420MI API call response error with Delivery Indexnumber: ” + dlix + ” and Picklist Suffixnumber ” + plsx;
        debug.Error(message);
        }
        } else {
        if(responseItemInformation != null) {
        itds = responseItemInformation.GetString(“ITDS”);
        newe = responseItemInformation.GetString(“NEWE”);
        }
        }
        } catch(ex) {
        if (debug.IsErrorEnabled) {
        message = “MMS200MI API call error: ” + ex + ” with Item Number: ” + itno;
        debug.Error(message);
        }
        }
        }

        // API response – MMS001MI
        //private function getItemInformationResponse(response : MIResponse){
        private function OnRunWorkerCompletedDescription(sender : Object, e : RunWorkerCompletedEventArgs){
        if (debug.IsInfoEnabled) {
        message = “API MMS200MI transaction Get is executed with Item number: ” + itno;
        debug.Info(message);
        }
        if(responseItemInformation != null && !responseItemInformation.HasError) {
        itds = responseItemInformation.GetString(“ITDS”);
        newe = responseItemInformation.GetString(“NEWE”);
        }
        }

        // Add button
        private function addButton(name : String, column : int, row : int, span : int, text : String) {
        var button = new Button();
        button.Name = name;
        button.Content = text;
        button.MinHeight = Configuration.ControlHeight;
        button.Height = Configuration.ControlHeight;
        button.Margin = Configuration.ControlMargin;
        button.Padding = Configuration.ControlPadding;
        setElementPosition(button, column, row, span);
        content.Children.Add(button);
        // Action – click on new button
        button.add_Click(OnClick);
        button.add_Unloaded(OnUnloaded);
        return button;
        }

        // Add label
        private function addLabel(name : String, column : int, row : int, span : int, text : String) {
        var label = new Label();
        label.Name = name;
        label.Content = text;
        label.MinHeight = Configuration.ControlHeight;
        label.Height = Configuration.ControlHeight;
        label.Margin = Configuration.ControlMargin;
        label.Padding = Configuration.ControlPadding;
        setElementPosition(label, column, row, span);
        content.Children.Add(label);
        return label;
        }

        // Place button, label, textbox on the panel
        private function setElementPosition(element, column, row, span) {
        Grid.SetColumn(element, column);
        Grid.SetRow(element, row);
        Grid.SetColumnSpan(element, span);
        }

        public function SetWaitCursor(wait) {
        var element = controller.Host.Implementation;
        element.Cursor = wait ? Cursors.Wait : Cursors.Arrow;
        element.ForceCursor = true;
        }

        // Action – leave program
        public function OnUnloaded(sender: Object, e: RoutedEventArgs) {
        button.remove_Click(OnClick);
        button.remove_Unloaded(OnUnloaded);
        }

        }

        }

      2. karinpb Post author

        Hi,
        I suggest that you read up on how to use a background worker. You also need to consider that after the click it would be nice to have a progress indication. After the MI call to LstPLViaPckLst you get the rows you might not even be on the same panel if the user clicked away. The result from a workerthread should be placed on the event object to the Completed function and not be a member variable of the script. Once you are in the Completed function you are on the UI tread and you cannot call MIAccess.execute.

        You should add method parameters to the methods so that it is clear what data you are manipulating. The call to GetItemBasic that you do for each line can be done directly in OnDoWork. For the error messages you can just have a StringBuilder where you stack the messages.

        It’s not clear to me what you need itds and newe for but you need to store the result that you get back in a structure, eg a Dictionary or something as you should loop and perform the next call directly in OnDoWork. You should also use the session Cache to store the information you have retreived from GetItemBasic, so that you don’t call to get basic item info from an item that you have already retrieved. Note that you can add and use an internal class here if you like.

        Is the purpose of the script simply to get a pickinglist with correct descriptions?

      3. Kent Åström

        Hi,

        I want to develop a script “CONFIRM” on the PMS420 e-panel, which press enter and update “REPORT WORK START” and finishes the program with F3.

        The script below does not work, what did I do wrong?

        import System;
        import System.Windows;
        import System.Windows.Controls;
        import MForms;
        import Mango.UI.Core;
        import Mango.UI.Core.Util;
        import Mango.UI.Services;
        package MForms.JScript {

        class PMS421F_confirm {

        // Declare log function. Both “Script Tool Console” and “Smart Office log file”
        static var logger : log4net.ILog;
        var debug, message;
        function log(message : String) {
        if (debug.Debug) {
        debug.Debug(message);
        } else {
        if(logger == null) {
        logger = Mango.Core.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        }
        logger.Debug(message);
        debug.WriteLine(message);
        }
        }

        // Declare script global variables
        public var listControl;
        var button, content;

        public function Init(element: Object, args: Object,
        controller : Object, debug : Object) {
        content = controller.RenderEngine.Content;
        listControl = controller.RenderEngine.ListControl;
        this.debug = debug;
        // Add button – “Confirm”
        button = new Button();
        button.Content = “Confirm”;
        Grid.SetColumnSpan(button, 15);
        Grid.SetColumn(button, 51);
        Grid.SetRow(button, 8);
        content.Children.Add(button);
        button.add_Click(OnClick);
        button.add_Unloaded(OnUnloaded);
        }

        // Action – click on new button
        // ENTER + F3
        public function OnClick(sender: Object, e: RoutedEventArgs) {
        var auto = new MFormsAutomation();
        auto.AddStep(ActionType.Key, “ENTER”);
        auto.AddStep(ActionType.Key, “F3”);
        var uri = auto.ToUri();
        DashboardTaskService.Manager.LaunchTask(new Task(uri));
        }

        // Action – leave program
        public function OnUnloaded(sender: Object, e: RoutedEventArgs) {
        button.remove_Click(OnClick);
        button.remove_Unloaded(OnUnloaded);
        }

        }

        }

  13. Kent Åström

    Hi, first I would like to inform you that I solved the problem above. Thank you for your response in that issue.

    However, I have a new script question that I hope you could guide me.
    In MWS420 is there an alternative 16 (Confirm all pick list lines). The alternative issue all lines on a pick list.
    I have created a script that checks for any item (read pick line) with zero weight. This check work fine. If there is an item/line with zero weight, I display those items/lines.
    However, M3 continues with issuing the pick list  I would like the (alt 16) to stop.
    My question to you is if you know that there is a solution to my problem, that it should be possible to stop M3 standard related alternative 16 if “connected” script gives an error message? I cannot see that there is any way to execute the script first and alt 16 synchronous.

    1. norpe

      The script could subscribe to the Requesting event and cancel the event if there if there is an item with zero weight and show an error message. If multiple items are selected you could actually cancel the event, change the selection in the list and then trigger the related options from the script again but without the items with zero weight. Not sure if I would recommend that though since you would change the items the user selected. It is probably better to just cancel the event and show a message.

  14. kentast

    Hi,
    I have created a new script based on “OIS100A_SalesAndOverdue”.
    I recieved error in OnRunWorkerCompleted “Object required” (try/Catch).
    The following code is executed
    if(responseGetItmBasicInfo != null) {
    debug.WriteLine(“OnRunWorkerCompleted reponseGetItmBasicInfo is not null”);
    }

    1. karinpb Post author

      If the error is between these line it is because you haven’t stored debug object as an instance variable. Check the unit method and make sure you save debug in the class.

  15. kentast

    HI,
    No, the error is in the try/Catch.
    Script show the following in the consolse.
    * OnRunWorkerCompleted with responseGetItmBasicInfo
    * OnRunWorkerCompleted: Object required

    public function OnRunWorkerCompleted(sender : Object, e : RunWorkerCompletedEventArgs) {if(responseGetItmBasicInfo != null) {
    debug.WriteLine(“OnRunWorkerCompleted with responseGetItmBasicInfo”);
    } else {
    debug.WriteLine(“OnRunWorkerCompleted responseGetItmBasicInfo == null”);
    }
    try {
    debug.WriteLine(“OnRunWorkerCompleted”);
    if(isReloaded) {
    debug.WriteLine(“OnRunWorkerCompleted – isReloaded”);
    return;
    }
    if(lastException != null) {
    debug.WriteLine(“lastExecption: ” + lastException);
    lastException = null;
    return;
    }
    if(responseGetItmBasicInfo != null) {
    textBoxWeight.Text = responseGetItmBasicInfo.Item[“NEWE”];
    }
    } catch(ex) {
    debug.WriteLine(“OnRunWorkerCompleted: ” + ex);
    }
    }

  16. Pingback: Calling a REST service from JScript | Developing for Infor Smart Office

Comments are closed.