Pressing KeyDown in a M3 list to load all rows

This post will cover something that you can do in JScript that is actaully on the edge of what you can but shouldn’t do. JScript is a great tool for doing small enhancements to M3 panels and S3 forms. However doning more advanced stuff requires a lot of experience. There are however a few tricks that you pick up along the way. In this post I’ll show you two useful things.

1. How to log to the Lawson Smart Office log file.
2. How to simulate pressing a key, in this case we will use page down.
3. How to run against your own local script instead of the central deployed onces.

This all started with a question from one of our consultants. They had a script that would load all rows in POS250. The script worked fine in the JScript editor but failed once it was deployed.

If it workes in the designer and not while deployed there is probably a timing issue. When you attach a script with the editor the program is already started but when the script is deployed it will acctually start a bit before the panel is loaded or visually loaded, becuase you have all the data but as it turned out – you are still in a request.

The example will cover how to load all available lines. The way to achieve that is to invoke PageDown to get a new set of rows until there are no more rows? How would you know that there are no more rows? The delta of new rows is less than 33. Think about it. A page is 33 rows. If you have a larger screen resolution there will be an automatic page down to get 66 items.

To be able to know what was happening with the script when it was deployed I need to log to the log file. The log file viewer is available under About Lawson Smart Office or as internal://log.

How to log to the Smart Office log file

I started by adding the logger to my class and a private method that would log to the output console in the editor as well as to the log file. In this case I will not log a lot of concatenated strings so I don’t check logger.IsDebugeEnabled before calling my method – which is a must if you are outputting a lot of data.

package MForms.JScript {
   class PageDown {
   	  var logger: log4net.ILog = Mango.Core.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType)
      var controller, debug, content;
      var currentRowCount;
      var isPaging;

      public function Init(element: Object, args: Object, controller : Object, debug : Object) {
        try
         {

         }
         catch(ex)
         {
           LogError("Exception in init",exception);
         }
      }

 private function LogError(message: String, exception: Exception){
       debug.WriteLine(message);
       if(exception != null){
         logger.Error(message, exception);
       }else{
        logger.Error(message);
       }

      }

      private function LogDebug(message: String){
      if(message != null){
      	 logger.Debug(message);
      	 if(debug){
           debug.WriteLine(message);
         }
       }
      }

How to simulate a key or Page Down

Next is to hook up the events for Requested and RequestCompleted. I need to check if I should disconnect the script of if I should try and get another page. Note that if you are doing stuff on threads you need to stop your logic to execute as well disconnecting the script does not mean that you background thread stops. So think about what kind of logic you do.

The example is pretty straight forward. The only trick I do to get it to work in the deployed version is that I need to dispatch the first page down to a later time, but still on the UI thread. I cannot simulate any key press in intit becuase the state of the instance controller is already requesting so a new request will simply be ignored with a line in the log file:

 DEBUG MForms.InstanceController.Execute  Duplicate request skipped

This is they way to call a void method using the dispatcher.

 var pageDownDelegate: VoidDelegate = PageDownInList;
 content.Dispatcher.BeginInvoke(DispatcherPriority.Background, pageDownDelegate);

PageDownInList is a method:

 private function PageDownInList(){
        LogDebug("Before page down");
        isPaging = true;
        controller.PageDown();
      }

The nice thing with the Log Viewer in Smart Office is that you can filter on the name of your class and get all the logs.

But how can you know that the InstanceController has a PageDown method? Well it is not that easy. There are generally two good options when it comes to using code you haven’t written yourself.

1. Read the documentation
2. Read the code (decompile)

You start with number 1. This is the script guide. For partners that have the Lawson Smart Office SDK it comes will a full code documentation). This is what I call the SDK API documentation. However it is NOT the same as the SDK Documentation menu found in the Mashup Designer. It will open the Lawson Smart Office Mashup SDK documentation. But reading about Mashups controlls and converter will not help you with the IInstanceController in MForms. What you need is the full SDK API and it look like this:

Looking at the documentation I can see that there is a PanelState property and that the is a PressKey method.

So what if you don’t have the documentation? You do have the dlls if you have the client installed. Search for one of it’s assemblies. Use a free tool like Jetbrains Decompiler to decompile and read the IInstanceController code. Jetbrains has some great tools including the Visual Studio plugin ReSharper that I just can’t live without.

The full script example

Here comes the full JScript example. Please note that it is provided “as is” and it is not production code.

]
import System;
import System.Windows;
import System.Windows.Controls;
import System.Windows.Threading;
import Mango.UI.Services;
import Mango.Core.Util;
import MForms;

package MForms.JScript {
   class PageDown {
   	  var logger: log4net.ILog = Mango.Core.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType)
      var controller, debug, content;
      var currentRowCount;
      var isPaging;

      public function Init(element: Object, args: Object, controller : Object, debug : Object) {
        try
         {
            //LogDebug("Initializing script");

            this.controller = controller;
            this.debug = debug;
            this.content = controller.RenderEngine.Content;
            this.isPaging = false;

            // Attach event handlers to be able to detach event handlers and list to page down requests.
            controller.add_Requested(OnRequested);
            controller.add_RequestCompleted(OnRequestCompleted);

            var items = controller.RenderEngine.ListControl.ListView.Items;
            currentRowCount = items.Count;
            LogDebug("currentRowCount in init "+ currentRowCount);
            if(currentRowCount == 33){
                LogDebug("Dispaching with BeginInvoke");
                //PageDownInList();
            	var pageDownDelegate: VoidDelegate = PageDownInList;
            	content.Dispatcher.BeginInvoke(DispatcherPriority.Background, pageDownDelegate);
            	return;
            }
         }
         catch(ex)
         {
           LogDebug("Exception in init");
           LogDebug(ex.ToString());
         }
      }

      private function PageDownInList(){
        LogDebug("Before page down");
        isPaging = true;
        controller.PageDown();
      }

      private function LogError(message: String, exception: Exception){
       debug.WriteLine(message);
       if(exception!=null){
         logger.Error(message, exception);
       }else{
        logger.Error(message);
       }

      }

      private function LogDebug(message: String){
      if(message!= null){
      	 logger.Debug(message);
      	 if(debug){
           debug.WriteLine(message);
         }
       }
      }

       public function UpdateRows()
      {
         var items = controller.RenderEngine.ListControl.ListView.Items;
         var delta = items.Count - currentRowCount;
         LogDebug("Delta "+ delta);

           // Check if we can load more rows
            var hasMoreRows = (delta == 33);

            if(hasMoreRows)
            {
               // Not enough rows, load more.
               LogDebug("Loading more list rows");
               currentRowCount = items.Count;
               LogDebug("currentRowCount "+ currentRowCount);

               isPaging = true;
               controller.PageDown();
               return;
            }
            else
            {
               LogDebug("No more rows to load");
            }

         LogDebug("Updating items" );

      }

       public function OnRequested(sender: Object, e: RequestEventArgs)
      {
         try
         {
            LogDebug("Page down onrequested "+e.CommandType);
            if(e.CommandType == "PAGE" && e.CommandValue == "DOWN")
            {
               // Do not disconnect events on page down.
               LogDebug("Page down request");
               return;
            }

            // Remove all event handlers.
            LogDebug("Request executed, removing event handlers.");
            controller.remove_Requested(OnRequested);
            controller.remove_RequestCompleted(OnRequestCompleted);
         }
         catch(ex)
         {
            LogDebug(ex);
         }
      }

       public function OnRequestCompleted(sender: Object, e: RequestEventArgs)
      {
         try
         {
            if(e.CommandType == "PAGE" && e.CommandValue == "DOWN")
            {
               // Page down complete, try to update list rows again.
               UpdateRows();
            }
         }
         catch(ex)
         {
            debug.WriteLine(ex);
         }
      }

   }
}

For a more detailed post on logging check out this post from Thibaud.

How to deploy a JScript on your local machine

If you want to run the JScripts deployed on your machine instead of those deployed on the server you can add a record to the registry with the path to your local script folder.
Start by adding the following key:

HKEY_CURRENT_USER\Software\Lawson\MangoDev

Then add a String value for LocalScriptPath, for example c:\\LocalJScripts.

Restart LSO and your scripts will be loaded from your local disk.

As you do changes to the script file a command to clear the script cache will come in handy. Use mforms://jscript/clear and you are ready to go.

You should always test your scripts on a server or locally before deploying them to test. Another good tip is that you should use the logger and check the logfile to verify that the script behaves as designed.
Fiddler is also a great tool for checking requests made to for example M3 APIs.

6 thoughts on “Pressing KeyDown in a M3 list to load all rows

  1. Al Johnson

    Hi Karin, great stuff as always. Question – this appears to stop at 9999 rows. Can we read beyond this, or are we limited by the M3 screen naturally stopping at this point?

    Al.

    1. Al Johnson

      Sorry Karin. Ignore question above. Small brain blank. Thought about it a bit more and answered my own question – clearly limited by M3 panel ability. Cheers, Al.

  2. RL

    Hi,

    regarding “How to simulate a key or Page Down” you are working with the fix value 33 for a page size. Is the page size everywhere equal 33 or is it better to be more flexible in this point?

    Regards, RL.

    1. karinpb Post author

      Hi RL,
      The page size for M3 is always 33. They have decided that a page in a list is 33 rows. On some clients we pre-load 66 rows if the screen resolution is high… so yes the page size is fixed.

  3. Dulan

    Hi Karin,

    I have tried to use it to Enter event and it didn’t work.
    Can you tell me the issue with below code. Thanks

    import System;
    import System.Windows;
    import System.Windows.Controls;
    import System.Windows.Threading;
    import Mango.UI.Services;
    import Mango.Core.Util;
    import MForms;

    package MForms.JScript {
    class SM_EDZ071B1_PageDownV2 {
    var logger: log4net.ILog = Mango.Core.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType)
    var controller, debug, content;
    var currentRowCount;
    var isPaging;
    var panelState :Object;

    public function Init(element: Object, args: Object, controller : Object, debug : Object) {

    this.panelState = controller.PanelState;

    try
    {

    //debug.WriteLine(“Initializing script”);

    this.controller = controller;
    this.debug = debug;
    this.content = controller.RenderEngine.Content;
    this.isPaging = false;

    controller.add_Requested(OnRequested);
    controller.add_RequestCompleted(OnRequestCompleted);

    var items = controller.RenderEngine.ListControl.ListView.Items;
    currentRowCount = items.Count;
    debug.WriteLine(“currentRowCount in init “+ currentRowCount);

    if(currentRowCount == 33){
    debug.WriteLine(“Dispaching with BeginInvoke”);
    //PageDownInList();
    var pageDownDelegate: VoidDelegate = PageDownInList;
    content.Dispatcher.BeginInvoke(DispatcherPriority.Background, pageDownDelegate);
    return;
    }

    }
    catch(ex)
    {
    debug.WriteLine(“Exception in init”);
    debug.WriteLine(ex.ToString());
    }
    }

    private function PageDownInList(){

    debug.WriteLine(“Before page down”);
    isPaging = true;
    controller.PageDown();
    }

    private function LogError(message: String, exception: Exception){

    debug.WriteLine(message);
    if(exception!=null){
    logger.Error(message, exception);

    }else{
    logger.Error(message);
    }

    }

    public function UpdateRows()
    {

    var items = controller.RenderEngine.ListControl.ListView.Items;
    var delta = items.Count – currentRowCount;
    debug.WriteLine(“Delta “+ delta);

    var hasMoreRows = (delta == 33);

    if(hasMoreRows)
    {

    debug.WriteLine(“Loading more list rows”);
    currentRowCount = items.Count;
    debug.WriteLine(“currentRowCount “+ currentRowCount);

    isPaging = true;
    controller.PageDown();
    return;
    }
    else
    {
    debug.WriteLine(“No more rows to load”);
    }

    debug.WriteLine(“Updating items” );

    }

    public function OnRequested(sender: Object, e: RequestEventArgs){

    debug.WriteLine(“Event triggered “+e.CommandType);
    try
    {

    if(e.CommandType == “KEY” && e.CommandValue == “ENTER”)
    {
    debug.WriteLine(“Enter Event triggered “+e.CommandType);

    var items = controller.RenderEngine.ListControl.ListView.Items;
    currentRowCount = items.Count;
    debug.WriteLine(“currentRowCount in init “+ currentRowCount);

    if(currentRowCount == 33){
    debug.WriteLine(“Dispaching with BeginInvoke”);

    var pageDownDelegate: VoidDelegate = PageDownInList;
    content.Dispatcher.BeginInvoke(DispatcherPriority.Background, pageDownDelegate);

    controller.PageDown();
    return;
    }
    }

    debug.WriteLine(“Page down onrequested “+e.CommandType);
    if(e.CommandType == “PAGE” && e.CommandValue == “DOWN”)
    {

    debug.WriteLine(“Page down request”);
    return;
    }

    debug.WriteLine(“Request executed, removing event handlers.”);
    controller.remove_Requested(OnRequested);
    controller.remove_RequestCompleted(OnRequestCompleted);
    }
    catch(ex)
    {
    debug.WriteLine(ex);
    }
    }

    public function OnRequestCompleted(sender: Object, e: RequestEventArgs)
    {
    debug.WriteLine(“Event triggered “+e.CommandType);
    try
    {
    if(e.CommandType == “PAGE” && e.CommandValue == “DOWN”)
    {
    // Page down complete, try to update list rows again.
    UpdateRows();
    }
    }
    catch(ex)
    {
    debug.WriteLine(ex);
    }
    }
    }
    }

    Regards,
    DCJ

    1. karinpb Post author

      Please be a bit more specific. What is the scenario and goal? Automatically page down until all rows are loaded? What is the log output?

Comments are closed.