Morfik 2.0 introduced a new model for the development of user-defined controls through the introduction of ‘composite controls’ and a method for defining their design-time behaviour within the IDE through scripting. There were a number of objectives that shaped the development of this new architecture. Foremost amongst them was the objective to combine graphical design and visual programming into one process within a single tool. It was also considered as important that user-created controls enjoy the same level of support by the IDE as those controls internal to the IDE. The other key objective was to use Morfik itself as the tool for building user-defined controls.
While Morfik 2.0 laid the foundation for user-defined controls it fell short in making the process of developing these controls easy enough for most developers to justify the effort required. Morfik 2.0 also had little in terms of a model for distribution of such controls. Widgets and Packages are Morfik’s answer to this challenge. Widgets provide a WYSIWYG way for building user-defined controls. They form a new class of project items similar to forms, report, tables, queries, modules and web-methods and occupy a top-level space within the project view.
|Figure 1: New Widget|
Widgets are similar to forms in that they provide a surface on which other controls can be placed. They also provide a suitable common class for delegation of control events. They however differ significantly from forms. Differences between a widget and a form include:
- - Widgets do not have bands
- - Widgets do not connect to data sources in the same way that forms do. Similar to controls, Widgets’ data sources are defined per instance rather than per class.
- - Widgets can be placed on forms and other widgets without the need for an intermediate control such as subforms.
- - Unlike forms there is no extra round trip to the server required for rendering widgets within the browser.
- - In addition to the browser and server modules widgets have an extra script module that defines advanced design time behaviour.
|Figure 2: Widgets Design Code|
A key feature of widgets is that as soon as a widget is saved for the first time it is automatically added to the Ribbon. Project widgets are added to the ‘Widgets’ drop-down menu located in the controls section on the Ribbon.
|Figure 3: Widgets on the Ribbon|
Working with Widgets
Working with widgets follows the same familiar visual programming model used in Morfik for editing forms, reports and other project items. The new widget designer allows the visual editing of widget properties, events and those of its parts. All Morfik built-in controls such as Button, TextLabel, TextEdit, etc., as well as other widgets previously defined can be placed inside a widget.
These controls (or widgets) are referred to as widget ‘parts’. It is important to differentiate widget parts from those controls that may be placed into a widget by the user after a widget’s design is completed and the widget is being used in the context of a form. Although not all widgets can do this, some widgets, such as the Accordion widget, (used for demonstration later in this document) can provide a container to which any control can be added. Widget parts are controls (or widgets) that are added when a widget is being defined; whilst widget child controls are those that are added after a widget is placed on a form.
When a new widget is created the system always adds a root container to Widget’s working surface. All subsequent widget parts are added to this container.
The “Edit Parts” Mode
Sometimes it may be desirable to edit a widget part after the widget is placed on a form. This could be the requirement of a widget whose properties are exposed through its parts or when the user wishes to customise the appearance of a widget through changing the graphical attributes of the widget parts. Since this may not be a common task for all widgets, the form designer does not normally allow a user to select any part that is inside a widget (unless specified through code when defining a widget. See Widget Parts Accessibility Options later in this section). The Edit Part command on the Form Design Ribbon enables the form designer to access individual widget parts for a given instance of a widget. Once selected all standard visual graphical design operations can be performed on the selected widget part.
|Figure 4: Edit Parts command|
Advanced Widget Programming
Although widgets can be created to perform any imaginable task, widgets can loosely be categorized by their application into two main groups.
- 1- Control Widgets - widgets that aim at creating new controls
- 2- Form Widgets - widgets that build form-like interfaces
The Accordion widget (used for demonstration later in this document) is a good example of a control widget. On the other hand, the kind of widgets that are found in a Security package such as Login widget are good examples of form widgets.
Form widgets can be built with very little coding. Most aspects of form widgets can be addressed through visual programming in much the same way as when building forms.
Control widgets on the other hand require a significant amount of coding, depending on the control that is being built, and this is considered to be an advanced area in widget building. The remaining parts of this section mostly relate to building Control Widgets.
The design-time behaviour of a widget is defined using a scripting language. The only language that is currently supported for writing Widget’s design-time behaviour uses the Object Pascal syntax. It is important to note that this limitation only applies to the Widget’s design-time code. Widget’s browser-side and server-side code can be written in any of the Morfik languages. (The current language limitation for design-time code is a temporary limitation. Future releases of Morfik will be using Morfik’s own compiler for this purpose and will therefore result in the availability of other syntaxes).
Another limitation of the current scripting language is that although very similar to the Object Pascal used in Morfik, the language syntax is not 100% identical. Most notably, it does not support type declaration (the only exception being Enumerated types). That means new types cannot be declared and consequently there is no compile-time type checking. Similarly there is no Intellisense® support, or squiggly check-as-you-type error reporting. Errors are reported at the time of execution (when a Widget is placed on a form) and appear in the Output panel, not in the Errors panel!
Currently there is no support for systematic debugging of widgets’ design-time scripts. A crude but highly effective workaround is to use the ShowMessage (displays a modal dialog) or OutputMessage (puts a message into the Output Panel) functions inside the script. For example, when debugging code that re-arranges widget parts you may use something similar to:
OutputMessage(„Left : „ + IntToStr(Left));
All design-time properties of a built-in control such as Left, Top, Caption, Picture, etc., can be accessed from the Widget design-time script. Whilst design-time scripts can (at least theoretically) access many of the IDE’s internal object properties and methods, it may not be obvious that there is not a meaningful way to make object classes, utility functions, helper methods, global variables and constants that are defined in the Morfik framework modules accessible to the script code.
Defining New Properties for Widgets
To define a new property for a widget one has to first add the following property declaration code to the design-time script associated with the widget:
Published Property DisplayVideo : Boolean; Property VideoID : String;
Please note that it is only necessary to specify the property name and type - storage is allocated automatically. The following predefined property types are currently supported in Morfik:
- - Integer
- - String
- - Double
- - Boolean
- - Color
- - Picture
- - DataField
A property type can also be of Enumerated type (See the Enumerated Properties section below).
In the case of needing to take an action in response to a change in a property value, the HandlePropertyChange method can be used. This method is invoked by the system every time the user selects a new value for a widget property. The PropName parameter can be used to determine which property has been changed.
The second step in defining a property for a widget is to add the property definition code to the server-side module of a widget. A Published property with the same name and type that were used in the design-time script in step one must be defined in the server-side code associated with the wizard. Please note that one has to provide Read and Write modifiers, the same way as when they are defined for any other property.
There is no additional coding required in the widget’s browser side module.
The special Config property of the widget’s parent class in the browser-side code must be used to gain access to a widget’s user-defined properties. The following example shows the use of the Config property in the browser-side code:
If Config['DisplayVideo'].toBoolean Then DisplayVideoWithID(Config['VideoID']);
It is important to note that the Config property returns the string representation of a property value. You might need to use type conversion functions such as toBoolean or toInteger to convert them to the required types in a given situation.
It is sometimes useful to define a property which is limited to a set of predefined values. To demonstrate how this can be done, imagine a property called MapType whose value can be either Roadmap or Satellite.
The first step would be to declare the enumerated type in the widget’s design-time script:
Type TMapType = (mtRoadMap : 'Roadmap', mtSatellite : 'Satellite View');
The code above defines the list of possible values (mtRoadMap, mtSatellite) that can be assigned to this property, and also provides user-friendly names that will be used in the Properties panel within Morfik IDE.
Subsequently a property of TMapType type can be defined in the Widget’s design-time script as:
Property MapType : TMapType;
The server-side code for declaring an Enumerated type is the same as with other types of properties. The server-side property needs to be defined in the Published section of the widget’s server-side code class definition as shown below:
Type TMapType = (mtRoadMap, mtSattelite); ... Widget1 = Class(Widget) Private FMapType : TMapType; ... Published Property MapType Read FMapType Write FMapType; ...
The browser-side code for declaring an Enumerated type requires the actual declaration of the Enumerated type and the use of the special config property to work with the enumerated type as shown in the example code below:
Type TMapType = (mtRoadMap, mtSatellite); ... If TMapType(Config['MapType']) = mtSatellite Then ...
Hiding unwanted Widget Properties in the Properties Panel
All widgets come with predefined properties common to all controls, such as Border and Font properties. In some cases these properties may not be applicable. For example, there may not be much point in keeping the inherited color property for a widget that provides a wrapper to the YouTube video services (it has no effect on how the control is actually rendered). In that case it would be good practice to remove the color property from the list of available properties. To do this, the HiddenProperties section should be added to the widget class definition, and list all properties that are not needed.
In the following example the color and font properties are removed from the Properties panel:
Published ... HiddenProperties Color, Font;
Defining Widget Events
To add an event the user must first declare a type for the event in the widget’s design-time script. The main purpose for type declaration is to specify the event parameters. It is good practice to always have the first method parameter declared as: Sender : TObject;. This makes it possible to reuse the same event handler to deal with events from multiple objects.
Once the type is declared, a browser-side event can be added using the Event keyword, and a server-side event is declared with ServerEvent keyword.
In the example below two event types are defined; one on the server-side without parameters, and one on the browser-side with one parameter of TDOMEvent type:
Type TServerNotifyEvent = Procedure(Sender : TObject); TBrowserEvent = Procedure(Sender : TObject; DOMEvent : TDOMEvent); Widget1 = Class(Widget) Published ServerEvent OnStart : TNotifyEvent; Event OnClick : TBrowserEvent; End;
|Note:||Note that “Of Object” is not used when declaring event types in design-time script, however it is required in the browser-side and server-side code modules.|
It is also worth mentioning that there is no error checking done for event declarations in the design-time code. This behaviour can be taken advantage of in situations requiring types that are not immediately available at the design-time script. For example, in the code snippet above there is no prior declaration of the TDOMEvent type. The browser-side and the server-side event declarations follow the standard pattern of event declaration as shown in the code example below:
Type TServerNotifyEvent = Procedure(Sender : TObject) of Object; Widget1 = Class(Widget) Private FOnStart : TServerNotifyEvent; Published Property OnStart : TServerNotifyEvent Read FOnStart Write FOnStart; End;
TBrowserEvent = Procedure(Sender : TObject; DOMEvent : TDOMEvent) of Object; Widget1 = Class(Widget) Private FOnClick : TBrowserEvent; Published Property OnClick : TBrowserEvent Read FOnClick Write FOnClick; End;
Referencing Widget Parts in design-time script
Widget parts are referenced within the widget design-time script in a similar way that controls are referenced in a form. For example, if a button with the name Button1 is used in a widget, it can be referenced in the widget design-time script as Widget1.Button1.
Creating Widget Parts and Controls Dynamically at design-time
In some cases it might be necessary to create parts at design time dynamically. To do this the CreatePart function can be used as in the example below:
Part := CreatePart('Image', 'ImagePart');
In this example ‘Image’ is the class of the part being created, and ‘ImagePart’ is the ID of the part, which can be used later on to identify this part as in the code snippet below:
GetPartByID('ImagePart').Picture := 'http://example.com/image.png';
Sometimes, it may be necessary to dynamically create widget controls (as opposed to widget parts) inside the widget design-time script. This can be achieved with the CreateControl function.
This function has only one parameter, control class, and can be used as shown in the following line of code:
Ctrl := CreateControl('DropDown');
Widget Parts Accessibility Option
When a widget is placed on a form, its parts aren’t necessarily accessible to the user unless the user selects the Edit Parts option on the Ribbon. It is possible to make a widget part always accessible to the user for editing, without the need for using the Edit Part option. This is achieved by setting the AlwaysSelectable property of the part to True as shown in the example code below:
Procedure Widget1.UpdateAfterPlacement; Begin Edit1.AlwaysSelectable := True; End;
The inverse of the above equally holds when the requirement is that a part be not selectable by the user. To achieve this the CanBeSelectedAtDesignTime property needs to be set to False.
Design-time Only Parts
There are occasions when a widget part is only needed at design time and not at run time. For example, when creating a widget that acts as a wrapper around Google’s YouTube player, the runtime rendering is done by Google’s YouTube player. At design time however, the creator of the widget may need to do some work in order to make the design time appearance bear some resemblance to the widget’s run-time appearance.
To make the appearance at design match that of the Widget’s run time appearance it is necessary to instantiate some widget parts. Under this circumstance, design-time only parts can be used. These are parts that are displayed at design-time but are not compiled into the application for widget’s run-time functionality. To flag a part as design-time only, the DesignTimeOnly property must be set to False for the selected parts, as shown in the example code snippet below:
ytPlay.DesignTimeOnly := True; ytPlay.CanBeSelectedAtDesignTime := False;
Please note that at design time it is not possible to hide controls by setting their Visible property to False. To hide a part at design-time, a part’s IsDesignVisible property must be set to False as shown in the example below:
Submit.IsDesignVisible := False;
Widget’s Special Methods
There are a number of predefined methods that are called by Morfik IDE form designer when a user works with a widget. Code can be placed in these methods to implement custom behaviour for the widget at design time. Special methods that are currently supported are:
UpdateAfterPlacement – This method is called immediately after a widget is placed on a form. This method is a suitable one for any initialization that happens once. For example this method would be a good place to set the DesignTimeOnly property for parts.
Loaded – This method is called immediately after the form that contains the widget has been loaded from the file, or when the widget has been pasted from the clipboard.
Please note that the UpdateAfterPlacement method is not called in these cases, so if there is a need to ensure that some code gets executed at least once each time a widget is instantiated, it is necessary to call it from both UpdateAfterPlacement and Loaded methods. In practice, the user only needs to place any part or control initialization code into the UpdateAfterPlacement method, since most properties are persistent, and once their values are set, they will be saved and loaded the next time the form is opened.
HandlePropertyChange – This method is called whenever the user selects a new value for any of the widget properties. For example, an accordion widget has to update its internal layout whenever the user changes the value of its Vertical property as shown in the example below:
Procedure mfk_Accordion.HandlePropertyChange(PropName : String); Begin If PropName = 'Vertical' Then Begin If Vertical Then Layout := lVertFlow Else Layout := lHorzFlow; LayoutChanged; End; End;
ArrangeParts – This method is called whenever a widget is moved or resized. This method is most suitable for implementation of custom layout of widgets’ internal parts. For example, a ProgressBar widget would most likely have 3 properties: MinPosition, MaxPosition and Position. The bar itself can be placed as a container inside the widget and its position updated whenever the value of any of these properties change. This technique is shown in the example below:
Procedure Widget1.ArrangeParts; Var ProgressWidth : Integer; CWidth : Integer; CHeight : Integer; Begin If Loading Then Exit; CWidth := Width - BorderWidth * 2; CHeight := Height - BorderWidth * 2; If MaxPosition > MinPosition Then ProgressWidth := Round(CWidth * ((Position - MinPosition)/ (MaxPosition - MinPosition))) Else ProgressWidth := 0; Progress.SetBounds(BorderWidth, BorderWidth, ProgressWidth, CHeight); End;
Here the code makes use of the Loading method to make sure widget properties are not accessed before it is fully loaded.
Please note that in some simple cases, setting the HorizontalPlacement or VerticalPlacement properties of a part could be enough to get the desired result. The following example shows how to keep the StatusLabel part in the center of the widget:
StatusLabel.HorizontalPlacement := hpCentre; StatusLabel.VerticalPlacement := vpCentre;
Registering Widgets with Morfik Ribbon – the RegisterWidget command
This method has to be called from the Register function inside a widget’s design-time script in order to add a widget to the Morfik Ribbon. It can also be used to specify a user-friendly name for a widget when it appears on the Ribbon. Since this name is used for display only, it can contain spaces as in the example below:
Procedure Register; RegisterWidget('My First Widget'); End;
Adding Widget Commands to the Form Designer Context Menu
It is possible to extend the form designer’s context menu for widgets. The additional menu commands will appear whenever the user right-mouse-clicks in the form design while the cursor is over the widget. Calling the RegisterWidgetAction method inside the Register function would register a command with the form designer’s context menu for that specific class of widgets. The following example demonstrates how to add a command to display version info:
Procedure Widget1.ShowAbout; Begin ShowMessage('Version 1.0'); End; Procedure Register; Begin RegisterWidget('Widget1'); RegisterWidgetAction('About This Widget', 'ShowAbout'); End;
|Note:||More than one command can be registered.|
If the state of a widget is changed in any of these commands, the IDE must be informed that the form has been modified. To do this the Modified method must be called after the change has been made as shown in the example below:
Procedure Widget1.MakeBigger; Begin Width := Width + 100; Modified; End; Procedure Register; Begin RegisterWidget('Widget1'); RegisterWidgetAction('Make It Bigger', 'MakeBigger'); End;
Placing a Widget’s custom information into an application’s HTML header
In some cases widgets might require some custom information to be placed in the HTML header of the page. To do this the , the RegisterPrintHTMLHeaderProc routine in the server-side part of the widget can be used.
Example – How to Build an Accordion Widget
In this section a sample widget will be created to build an accordion control. This control provides multiple panes for organising user controls with only one pane being displayed at any given time. The main point of this exercise is to examine various aspects of developing widgets that would lead to a better understanding of the new widget architecture. The entire project source for this example can be downloaded from the Morfik website.
A new widget can be created by selecting the New Item|New Widget option from the main menu.
|Figure 5: New Widget Command|
This action will create a new widget that will provide the basic design surface for an accordion control. Normally widgets are used as a container for commonly used interfaces (eg. Login interface) and solve common tasks which can then be reused in multiple places within an application (or can even be shared between different applications). In these cases user-defined properties and some basic scripting logic is used to control the appearance, location and size of individual controls.
In most cases this kind of widget represents a static set of controls. These types of widgets are referred to as Form Widgets. In the example here, however, a control widget will be created that implements the accordion behaviour and allows the user to dynamically add/remove accordion tabs.
Dropdown controls will be utilized as the building block for accordion tabs. The new dropdown control consists of a button and a container. The button will be used as an accordion bar, and the drop-down container will be used as a collapsible accordion pane.
The first step is to define the appearance of an accordion tab by using the visual widget designer:
|Figure 6: Widget Visual Designer|
This instance of the dropdown control will be purely used as a style-holder and for some fixed property values for all tabs that will be added to the accordion control.
There are two important properties that must be set for each dropdown control (Using the properties panel). The first one is the Floating property which must be set to False in order to make the dropdown control open inside its parent container. The second one is the GroupStyle property that defines the style within a container, allowing grouped items to share the same property values responsible for their appearance such as font, color, effect and styles.
The user now switches to the design-time script and starts programming the required logic, as well as adding one property and one event for this widget.
For this example, one new property, Vertical, and one new event, OnActiveChanged, is defined for the accordion widget:
Vertical Property – is used to provide an option for the user of the widget to organise the accordion widget tabs in vertical or horizontal directions. This property is defined in the code snippet below:
mfk_Accordion = Class(Widget) ... Published Property Vertical : Boolean; ... End;
OnActiveChanged Event – this event is fired whenever the active tab inside the accordion widget is changed. This property is defined in the code snippet below:
mfk_Accordion = Class(Widget) ... Published ... Event OnActiveChanged : TNotifyEvent; End;
The next step is to add the logic to arrange accordion tabs within the widget. Every time the widget size is changed or a tab becomes active there is a need to re-arrange all tabs within the widget. Setting the widget root container’s new layout property to ‘horizontal’/’vertical’ flow (depending on accordion orientation) is suitable for this task; however it will still be necessary to extend the flow layout logic to allow the accommodation of the drop-down part of the active tab, and the aligning of all tabs to the container sides.
By default widgets are created with a design-time script template that contains a few methods such as: ArrangeParts, UpdateAfterPlacement, HandlePropertyChange and Loaded. All that is now required is to fill them up.
ArrangeParts - this method is called whenever the size of the accordion widget or any part inside it is changed, providing the opportunity to rearrange the tabs within the widget. This method in this example will examine the value of the Vertical property defined earlier and arranges tabs in either a vertical or horizontal direction.
Procedure mfk_Accordion.ArrangeParts; Var L : TList; Begin L := TList.Create; Try GetChildList(L); If Vertical Then ArrangeVertical(L) Else ArrangeHorizontal(L); Finally L.Free; End; End;
The two ArrangeVertical and ArrangeHorizontal methods do the actual implementation for positioning of various elements in the widget. The logic behind the implementation of these two methods is fairly straightforward and for brevity is not included in the code snippet above. For details of implementation, please refer to the wizard project source.
UpdateAfterPlacement - this method is called when a widget is first inserted inside a form. In the UpdateAfterPlacement method the design tab can be removed; and a new one placed instead . Setting the ShareStyles property of the DesignTab control before removing it forces DesignTab’s associated style to be copied into the parent container; this style information will be reused by all other tabs. The NewTab method creates a new DropDown which represents an accordion tab and sets its default properties such as Caption, ButtonStyle, PopupPosition, and other properties that have been previously mentioned (Floating and ShareStyles).
Procedure mfk_Accordion.NewTab; Var Ctrl : TControl; Tab : TControl; Begin Tab := ActiveTab; Ctrl := CreateControl('DropDown'); With Ctrl Do Begin Name := UniqueName(Owner, 'AccordionTab' ); Button.Name := UniqueName(Owner, 'AccordionButton' ); Container.Name := UniqueName(Owner, 'AccordionContainer'); ButtonStyle := bsButton; Caption := 'Tab' + IntToStr(TabCount); Floating := False; ShareStyles := True; If Vertical Then PopupPosition := ppBottom Else PopupPosition := ppRight; If Tab <> Nil Then Begin Width := Tab.Width; Height := Tab.Height; End; Down := True; End; End; Procedure mfk_Accordion.RemoveDesignTab; Begin DesignTab.ShareStyles := True; DesignTab.Free; DesignTab := Nil; End; Procedure mfk_Accordion.UpdateAfterPlacement; Begin Vertical := True; RemoveDesignTab; NewTab; End
HandlePropertyChange - this method is executed when the value of any user-defined property is changed. In the case of the accordion widget, this method is used to detect any change in the value of the Vertical property defined earlier. Upon any change in the value of this property, the code below causes recalculation of the layout by calling the appropriate methods responsible for performing the actual task of laying out the widget.
Procedure mfk_Accordion.HandlePropertyChange(PropName : String); Begin If SameText(PropName, 'Vertical') Then Begin If Vertical Then Layout := lVertFlow Else Layout := lHorzFlow; LayoutChanged; End; End;
Loaded – this method is executed when the form containing the widget is loaded during the opening process. This method is used to set the down property of all drop down controls to True, as all dropdown controls inside the accordion tab will have their dropdown containers permanently open.
Procedure mfk_Accordion.Loaded; Var L : TList; Ctrl : TControl; Begin L := TList.Create; Try GetChildList(L); If L.Count > 0 Then Begin Ctrl := L; Ctrl.Down := True; End; Finally L.Free; End; End;
|Note:||It is important to note that despite the script’s superficial resemblance with widgets’ server-side code; it has no similarity in terms of the compiler’s ability to perform type checking. The script engine reports syntax errors only when the widget is saved. All other errors (such as misspelling of properties or method names) are only reported at the time of placing the widget on a form.|
Finally, in Register Procedure the Accordion widget is registered and default tasks associated with it, such as adding new tab, and switching between tabs are carried out. These actions will be accessible from the right-click popup menu at design time.
|Figure 7: Widget Design|
At this stage design-time behaviour is complete; all other designing tasks such as resizing, dragging, deleting tabs or changing properties of selected tabs are automatically supported by visual designer.
It may now be useful to elaborate on the application’s run-time logic. The server-side code is not really required, as the current implementation of the accordion widget does not allow addition or removal of any tab at run time. That means there is no need to extend or change any aspect of the accordion widget’s server-side html rendering.
On the browser-side however, there is a need to define a few methods and properties such as ActiveTab, ActiveIndex, TabCount, NextTab and PreviousTab. These methods and properties can be used to switch between different tabs programmatically and also have the ability to retrieve the active tab. HandleCreated method is used to make the first tab active and to attach an event handler which will be executed every time the user switches between tabs.
The framework’s GrowOrShrink method needs to be overridden. This is indeed the most interesting method on the browser side as new flow-layout is used to rearrange the accordion tabs, and all that is needed is to set the size of the active/inactive tabs and to leave the rest to the browser layout engine.
Once the widget is fully defined and saved it can be placed inside a form. The new Widget button is located in the ribbon:
|Figure 8: The new Accordion Widget on the Ribbon|
The user can now add a number of tabs, place controls inside tabs and press the Run button to see the result of in the browser:
|Figure 9: Two Accordion Widgets Placed inside a Form|