Friday 2nd October, 2015

Editor's server-side events

This is the second in a series of posts which describes the new features of Editor 1.5. Previously multi-row editing was the focus of discussion, while in this post I will look at the server-side events that have been added to the PHP and .NET libraries which are available as part of the Editor package and demonstrate how they can be used to increase the flexibility of the server-side software.

Why server-side events

The PHP and .NET Editor server-side libraries make is very simple to create a read / write table, potentially with complex actions such as file upload and joining tables, but still rooted with simple read / write operations. For larger applications this is a good start, but often simply isn't enough - you want to be able to perform certain actions based on the data that an end user performs.

A common example for server-side events, and one we'll explore below, would be writing data changes to a database log table to provide trace information and accountability. Another example might be to kick off an external process such as a build script based on the user input. Whatever the reason, events now provide this ability in Editor.

How to use

The PHP and .NET libraries provide events that are triggered on a per row basis - this is important as Editor 1.5 has multi-row editing abilities. Events are are triggered immediately prior to and immediately after creating, editing or deleting a row. The events before allow fields and input data to be modified, while the events after can be used to provide notification that the action has occurred.

The following events are triggered by the Editor libraries :

PHP name .NET name Description
preCreate PreCreate Triggered immediately prior to creating a new row
postCreate PostCreate Triggered immediately after a new row has been created
preEdit PreEdit Triggered immediately prior to updating an existing row
postEdit PostEdit Triggered immediately after an existing row has been updated
preRemove PreRemove Triggered immediately prior to deleting an existing row
postRemove PostRemove Triggered immediately after a row has been deleted

Each event has its own parameters that are passed into it, please refer to the PHP and .NET Editor events documentation for full details of these events and their options.

PHP

PHP does not have native events, so the Editor libraries present an interface that will immediately be familiar to anyone who has worked with jQuery before - an on() method that accepts the event name as the first parameter and a function as the second; this function will be executed when the event is triggered.

Consider for example the following Editor initialisation:

Editor::inst( $db, 'users' )
    ->fields(
        Field::inst( 'name' ),
        Field::inst( 'username' ),
        Field::inst( 'password' )
    )
    ->on( 'postCreate', function ( $e, $id, $values, $row ) {
        syslog( LOG_INFO, 'New user registered: '.$row['username'] );
    } )
    ->process( $_POST )
    ->json();

The majority of the above code will be familiar if you have used the Editor PHP libraries before - we have a user table with three fields. The interesting part here is the Editor->on() method - a syslog message is created whenever a new user is created (see below for more detailed logging).

That is all there is to using events with the PHP libraries - listen for the event and define a function that will take whatever action you require!

.NET

C# has native event handling built into the language and the Editor libraries use these methods that you will already be familiar with to present an API consistent with other C# libraries.

To listen for an event simply add the handler to the event name that you wish to listen for:

var editor = new Editor(Db, "users")
    .Model<UserModel>();

editor.PostCreate += (sender, e) =>
    AppLog( "New user registered: "+(string)e.Values["Username"] );

return Json(
    editor.Process(request).Data()
);

where AppLog is a local function that will write to a file or database, and UserModel is:

public class StaffModel
{
    public string Name { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }
}

Examples

We now know why events can be useful and also how they can be applied in our code, so lets take a look at applications of these techniques. The following examples will all use the simple users table defined above, and only the event handler code will be shown since everything else can stay the same.

Password fields

For password fields you will often want to write a new value to the database only when a new value is given by the end user. If there is no value given then the original should be retained. This isn't a problem for most fields since their values can easily be read from the database, but this is not the case with passwords, where you don't want to (and shouldn't be able to) read the actual values (they should after all be hashed - which you can do with a set formatter: PHP - .NET).

Using the preEdit / PreEdit event we can check to see if the user has submitted a value - if not, then we can stop the field from being written to so the password isn't set to be an empty string!:

PHP:
->on( 'preEdit', function ( $e, $id, $values ) {
    if ( $values['password'] === '' ) {
        $e->field( 'password' )->set( false );
    }
} );
.NET:
editor.PreEdit += (sender, e) => {
    if ( e.Values["Password"] == "" ) {
        editor.Field("Password").Set(false);
    }
};

E-mail notification

Occasionally you might have a form that is mission critical - you add validation to every field but you still want to know when editing occurs on the table so you can keep tabs on changes. With the post events this is super easy:

PHP:
->on( 'postCreate', function ( $editor, $id, $values, $row ) {
    mail( 'myself@localhost', 'Row created', 'New row with id '.$id.' created' );
} )
->on( postEdit', function ( $editor, $id, $values, $row ) {
    mail( 'myself@localhost', 'Row edited', 'Row with id '.$id.' edited' );
} )
->on( postRemove', function ( $editor, $id, $values ) {
    mail( 'myself@localhost', 'Row deleted', 'Row with id '.$id.' deleted' );
} )
.NET:
editor.PostCreate += (sender, e) =>
    Email( "Row created", "New row with id "+e.id+" created" );
editor.PostEdit += (sender, e) =>
    Email( "Row edited", "Row with id "+e.id+" edited" );
editor.PostRemove += (sender, e) =>
    Email( "Row deleted", "Row with id "+e.id+" deleted" );

where the function Email might be (see Scott Gu's blog for more details):

private void Email( string subject, string message )
{ 
    MailMessage message = new MailMessage();
    message.From = new MailAddress("sever@localhost);
    message.To.Add(new MailAddress("myself@localhost"));
    message.Subject = subject;
    message.Body = message;
     
    SmtpClient client = new SmtpClient();
    client.Send(message);
}

Logging

Of course getting an e-mail message for every row editing action on larger or more commonly edited tables is not going to be practical. However, being able to trace changes can be very important, even mandatory in some cases, at which point you would want to start considering writing log information to a database.

Again as events allow execution of any arbitrary function, we can easily write information into a database, particularly as the Editor server-side libraries provide database methods to allow simple SQL commands to be trivially executed.

The following show postEdit / PostEdit events only, but this could easily be extended to operate for create and remove events as well - with the insert potentially being moved to a function to ensure DRY.

PHP
->on( 'postEdit', function ( $editor, $id, $values, $row ) {
    $editor->db()->insert( 'log', array(
        'user'   => $_SESSION['username'],
        'action' => 'Edit users table row',
        'values' => json_encode( $values ),
        'row'    => $id,
        'when'   => date('c')
    ) );
} )
.NET
editor.PostCreate += (sender, e) => db.Insert("log", new Dictionary<string, object>{
    { "user",   Session["user_id"] },
    { "action", "Edit users table row" },
    { "values", JsonConvert.SerializeObject( values ) },
    { "row",    id },
    { "when",   DateTime.Now.ToString("h:mm:ss tt") }
});

Conclusion

This concludes the second in the three part series introducing Editor 1.5 - next up is the form submission options available in Editor 1.5.