-
Notifications
You must be signed in to change notification settings - Fork 0
Increment the Counter
In this step, we will add the ability to increment the counter by generating an event in the user interface. This will not require many changes to our code but will introduce the following concepts:
- Emitters
- the Default Emitter
- Application Model deltas
- transform-enable and transform-disable
Before we make any changes to the code, start up the development application:
lein repl
(use 'dev)
(run)and navigate to the Data Renderer.
http://localhost:3000/tutorial-client-data-ui.html
All of the changes that we will make will be made to the
tutorial-client.behavior namespace. Before we begin, require the
io.pedestal.app namespace.
[io.pedestal.app :as app]We are now ready to begin.
In the last section we touched briefly on the idea that we need some way to observe changes to the data model. We did not have to explicitly set this up but were able to rely on the defaults.
In Pedestal, Emitters are used to report change. We can always write our own emitter but Pedestal comes with a powerful one build in. The Default Emitter is added to the dataflow definition for us when we do not add one ourselves.
A dataflow description can include an :emit key which configures a
sequence of emitters. The default emitter which was added to our
dataflow can be manually configured like this:
(def example-app
{:version 2
:transform [[:inc [:my-counter] inc-transform]]
:emit [[#{[:*]} (app/default-emitter [])]]})This dataflow has the exact same behavior as if we had omitted the
:emit entry.
Emitters are configured with a vector, the same as transform functions. Each emitter vector has two elements: a set of inputs and an emitter function.
[#{[:*]} (app/default-emitter [])]The input set for the vector above is #{[:*]} and the emitter
function is the default emitter. The vector passed to
default-emitter is a prefix which will be added to all emitted
deltas. Let's make a slight modification to the definition above and
add a prefix.
(def example-app
{:version 2
:transform [[:inc [:my-counter] inc-transform]]
:emit [[#{[:*]} (app/default-emitter [:tutorial])]]})If we save this file and refresh the page we can see that
:my-counter now exists under :tutorial. We can see the emitted
deltas by looking at the console.
[:node-create [] :map]
[:node-create [:tutorial] :map]
[:node-create [:tutorial :my-counter] :map]
[:value [:tutorial :my-counter] nil 1]These deltas represent the change to our data model which was caused
by sending the initial message. Remember that we set this up in the
tutorial-client.start namespace. A renderer can consume this data
and draw changes in the user interface. We can think about these
deltas as describing changes to a tree, building up nodes and setting
values. The Data Renderer receives these changes and draws them
for us.
Each delta has the same general format:
[:op [:path :to :node] args]The op is one of:
:node-create
:node-destroy
:value
:attr
:transform-enable
:transform-disableFor more information about the application model and application model deltas see the Application overview document.
What we would like to do is let the renderer know that some user
action related to the :my-counter node can send a message which will
cause the counter to increment. We know the message that we need to
send.
{msg/type :inc msg/topic [:my-counter]}We use a :transform-enable delta to describe this.
[:transform-enable [:tutorial :my-counter]
:increment-counter [{msg/type :inc msg/topic [:my-counter]}]]Here the op is :transform-enable. The path is [:tutorial :my-counter]. The arguments to :transform-enable are the transform
name and a vector of messages. When this transform is triggered, all
of these messages will be sent.
There is a shorter way to write this.
[:transform-enable [:tutorial :my-counter] :inc [{msg/topic [:my-counter]}]]Each message which does not have a msg/type will be assigned a type
that is the same as the transform name.
We need to arrange for our emitter to send this :transform-enable
delta. There are many ways that this can be done. Since we only need
to send this once, when the application is initialized, we will create
an initialization emitter to do the job.
First we create an emitter function which will send this delta.
(defn init-emitter [_]
[[:transform-enable [:tutorial :my-counter] :inc [{msg/topic [:my-counter]}]]])Emitter functions receive one argument, which we are ignoring here, and return a sequence of deltas.
We would like for this emitter function to be called only during
initialization. To do this we will use the map form for configuring an
emitter and place this emitter function under the :init key.
(def example-app
{:version 2
:transform [[:inc [:my-counter] inc-transform]]
:emit [{:in #{[:*]} :fn (app/default-emitter :tutorial) :init init-emitter}]})Emitters placed under the :init key are called only when Focus
changes. Focus will be covered later in this tutorial.
With these few changes, the Data Renderer will now show a button which we can click to cause the counter to increment.
If you look in the console as you click this button you will see the deltas being rendered with each click.
[:value [:tutorial :my-counter] 1 2]You may be wondering why :transform-enable and :transform-disable
are necessary. They do not actually change the dataflow in any
way. Any message can be sent to the dataflow at any time. There are
several benefits that we get from using these deltas, including but
not limited to:
- a message abstraction
- improved support for testing
- automatic rendering
The most important of these benefits is the fact that it provides an
abstraction for messages. A :transform-enable delta assigns a name
to a sequence of messages. If these messages do not have any
parameters then the rendering code does not need to know anything
about the messages. It just needs to know how to arrange to send them
when something happens. If the messages do have parameters then it
only needs to know about the parameters. We could later decide to add
a new message or add a field to a message and the rendering code would
not have to change.
Test can be written which discover what actions may be performed by
responding to :transform-enable deltas.
Finally, having a standard way to describe messages allows us to automatically render user interfaces during development, generate administrator user interfaces and leverage lots of standard library functions for wiring up events.
We plan to make this application collaborative by allowing any number of people to connect to our service and everyone will be able to see everyone else's counter. This means that we will need to be able to send our counter value off to the server and receive counter values from others. In the next section, we will update the data model so that we have a place to store these counters. We will also add a simulator which will allow us to act as if we were sending and receiving counters even before we have created the service to handle this.
The tag for this step is step3.