Plugins authors should adhere to the following guidelines when developing plugins.
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.
Versioning
The plugin version should use a dotted version number, with the last number incrementing with each published change.
For example, if a plugin is first published as version 1.0.0, then this should be incremented to 1.0.1 when next published.
Exception Policy
The Exception Policy should be set to Abort if it performs critical business processes, otherwise the default Exception Policy of Report should be used.
A plugin with an Abort Exception Policy will not allow processes (such as the REST API or Plugin Scheduler) to logon if the plugin is unable to be loaded.
Execution Order
Plugins are compiled and loaded in the order of Execution Order, then plugin Name.
If you add a Plugin Reference (a reference to another plugin) then the Execution Order is automatically set to the Execution Order of the referenced plugin, incremented by one.
Forms
The Forms tab of the plugin is a register of forms that, when loaded will result in the SetupBeforeHandlers and Setup methods of the FormPlugin class being invoked.
You should only add forms to this list which your plugin has an interest in.
Business Logic
The Business Logic tab of the plugin is a register of business logic objects that, when loaded will result in the Setup method of the BusinessLogicPlugin class being invoked.
You should only add business logic to this list which your plugin has an interest in.
Assembly References
The Assembly References tab contains all the external assemblies the plugin is referencing.
Unused references should be deleted.
When a plugin is created, to be helpful and reduce the friction in getting a plugin working out of the box, we add all the references from the JiwaApplication.dll assembly. A lot of these references will not be needed and can be deleted.
References are also automatically added when a Form or Business Logic is added to their respective tabs - the assembly of the Form or Business Logic itself, as well as all references that the added Form or Assembly was referencing. A lot of these references will not be needed and can be deleted.
Removing unused Assembly References will improve performance.
Plugin References
The Plugin Reference tab allows the logic in another plugin to be accessible to the plugin the Plugin Reference is added to.
The referenced plugin should be enabled and in a compilable state.
When a plugin is added to the Plugin References tab, the Execution Order of the plugin is set to the execution order of the referenced plugin, and incremented by one to ensure that the plugins are compiled in an order able to satisfy the dependency requirements.
Embedded References
Third part assemblies can be attached to plugins on the Embedded References tab. When a plugin is compiled, the Embedded References are saved to the folder %ProgramData%\Jiwa Financials\Jiwa 7\{Jiwa Version}\{WindowsUser}\{SQL Server Name}\{Jiwa Database Name}\Plugins\{Jiwa username}\Compile and then loaded.
Custom Fields
Custom fields are defined on the Custom Fields tab.
Use meaningful names and descriptions.
Avoid abbreviating.
Use casing to help readability.
System Settings
System Settings are defined on the System Settings tab.
Use meaningful names and descriptions.
Avoid abbreviating.
Use casing to help readability.
Schedule
Schedules can be defined on the Schedule tab - these are only used by the ScheduledExecutionPlugin class, when the Jiwa Plugin Scheduler service is configured and running.
Use a meaningful name. If the schedule is daily at midnight, then “Daily Midnight” would be a reasonable name.
Double-right mouse click the Description to open the Schedule details dialog.
Schedules are not Enabled by default when added, so be sure to Enable the schedule.
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 that are not used
Delete any using (or Imports in VB) statements that are not 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:
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()); } }
And with the following Forms registered:
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.
The SetupBeforeHandlers method can be used to add handlers to control events and business logic before the form itself handles these events.
This is often useful in overriding the built-in behaviour - for example, adding a button click handler here will cause your code to respond to the event before the form does, and you can also short-circuit the event so the form doesn’t receive the event at all afterwards by throwing a JiwaApplication.Exceptions.ClientCancelledException which will silently fail, resulting in your plugin handling the event and the form not handling it with its built-in handler.
The JiwaForm parameter passed to the SetupBeforeHandlers method is the form being loaded - if you have registered multiple forms on the Forms tab on the plugin then the type of the JiwaForm parameter should be inspected to determine the code to execute.
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.
When a business logic object is created via the BusinessLogicFactory (as all business logic objects in Jiwa are), if the business logic is registered on the Business Logic tab of the plugin, then the Setup method of the BusinessLogicPlugin class is invoked after the business logic object is created.
Typically this is where you would add handlers to business logic events, and instantiate and setup your own objects.
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 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:
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:
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:
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:
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:
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:
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:
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.
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.
Common Mistakes
Some of the more common mistakes which can cause significant issues are:
Blocking a SQL Transaction with a UI prompt
An entire organisation can be ground to a halt by displaying a messagebox or dialog at the wrong time.
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.