Enhancing the Data-entry form class a little bit
In the previous articles of this series, we learned how we could create a simple Data-entry form class, which we can re-use very easily in all of our data-entry forms. Now, in this article, we'll enhance a little bit our class.
The "SalvarRegistro()" method
Do you remember our SalvarRegistro() Method? It contains some code that runs to save data in the table. If you open that method from our "CadastroBasico" class, you will see something like this:
*-- SalvarRegistro() Method =Tableupdate() Thisform.Refresh thisform.nStatus = 0 Messagebox("Registro salvo!")
This way, when this method is executed, all it will do is try to save the record. But, what happens if you need to validate the data before saving it? And what happens if some errors occur on the save attempt?
Forcing an error
For example, let's force a situation that will fire an error:
This is a sort of error which occurs very often, depending on how you generate your primary-keys.
Validating data and handling errors
Let's change the SalvarRegistro() method in order to address those issues:
*-- Try to validate the data If This.Validardados() *-- if the data is valid, try to save it. If Tableupdate() Thisform.Refresh Thisform.nStatus = 0 Messagebox("Registro salvo!") Return .T. Else
*-- If an error has occurred when saving, try to handle the error. Local laErro(1,5), lnErro Aerror(laErro) lnErro = laErro(1,1) Do Case Case lnErro = 1884 && Uniqueness of index violated If laErro(1,3) = "PRIMARY" && Primary-key Replace (Field(1)) with (Evaluate(Field(1)) + 1) Else && Candidate key Messagebox("Candidate-key violation!") Endif EndCase Return .F. Endif Else Return .F. Endif
As you can see, the code now starts invoking the ValidarDados() method and testing the value returned from it. You must create this method in the class. The big gotcha here is that you do not have to fill this method in the class. That's it. The method must be empty. But, why is this like that? Well, this class will be used for a lot of forms and for each form we'll validate different data. So, we cannot foresee all the validations we'll be doing. But we do KNOW that we'll validate data before saving and that's enough for a while.
If the ValidarDados() method returns .T., we can try to save the data. Otherwise, the SalvarRegistro() method must return .F.
Then, we will try to save the data using the TableUpdate() function. If this function returns .T., that means our data was saved and we can let the user know that the record has been saved. But, if TableUpdate() returns .F., that means an error occurred. So, we must handle it.
We can obtain information about the error using the AError() function. This function will create (or use an already created) array variable (in this example, named laErro), containing details about the last error which occurred.
Now, we just need to do a DO CASE structure and test the number of the error. By that, we can decide what sort of treatment we'll apply on this error.
On this example, if the error which occurred has the error number 1884 (Uniqueness of index "name" violated.) - as this was the case when we generated two records with the same ID - we'll test what sort of index was violated. If the index violated was a "PRIMARY" one, we'll increment the value on the first field of the table (by convention, I'll always put my primary key as the first field on any table). Otherwise, if the index violated was a "CANDIDATE" one, we'll just show a message box. Of course, this is a very simple process, you will implement a more detailed handling for this sort of error.
Don't forget to create a fresh new method called ValidarDados(), with no code inside it!
It's now time to save the class. You may go in our CadFornece.scx form as we will change it a little bit.
Don't forget to validate the data
Let's suppose we need to apply some validations on the data of this form. For example, lets assume the "Estado" field cannot be left empty and that the "Data Cadastro" field must not have a date less than 20/03/1980. Well, in order to achieve this, we will put some code inside the ValidarDados() method. Just open this method on the form and put the following code:
With Thisform If Empty(Alltrim(.txtc_UF.Value)) Messagebox("O campo Estado deve ser preenchido!") .txtc_UF.SetFocus() Return .F. Endif If .txtd_Cadastro.Value < {^1980-03-20} Messagebox("A Data de Cadastro deve ser Maior que 20/03/1980!") .txtd_Cadastro.SetFocus() Return .F. EndIf Endwith
This doesn't represent a big problem. If any of the defined conditions failed, the method will return .F. Remember that the SalvarRegistro() method is testing the success of the operation. So, if this method returns .F., the SalvarRegistro() method will not even try to save the data on the table.
Hooks, Hooks and more Hooks!
In a lot of cases, we need to "extend" the functionalities of our classes, because it's impossible to cover all the possibilities when we're creating them. For example, in some cases you might want to send an email just before a record is saved (in order to inform someone about the changes). In another case, you might want to upload some file to a server after a record has been deleted. In order to provide such extensibility, we need to create "hooks" on our classes.
For example, first, you must create two new methods on our "CadastroBasico" form class: a method called AntesDeSalvar() (aka BeforeSave()), and another called DepoisDeSalvar() (aka, AfterSave()). This method must not include any code.
Then, let's change again our SalvarRegistro() method. Now, it will look like this:
If This.AntesDeSalvar() If This.Validardados() If Tableupdate() Thisform.Refresh Thisform.nStatus = 0 This.DepoisDeSalvar() Messagebox("Registro salvo!") Return .T. Else Local laErro(1,5), lnErro Aerror(laErro) lnErro = laErro(1,1) Do Case Case lnErro = 1884 && Violação da unicidade de índice If laErro(1,3) = "PRIMARY" && Chave-primária Replace (Field(1)) With (Evaluate(Field(1)) + 1) Else && Chave-candidata Messagebox("Violação de chave-candidata!") Endif Endcase Return .F. Endif Else Return .F. Endif Endif
As you can see, on the first line we're invoking the AntesDeSalvar() method. This way, before trying to save or even validating the data, this method will be called. Typically, you'll fill this method with some data prepared for the upcoming validations and save actions.
Then, you can see the implementation of the DepoisDeSalvar() method. This method is called right after the save was successful. Typically, you'll fill this method with code to send an email, or any other process that must be executed after a saving.
Let's test it. Open the CadFornece.scx form and then open the AntesDeSalvar() method. Put the following code into it:
MessageBox("We are preparing some data which are required for the save process...")
MessageBox("We just saved the data. Now, we can send an email for someone.")
Run the form and try to edit or add a record. Finally, click on the save button. You should now see the results.
Conclusion
So, when you create your classes, keep in mind to benefit of a hook approach. That will give you some wonderful possibilities of extensibility! I think you got the idea.
Ok, that's all folks! See you on the next article of this series.
Source-code for this article.