Menu

A Conventional Blog for Convention Based Coders

Month: September 2016

It’s sometimes helpful to know in code if a validation has been performed, or if an individual field has changed its state.

While there are no events currently raised by the ValidationController, we can add our own thanks to some rather snazzy built-in features of Aurelia.

If you’ve read my posts in Gitter, you may have noticed me ranting on about the Event Aggregator, and how frustrated I get at it being misused by people as an event queue, while in reality it’s a very simple pub-sub service. Well in this case the fact that it is simply a pub-sub service is perfect for our needs.

When @jdanyow pointed me to the Event Aggregator code for event handling, my first comment was that the global application doesn’t need to know about every event, only those in the scope of the validation controller, then he pointed out this fascinatingly simple little piece of code:

Now this is awesome. This is a drop-in pub-sub handler re-using the event aggregator, intended for use at a local scope level. At the end of the day the default EventAggregator is exactly the same, implemented as a singleton for our entire application, but that simple instruction lets us inject event handling into any object instance we create.

Almost… unfortunately this is tricky for TypeScript because this code is injecting functions onto an object instance, and people will also argue that this is an anti-pattern, because you’re morphing someone else’s code, but at least it exposes the fact that new instances of Event Aggregators can be used anywhere… so let’s just extend the base EventAggregator class, and use that.

So, let’s apply it to the validation service!

Now where can we go to hear every time validation is performed? A validation Renderer! This is called for every render operation, and is passed in all of the information regarding the result, including the messages, related UI elements, and the reason for the change (this is not yet published, but should be in the next day or so).

So do we want to hack our existing renderer do add this? Well we could, or we could simply add a new instance, as the controller allows us to attach as many renderers as we like.

OK, so let’s create a custom renderer to raise out events:

event-renderer.js

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

exportclassEventRendererextendsEventAggregator{

render(instruction){

this.publish('validate',instruction);

for(let{error,elements}of instruction.unrender){

for(let element of elements){

this.publish('hide',{element,error});

}

}

for(let{error,elements}of instruction.render){

for(let element of elements){

this.publish('show',{element,error});

}

}

}

}

Then we’ll add it to our controller, along with the usual one we want to display our errors, and while we’re at it we’ll add a few subscriptions to monitor various events occurring in the controller:

Remember though that these events can be used to detect changes visually on the screen, but can’t be used to indicate the overall validity of a form, as some errors may be for field with rules which have not yet been validated, as the user has not yet tabbed through or form-validated.

One additional nice feature is that the result above shows 6 ‘render’ errors, but only 5 listed. So where’s the 6th? Well that’s a validation rule against a field that has no UI element, so in my implementation I’m not raising a ‘show’ event. But the error is available through the ‘validate’ event, and if you wanted you could easily generate an additional event for those ‘hidden’ failures:

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

for(let{error,elements}of instruction.unrender){

if(!elements.length)

this.publish('hide-non-ui',{null,error});

else

for(let element of elements){

this.publish('hide',{element,error});

}

}

for(let{error,elements}of instruction.render){

if(!elements.length)

this.publish('show-non-ui',{null,error});

else

for(let element of elements){

this.publish('show',{element,error});

}

}

Oh, and don’t forget to dispose of your EventAggregator when you’re finished!

The return value from subscribe is function called dispose() that closes the subscription, and without this call your VM may be left sitting in memory (and may continue to react to published events)