Types of data dictionaries
Data dictionaries is simply a terminology used for specific application data that is used to control how your application works. This terminology has several other synonyms that different development team could use. The main goal is to rely on data sets which helps you control business logics and other related needs in your application.
At minimum, I like to rely on a small set of tables to control the list of tables I have in my application and related data, the field validations and the relationships between each of them. In this article, I discuss the use of the field validation table to control HTML forms submitted by the user.
Benefits of data dictionaries
So, those were the old days. Then came the venue of using a data dictionary to handle those requirements. This is when the field table is used, where among other things, it contains whatever is necessary to provide the minimum capabilities in your application to support those validation rules.
One thing I realized over the years, when switching from desktop to Web applications, is the amount of validation rules in place is usually much bigger for the Web applications. In a desktop environment, most of the times, we know the users. In a Web environment, most of the times, the application is public. Thus, we face the situation that we have, most of the times, much more users using the application and for most of them, we simply do not know who they are. So, there is usually a need to add more validations into the application, in order to make it more robust and more secure. From experience, after having built Web applications for more than 10 years now, I can say that I have faced all kind of situations, where some of them include users who just try to enter about everything in a field, data that does not make any sense and does not relate into your application. One reason is simply because many individuals just do not want to give personal information online. Another one is simply because they have bad intentions. Whatever the reason is, the more validation rules you have in place, the better your application will be.
So, this brings us to the point where we can use a table to gather the related data about those required validations. This is really nice because you can add validation rules for a specific field without changing anything in your code. Some common validation rules include validations such as the desire to validate a field to avoid empty values, to make sure the field is not saved in uppercase, to validate a province with a country and to validate a numeric field into a specific range. This article discusses the use of over 30 validation rules. It does not cover all of them nor will it ever be. No matter as generic as you could try to make it, there will always be specific validation rules that will need to be inside your code as they are really customized into the business logic of your application. But, this should give you a good start or provide you some good tips on how you can enhance your actual infrastructure in place for such needs.
The field validation table
The field validation table holds information about specific fields of various tables that are used in the application at the validation level. Not all of the fields of your tables should be defined in here. But, if some are there, they will be processed when time comes.
That table structure is as followed:
The Validation object
I have defined a Validation object which allows me to set some property values, used for validation process, before calling the ValidationFromField() method. The object is defined as followed:
The Init() method
The Init() method contains the definition of all validation messages in English, French, Portuguese and Spanish. Those messages have been removed from the Message.dbf table in order to provide ready to go code whenever this is used on a new server. It allows us to benefit of a validation object without having to rely on a Message.dbf table and make sure the proper records are inserted. The Message.dbf table is usually used to provide string access by the use of a data dictionary to be able to maintain the string content directly from a table and to support foreign interfaces.
Some strings in foreign languages are not translated. Thus, they are holding the English values for now. This can be easily adjusted by simply adjusting the proper messages.
* 1 We have some tags which are not in PRE * 2 We have a A tag but it contains something else than HREF * 3 We have a tag which contains a style * 4 We have a FONT tag which contains something else than SIZE, COLOR and FACE * 5 We have some obscenities * 6 The field cannot be empty * 7 The field cannot be in uppercase * 8 The email cannot be blank * 9 You need to have at least one dot in your email * 10 You need to have at least one @ in your email * 11 You cannot have an email starting with @ * 12 The last character of your email cannot be a dot * 13 You cannot have more than one @ in your email * 14 You cannot have a space in the email * 15 You cannot have a comma in the email * 16 The email is not valid * 17 You need to have at least five characters in your email * 18 After the last dot, you cannot have a number in your email * 19 You cannot have a dot after the @ in the email * 20 You cannot have a semi colon in the email * 21 The country and province/state combination is not valid * 22 You have to select a province/state * 23 The ##Field## field already exists, please choose a different one * 24 A canadian postal code should be seven characters including a space in the middle. * It has to be a letter, followed by a digit, a letter, a space, a digit, a letter and a digit. * 25 A US zip code should be five digits or 10 characters starting with five digits, * followed by a dash and four digits * 26 The ##Field## field should be between ##NumericLo## and ##NumericHi## * 27 The ##Field## field should have at least ##MinLength## characters * 28 The ##Field## field cannot contain the ##Character## character * 29 The ##Field## field cannot contain the ##String## word * 30 The ##Field## field cannot be equal to ##String## * 31 The ##Field## field is invalid * 32 The ##Field## field cannot content an initial * 33 The ##Field## field should be zero or between ##NumericLo## and ##NumericHi## * 34 The ##Field## field should not be longer than ##MaxLength## characters * 35 The ##Field## field cannot contain a character * Text in English This.aMessage[1,1]='The IMG, TABLE, HTML, BODY, FORM, BLINK, OBJECT, SERVER, STYLE, IFRAME, '+; 'TR, TD, SELECT, HR, DIV, SPAN, FRAME, BUTTON, INPUT, TEXTAREA, TBODY, MARQUEE, TITLE tags or '+; 'any scripting are only allowed when between a PRE tag. If you wish to include some code, just '+; 'include them between the PRE tag. The application will make sure your '+; 'code is well formatted.' This.aMessage[2,1]='For a A HREF tag, you cannot include a style attribute. '+; 'There is also no need to put a TARGET as this will be done automatically by the application.' This.aMessage[3,1]='You cannot include a style attribute inside a tag.' This.aMessage[4,1]='For the FONT tag, only the SIZE, COLOR and FACE attributes are supported.' This.aMessage[5,1]='The content you submitted contains some obscenities. '+; 'Please, adjust it in order to assure that its content reflects a proper content.' This.aMessage[6,1]='The ##Field## field cannot be empty.' This.aMessage[7,1]='Please, do not put the content of the ##Field## field in uppercase.' This.aMessage[8,1]='The email cannot be blank.' This.aMessage[9,1]='You need to have at least one dot in your email.' This.aMessage[10,1]='You need to have at least one @ in your email.' This.aMessage[11,1]='You cannot have an email starting with @.' This.aMessage[12,1]='The last character of your email cannot be a dot.' This.aMessage[13,1]='You cannot have more than one @ in your email.' This.aMessage[14,1]='You cannot have a space in the email.' This.aMessage[15,1]='You cannot have a comma in the email.' This.aMessage[16,1]='The email is not valid.' This.aMessage[17,1]='You need to have at least five characters in your email.' This.aMessage[18,1]='After the last dot, you cannot have a number in your email.' This.aMessage[19,1]='You cannot have a dot after the @ in the email.' This.aMessage[20,1]='You cannot have a semi colon in the email.' This.aMessage[21,1]='The country and province/state combination is not valid.' This.aMessage[22,1]='You have to select a province/state.' This.aMessage[23,1]='The ##Field## field already exists, please choose a different one.' This.aMessage[24,1]='A canadian postal code should be seven characters including a '+; 'space in the middle. It has to be a letter, followed by a digit, a letter, a space, '+; 'a digit, a letter and a digit.' This.aMessage[25,1]='A US zip code should be five digits or 10 characters starting '+; 'with five digits, followed by a dash and four digits.' This.aMessage[26,1]='The value of the ##Field## field should be between ##NumericLo## and ##NumericHi##' This.aMessage[27,1]='The value of the ##Field## field should have at least ##MinLength## characters' This.aMessage[28,1]='The ##Field## field cannot contain the ##Character## character' This.aMessage[29,1]='The ##Field## field cannot contain the ##String## word' This.aMessage[30,1]='The ##Field## field cannot be equal to ##String##' This.aMessage[31,1]='The value of the ##Field## field is invalid' This.aMessage[32,1]='The value of the ##Field## field cannot content an initial' This.aMessage[33,1]='The value of the ##Field## field should be zero or between '+; '##NumericLo## and ##NumericHi##' This.aMessage[34,1]='The value of the ##Field## field should not be longer than '+; '##MaxLength## characters' This.aMessage[35,1]='The ##Field## field cannot contain a character.' * Text in French This.aMessage[1,2]="Les tag IMG, TABLE, HTML, BODY, FORM, BLINK, OBJECT, SERVER, STYLE, "+; "IFRAME, TR, TD, SELECT, HR, DIV, SPAN, FRAME, BUTTON, INPUT, TEXTAREA, TBODY, MARQUEE, "+; "TITLE ou du scripting ne sont permis que lorsqu'ils sont dans un tag PRE. "+; "Si vous voulez inclure du code, vous pouvez l'inclure à l'intérieur du tag "+; "PRE. L'application va faire en sorte que votre code "+; "soit bien formatté." This.aMessage[2,2]="Pour le tag A HREF, vous ne pouvez pas inclure un attribut de "+; "style. Il n'est aussi pas nécessaire d'inclure un TARGET car l'application "+; "le fera pour vous." This.aMessage[3,2]='Vous ne pouvez pas inclure un tag avec des attributs de style.' This.aMessage[4,2]='Pour le tag FONT, seul les attributs SIZE, COLOR et FACE sont supportés.' This.aMessage[5,2]="Le contenu que vous avez envoyé contient des mots vulgaires. "+; "S.v.p., veuillez l'ajuster afin d'assurer que son contenu soit respectable." This.aMessage[6,2]='Le champ ##Field## ne peut être vide.' This.aMessage[7,2]='S.v.p., ne pas écrire le contenu du champ champ ##Field## en majuscule.' This.aMessage[8,2]="L'adresse de courrier électronique ne peut être vide." This.aMessage[9,2]='Vous devez avoir au moins un point dans votre adresse de courrier électronique.' This.aMessage[10,2]='Vous devez avoir au moins un @ dans votre adresse de courrier électronique.' This.aMessage[11,2]='Vous ne pouvez avoir une adresse de courrier électronique débutant par @.' This.aMessage[12,2]='Le dernier caractètre de votre adresse de courrier '+; 'électronique ne peut être un point.' This.aMessage[13,2]="Vous ne pouvez avoir plus d'un @ dans votre adresse de courrier électronique." This.aMessage[14,2]='Vous ne pouvez pas avoir un espace dans votre adresse de courrier électronique.' This.aMessage[15,2]='Vous ne pouvez pas avoir une virgule dans votre adresse de courrier électronique.' This.aMessage[16,2]="L'adresse de courrier électronique n'est pas valide." This.aMessage[17,2]='Vous devez avoir au moins 5 caractères dans votre adresse de '+; 'courrier électronique.' This.aMessage[18,2]='Après le dernier point, vous ne pouvez avoir un chiffre dans '+; 'votre adresse de courrier électronique.' This.aMessage[19,2]='Vous ne pouvez pas avoir un point après le @ dans votre adresse '+; 'de courrier électronique.' This.aMessage[20,2]='Vous ne pouvez pas avoir un point virgule dans votre adresse de '+; 'courrier électronique.' This.aMessage[21,2]="La combinaison pays et province/état n'est pas valide." This.aMessage[22,2]='Vous devez sélectionner une province/état.' This.aMessage[23,2]="Le champ ##nom d'usager## existe déjà. S.V.P., veuillez en choisir un autre." This.aMessage[24,2]="Un code postal canadian doit contenir sept caractères et avoir "+; "un espace au milieu. Il doit commencer par une lettre, suivi d'un chiffre, une "+; "lettre, un espace, un chiffre, une lettre et un chiffre." This.aMessage[25,2]="Un zip code américain doit être de cinq chiffres ou de 11 "+; "caractères commencant pour cinq chiffres, suivi d'un tiret et de quatre chiffres." This.aMessage[26,2]='La valeur du champ ##Field## doit être entre ##NumericLo## et ##NumericHi##' This.aMessage[27,2]='La valeur du champ ##Field## doit contenir au moins ##MinLength## caractères' This.aMessage[28,2]='La valeur du champ ##Field## ne peut contenir le caractère ##Character##' This.aMessage[29,2]='La valeur du champ ##Field## ne peut contenir le mot ##String##' This.aMessage[30,2]='La valeur du champ ##Field## ne peut être égale à ##String##' This.aMessage[31,2]='La valeur du champ ##Field## est invalide' This.aMessage[32,2]='La valeur du champ ##Field## ne peut contenir une initiale' This.aMessage[33,2]='La valeur du champ ##Field## doit être zéro ou entre '+; '##NumericLo## et ##NumericHi##' This.aMessage[34,2]='La valeur du champ ##Field## ne doit pas dépasser ##MaxLength## '+; 'caractères' This.aMessage[35,2]='Le champ ##Field## ne peut contenir de caractère.' * Text in Portuguese This.aMessage[1,3]='As tags IMG, TABLE, HTML, BODY, FORM, BLINK, OBJECT, SERVER, STYLE, '+; 'IFRAME, TR, TD, SELECT, HR, DIV, SPAN, FRAME, BUTTON, INPUT, TEXTAREA, TBODY, MARQUEE, '+; 'TITLE ou qualquer script não são permitidos quando estiverem entre a tag PRE. '+; 'Se você deseja incluir algum código em sua, apenas inclua-o entre tag '+; 'PRE. O parser do application cuidará para que seu código seja '+; 'bem formatado.' This.aMessage[2,3]='Para uma tag A HREF, você não pode incluir um atributo de estilo. '+; 'Também não há a necessidade de um TARGET visto que isto é feito automaticamente '+; 'pelo application.' This.aMessage[3,3]='Você não pode incluir um atributo de estilo dentro de uma tag.' This.aMessage[4,3]='Para a tag FONT, somente são suportados os atributos SIZE, COLOR e FACE.' This.aMessage[5,3]='Sua mensagem contém algum obscenidade. Por favor, ajuste-a '+; 'para assegurar que reflita um conteúdo apropriado.' This.aMessage[6,3]='The ##Field## field cannot be empty.' This.aMessage[7,3]='Please, do not put the content of the ##Field## field in uppercase.' This.aMessage[8,3]='O email não pode estar em branco.' This.aMessage[9,3]='Você deve ter pelo menos um ponto em seu email.' This.aMessage[10,3]='Você deve ter pelo menos um @ em seu email.' This.aMessage[11,3]='O email não pode começar com @.' This.aMessage[12,3]='O último caracter do email não pode ser um ponto.' This.aMessage[13,3]='Você não pode ter mais que uma @ no seu email.' This.aMessage[14,3]='Você não pode ter espaços em seu email.' This.aMessage[15,3]='Você não pode ter vírgula em seu email.' This.aMessage[16,3]='O email é inválido.' This.aMessage[17,3]='Você deve ter pelo menos cinco caracteres em seu email.' This.aMessage[18,3]='Após o último ponto, você não pode ter números em seu email.' This.aMessage[19,3]='No seu email, você não pode ter um ponto logo após o @.' This.aMessage[20,3]='Você não pode ter ponto-e-vírgula no seu email.' This.aMessage[21,3]='The country and province/state combination is not valid.' This.aMessage[22,3]='You have to select a province/state.' This.aMessage[23,3]='The ##Field## field already exists, please choose a different one.' This.aMessage[24,3]='A canadian postal code should be seven characters including a '+; 'space in the middle. It has to be a letter, followed by a digit, a letter, a space, '+; 'a digit, a letter and a digit.' This.aMessage[25,3]='A US zip code should be five digits or 10 characters starting '+; 'with five digits, followed by a dash and four digits.' This.aMessage[26,3]='The value of the ##Field## field should be between ##NumericLo## and ##NumericHi##' This.aMessage[27,3]='The value of the ##Field## field should have at least ##MinLength## characters' This.aMessage[28,3]='The ##Field## field cannot contain the ##Character## character' This.aMessage[29,3]='The ##Field## field cannot contain the ##String## word' This.aMessage[30,3]='The ##Field## field cannot be equal to ##String##' This.aMessage[31,3]='The value of the ##Field## field is invalid' This.aMessage[32,3]='The value of the ##Field## field cannot content an initial' This.aMessage[33,3]='The value of the ##Field## field should be zero or between '+; '##NumericLo## and ##NumericHi##' This.aMessage[34,3]='The value of the ##Field## field should not be longer than '+; '##MaxLength## characters' This.aMessage[35,3]='The ##Field## field cannot contain a character.' * Text in Spanish This.aMessage[1,4]='Los tags IMG, TABLE, HTML, BODY, FORM, BLINK, OBJECT, SERVER, STYLE, '+; 'IFRAME, TR, TD, SELECT, HR, DIV, SPAN, FRAME, BUTTON, INPUT, TEXTAREA, TBODY ,MARQUEE y '+; 'TITLE sólo están permitidos dentro del tag PRE. Si desea incluír código en su, '+; 'hágalo encerrado en los tag PRE. El parser de application se '+; 'asegurará que su código quede bien formateado.' This.aMessage[2,4]='Para el tag A HREF, no puede incluir atributos de estilo. '+; 'No hay necesidad de poner TARGET ya que esto es manejado automáticamente por '+; 'application.' This.aMessage[3,4]='No puede incluir atributos de estilo.' This.aMessage[4,4]='Para el tag FONT sólo se soportan los atributos SIZE, COLOR y FACE.' This.aMessage[5,4]='Su mensaje contiene obscenidades. Por favor, modifíquelo para '+; 'asegurarse que refleje un contenido apropiado.' This.aMessage[6,4]='The ##Field## field cannot be empty.' This.aMessage[7,4]='Please, do not put the content of the ##Field## field in uppercase.' This.aMessage[8,4]='La dirección de email no puede estar en blanco.' This.aMessage[9,4]='Debe tener al menos un punto en su email.' This.aMessage[10,4]='Debe tener al menos una @ en su email.' This.aMessage[11,4]='El email no puede comenzar con @.' This.aMessage[12,4]='El último caracter de la dirección de email no puede ser un punto.' This.aMessage[13,4]='No puede tener más de una @ en su email.' This.aMessage[14,4]='No puede haber espacios en el email.' This.aMessage[15,4]='No puede tener una coma en su email.' This.aMessage[16,4]='La dirección de correo electrónico no es válida.' This.aMessage[17,4]='Tiene que tener al menos cinco caracteres en su email.' This.aMessage[18,4]='Luego del último punto no puede tener números en su email.' This.aMessage[19,4]='No puede tener un punto justo detrás de @ en su email.' This.aMessage[20,4]='No puede tener un punto y coma en su email.' This.aMessage[21,4]='The country and province/state combination is not valid.' This.aMessage[22,4]='You have to select a province/state.' This.aMessage[23,4]='The ##Field## field already exists, please choose a different one.' This.aMessage[24,4]='A canadian postal code should be seven characters including a '+; 'space in the middle. It has to be a letter, followed by a digit, a letter, a space, '+; 'a digit, a letter and a digit.' This.aMessage[25,4]='A US zip code should be five digits or 10 characters starting '+; 'with five digits, followed by a dash and four digits.' This.aMessage[26,4]='The value of the ##Field## field should be between ##NumericLo## and ##NumericHi##' This.aMessage[27,4]='The value of the ##Field## field should have at least ##MinLength## characters' This.aMessage[28,4]='The ##Field## field cannot contain the ##Character## character' This.aMessage[29,4]='The ##Field## field cannot contain the ##String## word' This.aMessage[30,4]='The ##Field## field cannot be equal to ##String##' This.aMessage[31,4]='The value of the ##Field## field is invalid' This.aMessage[32,4]='The value of the ##Field## field cannot content an initial' This.aMessage[33,4]='The value of the ##Field## field should be zero or between '+; '##NumericLo## and ##NumericHi##' This.aMessage[34,4]='The value of the ##Field## field should not be longer than '+; '##MaxLength## characters' This.aMessage[35,4]='The ##Field## field cannot contain a character.'
The Validate() method
The Validate() method executes the validation rules, based on Field.dbf, on the current record of the given alias. If a validation fails, the cValidationMessage property will be initialized with the proper validation message.
The method uses the cAlias property to locate the table record in Table.dbf, another data dictionary holding information about each table of the application, to get the primary key of the table. This primary key can then be used in the SQL command to extract all the related fields we have for this table in Field.dbf for the validation.
Not all the fields may be present in the HTML form in regards to the list of fields we have in Field.dbf for the validation. A small function IsProcess() is called to make sure the field is present. If it is, the validation will occur.
Another function is used to retrieve the HTML field value of each field present in the form, in regards to Field.dbf for the validation. This function is called GetProcess(). It returns the HTML field value.
Here is the code of the Validate() method:
* Validation from Field.dbf LOCAL lcAlias,lnLen,lcTable,lnNumero,lcField,lcFieldMemory LOCAL lnOldSel,lcUpper,lnOldRec,lnNoProvinceValue,lnNoCountryValue,lcRight,lnNumeroTable LOCAL lcCodeValue,lcCharReject,lnCounter,lcCharacter,lcStringNot,lcString lnOldSel=SELECT() lnNumero=This.nNumero lcAlias=This.cAlias * Get the table ID SELECT Table lnLen=LEN(Table) lcTable=PADR(lcAlias,lnLen,' ') SEEK UPPER(lcTable) ORDER TAG Table lnNumeroTable=Numero * Get all the fields for this table for the validation SELECT Field.Field,Field.Mandatory,Field.Uppercase,Field.PutUp,Field.PutUpMod,; Field.Email,Field.SaveUpper,Field.SaveLower,Field.URL,Field.Province,Field.Unique,; Field.Code,Field.Tag,Field.Style,Field.Obscenity,Field.Anglais,Field.Francais,Field.Admin,; Field.Initial,Field.NumericF,Field.NumericHi,Field.NumericLo,Field.MinLength,; Field.CharReject,Field.StringNot,Field.ValueNot,Field.SameKey,Field.ZeroValue,; Field.MaxLength,Field.CreditCard FROM Field; WHERE NoTable=lnNumeroTable INTO CURSOR TempSaveTemplate * Apply the validation on all fields SCAN * If we are in the administration module and if we bypass the validation IF This.lAdmin AND Admin LOOP ENDIF lcField=ALLTRIM(Field) * Make sure the field is present on the form IF IsProcess(lcField) * Type of field SELECT(lcAlias) This.cType=TYPE(lcField) DO CASE CASE This.cType='C' lcFieldMemory='lc'+lcField CASE This.cType='N' lcFieldMemory='ln'+lcField CASE This.cType='I' lcFieldMemory='ln'+lcField CASE This.cType='M' lcFieldMemory='lm'+lcField CASE This.cType='D' lcFieldMemory='ld'+lcField CASE This.cType='T' lcFieldMemory='lt'+lcField ENDCASE SELECT TempSaveTemplate This.xValue=EVALUATE(lcFieldMemory) This.cField=ALLTRIM(&gcLanguageText) * The content cannot be empty IF Mandatory DO CASE CASE INLIST(This.cType,'C','M') IF LEN(This.xValue)=0 This.nErrorValidation=6 This.cValidationMessage=STRTRAN(This.aMessage[6,gnLanguage],'##Field##',This.cField) SELECT(lnOldSel) RETURN .F. ENDIF CASE INLIST(This.cType,'N','I') IF This.xValue=0 This.nErrorValidation=6 This.cValidationMessage=STRTRAN(This.aMessage[6,gnLanguage],'##Field##',This.cField) SELECT(lnOldSel) RETURN .F. ENDIF ENDCASE ENDIF * No uppercase IF Uppercase IF LEN(This.xValue)>0 IF This.xValue=UPPER(This.xValue) * This is to avoid something like 11111 to be considered upper IF VAL(This.xValue)=0 This.nErrorValidation=7 This.cValidationMessage=STRTRAN(This.aMessage[7,gnLanguage],'##Field##',This.cField) RETURN .F. SELECT(lnOldSel) ENDIF ENDIF ENDIF ENDIF * We need to check for valid characters * If certain characters are present with a PRE tag, then, that's ok IF Tag IF NOT This.CheckForValidCharacter() This.cValidationMessage=This.ReturnMessage() RETURN .F. ENDIF ENDIF * We need to remove any style from specific tags IF Style IF NOT This.CheckStyle() This.cValidationMessage=This.ReturnMessage() RETURN .F. ENDIF ENDIF * Verify is the string contains obscenities IF Obscenity IF NOT This.Obscenity() This.cValidationMessage=This.ReturnMessage() RETURN .F. ENDIF ENDIF * Province or state IF Province lnNoProvinceValue=lnNoProvince lnNoCountryValue=lnNoCountry * If we have additional addresses in the table, we will bind the country * field with the same province field lcRight=RIGHT(lcField,1) IF VAL(lcRight)>0 lnNoProvinceValue=EVALUATE('lnNoProvinc'+lcRight) lnNoCountryValue=EVALUATE('lnNoCountry'+lcRight) ENDIF * If a province has been selected, it has to match the country IF lnNoProvince>0 SELECT Province SEEK lnNoProvince ORDER TAG Numero IF lnNoCountry<>NoCountry This.nErrorValidation=21 This.cValidationMessage=This.ReturnMessage() SELECT(lnOldSel) RETURN .F. ENDIF SELECT TempSaveTemplate ENDIF * If US or Canada, a province is required IF lnNoProvince=0 AND INLIST(lnNoCountry,1,2) This.nErrorValidation=22 This.cValidationMessage=This.ReturnMessage() SELECT(lnOldSel) RETURN .F. ENDIF ENDIF * Email IF Email This.cEmail=This.xValue IF NOT This.ValidateEmail() SELECT(lnOldSel) RETURN .F. ENDIF This.xValue=LOWER(This.xValue) ENDIF * Unique IF Unique SELECT(lcAlias) IF lnNumero=0 OR (lnNumero>0 AND UPPER(EVALUATE(lcField))<>UPPER(This.xValue)) lnLen=LEN(EVALUATE(lcField)) lcUpper=UPPER(This.xValue) lnOldRec=SaveRec() SEEK PADR(lcUpper,lnLen,' ') ORDER TAG (lcField) IF FOUND() This.nErrorValidation=23 This.cValidationMessage=This.ReturnMessage() This.cValidationMessage=STRTRAN(This.cValidationMessage,'##Field##',&gcLanguageText) SELECT(lnOldSel) RETURN .F. ENDIF RestRec(lnOldRec) ENDIF SELECT TempSaveTemplate ENDIF * Postal code or zip code IF Code * If we have additional addresses in the table, we will bind the country * field with the same province field lcRight=RIGHT(lcField,1) lcCodeValue=lcCode IF VAL(lcRight)>0 lnNoCountryValue=EVALUATE('lnNoCountry'+lcRight) lcCodeValue=EVALUATE('lcCode'+lcRight) ENDIF * Canada IF lnNoCountry=1 * Must be seven characters IF LEN(lcCodeValue)<>7 This.nErrorValidation=24 This.cValidationMessage=This.ReturnMessage() SELECT(lnOldSel) RETURN .F. ENDIF * Must have a space in the middle IF SUBSTR(lcCodeValue,4,1)<>' ' This.nErrorValidation=24 This.cValidationMessage=This.ReturnMessage() SELECT(lnOldSel) RETURN .F. ENDIF * Must be alpha, digit, alpha, space, digit, alpha, digit IF NOT ISALPHA(SUBSTR(lcCodeValue,1,1)) AND NOT ISDIGIT(SUBSTR(lcCodeValue,2,1)) AND; NOT ISALPHA(SUBSTR(lcCodeValue,3,1)) AND NOT ISDIGIT(SUBSTR(lcCodeValue,5,1)) AND; NOT ISALPHA(SUBSTR(lcCodeValue,6,1)) AND NOT ISDIGIT(SUBSTR(lcCodeValue,7,1)) This.nErrorValidation=24 This.cValidationMessage=This.ReturnMessage() SELECT(lnOldSel) RETURN .F. ENDIF ENDIF * United States IF lnNoCountry=2 * Must be five digits or five digits, followed by a dash and four digits IF LEN(lcCodeValue)<>5 AND LEN(lcCodeValue)<>10 This.nErrorValidation=25 This.cValidationMessage=This.ReturnMessage() SELECT(lnOldSel) RETURN .F. ENDIF * Must be all digits IF LEN(lcCodeValue)=5 IF NOT ISDIGIT(SUBSTR(lcCodeValue,1,1)) OR NOT ISDIGIT(SUBSTR(lcCodeValue,2,1)) OR; NOT ISDIGIT(SUBSTR(lcCodeValue,3,1)) OR NOT ISDIGIT(SUBSTR(lcCodeValue,4,1)) OR; NOT ISDIGIT(SUBSTR(lcCodeValue,5,1)) This.nErrorValidation=25 This.cValidationMessage=This.ReturnMessage() SELECT(lnOldSel) RETURN .F. ENDIF ENDIF * Must be five digits, followed by a dash and four digits IF LEN(lcCodeValue)=10 IF NOT ISDIGIT(SUBSTR(lcCodeValue,1,1)) OR NOT ISDIGIT(SUBSTR(lcCodeValue,2,1)) OR; NOT ISDIGIT(SUBSTR(lcCodeValue,3,1)) OR NOT ISDIGIT(SUBSTR(lcCodeValue,4,1)) OR; NOT ISDIGIT(SUBSTR(lcCodeValue,5,1)) OR NOT SUBSTR(lcCodeValue,6,1)='-' OR; NOT ISDIGIT(SUBSTR(lcCodeValue,7,1)) OR NOT ISDIGIT(SUBSTR(lcCodeValue,8,1)) OR; NOT ISDIGIT(SUBSTR(lcCodeValue,9,1)) OR NOT ISDIGIT(SUBSTR(lcCodeValue,10,1)) This.nErrorValidation=25 This.cValidationMessage=This.ReturnMessage() SELECT(lnOldSel) RETURN .F. ENDIF ENDIF ENDIF This.xValue=UPPER(This.xValue) &lcFieldMemory=This.xValue ENDIF * Initial IF Initial IF LEN(This.xValue)=1 OR (LEN(This.xValue)=2 AND SUBSTR(This.xValue,2,1)='.') This.nErrorValidation=32 This.cValidationMessage=STRTRAN(This.aMessage[32,gnLanguage],'##Field##',This.cField) SELECT(lnOldSel) RETURN .F. ENDIF ENDIF * Minimum length IF MinLength>0 IF This.cType='M' IF LEN(This.xValue)0 IF This.cType='M' IF LEN(This.xValue)>MaxLength This.nErrorValidation=34 This.cValidationMessage=STRTRAN(This.aMessage[34,gnLanguage],'##Field##',This.cField) This.cValidationMessage=STRTRAN(This.cValidationMessage,'##MaxLength##',; ALLTRIM(STR(MaxLength))) SELECT(lnOldSel) RETURN .F. ENDIF ENDIF ENDIF * Characters rejected lcCharReject=ALLTRIM(CharReject) IF LEN(lcCharReject)>0 IF INLIST(This.cType,'C','M') FOR lnCounter=1 TO LEN(lcCharReject) lcCharacter=SUBSTR(lcCharReject,lnCounter,1) IF lcCharacter$This.xValue This.nErrorValidation=28 This.cValidationMessage=STRTRAN(This.aMessage[28,gnLanguage],'##Field##',This.cField) This.cValidationMessage=STRTRAN(This.cValidationMessage,'##Character##',lcCharacter) SELECT(lnOldSel) RETURN .F. ENDIF NEXT ENDIF ENDIF * Strings rejected lcStringNot=ALLTRIM(StringNot) IF LEN(lcStringNot)>0 IF INLIST(This.cType,'C','M') FOR lnCounter=1 TO ParmCnt(lcStringNot) lcString=GetParm(lcStringNot,lnCounter) IF UPPER(lcString)$UPPER(This.xValue) This.nErrorValidation=29 This.cValidationMessage=STRTRAN(This.aMessage[29,gnLanguage],'##Field##',This.cField) This.cValidationMessage=STRTRAN(This.cValidationMessage,'##String##',lcString) SELECT(lnOldSel) RETURN .F. ENDIF NEXT ENDIF ENDIF * Values rejected lcStringNot=ALLTRIM(ValueNot) IF LEN(lcStringNot)>0 IF INLIST(This.cType,'C','M') FOR lnCounter=1 TO ParmCnt(lcStringNot) lcString=GetParm(lcStringNot,lnCounter) IF UPPER(lcString)==UPPER(This.xValue) This.nErrorValidation=30 This.cValidationMessage=STRTRAN(This.aMessage[30,gnLanguage],'##Field##',This.cField) This.cValidationMessage=STRTRAN(This.cValidationMessage,'##String##',lcString) SELECT(lnOldSel) RETURN .F. ENDIF NEXT ENDIF ENDIF * Numeric field IF NumericF IF ZeroValue IF This.xValue<>0 AND NOT (This.xValue>=NumericLo AND This.xValue<=NumericHi) This.nErrorValidation=33 This.cValidationMessage=STRTRAN(This.aMessage[33,gnLanguage],'##Field##',This.cField) This.cValidationMessage=STRTRAN(This.cValidationMessage,'##NumericLo##',; ALLTRIM(STR(NumericLo))) This.cValidationMessage=STRTRAN(This.cValidationMessage,'##NumericHi##',; ALLTRIM(STR(NumericHi))) RETURN .F. ENDIF ELSE IF NOT (This.xValue>=NumericLo AND This.xValue<=NumericHi) This.nErrorValidation=26 This.cValidationMessage=STRTRAN(This.aMessage[26,gnLanguage],'##Field##',This.cField) This.cValidationMessage=STRTRAN(This.cValidationMessage,'##NumericLo##',; ALLTRIM(STR(NumericLo))) This.cValidationMessage=STRTRAN(This.cValidationMessage,'##NumericHi##',; ALLTRIM(STR(NumericHi))) RETURN .F. ENDIF ENDIF ENDIF * Same key not accepted IF SameKey IF INLIST(This.cType,'C','M') IF LEN(This.xValue)>0 AND UPPER(This.xVvalue)=REPLICATE(SUBSTR(This.xValue,1,1),; LEN(This.xValue)) This.nErrorValidation=31 This.cValidationMessage=STRTRAN(This.aMessage[31,gnLanguage],'##Field##',This.cField) RETURN .F. ENDIF ENDIF ENDIF * Credit card IF CreditCard * Verify for all digits FOR lnCounter=1 TO LEN(This.xValue) lcString=SUBSTR(This.xValue,lnCounter,1) IF NOT ISDIGIT(lcString) This.nErrorValidation=35 This.cValidationMessage=STRTRAN(This.aMessage[35,gnLanguage],'##Field##',This.cField) SELECT(lnOldSel) RETURN .F. ENDIF NEXT * We need to have at least 15 characters IF LEN(This.xValue)<15 This.nErrorValidation=27 This.cValidationMessage=STRTRAN(This.aMessage[27,gnLanguage],'##Field##',This.cField) This.cValidationMessage=STRTRAN(This.cValidationMessage,'##MinLength##','15') SELECT(lnOldSel) RETURN .F. ENDIF * We cannot have more than 16 characters IF LEN(This.xValue)>16 This.nErrorValidation=34 This.cValidationMessage=STRTRAN(This.aMessage[34,gnLanguage],'##Field##',This.cField) This.cValidationMessage=STRTRAN(This.cValidationMessage,'##MaxLength##','16') SELECT(lnOldSel) RETURN .F. ENDIF ENDIF * PutUp IF PutUp This.xValue=PutUp(This.xValue) &lcFieldMemory=This.xValue ENDIF * If we save in uppercase IF SaveUpper This.xValue=UPPER(This.xValue) &lcFieldMemory=This.xValue ENDIF * If we save in lowercase IF SaveLower This.xValue=LOWER(This.xValue) &lcFieldMemory=This.xValue ENDIF * URL IF URL This.xValue=AdjustUrl(This.xValue) &lcFieldMemory=This.xValue ENDIF ENDIF ENDSCAN SELECT(lnOldSel)
The credit card field
The credit card field in Field.dbf is used to identify a field for credit card validations in regards to the number. For now, what the code does is to make sure we only have digits, that we at least 15 characters and not more than 16. This is good enough to validate the basic rules for Visa, Mastercard and American Express.
With the current design of Field.dbf and this object, it would have been possible to achieve the same without defining any code for this credit card validation. We already have a field in Field.dbf to identify if the field should be numeric, another one for the minimum length and another one for the maximum length. However, it has been done like that for expandability in order to enhance this code later on to insert additional credit card verifications such as a proper credit card number before querying the gateway and some other similar functions. This also allows us to only check one field in Field.dbf to obtain this logic instead of defining multiple ones to achieve the same.
Putting it all together
For our demo, I am including a test form for a data entry of the address of a member. I have included all the fields that appear in the form in Field.dbf. Thus, all of them applies, in this case, to our validation object. They will be processed one by one. As soon as a validation message applies, the process will stop and the control returns to the caller object. The caller object can then use of cValidationMessage property to extract the validation message.
Here is a representation of the Field.dbf table for the fields that belongs to the member table in regards to our test form:
When we update a record, it looks like this:
Our test form looks as follow:
When the user tries to save, if a validation message applies, we can then regenerate the form, with the pre-entered values the user entered, and the validation message. So, from this point, the user can continue to work on the form by adjusting the related field value based on the validation message he received.
In the case of our test form, if the user tries to save, he should get the following message: