Extending Modules with Dojo Toolkit

This article describes the basic building blocks and steps to extend a client-side control in Infor CRM 8.3.x, using the various modules/methods of the Dojo Toolkit.

If you’ve worked with Infor CRM in the past (formerly SalesLogix), you’ll be familiar with all of the product transitions, jumps, and hurdles, all shrouded in an undocumented behemoth of a project.

In this article, we will investigate and implement the requirements for a list/detail view, while revealing some of the inner-workings of the CRM system itself.

The default implementation of the Notes/History view is comprised of a GridView control spanning the entire workspace. Our eventual result will look like this:

The split-view we’re aiming to create

First Steps

Upon inspecting the compiled controls on the DOM, it is somewhat apparent that their naming convention and client-facing Javascript has been added programmatically through some sort of variable substitution:

Generated names

Although that may sound vague, what we’re seeing here is a resulting declarative approach to dijit widget creation, so bear with me as we dive deeper into the dojo toolkit.

Where does this convoluted naming scheme come from and how do we access it? Under the webroot directory of our project (SupportFiles/SmartParts/History), there is an ASP control file with a C# supporting code file, both titled “NotesHistoryList.ascx” and “NotesHistoryList.ascx.cs”. The only markup within the control file is a placeholder, where we can assume the Javascript control will reside:

placeholder for our dojo widget

Moving over to the C# code file, the Page_Load method provides insight into how this is built:

NotesHistoryList.ascx.cs

There is a lot going on here, but essentially what we have is a string representation of what will be the Javascript that gets invoked at startup. The telling parts are the instantiation of a new NotesHistoryList. For readability sake, let’s temporarily extract all of what makes up this string into its own JS file:

For readability, our Javascript extracted from our C# file

Now that we have something readable, breaking it down into it’s concatenated pieces, we can describe the following:

  • The AMD loader is providing our dependencies asynchronously (dojo/ready, Sage/UI/NotesHistoryList) for the subsequent function declaration
  • The callback function itself, passing in ‘ready’ and ‘NotesHistoryList’ objects as our parameters
  • The bulk of the function, starting with “window.setTimeout”
  • And the substituted parameters (getMyWorkspace(), ID, etc…) which make up the convoluted naming convention we first saw using debug tools (…more on this later)

Essentially, the NotesHistoryList will display a subset of History data from a provided data source, where the parent relationship name is equal to another entity, in this case, an Account. This is apparent from the above configuration parameters in the provided argument for new NotesHistoryList({…config})

The next step towards the eventual goal of attaining a split-screen view is to listen for clicks in order to capture the data from each row of the grid control. We need to dig further into the definition of the NotesHistoryList module.

Finding an attach point for our Event

Before going any further, it’s important to mention that the NotesHistoryList module, like its parent module(s), is constructed using Dojo toolkit methods:

Module dependencies

The Sage/UI/NotesHistoryList is declared as a module using the define AMD loader method, followed by an array of module dependencies.

callback function

And a callback function, which will return an object (module)

Within this callback function body, we declare our locally scoped variables, helper methods, etc… In this case, we have a variable notesHistoryList which is defined by using the declare method:

notesHistoryList property
  • The first argument is the widget name (Sage.UI.NotesHistoryList)
  • The second argument (_Widget) is the object whose prototype will be used as a base for the new class (note the array syntax, as we can have multiple inheritances)
  • Lastly, an object which contains all the properties and methods

** Because Javascript (at the time of writing these modules) didn’t have a class system, Dojo provides an inheritance pattern with declare. This is essentially prototypal inheritance, allowing our child-class to share it’s parents’ prototype **

For example, in our notesHistoryList module above, the destroy property has a function value that closes any connections before running this.inherited(arguments). This is essentially extending the functionality of the parent, calling the parent (_Widget) method of the same name (destroy), passing the same parameters:

extending functionality of modules

With these pieces, we can begin to understand how the notesHistoryList module is built. Recall our initial C# Page_Load method, where we used a StringBuilder to instantiate a new NotesHistoryList():

  • Our instance of the NotesHistoryList module is assigned to “a”
  • We invoke a.startup() to initialize our grid (connections, columns, any provided options)

The key to adding an event listener resides within the startup property method of notesHistoryList, specifically after we inherit the parent methods of the parent GridView:

startup();
  • The parent GridView module is another Sage/UI module of another parent module, Grid
  • The Grid module ultimately extends from the dojo toolkit dgrid
  • As previously mentioned, dojo provides this inheritance pattern which allows modules to share and extend their parents’ prototype- exactly what is happening here.
  • Understanding this pattern reveals where related methods, such as onRowDblClick may reside. If we look at the last declaration above, we can see that grid.grid.onRowDblClick is a deeply nested reference to the base Grid class:
  • - — NotesHistoryList initializes a GridView
  • - — grid.createGridView() initializes a Grid
  • - — grid.grid is in reference to the Grid module, which directly extends dojo’s dgrid.

Therefore, if we open and search through the Sage.UI.Controls.Grid module, we will undoubtedly find the declaration of onRowDblClick, as well as other stub methods indicating the ability to override:

Sage.UI.Controls.Grid

Implementing onRowClick functionality

The default _onRowClick functionality does some initial row-expansion, followed by the invocation of our soon to be implemented stub method. The remaining question is: Where do we declare the event?

  1. The way it appears the dojo toolkit recommends is to “extend” a module similarly to how NotesHistoryList is built, though this seems like a lot of extra boilerplate for one additional property.
  2. Another way would be to use dojo/aspect to attach the desired functionality. As the documentation states:

“The dojo/aspect module provides aspect-oriented programming facilities to attach additional functionality to existing methods.”

We could use an aspect method such as aspect.after(GridView.prototype, ‘setOnClick’, function(fn){if(this.id == <gridName> inject some code)}

3. The third and easiest way is to add our desired functionality inside of our C# code-behind file (NotesHistoryList.ascx.cs), directly after we instantiate our NotesHistoryList and invoke startup():

NotesHistoryList.ascx.cs
  • We’re simply getting a reference to the parent method from our child instance
  • This is already registered as a startup script for our NotesHistoryList ASP control, so we don’t have to provide it elsewhere or search for the instance of our grid, conditionally.

The last step is to display the contents of the LongNotes into our detail textarea. On the NotesHistoryList.ascx control we add an additional textarea column:

NotesHistoryList.ascx

Finally, as seen in the startup script above, we get a reference to the DOM control using a JQuery selector $(‘#historyDetailTextArea’) and set its value to the clicked row’s data.

This concludes the modifications required for a custom split-view. I hope the scope of this article has helped with any development endeavors directly or related.

Web Developer