Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Plugins authors should adhere to the following guidelines when developing plugins.

Name

The plugin name should be concise and convey some meaning as to what the purpose of the plugin is.

...

Don’t Edit Standard Plugins

Plugins which Jiwa ships with can be updated whenever the software is upgraded, which will undo any modifications made.

Instead, Copy the plugin, provide a meaningful name and disable the original plugin.

You should also change the Author name to your own name when you copy a plugin.

Use XML Export / Import

Use the XML Export and XML Import function of the Utilities tab of the Plugin Maintenance form to transport plugins.

Name

The plugin name should be concise and convey some meaning as to what the purpose of the plugin is.

If you find it difficult to name because the plugin functions are so varied then you should consider splitting the plugin into several plugins.

Ideally, plugins should perform their own discrete functions specific to a purpose.

...

Description

Place a meaningful description in the plugin description.

The description should explain the following:

Purpose of the plugin

A brief summary of what the plugin does. Even if there is an external link to a documentation page, the plugin description should still offer a summary of purpose.

System Settings

If the plugin defines any System Settings, the plugin description should at the very least alert the reader to this fact. The System Setting descriptions can explain each setting, but the plugin description should mention that there are System Settings so the reader can know to examine and set them.

Custom Fields

If the plugin defines any Custom Fields, the plugin description should at the very least alert the reader to this fact. The Custom Field descriptions can explain each setting, but the plugin description should mention that there are Custom Fields so the reader can know to examine and set them.

Deployment Instructions

If there are deployment steps beyond just importing the plugin, then these should be listed in numbered order.

Author

The Author should identify the organisation or individual who authored the plugin.

...

Schedules are not Enabled by default when added, so be sure to Enable the schedule.

Code

...

Notes

The notes tab can be used for any purpose - deployment instructions and change history are commonly used.

...

Documents

Often used to store SQL Scripts for deployment, but can be any binary file store.

Code

General coding guidelines

Be Tidy

  • Delete any classes automatically created that you are not usingused

  • Delete any using (or Imports in VB) statements you that are not using

It is important to understand that some plugin classes are created and instantiated at logon time, and the same instance is used to invoke the methods within the class.

These classes are those implementing these interfaces:

  • IJiwaFormPlugin

  • IJiwaBusinessLogicPlugin

  • IJiwaApplicationManagerPlugin

  • IJiwaCustomFieldPlugin

  • IJiwaLineCustomFieldPlugin

  • IJiwaSystemSettingPlugin

  • IJiwaScheduledExecutionPlugin

So, as an example, consider the following code:

...

languagec#

...

  • necessary

  • Don’t comment out code, delete it

  • Align code blocks consistently

  • Don’t leave multiple blank lines - there is no reason to have two or more consecutive blank lines.

  • Group like things together - such as event handlers together

  • Use regions to group related classes, methods or properties together

Be Consistent

  • Use consistent case conventions - ours have evolved to:

    • PascalCase for Class, Property and Method names

    • camelCase for local variables or anything not publicly accessible

  • Use consistent plural / singular

    • Tables and classes are singular - eg: SalesOrder not SalesOrders

    • Lists and Collections are plural - eg: SalesOrders

Be Clear

  • Comment what the code is doing, but sensibly.

  • Don’t use overly terse names - invemadd is not acceptable, invoiceEmailAddress is.

Understanding Plugin Classes

It is important to understand that some plugin classes are created and instantiated at logon time, and the same instance is used to invoke the methods within the class.

These classes are those implementing these interfaces:

  • IJiwaFormPlugin

  • IJiwaBusinessLogicPlugin

  • IJiwaApplicationManagerPlugin

  • IJiwaCustomFieldPlugin

  • IJiwaLineCustomFieldPlugin

  • IJiwaSystemSettingPlugin

  • IJiwaScheduledExecutionPlugin

So, as an example, consider the following code:

Code Block
languagec#
public class FormPlugin : System.MarshalByRefObject, JiwaFinancials.Jiwa.JiwaApplication.IJiwaFormPlugin
{
	public int Counter = 0;
	
    public override object InitializeLifetimeService()
    {
        // returning null here will prevent the lease manager
        // from deleting the Object.
        return null;
    }

    public void SetupBeforeHandlers(JiwaFinancials.Jiwa.JiwaApplication.IJiwaForm JiwaForm, JiwaFinancials.Jiwa.JiwaApplication.Plugin.Plugin Plugin)
    {
		Counter++;
    }

    public void Setup(JiwaFinancials.Jiwa.JiwaApplication.IJiwaForm JiwaForm, JiwaFinancials.Jiwa.JiwaApplication.Plugin.Plugin Plugin)
    {
		System.Windows.Forms.MessageBox.Show(Counter.ToString());
    }
}

...

Each time the sales order form or quote form is loaded, a messagebox displays an incrementing number - illustrating that the variable Counter and hence the class is not created when the form is, but the same class instance is invoked when forms are loaded.

FormPlugin class

The FormPlugin class implements the IJiwaFormPlugin interface - it contains two methods of interest - SetupBeforeHandlers and Setup.

SetupBeforeHandlers

When a form is created via the FormFactory (as all forms in Jiwa are), if the form is registered on the Forms tab of the plugin, then the SetupBeforeHandlers method of the FormPlugin class is invoked after the form is created, but before the form has added any event handlers for the control or business logic.

...

Code Block
languagec#
public void SetupBeforeHandlers(JiwaFinancials.Jiwa.JiwaApplication.IJiwaForm JiwaForm, JiwaFinancials.Jiwa.JiwaApplication.Plugin.Plugin Plugin)
{
  if (JiwaForm is JiwaFinancials.Jiwa.JiwaPurchaseOrdersUI.MainForm)
  {
		// the Purchase Order form
  }
  else
  {
		// some other form
  }		
}

Setup

The Setup method, like the SetupBeforeHandlers method, is only invoked if the form is registered on the Forms tab of the plugin, but the Setup method is invoked after the form has added its event handlers for controls and business logic.

Typically this is where custom controls are defined and added to the form, and where handlers to business logic or form control events are added.

BusinessLogicPlugin class

The BusinessLogicPlugin class implements the IJiwaBusinessLogicPlugin interface, and has only one method of interest - Setup.

...

The JiwaBusinessLogic parameter passed to the Setup method is the business logic that has been created - if you have registered multiple business logic objects on the Business Logic tab on the plugin then the type of the JiwaBusinessLogic parameter should be inspected to determine the code to execute.

...

Care must be taken if interacting with the User Interface within handlers of business logic objects added from the Setup method of the BusinessLogicPlugin class.

You must not assume there is a user to respond to or dismiss message boxes or dialogs - events may be raised from services such as the REST API which are using the business logic.

...

Note

Care must be taken if interacting with the User Interface within handlers of business logic objects added from the Setup method of the BusinessLogicPlugin class.

You must not assume there is a user to respond to or dismiss message boxes or dialogs - events may be raised from services such as the REST API which are using the business logic and have no user interface.

User interaction should be performed in the FormPlugin class, which is guaranteed a user interface - add the handlers to business logic events there, not in the BusinessLogicPlugin class.

ApplicationManagerPlugin class

This class has it’s Setup method invoked are part of the Logon process, after plugins have been compiled and loaded, and most logon operations completed.

The LoggedOn event is the last action of the logon process, and plugins can add a handler for that in the Setup method of the ApplicationManagerPlugin class:

Code Block
languagec#
public class ApplicationManagerPlugin : System.MarshalByRefObject, JiwaFinancials.Jiwa.JiwaApplication.IJiwaApplicationManagerPlugin
{
    public override object InitializeLifetimeService()
    {
        // returning null here will prevent the lease manager
        // from deleting the Object.
        return null;
    }

    public void Setup(JiwaFinancials.Jiwa.JiwaApplication.Plugin.Plugin Plugin)
    {
		Plugin.Manager.LoggedOn += delegate() 
			{ 
				// code to execute when logged on here
			};
    }
}

CustomFieldPlugin class

The CustomFieldPlugin class is used for the display and interaction with custom fields. It uses the following methods:

FormatCell

This is used to format the Spread grid cell of the custom field contents. Often used for setting combo-box items, for example:

Code Block
languagec#
public void FormatCell(JiwaFinancials.Jiwa.JiwaApplication.IJiwaBusinessLogic BusinessLogicHost, JiwaFinancials.Jiwa.JiwaApplication.Controls.JiwaGrid GridObject, JiwaFinancials.Jiwa.JiwaApplication.IJiwaForm FormObject, int Col, int Row, JiwaFinancials.Jiwa.JiwaApplication.IJiwaCustomFieldValues HostObject, JiwaFinancials.Jiwa.JiwaApplication.CustomFields.CustomField CustomField, JiwaFinancials.Jiwa.JiwaApplication.CustomFields.CustomFieldValue CustomFieldValue)
{
  // FormatCell handler for a combo custom field
  if (CustomField.PluginCustomField.Name == "MyCustomFieldName") 
  {
		var typeCell = new FarPoint.Win.Spread.CellType.ComboBoxCellType();			
		typeCell.Items = new string[] { "Option 1", "Option 2", "Option 3"};
		typeCell.ItemData = new string[] { "1", "2", "3"};
		typeCell.EditorValue = FarPoint.Win.Spread.CellType.EditorValue.ItemData;                  
		GridObject.ActiveSheet.Cells[Row, GridObject.ActiveSheet.GetColumnFromTag(null, "Contents").Index].CellType = typeCell;
  }
}

ReadData

This is used most typically to map an ID from a custom field contents to a display value - such as an InventoryID to a Part No and / or Description, for example:

Code Block
languagec#
public void ReadData(JiwaFinancials.Jiwa.JiwaApplication.IJiwaBusinessLogic BusinessLogicHost, JiwaFinancials.Jiwa.JiwaApplication.Controls.JiwaGrid GridObject, JiwaFinancials.Jiwa.JiwaApplication.IJiwaForm FormObject, int Row, JiwaFinancials.Jiwa.JiwaApplication.IJiwaCustomFieldValues HostObject, JiwaFinancials.Jiwa.JiwaApplication.CustomFields.CustomField CustomField, JiwaFinancials.Jiwa.JiwaApplication.CustomFields.CustomFieldValue CustomFieldValue)
{
  // ReadData handler for a lookup custom field for an inventory item
  if (CustomField.PluginCustomField.Name == "MyCustomFieldName") 
  {
		if (CustomFieldValue.Contents.Trim().Length > 0) 
		{
			JiwaFinancials.Jiwa.JiwaApplication.Entities.Inventory.Inventory inventoryItem = CustomField.Manager.EntityFactory.CreateEntity<JiwaFinancials.Jiwa.JiwaApplication.Entities.Inventory.Inventory>();
			try
			{
				inventoryItem.ReadRecord(CustomFieldValue.Contents);
				CustomFieldValue.DisplayContents = String.Format("{0} - {1}", inventoryItem.PartNo, inventoryItem.Description);
			}
			catch(JiwaFinancials.Jiwa.JiwaApplication.Exceptions.RecordNotFoundException notFoundEx)
			{
				CustomFieldValue.DisplayContents = "";
			}
		}
		else
		{
			CustomFieldValue.DisplayContents = "";
		}
  }
}

ButtonClicked

This is used to react to a button click of the lookup button to the right of the custom field contents for Lookup type custom fields - usually to display a search window - for example:

Code Block
languagec#
public void ButtonClicked(JiwaFinancials.Jiwa.JiwaApplication.IJiwaBusinessLogic BusinessLogicHost, JiwaFinancials.Jiwa.JiwaApplication.Controls.JiwaGrid GridObject, JiwaFinancials.Jiwa.JiwaApplication.IJiwaForm FormObject, int Col, int Row, JiwaFinancials.Jiwa.JiwaApplication.IJiwaCustomFieldValues HostObject, JiwaFinancials.Jiwa.JiwaApplication.CustomFields.CustomField CustomField, JiwaFinancials.Jiwa.JiwaApplication.CustomFields.CustomFieldValue CustomFieldValue)
{
  // ButtonClicked handler for a lookup custom field for an inventory item
  if (CustomField.PluginCustomField.Name == "MyCustomFieldName") 
  {
		JiwaFinancials.Jiwa.JiwaApplication.Entities.Inventory.Inventory inventoryItem = CustomField.Manager.EntityFactory.CreateEntity<JiwaFinancials.Jiwa.JiwaApplication.Entities.Inventory.Inventory>();
		inventoryItem.Search(FormObject.Form, "", "");
		CustomFieldValue.Contents = inventoryItem.RecID;
		CustomFieldValue.DisplayContents = String.Format("{0} - {1}", inventoryItem.PartNo, inventoryItem.Description);
  }
}

LineCustomFieldPlugin class

Much like the CustomFieldPlugin class, the LineCustomFieldPlugin class is used for the display and interaction with custom fields - but for custom fields on lines (grids) - such as sales order lines. It uses the following methods:

FormatCell

This is used to format the Spread grid cell of the custom field contents. Often used for setting combo-box items, for example:

Code Block
languagec#
public void FormatCell(JiwaFinancials.Jiwa.JiwaApplication.IJiwaBusinessLogic BusinessLogicHost, JiwaFinancials.Jiwa.JiwaApplication.Controls.JiwaGrid GridObject, JiwaFinancials.Jiwa.JiwaApplication.IJiwaForm FormObject, int Col, int Row, JiwaFinancials.Jiwa.JiwaApplication.IJiwaLineCustomFieldValues HostItem, JiwaFinancials.Jiwa.JiwaApplication.CustomFields.CustomField CustomField, JiwaFinancials.Jiwa.JiwaApplication.CustomFields.CustomFieldValue CustomFieldValue)
{
  // FormatCell handler for a combo custom field
  if (CustomField.PluginCustomField.Name == "MyCustomFieldName") 
  {
		var typeCell = new FarPoint.Win.Spread.CellType.ComboBoxCellType();			
		typeCell.Items = new string[] { "Option 1", "Option 2", "Option 3"};
		typeCell.ItemData = new string[] { "1", "2", "3"};
		typeCell.EditorValue = FarPoint.Win.Spread.CellType.EditorValue.ItemData;                  
		GridObject.ActiveSheet.Cells[Row, GridObject.ActiveSheet.GetColumnFromTag(null, CustomField.PluginCustomField.GridColumnName).Index].CellType = typeCell;
  }
}

Note that the only difference between the FormatCell of the LineCustomFieldPlugin class and the FormatCell if the CustomFieldPlugin class is the column tag is CustomField.PluginCustomField.GridColumnName instead of “Contents”.

ReadData

Identical in nature to the ReadData method of the CustomFieldPlugin class.

This is used most typically to map an ID from a custom field contents to a display value - such as an InventoryID to a Part No and / or Description, for example:

Code Block
languagec#
public void ReadData(JiwaFinancials.Jiwa.JiwaApplication.IJiwaBusinessLogic BusinessLogicHost, JiwaFinancials.Jiwa.JiwaApplication.Controls.JiwaGrid GridObject, JiwaFinancials.Jiwa.JiwaApplication.IJiwaForm FormObject, int Row, JiwaFinancials.Jiwa.JiwaApplication.IJiwaLineCustomFieldValues HostItem, JiwaFinancials.Jiwa.JiwaApplication.CustomFields.CustomField CustomField, JiwaFinancials.Jiwa.JiwaApplication.CustomFields.CustomFieldValue CustomFieldValue)
{
  // ReadData handler for a lookup custom field for an inventory item
  if (CustomField.PluginCustomField.Name == "MyCustomFieldName") 
  {
		if (CustomFieldValue.Contents.Trim().Length > 0) 
		{
			JiwaFinancials.Jiwa.JiwaApplication.Entities.Inventory.Inventory inventoryItem = CustomField.Manager.EntityFactory.CreateEntity<JiwaFinancials.Jiwa.JiwaApplication.Entities.Inventory.Inventory>();
			try
			{
				inventoryItem.ReadRecord(CustomFieldValue.Contents);
				CustomFieldValue.DisplayContents = String.Format("{0} - {1}", inventoryItem.PartNo, inventoryItem.Description);
			}
			catch(JiwaFinancials.Jiwa.JiwaApplication.Exceptions.RecordNotFoundException notFoundEx)
			{
				CustomFieldValue.DisplayContents = "";
			}
		}
		else
		{
			CustomFieldValue.DisplayContents = "";
		}
  }
}

ButtonClicked

Identical in nature to the ButtonClicked method of the CustomFieldPlugin class, typically used to react to a button click of the lookup button to the right of the custom field contents for Lookup type custom fields - usually to display a search window - for example:

Code Block
languagec#
public void ButtonClicked(JiwaFinancials.Jiwa.JiwaApplication.IJiwaBusinessLogic BusinessLogicHost, JiwaFinancials.Jiwa.JiwaApplication.Controls.JiwaGrid GridObject, JiwaFinancials.Jiwa.JiwaApplication.IJiwaForm FormObject, int Col, int Row, JiwaFinancials.Jiwa.JiwaApplication.IJiwaLineCustomFieldValues HostItem, JiwaFinancials.Jiwa.JiwaApplication.CustomFields.CustomField CustomField, JiwaFinancials.Jiwa.JiwaApplication.CustomFields.CustomFieldValue CustomFieldValue)
{
  // ButtonClicked handler for a lookup custom field for an inventory item
  if (CustomField.PluginCustomField.Name == "MyCustomFieldName") 
  {
		JiwaFinancials.Jiwa.JiwaApplication.Entities.Inventory.Inventory inventoryItem = CustomField.Manager.EntityFactory.CreateEntity<JiwaFinancials.Jiwa.JiwaApplication.Entities.Inventory.Inventory>();
		inventoryItem.Search(FormObject.Form, "", "");
		CustomFieldValue.Contents = inventoryItem.RecID;
		CustomFieldValue.DisplayContents = String.Format("{0} - {1}", inventoryItem.PartNo, inventoryItem.Description);
  }
}

SystemSettingPlugin class

Similar to the custom field classes, this class has methods used for the display and interaction with system settings.

ScheduledExecutionPlugin class

This class has only relevance when the Jiwa Plugin Scheduler Service is configured and running.

It contains three methods:

Execute

This is executed for each schedule defined against the plugin, as they fall due. The template code has a lock semaphore in place to prevent pre-empting any already running execution.

Code Block
languagec#
public void Execute(JiwaFinancials.Jiwa.JiwaApplication.Plugin.Plugin Plugin, JiwaFinancials.Jiwa.JiwaApplication.Schedule.Schedule Schedule)
    {
        lock (JiwaFinancials.Jiwa.JiwaApplication.Manager.CriticalSectionFlag)
        {
            // place processing code in here
        }
    }

OnServiceStart

This method is invoked when the service is starting.

It is often used to add handlers for the Windows File System to detect when a file appears in a folder (File Watcher) - note that no schedule is needed for such handlers.

OnServiceStopping

This method is invoked when the service is stopping.

Understanding Business Logic Behaviours

When writing plugins to interact with business logic objects, particularly handling events raised by these objects, it is important to understand the behaviours and sequence of events.

The Save

When the Save() method of a business logic object is invoked, the following occurs in order:

  1. If no SQL Transaction is already started, one is started

  2. The SaveStart event is raised. It is safe to perform long running actions and UI interactions

  3. SQL commands are issued to INSERT, UPDATE and DELETE

  4. The SaveEnding event is raised. It is unsafe to perform long running actions and UI interactions

  5. The SQL transaction, if started by this business logic object is committed

  6. The SaveEnd event is raised. It is safe to perform long running actions and UI interactions

If any exception is thrown during the save (even by a plugin) then if the business logic object had started a transaction, then it will RollBack that transaction and all SQL commands issued since the transaction started will be undone.

Plugins that wish to use the same SQL transaction to update data to ensure consistency should do this in either the SaveStart or SaveEnding events. If using the SaveEnding event then ensure no user interaction is made.

The Read

When the Read(string RecID) of a business logic object is invoked, the following occurs in order:

  1. The ReadStart event is raised

  2. The Clear() method is invoked to clear contents of properties, private members and collections - this will in turn raise the ClearStart and then ClearEnd events.

  3. The data is read

  4. The ReadEnd event is raised

JiwaCollections

All collections or lists in Jiwa are of type JiwaCollection. Each item in the collection inherit from the JiwaCollectionItem class.

Sales order lines, purchase order lines, debtor delivery addresses, Bill input items are all JiwaCollections.

There are several events of JiwaCollections which can be subscribed to, via the public property of the business logic object.

Note that the JiwaCollection events are not raised during the normal read of a business logic object.

Adding

The Adding event is raised when an item is being added to the collection. A CancelEventArgs argument allows this add to be cancelled.

Added

The Added event is raised when an item is added to the JiwaCollection. The item argument is the item which was added.

The code below shows a handler for a sales order lines, displaying a message box of the PartNo property of the sales order line when added to the collection.

Code Block
languagec#
public class FormPlugin : System.MarshalByRefObject, JiwaFinancials.Jiwa.JiwaApplication.IJiwaFormPlugin
{
	public override object InitializeLifetimeService()
    {
        // returning null here will prevent the lease manager
        // from deleting the Object.
        return null;
    }

    public void SetupBeforeHandlers(JiwaFinancials.Jiwa.JiwaApplication.IJiwaForm JiwaForm, JiwaFinancials.Jiwa.JiwaApplication.Plugin.Plugin Plugin)
    {
    }
	
    public void Setup(JiwaFinancials.Jiwa.JiwaApplication.IJiwaForm JiwaForm, JiwaFinancials.Jiwa.JiwaApplication.Plugin.Plugin Plugin)
    {
		JiwaFinancials.Jiwa.JiwaSalesUI.SalesOrder.SalesOrderEntryForm salesOrderForm = (JiwaFinancials.Jiwa.JiwaSalesUI.SalesOrder.SalesOrderEntryForm)JiwaForm;
		salesOrderForm.SalesOrder.SalesOrderLines.Added += delegate(JiwaFinancials.Jiwa.JiwaSales.SalesOrder.SalesOrderLine salesOrderLine)
			{
				System.Windows.Forms.MessageBox.Show(String.Format("Added '{0}'", salesOrderLine.PartNo));
			};
    }
}

Changed

The Changed even is raised when a property if the item changes. The PropertyChangedEventArgs argument has a PropertyName property which contains the name of the property which changed.

Removing

The Removing event is raised when an item is being removed from the JiwaCollection.

Removed

The Removed event is raised when an item is removed from the JiwaCollection.

Common Mistakes

Some of the more common mistakes which can cause significant issues are:

...

If a plugin is awaiting user interaction and there are un-committed transactions pending, then other users will be blocked from reading or writing to the same tables or pages of the database.

The SaveEnding event of all business logic objects is raised when the business logic has created a SQL Transaction, issued SQL inserts, updates or deletes and not yet committed the transaction.

Awaiting user interaction, or any long running operation - such as I/O or external API’s should not be done in handlers of the SaveEnding event.

The SaveEnding event of all business logic objects is raised when the business logic has created a SQL Transaction, issued SQL inserts, updates or deletes and not yet committed the transaction.

Awaiting user interaction, or any long running operation - such as I/O or external API’s should not be done in handlers of the SaveEnding event.

DRY (Don’t Repeat Yourself)

If there is code that is going to be needed by multiple plugins don’t repeat the code in multiple places, instead create a plugin and create a public class within this plugin to hold the code and then add a plugin reference to each of the plugins that need to access this code and call this new class. By doing this you don’t have to maintain multiple copies of the same code.