Adding a new column in a M3 List

Every now and then we do get questions about manipulating the M3 List. It is possible to add a column using JScript but there is no supported API for the different manipulations. But that does not stop the creative people out there. Now I would like to give you a heads up becuase the list implementation has changed slightly in Lawson Smart Office 10.

You can no longer access the Items property on the listview directly.

If you get the following error in your script: “Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead”, you need to use the ItemsSource on the listview instead.

We hope to introduce a supported API in coming version but until then this is the new way to do it in LSO 10. Note however that implementations details might change and you can only expect support for the APIs documented in the JScript Developers guide for M3.

Below is what the added column, “My Column”, will look like in CRS610.

Below is a JScript that will add a column header and a few rows.

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

package MForms.JScript {
   class AddColumnSimple {
      var listView;

      public function Init(element: Object, args: Object, controller : Object, debug : Object) {
         debug.WriteLine("Script Initializing.");
         if(element != null) {
            debug.WriteLine("Connected element: " + element.Name);
         }
         try {
            listView = controller.RenderEngine.ListViewControl;
            var listControl = controller.RenderEngine.ListControl;
            var columns = listView.View.Columns; // System.Windows.Controls.GridViewColumnCollection

            // Add header
            var columnHeader = new GridViewColumnHeader();
            columnHeader.Content = "My Column";
            var column = new GridViewColumn();
            column.Header = columnHeader;
            column.CellTemplateSelector = new ListCellTemplateSelector(columns.Count, listControl.Columns);
            columns.Add(column);

            var rows:IList;
            if (listView.ItemsSource != null){
                debug.WriteLine("New version of ListView");
                rows = IList(listView.ItemsSource); // Use the ItemsSource to get data
            }else{
                debug.WriteLine("Old version of ListView");
                rows=listView.Items; //Get the items
            }

            var columnCount=columns.Count;

            for (var i = 0; i < rows.Count; i++) {
               var row = rows[i];
               var newItems = new String[columnCount];
               row.Items.CopyTo(newItems,0);
               row.Items=newItems;
               // Replace row
               rows.RemoveAt(i);
               rows.Insert(i,row);
            }

            // The new placeholder is in the list - add some data
            var index=columns.Count-1;

            // Replace with a for loop to loop all rows
            rows[0].Items[index] = "Extra data row 1";
            rows[1].Items[index] = "Data row 2";

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

The code is very basic and should be considered as a starting point.

Thibaud has a more detailed post in his blog.
Be sure to read the comments since there are issues on scrolling and threading that you should consider.

A general note on any “Get data per list row” scenario is that it is not a good idea. Those kind of modifications should be done in the M3 program.
On the other hand, if you already have access to the data and the new column data is calculated from already existing columns, then you have an excellent scenario for this kind of trick!

67 thoughts on “Adding a new column in a M3 List

  1. potatoit

    Hi Karin,

    great post, if Smart Office is now using the ItemsSource now does that mean the behaviour of retrieving the records has changed at all? Or does it use an ObservableCollection and just add the extra items to that and then the ListView automatically populates?
    If it uses an ObservableCollection will that be exposed (and documented) to let us subscribe to it? I could see that being very useful. 🙂

    if you’re looking at introducing APIs around the ListViews, it would be really nice if the TextBoxes in the ColumnHeaders were exposed as properties – along with a method which would apply the filter.
    Eg. the “Item Number” TextBox in the MMS001/B1 ListView

    Cheers,
    Scott

  2. norpe

    The TextBoxes for the position fields in the column headers are already exposed by the PositionFields property of the ListControl class.

    To apply a filter from JScript you would set the values in the TextBoxes and then apply the filters using:
    controller.PressKey(MNEProtocol.KeyEnter);

    1. Scott

      Well I’ll be…

      Thank you, that certainly makes them easier to work with 🙂

      Cheers,
      Scott

  3. David

    Is it possible to add a column to an S3 form (such as PO20.1) using this technique?
    As a broader question on S3 and scripting, how do you attach a script to a list view of a form?
    OR do you attach the script to the form view?

    I have watched all of the scripting videos and read the Developers Guide but don’t see how to attach scripts to a list view.

    1. karinpb Post author

      Hi David,
      I checked with a collegue and this is his reply.

      “Adding a column to an S3 List is only done from the Choose Columns dialog. To open, tight-click on a column header and select “More” or from the menus select Tools -> Personalization -> Choose Columns.

      Scripting in S3 can only be attached to the standard forms view and not the list view.”

      Regards
      Karin

  4. Jean Monchery

    Thanks Karin,
    That really helped me out. I have successfully added a new column, but I am not able to display my updated rows as I scroll up.

    1. karinpb Post author

      Hi Jean,
      Do you mean scroll up or scroll down? Like PageDown or PageUp.
      My guess is that you mean page down. As you page down new rows are retrieved from the backend and if you only add rows when the form is loaded you will only have the initial 33 (or 66) so you need to add your column data for those new rows as well.

      This blog might help you get started .

  5. Mikael H

    Hi. You said “A general note on any “Get data per list row” scenario is that it is not a good idea”. What I understand you could get bad performance, correct ?
    It is a pity because this is a really good example how to show the good aspects of LSO-scripts instead of M3-modifications for a customer. Is it possible for you to explain the downsides a bit more and how we could avoid the performance problems with for example adding a new column based on MI-calls (MDBREADMI) ? Thanks a lot /mikael

    1. norpe

      The best solution for this is for the BE program to return the data. If the program is using View lists it might be possible to add the column in the View. For static lists I guess the program has to be modified.

      The reason we say it is not a good idea is that there are a lot of potential issues with doing this both on the client and on the server. If each program start results in 33 additional MI-transaction calls that could be an issue with a lot of users. If the users than starts to page down quickly in the list this will generate hundreds of MI-calls, for each user.

      It is also very difficult to make a correct implementation of this behavior. The script should stop making calls if the user navigates to another panel, perhaps it should not load additional data for list row that are not visible yet etc. Covering all possibilities and error handling is complicated for this scenario.

      We’re not saying that you should never do this, but if you do you should be aware of the consequences, test with more than one user a a time, make sure there are no performance issues and make sure that the script implementation is solid.

  6. Pingback: How to add a column to a list (continued) « M3 ideas

  7. Amanda & TOve

    Hi
    We have just found your blog, great!
    We have a problem in a Mashup we are trying to build.
    We have one listpanel CRS610 and one listpanel MMS002, we wan’t to use information from this panels to trigger a new listpanel (OIS320)
    Customer from CRS610 and Item from MMS002. However we can’t figure out how to set up the events, when we choose cuurentItemChanged in one list this overwrites the value from the other. (either custoerm or item is blank) is there a way to work past this issue? thanks in advance!

    1. karinpb Post author

      Hi,
      The solution is to bind the customer and item from the respective list using the CurrentItem property on the ListPanel. You can read about the property in the API documentation found under the help menu. The reason the value is empty now is because it’s not available in the panel. You need to add cuno / itno with the value set using a Binding.

      In that case you will have both values when both lists have a selection.

      If you only want to load OIS320 when both customer and item is selected you can add a button for triggering OIS320, or try adding conditions in the event.

      I can try and create an example tomorrow…

  8. Gaston

    Karin,

    I did apply most of the same code to add a new column to the OIS320/B List, the value for this new column is a calculation from another column within the same list. My script was successfully tested and then released into Production. However, one user in Production does not see the new column in the list as soon it opens OIS320/B, the new column only appears for this user after pressing Action/F8-Scroll Forward. In my case, as soo as I open OIS320 I can see the new column and all works fine. Do you know if perhaps a setting is missing for this user or if it is script related?

    Thank you,
    Gaston

    1. karinpb

      Hi,
      First of all, is this in a list och detail screen? What is your scenario? Is it to show a checkbox instead of 1/0 in a list? Or something else?

  9. Jean Monchery

    Hi Karin,
    While trying to copy a B Panel list view
    (var newItems = new String[row.Items.length];
    row.Items.CopyTo(newItems, 0) ) I got an error because the B Panel I was copying had editable cells. How can I add editable cells as my custom column?

    1. karinpb Post author

      Hi!
      What error message did you get exactly? Are you in a version where you cannot use ItemsSource instead? Becuase if you manipulate Items and are getting an error I’m suspecting that you get a framework element which is attached to the visual tree so you can not move it around without hitting the issue that it already has a parent. I don’t know how to fix this on the top of my head. Set parent to null? What’s the program? And I assume you don’t want to add an editable cell, but some kind of view only value? (calculated I hope)

  10. Jean Monchery

    The version of LSO that I’m currently using is 10.0.4.1.39, and I am using the ItemSource to get the data. the error I am getting is “Error: At least one element in the source array could not be cast down to the destination array type”. The program I am working with is MWS420/B1, I want to be able to manipulate the editable cells.

    1. norpe

      In editable lists the row will contain EditableCell (Mango.UI.Services.Lists.EditableCell) objects instead of strings for the cells that can be edited. If you just need to get a cell value you should always use a utility method called GetCellText in the MFormsUtil class.

      Example: Gets the text for the first cell
      var text = MForms.MFormsUtil.GetCellText(listRow, 0);

      If you need to set values the EditableCell object has a Text property.

      1. Nicholas

        Hello,

        In PMS230, there is a schedule number field that uses what I think is an editable cell. I want to use jscript to make it NOT editable. What line of code could I use to disable editting on that field?

  11. Gaston

    Hi Karin,
    When adding a new column to a list like OIS101B, I always encounter the case where my new column header shows multiple times as a result of navigating away from the panel. How could I freeze or ontrol my column header to appear only once in the view?

    Thank you,
    Gaston

    1. norpe

      The list controls are cached in many cases so the script must make sure that it does not add the same column more than once. If you navigate from B to E and then back the list control will in most cases be the same. Doing a refresh (F5) on a B-panel or changing sorting order / view will create a new list and so on. To handle all these cases the script needs to check if the column already exists.

      1. Gaston

        Hi norpe,
        I am now checking my columns in order to show the new column one time when navigating to another pannel and back so it is not showing mutiple times.
        In all of the examples I have seen, the new column always get added to the end of the list view, is it feasible to insert the new column in between existing ones?

        Thank you for your help,
        Gaston

      2. norpe

        It should be possible to add a new column between existing columns. The existing columns are already bound to the data using the original column index. Just make sure to test the solution so that it does not break any framework functionality such as show/hide columns in a static list etc.

  12. FLC

    I’m trying to create some kind of clickable shortcut or link in my new column. Is it possible to add an “OnClick” event for every row in this column that runs some code? I have also experimented with adding buttons instead of plain text to the column but without success. Do you have any other suggestions for how to solve this? (I don’t want the whole row to be clickable, just the content of this column. The content can be an email address or an invoice number for example and the code might call a ListOption etc.)

    1. norpe

      It should be possible by creating a template in code and using AddHandler to get the click events. I’ll try to get time to write a post about how to do that in more detail.

  13. Jean

    On the previous version of LSO (version 9.0) this code segment use to work
    var border = VisualTreeHelper.GetChild(listView, 0);
    var grid = VisualTreeHelper.GetChild(border, 0);
    this.scrollViewer = VisualTreeHelper.GetChild(grid, 3);
    this.scrollViewer.add_ScrollChanged(OnScrollChanged);

    but ever since the upgrade I’ve been receiving this error:

    System.ArgumentOutOfRangeException: Specified index is out of range or child at index is null. Do not call this method if VisualChildrenCount returns zero, indicating that the Visual has no children.
    Parameter name: index
    Actual value was 3.
    at System.Windows.FrameworkElement.GetVisualChild(Int32 index)
    at System.Windows.Media.Visual.InternalGet2DOr3DVisualChild(Int32 index)
    at System.Windows.Media.VisualTreeHelper.GetChild(DependencyObject reference, Int32 childIndex)

    is there a new way to access the scrollViewer with the new version of LSO?

    1. karinpb Post author

      Hi,
      There is always a chance that the visual tree might change between versions. When we did the new style a lot has changed. You can never rely on the visual tree like that.

      You can make the code more robust by using one of the method that looks for a child of a specific type, like ScrollViewer instead. I’ll check the exact method name tomorrow.

    2. norpe

      Jscript code should never expect elements in a control template to be on a specific index. This is an implementation detail that might change between versions. To find parts of a control such as the ScrollViewer the script should either traverse the visual tree or use existing helper methods for finding the element.

      JScript Example using a method in the framework Helpers class:

      import System;
      import System.Windows;
      import System.Windows.Controls;
      import MForms;
      import Mango.UI.Utils;

      package MForms.JScript {
      class FindChildTest {

      var typeScrollViewer : Type = Type.GetType("System.Windows.Controls.ScrollViewer, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");

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

      var listControl = controller.RenderEngine.ListControl;
      if (listControl == null) {
      debug.WriteLine("No list found");
      return;
      }

      var scrollViewer = Helpers.FindElementOfType(listControl.ListView, typeScrollViewer);
      if (scrollViewer != null) {
      debug.WriteLine("Found ScrollViewer");
      // TODO ...
      } else {
      debug.WriteLine("No ScrollViewer found");
      }
      }
      }
      }

  14. Priyantha

    How can I read only m3 column . program name is m3 supplier invoice and column name is Inv Qty . please help me.

    1. karinpb Post author

      Hi,
      I’m afraid we can’t help with specific scripts but we can help you with tips. Start by finding out the field names of those fields, the read this postfor tips on how to get the value.
      If that info is not enough try and investigate the visual tree with Snoop UI. If you still have issues let me know the name of the fields. I think we have had a question on read only fields before. Have you searched the blog?

  15. Lasse

    Hi
    I have created a new column and new data is populating the new column.
    Now to the issue, how do I make the data in the new column to be a click-able URL?
    //Lasse

  16. Pingback: Hyperlink columns in M3 lists | Developing for Infor Smart Office

  17. Jean Monchery

    I want to know if there’s a way to get and set the color of text in the browse list through a J Script instead of using personalize.

    1. norpe

      If you mean the list in the F4 Browse dialog there is no supported way of changing the colors in that list.

      In the normal lists you can use custom cell templates to change color using JScript. I’ll show how to do this in a future blog post.

  18. Sam N

    Hi Guys,
    Feels good to be writing you again. You have inserted text values (String) in the new columns(new fields in the new column). How can I insert a new column full of check boxes instead of Strings.

    Thanks.
    Sam

  19. ZaherElShamy

    thanks alot ,, i want to Remove an existing column ,,, for example if i want to Hide the first Column ,,i used that Code in Script DLL
    IList columns = (IList)listControl.Columns;
    columns.Remove(columns[0]);
    —–
    actually the number of Columns is decreased ,, but on the View the Column STill exists ,,, i found using Google the Property AutoGenerateColumnsProperty ,, but i don’t know where i can find it . i think i need to set it to false before removing the required column.
    regards

    1. karinpb Post author

      Hi,
      The MForms list is generated in code and it is not a DataGrid so AutoGenerateColumnsProperty does not apply.
      If it is a GridView you should be able to set the width to 0 on the GridViewColumnHeader. Removing it is not the trick.

  20. Jean Monchery

    I have a Jscript that does some calculations on a list view but I’m noticing that going back to the browse after I’ve double clicked the item the script is executed twice on all items except the item I previously selected. Is there a way to stop this from happening?

    1. karinpb Post author

      If you mean back to the browse as back to the list you must unattach your events. Going back will load the script again and you will attach the eventhandler again. Failing to detach the eventhandles will lead to memory issues.

      1. Jean Monchery

        I did detach my events by using scrollViewer.remove_ScrollChanged(OnScrollChanged). and
        controller.remove_RequestCompleted(OnRequestCompleted) but it’s still re-executing the script, is there another or better way to do it?

      2. karinpb Post author

        Hi, No controller.remove_RequestCompleted(OnRequestCompleted) is the right way to do it. Can you share more of your script? What version are you running? Why are you checking the Scroll on the list?

  21. Jean Monchery

    I’ll be glad to give some more info. Im using version 10.2.0.049, I have an event to do the calculation as the user scrolls.
    var typeScrollViewer : Type = Type.GetType(“System.Windows.Controls.ScrollViewer, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35”);
    this.scrollViewer = Helpers.FindElementOfType(listControl.ListView, typeScrollViewer);
    if (scrollViewer != null) {
    this.scrollViewer.add_ScrollChanged(OnScrollChanged);
    }
    controller.add_RequestCompleted(OnRequestCompleted);
    //This is the code inside OnRequestCompleted( minus the try and catch)

    var controller : MForms.InstanceController = sender;
    if (controller.RenderEngine == null) {
    var controller;
    content = null;
    debug = null;
    rows = null;
    columns = null;
    oldCount = 0,
    newCount = 0;
    listControl = null;
    columnIndex = null;
    // program is closing, cleanup
    //remove any objects
    scrollViewer.remove_ScrollChanged(OnScrollChanged);
    controller.remove_RequestCompleted(OnRequestCompleted);
    }

    1. atawila0

      I added a column that adds/subtracts two values from the grid but, it calculates only 33 rows and then as you scroll down it does not calculate the values from the other rows. Anybody can help me with this?

      1. karinpb Post author

        Hi, You need to subscribe to requested and requestCompleted event so that you can re-calculate the new rows as more rows are loaded. But you have to be careful and unload the script and eventhandlers as soon at the user navigates to another panel. You also need to keep track of / check if you have already calculated a row and test with positioning etc as well. This post should help you get started.

  22. Pingback: Adding Columns to a ListView – when there are Editable Cells | Potato IT

  23. Ashraf A.Elfattah

    The script works fine except that when i press apply btn “which already exist on Smartoffice screen not added by the script” and bound to “ENTER” action, it creates another col every time i press that btn.
    How should i avoid replicating the col?
    Regrads

    1. karinpb Post author

      Hi,
      Have you read and applied the tricks in the other comments?
      The blog post is just a simple example to get you started and it is not a complete solution. In you case the init is called after any apply or refresh (F5) or whatever. You need to know exactly when the column should be added. By checking the count or by using the instance cache and store information that you have already added row etc.

      Consider that any call to the server might recreate the entire panel. It is crutial that the script is de-tached correctly. Below is an example script – that is not correct – that will show how the requests are made. To play around with it you need to set the LocalScriptPath and run it locally – it is not the same to run it from the script tool.

      When you add another column you must test the following: pressing F5, selecting different filters/views, selecting a list in the row and going back, scrolling down with mouse and pressing page down etc.

      Good luck and make sure to read anything that you can find in blogs on this topic.

      import System;
      import System.Windows;
      import System.Windows.Controls;
      import Mango.Core.Util;
      import MForms;
      
      package MForms.JScript {
         class ApplyButtonTest {
         var logger: log4net.ILog = Mango.Core.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType)
         var controller, debug, content;
            public function Init(element: Object, args: Object, controller : Object, debug : Object) {
               LogDebug("Script Initializing.");
              
               if(element != null) {
                  LogDebug("Connected element: " + element.Name);
               }
      
                this.controller = controller;
                this.debug = debug;
                this.content = controller.RenderEngine.Content;
                
                // Attach event handlers to be able to detach event handlers and list to page down requests.
                controller.add_Requested(OnRequested);
                controller.add_RequestCompleted(OnRequestCompleted);
            }
            
             public function OnRequested(sender: Object, e: RequestEventArgs)
            {
               LogDebug("OnRequested "+e.CommandType + "Value. "+e.CommandValue);
               // TODO check condition when not to detach event handleds
               // Then always detach for anything else
            }
       
             public function OnRequestCompleted(sender: Object, e: RequestEventArgs)
            {
              LogDebug("OnRequestCompleted "+e.CommandType+ " "+ e.CommandValue);
            }
            
             private function LogDebug(message: String){
            if(message!= null){
               logger.Debug(message);
               if(debug){
                 debug.WriteLine(message);
               }
             }
            }
      
         }
      }
      

      If you have a Smart Office version that has the log viewer widget make sure you have it and that debug is enabled both in user settings and in the settings on the widget. Then check the different requests and each time there is an init there is another copy of you script running and you need to detach events so that only one instance of the script is running.

  24. Heiko

    Hi,

    I have a JScript which changes single values in the list at runtime. I don’t use the approach to replace all listrows, because this breaks the scroll action of a user who is using the keyboard to scroll down. Instead, I change the value in the ContentPresenter. This doesn’t influence keyboard scrolling.

    But when the user wants to export selected rows to Excel, I need to change the item source at runtime; therefore I added a handler to the “Export to Excel” menu and my routine changes the selected listrows, before the “Export to Excel” dialog appears (There is a special handling if only one or zero rows are selected, indicating that the user may want to export all rows). Of course replacing the listitems removes the selection state of all affected rows; therefore the app collects the selected items before they are changed and after that selects them again.

    This works very fine in a lot of programs such as MMS001/002/003, PPS201, DPS170, etc etc.

    But in other programs such as MMS060 or MWS060, my programmatic selection is removed in the moment when the “Export to Excel” dialog is displayed, and instead the next listrow below the last listrow of my selection becomes the selected row. Any idea about the reason why this programs react different and how to avoid this ?

    Furthermore I noticed that, under the same conditions, all my variables got lost (listRows, listView, column information, instance variables); I had to re-initialize them, instance variables could only be transfered by writing and retrieving them from the instance cache. Any idea ? Again, it works without problems in most programs, only i.e. MMWS060/MWS060 are causing problems.

    1. Heiko

      Shame on me 😦 I made a mistake: I realized, that I had to re-initialize listRows and listView in my handler subroutine in order to catch up a new sorting order, but defined new local variables in the subroutine and then assigned the index on the original instance variable. Using the instance variable in the subroutine too solved the problem.

  25. Dirk

    I want to add a column to a list which is retrieved through API using MIWorker. The problem is now that the “on response” function is running separately from my loop through all rows. How can built in wait so it waits until it gets a response before continuing with the next row?

    1. karinpb Post author

      Hi Dirk,
      I assume “on response” is the callback from the MIWorker. You store the index you are in as a varible and create the next MIWorker in the “on response” method. But I would only do it like that if the rows somehow have a dependency on eachother. It will take much longer time to complete the API calls in a serial fashion and having a call per line is generally not recommended. So what is the scenario more exactly? I think the MIRequest has a tag property that you can set to any data and then you can pick up that data in the reply. That way you wouldn’t store a single variable to keep track of the row you would pass it via the request and then that information is in the response as well. At least I think it was designed to allow passing on data like that. Check the API documentation for MIResponse and MIRequest to see if there is a field that you can pass data in so that you can keep track of the state.

  26. Dirk

    Hi Karin, can you indicate where I can find exactly on the intranet API documentation about MIRequest (SDK)?

    1. karinpb Post author

      It is included in the Smart Office SDK download available if you have access to download that product. We have an alternative SDK download location in the Infor’s intranet. I’ll email you the link.

  27. Farhat

    Hi,

    I have a requirement. I need to create a list in PPS300/E which display data from MITLOC table on selected item number and warehouse in PPS300.

    How i can implement it.

    Give any suggestion.
    I tried it by javascript but not succeeded as lack of knowledge about JScript, please suggest some examples.

    Regards
    Farhat Khan

  28. Doms Molina

    Hi,

    I have a requirement wherein I need to load images that comes from IDM. Can we load images onto the new column we just created? We are also looking into the possibility of doing this via mashup. They don’t want it only to show when an item is selected from list. They want every corresponding image to load together with the list. Is this possible?

    1. karinpb Post author

      Yes it is possible, as long as you can query IDM with the data needed to get the image. But I don’t have an example for it. Not sure what you mean via a mashup but it is possible with JScript and you can run a JScript in a list in a Mashup.

      1. karinpb Post author

        What size do you expect the image to be as I would guess the image will not be viewable as the height is very low. This is probably why the user cases are usually to add a link to the image if there is one or to have a bigger preview area that you load for the selected row only.

      1. Doms Molina

        A bit yeah, but if we can make it small then just add a tool tip for the zoomed version it’s also fine 🙂

  29. Lokesh

    Hi Karlin,
    While I adding a new column only column header is adding. If I run my script again the new column is adding to the Previous heading column. Can I know where I done the mistake

Comments are closed.