tutorial: building apps with Déjà Vu
This tutorial covers the fundamentals of Déjà Vu through a social news aggregation app SN, that is a simple clone of Hacker News. In SN, users can submit links, which can then be voted up by other members. Users can also comment on a post or comment and upvote comments. The code of the app can be found in samples/sn.
contents
including and configuring concepts
choosing concepts
The process of building a Déjà Vu app begins by navigating the catalog of concepts to find the concepts that provide the functionality you need for your app. The documentation accompanying a concept includes information about the configuration options (e.g., their effect on behavior) and the exported components. Concept components control a patch of the screen, are interactive, and can read and write back-end data. They also have input and output properties.
SN uses the Authentication concept to handle user authentication, Comment to comment on posts and reply to comments, and Scoring twice: for keeping track of upvotes on both posts and on comments separately. It also uses Property to save a post’s author, title, and link—the Property concept gives you a data-model-defining facility for simple CRUD behavior.
including concepts
The concepts used by the app are specified in the app’s JSON config file (dvconfig.json). The code for SN’s config file is shown below:
The usedConcepts
object has one key-value pair per concept instance. The key
(e.g., post
on line 6)
determines the name that is going to be used in the HTML to refer to that instance.
The value is another object with two
optional
key-value pairs: concept
for providing the name of the concept to
be
instantiated (e.g., property
on line 7), and
config
for specifying the configuring options
for the concept instance (e.g., the object in lines
8-18).
If no concept name is provided, the concept instantiated is
the one with name equal to the
instance name. Thus, on line 4,
the concept to be instantiated for authentication
is Authentication.
If no
configuration object is given, the default configuration
for that concept is used.
The format of the values of configuration options
is also JSON.
configuring concepts
In SN, we only have to configure Property.
Property accepts a configuration variable (schema
) that expects a
JSON Schema to describe the objects it will be
saving.
We use schema
to specify the type of properties we expect our
objects (the posts) to have (an author, a title, and a url). The effect of this is that when we
include a component from Property,
such as create-object
,
the component will allow
the user to input only those fields—author, title, and url.
Moreover, since we specified that
that the format of the
url field is url
(line 14) and that
all fields (author, title, and url) are
required (line 16), create-object
will expect the user to provide a value for each
one and check that the value given for
the url field is
a valid URL. If the user
doesn’t provide a value for each field or
the value for url is invalid, create-object
will show an
error message.
routes
In the app’s config file, we also define the
name (line 2) and
routes (lines 23-27) of our app.
Each route
maps a URL path to a component.
Any app component can be a page (i.e., accessible
via a URL).
SN’s homepage
is the component home
(line 24) because path
is empty.
If the user navigates to “/login”, the login
component
will be shown (line 25) and if they navigate to “/item”, the
show-post-details
component will be shown (line 26).
linking components
Each app
component is written in a separate HTML file.
Excerpts
of the code for SN’s submit-post
and show-post
components, together with a
screenshot
of how they appear to users, are shown below:
Our template language looks, by design, similar to other template languages. To create an app component, users include components and synchronize them to implement the desired functionality.
including components
App components can contain other components, which can be
concept components (i.e., components defined by concepts) or app components (i.e., components that
are defined as part of the app being developed).
Components are included as if they were HTML
elements, with the tag given by the
concept instance or app name,
followed by the component name.
Thus, submit-post
includes one app component,
navbar
(line 2); two concept components, create-object
of the
post
instance of Property
(lines 5-8), and
create-score
of the scoreposts
instance of
Scoring (lines 9-10);
and one built-in component,
dv.gen-id
(line 4),
which generates a unique ID
(built-in components
can be regarded as free-standing concept components).
I/O binding
Inputs to a component are bound with the syntax property=expr
.
Template expressions can include
literal values, app component inputs,
outputs from other components
on the page, and
standard
operators. The
syntax of template expressions is
similar to that of JavaScript expressions,
but no function calls, or JavaScript
operators
that produce side-effects are allowed.
Components can be fired repeatedly, and the output variables hold the values from the last execution. This is how a selector widget such as a dropdown would typically be connected to another component: the dropdown sets an output property every time it is activated containing the choice the user made, which is then bound to the input variable of components that use that choice.
Some inputs are for customizing
appearance and have no impact on the behavior of the component.
For example, as a result of setting
buttonLabel
to "Submit"
on line 7 of submit-post
,
create-object
’s button will carry the label “Submit” instead of the
default button label “Create Post”.
The hidden
parameter of show-object
in show-post
marks show-object
as hidden.
Hidden components are not shown to the user, but still run
as if they were visible. Thus
the object data itself is still loaded, emitted as
an output, and used in several parts of the view—the title
and the link are used and shown through lines 5-6, while
the author is displayed on line 11.
App component inputs are preceded with \$
. For example, show-post
has an input named id
that it uses in lines 2, 4, 9, and 12.
Based on this input, how-object
will show the post whose ID matches the given one;
upvote
will use the ID as the target of the score if one is created;
show-target
will show the score with the given ID;
and clicking on the “comments” link will take
the user to show-post-details
with its
input id
set to the given ID.
ID sharing
To bind entities in different concepts we use a common identifier.
In submit-post
, for example,
the same ID, generated by gen-id
(line 4), is passed
to create-object
(line 5) and create-score
(line 9). As a result,
create-score
will create a score
with the same target ID as the object create-object
will create.
Similarly, in show-post
, we feed
the id
input
to show-object
(line 2) and
show-target
(line 9). Each of these components loads and
displays its own
view of the post entity; the effect when put together is to
display a SN post object.
synchronizing components
action types
Concept components have two actions: an evaluation action (eval) and an execution action (exec). The concept author determines what triggers the evaluation or execution of the component. Typically, the loading of the component itself triggers the evaluation of a component, and some user interaction (e.g., a button click) triggers its execution. What happens on eval or exec is also up to the author of the concept—the only restriction is that an eval action cannot produce a side effect. App components don’t have actions. This is because app components have no back-end functionality of their own—all data and behavior is pushed to concepts.
Eval/exec actions support the conventional user interaction pattern of web apps: data is loaded and displayed, and then the user executes commands to mutate the data. But perhaps concept components could offer arbitrary action types. This would require more work from the user (who would now have to specify what action types are to be coordinated), but could allow more complicated applications to be built, without requiring the modification or creation of a concept.
synchronizing actions
There are two kinds of app component: a regular component and
a transaction (tx) component.
A regular component
allows any of its children
components to eval/exec without
synchronization.
A tx component, on the other
hand, synchronizes
the eval/exec of the concept components it wraps.
As a result, the eval (or exec) of one concept
component triggers the eval (or exec) of all its
sibling concept components and they either
complete in their entirety (if all succeeded) or
have no effect whatsoever other than displaying an error
message to the user (if one or more aborts).
Instead of putting each component in separate HTML files,
you can wrap elements in another component with the dv.tx
tag to create an
anonymous tx component with content equal to the content of the tag.
In SN’s submit-post
,
the tx is triggered by
create-object
(line 5)
when the user clicks on the button.
This is because
the button in the create-object
component of Property causes
the component to execute on click,
and
since create-object
is wrapped in a dv.tx
, it will trigger the execution of all its sibling concept components. As a result,
a new post and a new score will be created, bound by
the shared id (the target id of the score is the post id).