Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / XSLT

Model-driven development and prototyping by sample (part 2)

5.00/5 (1 vote)
4 Aug 2013CPOL7 min read 13.8K   25  
Customizing generated code to support phone number formatting

Introduction 

In my last article I have shown how to create a simple application for managing contacts. The contacts have phone numbers that were not specially formatted and thus they were simple text boxes. To demonstrate a small effect of model driven development, the first version needs to get enhanced to support phone numbers as attribute types.

Background 

Changes in the design or simply in the requirements usually results in an impact of change that one may not capture well. One easy sample is the change of a more generic type used into a customized business type that makes more sense. The requirement may be, that all telephone numbers must be formatted for international application users. This means 0 049 (0)xx?? xxx xxx xxx?? for a german number. After having written a big application with several occurences of the phone number one may not know how much changes are needed. All forms have to be modified, all reports are also affected. Validation rules may be inserted and optionally a setting to support the activation or deactivation of formatted phone numbers or a fixed international format versus a data dependent format.

A class could be written that returns the currently used formatting rule and type. This is then used for all control setup code. But still it has to be applied for all occurences of phone number fields in forms, grids, lookup dropdown fields and the like.

Prepare to model this instead

In the first version a phonenumber has been added as a type. This was a first draft on how to get phone number fields modeled. Today a decision has been made that the type should be named like this: PhoneNumber, but the UML model is now frozen regarding the type. For sample this can occur when different teams declare modeling rules and writing XSLT templates. You have phonenumber and PhoneNumber. A mapping is required in this case and it is possible. 

To get prepared to use the model, take the generated code, pick one occurence and make the required changes and compile the code to ensure the changes are correct and as desired.

To enable runtime configurable formatting, at least when the form opens, I have chosen to place the custom code into the constructor after InitializeComponent();

The textbox would be setup like this for a simple fixed form: 
// Phonenumber format
this.txtNumber.Properties.Mask.EditMask = MaskedTextProvider.GetPhoneNumberFormat(MaskedTextProvider.STR_EnUS);
this.txtNumber.Properties.Mask.MaskType = MaskedTextProvider.GetPhoneNumberMaskType(MaskedTextProvider.STR_EnUS);

The textbox for the LayoutControl based form gets the following custom code (small differences to be fixed later):

C#
// Phonenumber format
this.textNumber.Properties.Mask.EditMask = MaskedTextProvider.GetPhoneNumberFormat(MaskedTextProvider.STR_EnUS);
this.textNumber.Properties.Mask.MaskType = MaskedTextProvider.GetPhoneNumberMaskType(MaskedTextProvider.STR_EnUS);
 

The following sample is to show, how a column in a grid view can be formatted:

C#
// Phonenumber format
this.repositoryItemTextEditNumber.Mask.EditMask = MaskedTextProvider.GetPhoneNumberFormat(MaskedTextProvider.STR_EnUS);
this.repositoryItemTextEditNumber.Mask.MaskType = MaskedTextProvider.GetPhoneNumberMaskType(MaskedTextProvider.STR_EnUS);

this.repositoryItemTextEditNumber.Mask.UseMaskAsDisplayFormat = true;

The new class used here to provide the formatting becomes a central point to setup formatted text. The class is technically manually written code but generated every times when the transformation takes place. The class will not get developed in this article, but it is included in the download.

Enhancing the type mappings

To begin with the extensions, it showed up to me to start with the mapping from the UML type to the representation in the database and the internal model type of the prototyper. The phone number is stored in the database in text format with a bpchar column (a supported database column type by the prototyper). The internal Prototyper based type is that we have defined as of PhoneNumber. This type is used in the formularfoelds datastructure (or table) to define the custom type in the UI. So if you are using phonenumber in the UML model then there is a mapping required. The type mapping in the file XMISysImport.CreateFormularDefinition.Sqlite.xsl is responsible for the internal type representation in the prototyper. Change the xsl:variable MappedDataType that it will look like the following:  

XML
<!-- Mapped to internal type representation. This is not a backend type. -->
<xsl:variable name="MappedDataType">
<xsl:choose>
<xsl:when test="$DataType='float'">Float</xsl:when>
<xsl:when test="$DataType='string'">String</xsl:when>
<xsl:when test="$DataType='phonenumber'">PhoneNumber</xsl:when>
<xsl:when test="$DataType='bigstring'">String</xsl:when>
<xsl:when test="$DataType='date'">String</xsl:when>
<xsl:when test="$DataType='int'">Integer</xsl:when>
<xsl:when test="$DataType='bool'">Bit</xsl:when>
<xsl:otherwise>Undefined</xsl:otherwise>
</xsl:choose>
</xsl:variable> 

Then the type for the database has to be mapped as well. The following file contains a mapping for the Sqlite database (XMISysImport.FillSchemaAssociations.Sqlite.xsl):  

XML
<xsl:variable name="dbtype">
<xsl:choose>
	<xsl:when test="//UML:DataType[@xmi.id=$dbtyperef]/@name='bool'">BIT</xsl:when>
	<xsl:when test="//UML:DataType[@xmi.id=$dbtyperef]/@name='date'">DATETIME</xsl:when>
	<xsl:when test="//UML:DataType[@xmi.id=$dbtyperef]/@name='int'">int4</xsl:when>
	<xsl:when test="//UML:DataType[@xmi.id=$dbtyperef]/@name='string'">bpchar</xsl:when>
	<xsl:when test="//UML:DataType[@xmi.id=$dbtyperef]/@name='phonenumber'">bpchar</xsl:when>
	<xsl:when test="//UML:DataType[@xmi.id=$dbtyperef]/@name='bigstring'">TEXT</xsl:when>
	<xsl:when test="//UML:DataType[@xmi.id=$dbtyperef]/@name='float'">FLOAT</xsl:when>
</xsl:choose>
</xsl:variable>

The mapping in the database is bpchar, a more general type. It may be required to change the prototyper code, if you use a completely new (not yet supported) type.

If you have made these changes to at least for the Sqlite database you will be able to change the UML model attribute for the phone number in the Telephone class to the type phonenumber.

When you reimport the model (do not forget to override the database), you will be able to open the form and still see a simple text box. This is because of the representation as a bpchar backend type and thus the prototyper knows how to display the field. The form fields are backend type driven, not model type driven.

If you then start to generate code, you will find errors in the generated code. The new internal type PhoneNumber is not yet supported and this leads to incomplete code.  Some parts are generated without asking for the type and thus are always generated. This is in one point of view good, but when you like to get all occurences customized, this is bad, as you cannot find easily the snippets based on, say,

'<xsl:when test="@dbtype='Bit'">'. 

Here I pay back my laziness Smile | <img src= " src="http://www.codeproject.com/script/Forums/Images/smiley_smile.gif" />

Enhancing the affected templates by example

The example here shows not all changes made. To get a complete working change, the download contains all changes exept for the reports the application support. The required changes are not yet worked out and may be a left for you to try - make backups :-)

Here I will modify the simple CRUD form view template (SimpleCRUDFormView.cs.xsl). The plan is to extend the constructor with code for attributes of a specific type (PhoneNumber). To do this, locate the constructor and insert the following code after the call to InitializeComponent();

XML
		public TelephoneSimpleCRUDFormView()
		{
			InitializeComponent();
 
<xsl:for-each select="//lbDMF/formularfields/formular[@formularid=$FormularID]">
<xsl:variable name="FieldName" select="@name"/> 
<xsl:variable name="TableName" select="@tablename"/>
 
<xsl:choose>
	<xsl:when test="@isfk='1'">
        </xsl:when>
	<xsl:when test="//lbDMF/columntypes/columntype[@name=$FieldName][@tablename=$TableName][@specialcolumn='1']">
	</xsl:when>
	<xsl:otherwise>
		<xsl:choose>
			<xsl:when test="@dbtype='PhoneNumber'">
            // Phonenumber format
            this.txtNumber.Properties.Mask.EditMask = MaskedTextProvider.GetPhoneNumberFormat(MaskedTextProvider.STR_EnUS);
            this.txtNumber.Properties.Mask.MaskType = MaskedTextProvider.GetPhoneNumberMaskType(MaskedTextProvider.STR_EnUS);
			</xsl:when>
			<xsl:otherwise>
			</xsl:otherwise>
		</xsl:choose>
	</xsl:otherwise>
</xsl:choose>
</xsl:for-each> 

The effect for this is the following: If you model your attribute to be a phonenumber, the additional setup for the control takes place. But there is another requirement to not forget. The designer generated code must also know the new type in each xsl:choose snippet, as this is not always done by a generic xsl:choose snippet. 

The label must be present (SimpleCRUDFormView.Designer.cs.xsl): 

XML
<xsl:when test="@dbtype='Bit'">
System.Windows.Forms.Label lbl<xsl:value-of select="@name"/>;
</xsl:when>
<xsl:when test="@dbtype='PhoneNumber'">
System.Windows.Forms.Label lbl<xsl:value-of select="@name"/>;
</xsl:when>

 The text field must also be present (SimpleCRUDFormView.Designer.cs.xsl):

XML
<xsl:when test="@dbtype='Bit'">
private DevExpress.XtraEditors.TextEdit txt<xsl:value-of select="@name"/>;
</xsl:when>
<xsl:when test="@dbtype='PhoneNumber'">
private DevExpress.XtraEditors.TextEdit txt<xsl:value-of select="@name"/>;
</xsl:when>

Due to the fact, that the setup for the text field and the label is not coded in a xsl:choose snipped per type, there is no requirement for action. It may become so, if the type of control changes.

Go throughout all the other templates and make changes if they are required to make the phonenumber support complete. You may search for <xsl:when test="@dbtype='Bit'">.

To enable similar formatting for the grid views, you may need to do more work. Each column that shows a phonenumber, must get a custom editor as the default editor did not know about the formatting or at least I have not found a way to modify the existing. 

Here is the code that is responsible for setting up the formatting rule:

XML
			grid<xsl:value-of select="$FormularName"/>View.MouseDown += grid_MouseDown;
			
<!-- Custom formatted fields -->			
<xsl:for-each select="//lbDMF/formularfields/formular[@formularid=$FormularID]">
<xsl:variable name="FieldName" select="@name"/> 
<xsl:variable name="TableName" select="@tablename"/>
 
<xsl:choose>
	<xsl:when test="@isfk='1'">
        </xsl:when>
	<xsl:when test="//lbDMF/columntypes/columntype[@name=$FieldName][@tablename=$TableName][@specialcolumn='1']">
	</xsl:when>
	<xsl:otherwise>
		<xsl:choose>
			<xsl:when test="@dbtype='PhoneNumber'">
            // Phonenumber format
            this.repositoryItemTextEdit<xsl:value-of select="$FieldName"/>.Mask.EditMask = MaskedTextProvider.GetPhoneNumberFormat(MaskedTextProvider.STR_EnUS);
            this.repositoryItemTextEdit<xsl:value-of select="$FieldName"/>.Mask.MaskType = MaskedTextProvider.GetPhoneNumberMaskType(MaskedTextProvider.STR_EnUS);
 
            this.repositoryItemTextEdit<xsl:value-of select="$FieldName"/>.Mask.UseMaskAsDisplayFormat = true;
			</xsl:when>
			<xsl:otherwise>
			</xsl:otherwise>
		</xsl:choose>
	</xsl:otherwise>
</xsl:choose>
</xsl:for-each> 
			
<!-- Custom formatted detail fields -->			
	<xsl:for-each select="//lbDMF/formularactions/action[@formularid=$FormularID]">
		<xsl:variable name="actionid" select="@actionid"/>
		<xsl:variable name="event" select="@event"/>
		<xsl:for-each select="//lbDMF/actionsteps/action[@actionid=$actionid]">
 
		<xsl:variable name="tempDetailFormularName" select="@what"/>
		<xsl:variable name="DetailFormularName">
	<xsl:call-template name="SubstringReplace">
		<xsl:with-param name="stringIn">
	<xsl:call-template name="SubstringReplace">
		<xsl:with-param name="stringIn">
	<xsl:call-template name="SubstringReplace">
		<xsl:with-param name="stringIn">
			<xsl:value-of select="$tempDetailFormularName"/>
		</xsl:with-param>
		<xsl:with-param name="substringIn" select="'-'"/>
		<xsl:with-param name="substringOut" select="''"/>
	</xsl:call-template>
		</xsl:with-param>
		<xsl:with-param name="substringIn" select="'>'"/>
		<xsl:with-param name="substringOut" select="''"/>
	</xsl:call-template>
		</xsl:with-param>
		<xsl:with-param name="substringIn" select="' '"/>
		<xsl:with-param name="substringOut" select="''"/>
	</xsl:call-template>
		</xsl:variable>		
	
	<xsl:choose>
		<xsl:when test="@steptyp='4'">
<xsl:variable name="DetailFormularID" select="//lbDMF/formulare/formular[@name=$DetailFormularName][@applicationid=$ApplicationID]/@ID"/>
<xsl:for-each select="//lbDMF/formularfields/formular[@formularid=$DetailFormularID]">
<xsl:variable name="FieldName" select="@name"/> 
<xsl:variable name="TableName" select="@tablename"/>
<xsl:choose>
	<xsl:when test="@isfk='1'">
	</xsl:when>
	<xsl:otherwise>
		<xsl:choose>
			<xsl:when test="@dbtype='PhoneNumber'">
            // Phonenumber format
            this.repositoryItemTextEditDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="@name"/>.Mask.EditMask = MaskedTextProvider.GetPhoneNumberFormat(MaskedTextProvider.STR_EnUS);
            this.repositoryItemTextEditDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="@name"/>.Mask.MaskType = MaskedTextProvider.GetPhoneNumberMaskType(MaskedTextProvider.STR_EnUS);
 
            this.repositoryItemTextEditDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="@name"/>.Mask.UseMaskAsDisplayFormat = true;
			</xsl:when>
			<xsl:otherwise>
			</xsl:otherwise>
		</xsl:choose>
	</xsl:otherwise>
</xsl:choose>
</xsl:for-each>

The designer code for this template needs several changes. First is the creation of the instance:

XML
<xsl:for-each select="//lbDMF/formularfields/formular[@formularid=$FormularID]">
<xsl:variable name="FieldName" select="@name"/> 
<xsl:variable name="TableName" select="@tablename"/>
<xsl:choose>
	<xsl:when test="@isfk='1'">
            this.repositotyItemLookupEdit<xsl:value-of select="@name"/> = new DevExpress.XtraEditors.Repository.RepositoryItemLookUpEdit();
            this.gridColumn<xsl:value-of select="@name"/> = new DevExpress.XtraGrid.Columns.GridColumn();
	</xsl:when>
	<xsl:otherwise>
            this.gridColumn<xsl:value-of select="@name"/> = new DevExpress.XtraGrid.Columns.GridColumn();
		<xsl:choose>
			<xsl:when test="@dbtype='PhoneNumber'">
            // Phonenumber format
			this.repositoryItemTextEdit<xsl:value-of select="$FieldName"/> = new DevExpress.XtraEditors.Repository.RepositoryItemTextEdit();;
			</xsl:when>
			<xsl:otherwise>
			</xsl:otherwise>
		</xsl:choose>
	</xsl:otherwise>
</xsl:choose>
</xsl:for-each> 

Then the detail grid columns setup snippet gets enhanced:

XML
             this.grid<xsl:value-of select="$DetailFormularName"/>View = new DevExpress.XtraGrid.Views.Grid.GridView();
		
		
<xsl:variable name="DetailFormularID" select="//lbDMF/formulare/formular[@name=$DetailFormularName][@applicationid=$ApplicationID]/@ID"/>
<xsl:for-each select="//lbDMF/formularfields/formular[@formularid=$DetailFormularID]">
<xsl:variable name="FieldName" select="@name"/> 
<xsl:variable name="TableName" select="@tablename"/>
<xsl:choose>
	<xsl:when test="@isfk='1'">
	</xsl:when>
	<xsl:otherwise>
            this.gridColumnDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="@name"/> = new DevExpress.XtraGrid.Columns.GridColumn();
		<xsl:choose>
			<xsl:when test="@dbtype='PhoneNumber'">
            // Phonenumber format
			this.repositoryItemTextEditDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="@name"/> = new DevExpress.XtraEditors.Repository.RepositoryItemTextEdit();;
			</xsl:when>
			<xsl:otherwise>
			</xsl:otherwise>
		</xsl:choose>
	</xsl:otherwise>
</xsl:choose>
</xsl:for-each>

Then the common grid column gets optionally a custom ColumnEdit instance:  

XML
// 
// gridColumn<xsl:value-of select="$FieldName"/>
// 
this.gridColumn<xsl:value-of select="$FieldName"/>.Caption = "<xsl:value-of select="$FieldName"/>";
this.gridColumn<xsl:value-of select="$FieldName"/>.Name = "gridColumn<xsl:value-of select="$FieldName"/>";
this.gridColumn<xsl:value-of select="$FieldName"/>.FieldName = "_<xsl:value-of select="$FieldName"/>";
this.gridColumn<xsl:value-of select="$FieldName"/>.Visible = true;
this.gridColumn<xsl:value-of select="$FieldName"/>.VisibleIndex = <xsl:value-of select="position()-1"/>;
<xsl:if test="@dbtype='PhoneNumber'">
// Phonenumber format
this.gridColumn<xsl:value-of select="$FieldName"/>.ColumnEdit = this.repositoryItemTextEdit<xsl:value-of select="$FieldName"/>;
</xsl:if> 

In that case I have used simple xsl:if statements that are also possible. Then the detail columns get optionally an editor:

XML
// 
// gridColumnDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="$FieldName"/>
// 
this.gridColumnDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="$FieldName"/>.Caption = "<xsl:value-of select="$FieldName"/>";
this.gridColumnDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="$FieldName"/>.Name = "gridColumnDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="$FieldName"/>";
this.gridColumnDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="$FieldName"/>.FieldName = "_<xsl:value-of select="$FieldName"/>";
this.gridColumnDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="$FieldName"/>.Visible = true;
this.gridColumnDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="$FieldName"/>.VisibleIndex = <xsl:value-of select="position()-1"/>;
<xsl:if test="@dbtype='PhoneNumber'">
// Phonenumber format
this.gridColumnDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="$FieldName"/>.ColumnEdit = this.repositoryItemTextEditDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="@name"/>;
</xsl:if>

After all, the variables for the custom editors need to be setup (detail columns and then master columns): 

XML
		<xsl:when test="@steptyp='4'">
		private DevExpress.XtraGrid.Views.Grid.GridView grid<xsl:value-of select="$DetailFormularName"/>View;
 
<xsl:variable name="DetailFormularID" select="//lbDMF/formulare/formular[@name=$DetailFormularName][@applicationid=$ApplicationID]/@ID"/>
<xsl:for-each select="//lbDMF/formularfields/formular[@formularid=$DetailFormularID]">
<xsl:variable name="FieldName" select="@name"/> 
<xsl:variable name="TableName" select="@tablename"/>
<xsl:choose>
	<xsl:when test="@isfk='1'">
	</xsl:when>
	<xsl:otherwise>
        private DevExpress.XtraGrid.Columns.GridColumn gridColumnDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="$FieldName"/>;
			<xsl:if test="@dbtype='PhoneNumber'">
            // Phonenumber format
        private DevExpress.XtraEditors.Repository.RepositoryItemTextEdit repositoryItemTextEditDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="@name"/>;
			</xsl:if>
	</xsl:otherwise>
</xsl:choose>
</xsl:for-each>

And the master column:

XML
<xsl:for-each select="//lbDMF/formularfields/formular[@formularid=$FormularID]">
<xsl:variable name="FieldName" select="@name"/> 
<xsl:variable name="TableName" select="@tablename"/>
<xsl:choose>
	<xsl:when test="@isfk='1'">
        private DevExpress.XtraGrid.Columns.GridColumn gridColumn<xsl:value-of select="$FieldName"/>;
		private DevExpress.XtraEditors.Repository.RepositoryItemLookUpEdit repositotyItemLookupEdit<xsl:value-of select="$FieldName"/>;
		private System.Windows.Forms.BindingSource <xsl:value-of select="$FieldName"/>BindingSource;
	</xsl:when>
	<xsl:otherwise>
        private DevExpress.XtraGrid.Columns.GridColumn gridColumn<xsl:value-of select="$FieldName"/>;
			<xsl:if test="@dbtype='PhoneNumber'">
            // Phonenumber format
        private DevExpress.XtraEditors.Repository.RepositoryItemTextEdit repositoryItemTextEdit<xsl:value-of select="$FieldName"/>;
			</xsl:if>
	</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
	}
}
</xsl:template>
</xsl:stylesheet>

Using the code

The code with all required changes is attached as a zip file. You need to copy the contents from the zip file into the C:\ folder where lbDMF is located to override all files I have changed. 

Download lbDMF.zip

Points of Interest 

The article showed up how to enhance a model driven development approach. New business types can be added with more efford compared to the amount of work when done manually. This is true for one occurence, but when you come to a point where the manual changes take longer than one day, the model driven approach begins to pay back. (Actually I have made these changes in one day inclusing this article!)

Next steps may be validation and custom data based formatting stuff as noted above. This is worth another article.

Also an interesting point to show in an article is to take another framework and a complete different template to generate code.  Here I wish to get some feedback in the discussion about what framework I should choose at best. I may chose ASP.NET (generic - not DevExpress), CakePHP or wxWidgets. Please comment on what is the best choice.

History

Initial release.



License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)