Responding to events

    We will start with an application that renders widgets containing the portrait and names of several employees for the hypothetical company, “Biz-E Corp”. In this tutorial, you will learn how to add event listeners to these widgets so that they show additional information about each worker including a list of their active tasks.

    Prerequisites

    You can open the or download the demo project and run to get started.

    You should install the @dojo/cli command globally. Refer to the article for more information.

    You also need to be familiar with TypeScript as Dojo uses it extensively.

    Create an event listener.

    In Creating widgets, we created an application that contains several widgets that render worker information. In this tutorial, you will add event listeners to these widgets to show additional information about an employee when clicking on the widget.

    The first step is to add the listener itself. In Dojo, event listeners get assigned like any other property passed to the rendering function, v. Look at the Worker widget that is in src/widgets. Currently, the top level DNode has one property assigned: classes.

    Update the object containing that property as follows.

    The onclick property registers a function to call when clicking on the node to which it is attached. In this case, the registered function is a method called flip.

    Add a basic implementation for that method within the Worker class.

    1. flip(): void {
    2. console.log('the flip method has been fired!');
    3. }

    Now, run the app (using dojo build -m dev -w -s) and navigate to . Once there,

    Open the console window and click on any of the worker widgets to confirm that the flip method gets called as expected.

    Using event handlers

    Add a second visual state.

    Now that we have an event handler, it is time to extend the render method to show detailed information about a worker in addition to the current overview. For the sake of this tutorial, we will call the current view the front and the detailed view the back.

    We could add the additional rendering logic in the current render method, but that method could become difficult to maintain as it would have to contain all of the rendering code for both the front and back of the card. Instead, we will generate the two views using two private methods and then call them from the render method.

    Create a new private method called _renderFront and move the existing render code inside it.

    1. private _renderFront() {
    2. const {
    3. firstName = 'firstName',
    4. lastName = 'lastName'
    5. } = this.properties;
    6. return v('div', {
    7. classes: this.theme(css.workerFront),
    8. onclick: this.flip
    9. }, [
    10. v('img', {
    11. classes: this.theme(css.image),
    12. src: 'https://dojo.io/tutorials/resources/worker.svg' }),
    13. v('div', [
    14. v('strong', [ `${lastName}, ${firstName}` ])
    15. ])
    16. ]
    17. );
    18. }

    Create another private method called _renderBack to render the back view.

    This code is not doing anything new. We are composing together multiple virtual nodes to generate the elements required to render the detailed view. This method does, however, refer to some properties and CSS selectors that do not exist yet.

    We need to add three new properties to the WorkerProperties interface. These properties are the email address of the worker, the average number of hours they take to complete a task, and the active tasks for the worker.

    Update the WorkerProperties interface.

    1. export interface WorkerProperties {
    2. firstName?: string;
    3. lastName?: string;
    4. email?: string;
    5. timePerTask?: number;
    6. tasks?: string[];
    7. }

    Now, we need to add the CSS selectors that will provide the rules for rendering this view’s elements.

    Open worker.m.css and replace the existing classes with the following.

    1. box-sizing: border-box;
    2. display: inline-block;
    3. margin-bottom: 20px;
    4. padding-left: 10px;
    5. vertical-align: middle;
    6. }
    7. .image {
    8. margin: 0 auto 20px;
    9. max-width: 250px;
    10. width: 100%;
    11. }
    12. .imageSmall {
    13. margin-bottom: 20px;
    14. vertical-align: middle;
    15. width: 40%;
    16. }
    17. .label {
    18. font-weight: bold;
    19. }
    20. .task {
    21. border: solid 1px #333;
    22. border-radius: 0.3em;
    23. margin-top: 3px;
    24. padding: 3px;
    25. text-align: left;
    26. }
    27. .worker {
    28. flex: 1 1 calc(33% - 20px);
    29. margin: 0 10px 40px;
    30. max-width: 350px;
    31. min-width: 250px;
    32. position: relative;
    33. /* flip transform styles */
    34. perspective: 1000px;
    35. transform-style: preserve-3d;
    36. }
    37. .workerFront, .workerBack {
    38. border: 1px solid #333;
    39. border-radius: 4px;
    40. padding: 30px;
    41. backface-visibility: hidden;
    42. transition: all 0.6s;
    43. transform-style: preserve-3d;
    44. }
    45. .workerBack {
    46. font-size: 0.75em;
    47. height: 100%;
    48. left: 0;
    49. position: absolute;
    50. top: 0;
    51. transform: rotateY(-180deg);
    52. width: 100%;
    53. }
    54. .workerFront {
    55. text-align: center;
    56. transform: rotateY(0deg);
    57. z-index: 2;
    58. }
    59. .reverse .workerFront {
    60. transform: rotateY(180deg);
    61. }
    62. .reverse .workerBack {
    63. transform: rotateY(0deg);
    64. }

    We also need to update the CSS selector for the front view by changing the selector from css.worker to css.workerFront.

    Finally, we need to update the render method to choose between the two rendering methods.

    In general, the use of private state is discouraged. Dojo encourages the use of a form of the pattern, where the properties passed to the component by its parent control the behavior of the component. This pattern helps make components more modular and reusable since the parent component is in complete control of the child component’s behavior and does not need to make any assumptions about its internal state. For widgets that have state, the use of a field to store this kind of data is standard practice in Dojo. Properties are used to allow other components to view and modify a widget’s published state, and private fields are used to enable widgets to encapsulate state information that should not be exposed publicly.

    Use that field's value to determine which side to show.

    1. protected render() {
    2. return v('div', {
    3. classes: this.theme([ css.worker, this._isFlipped ? css.reverse : null ])
    4. }, [
    5. this._renderFront(),
    6. this._renderBack()
    7. ]);
    8. }

    Confirm that everything is working by viewing the application in a browser. All three cards should be showing their front faces. Now change the value of the _isFlipped field to true and, after the application re-compiles, all three widgets should be showing their back faces.

    To re-render our widget, we need to update the flip method to toggle the _isFlipped field and invalidate the widget

    Replace the flip method with this one.

    1. flip(): void {
    2. this._isFlipped = !this._isFlipped;
    3. }

    Now, the widget may flip between its front and back sides by clicking on it.

    Provide additional properties.

    Currently, several of the properties are missing for the widgets. As an exercise, try to update the first widget to contain the following properties:

    This change will pass the specified properties to the first worker. The widget’s parentis responsible for passing properties to the widget. In this application, Workerwidgets are receiving data from the App class via the .

    Summary

    In this tutorial, we learned how to attach event listeners to respond to widget-generated events. Event handlers get assigned to virtual nodes like any other Dojo property.

    If you would like, you can open the completed demo application on or alternatively download the project.

    In , we will work with more complicated interactions in Dojo by extending the demo application, allowing new Workers to be created using forms.