Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Early Tutors: Initial pass at a new tutorial living in the repo #1846

Merged
merged 7 commits into from
Mar 29, 2018
Merged
72 changes: 72 additions & 0 deletions docs/getting-started-tutorial/1-view-first-development.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
:idprefix:
:idseparator: -
:toc: right
:toclevels: 2

# View-first Development

If you're developing a user-facing web site or application, one of Lift's
greatest improvements over existing systems is view-first development.
View-first development thoroughly separates the process of creating the user
interface from the process of putting data from the system into it. This way,
you can stay focused on users when you're creating the user interface and
focus on the interface between your backend and the HTML only when
you're working on the backend.

The flip side of view-first development is that it takes some getting used to
if you are accustomed to the typical web MVC framework. The first stop when
figuring out what's going on in a typical web MVC setup is the controller. In
Lift, your first stop is your HTML file. Everything starts in the HTML, where
you decide what it is that you want to present to the user. You don't just think
about user interactions first, you *build* them first, and let them guide your
development forward and inform it at every step of the way. Turning a usability
tested, high-fidelity mockup into a live page has never been so
straightforward.

For our chat app, we're going to focus first on two use cases, formulated as
user stories:

- As a chatter, I want to post a message so that others can see it.
- As a chatter, I want to see messages from me and others so that I can keep
track of the conversation and contribute in context.

To start with, we'll set up a simple `chat.html` page in our `src/main/webapp`
directory (where all HTML files go). All we really need in there for now is a
list of chat messages so far, and a box to put our own chat message into. So,
here's some base HTML to get us going:

```html:src/main/webapp/index.html
<!DOCTYPE html>
<html>
<head>
<title>Chat!</title>
</head>

<body>
<section id="chat">
<ol class="messages">
<li>Hi!</li>
<li>Oh, hey there.</li>
<li>How are you?</li>
<li>Good, you?</li>
</ol>
<form class="send-message">
<label for="new-message">Post message</label>
<input id="new-message" type="text">
<input type="submit" value="Post">
</form>
</section>
</body>
</html>
```

While we're not using it here, it's probably a good idea to start off with
http://html5boilerplate.com[HTML5 Boilerplate]. Indeed, the default Lift
templates all start with exactly that footnote:[Ok, so not exactly. IE
conditional comments need a little additional work in Lift, because Lift is
smart enough to strip all HTML comments in production mode.].

When it comes to user testing, notice that our view is fully-valid HTML, with
placeholder data. It is, in effect, a high-fidelity mockup. And now that we've
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you use high-fidelity with a hyphen. Earlier you use it without. I think we should use the hyphen version consistently.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your eye for detail brings a tear to my eye ;)

got our view sorted out (and, ideally, tested with users), we can start hooking
up link:2-the-lift-menu-system.adoc[the Lift side].
32 changes: 32 additions & 0 deletions docs/getting-started-tutorial/2-the-lift-menu-system.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
:idprefix:
:idseparator: -
:toc: right
:toclevels: 2

# The Lift Menu System

Another distinguishing characteristic of Lift is that it is *secure by
default*. Amongst other things, this means that if you enable Lift's `SiteMap`
menu system, you can't access a file in your `src/main/webapp` directory through
your application unless you explicitly define that it's meant to be accessed.

Hooking up a simple page in `SiteMap` is easy, and seems redundant; rest
assured, we'll explore the real power of `SiteMap` as the application becomes
more complicated. All you have to do for the chat page is add a line to your
`SiteMap.scala` that names the page and points to the file in the `webapp`
directory:

```src/scala/bootstrap/liftweb/Boot.scala
...
Menu.i("Chat") / "chat"
...
```

The string passed to `i` is the name of this menu. We can use that to
link:menu-links[automatically render links for our menu]. It gets processed
through Lift's internationalization system, but since we've got no
internationalization set up for now it'll just go through unchanged. The part
after the `/` specifies where the template will be found—in our case, in the
`chat.html` file directly under `src/main/webapp`.

With that out of the way, we can move on to link:3-adding-snippet-bindings.adoc[bringing our HTML to life].
74 changes: 74 additions & 0 deletions docs/getting-started-tutorial/3-adding-snippet-bindings.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
:idprefix:
:idseparator: -
:toc: right
:toclevels: 2

# Adding Snippet Bindings

In most frameworks, a page's data is looked up by a controller, and backend
code clutters the HTML to produce the correct rendering of the data. This
process is usually done through what amounts to little more than string
munging. Lift throws this paradigm away entirely in favor of a much better
approach based on entities called snippets.

Snippets let you refer to a block of code that is responsible for rendering a
particular part of your page. You add these references by augmenting your HTML
with a few completely valid `data-` attributes that get stripped before the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would a link to the HTML5 specification or some other explanation for data attributes be useful here?

HTML is then sent to the browser. These snippets then take your HTML, fully
parsed into a valid DOM tree, and transform it, providing true decoupling
between your business logic and your template, and an extra level of
security footnote:[We already mentioned that Lift is secure by default, and
another way that manifests is that the template HTML is turned into a
first-class XML tree early in the processing cycle, and snippets just transform
that tree. That means script injection and a variety of other attacks are
significantly more difficult against a Lift codebase.].
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm torn on whether or not the contents of this footnote should be part of the regular paragraph. No further comment than that, just wanted to bring it to your attention.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this is a tutorial, I decided I wanted to keep the top-level content super-focused on implementation rather than reasoning.



Let's look at our chat app specifically. We're going to bind two things: the
list of chat messages, and the text input that lets us actually chat. To the
`ol` that contains the chat messages, we add:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original markup was two pages back. Perhaps we should reproduce it here so the reader is reminded of the overall structure?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes… But let me punt on that a bit. I'm wanting to do a little preprocessing pass on these guys that handles building some of that for you.


```html:src/main/webapp/index.html
<ol class="messages" data-lift="Chat.messages">
```

And to the input form:

```html:src/main/webapp/index.html
<form class="send-message" data-lift="Chat.sendMessage">
```

The two references in the `data-lift` attributes we added indicate two methods
in a class called `Chat`, which Lift searches for in the `code.snippet` package
footnote:[This can be changed using
link:++https://liftweb.net/api/30/api/index.html#net.liftweb.http.LiftRules@addToPackages(what:String):Unit++[`LiftRules.addPackage`.].
We'll write a very basic version that just passes through the contents of the
list and form unchanged, and then in the next section we'll start adding some
behavior. In `src/main/scala/code/snippet/Chat.scala`, add:

```scala:src/main/scala/code/snippet/Chat.scala
package code
package snippet

import scala.xml._

object Chat {
def messages(contents: NodeSeq) = contents
def sendMessage(contents: NodeSeq) = contents
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be really cool at some point in the future to take a shot at integrating dexy so all of these have to compile or something. But that's not for now. =)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

100% part of my plan, yeah.

```

Note that the methods referred to from the template can either take a
`NodeSeq` footnote:[What's a `NodeSeq`? Scala uses a `NodeSeq` to represent an
arbitrary block of XML. It is a __seq___uence of >= 1 __node___s, which can in
turn have children.] and return a `NodeSeq`, or they can take no parameters and
return a `(NodeSeq)=>NodeSeq` function. The `NodeSeq` that is passed in is the
element that invoked the snippet in the template, minus the `data-lift`
attribute. The `NodeSeq` that is returned replaces that element completely in
the resulting output.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worthwhile to include an example of changing the chat page directly by just returning a static NodeSeq. As it is we make reference to the fact that this replacement can happen, but don't provide a way for the user to try it out on their own without them taking some additional logical steps.

I think the addition of that will make the utility of CSS Transforms more obvious, too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point… There are definitely some good spots here to suggest exploration, I think. e.g., “try changing contents to

I am the walrus!

” and see what happens!


Now that we have our snippet methods set up, we can move on to actually showing
some data in them. Right now all they do is pass their contents through
unchanged, so rendering this page in Lift will look just the same as if we just
opened the template directly. To transform them and display our data easily, we
use link:4-css-selector-transforms.adoc[CSS Selector Transforms].
172 changes: 172 additions & 0 deletions docs/getting-started-tutorial/4-css-selector-transforms.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
:idprefix:
:idseparator: -
:toc: right
:toclevels: 2

# CSS Selector Transforms

Because Lift operates by transforming HTML trees, we need an easy way to
specify those transformations. Otherwise we'd be doing a bunch of recursive
tree searches and munges, which would get ugly, unpleasant, and probably end up
being a performance nightmare. To deal with transformations easily, we instead
use a small subset of CSS selectors, with a few Lift variations that allow us to
maximize performance and address additional use cases around tree
transformation.

We'll leave forms for the next section, as forms always come with a catalog of
related functionality, and focus on binding the list of chat messages in this
section. We'll also add a new message before every page load, so that we can see
the list changing.

First, we'll define a variable to hold the messages:

```scala:src/main/scala/code/snippet/Chat.scala
...
object Chat {
var messageEntries = List[String]()
...
}
```

Then, we can change the definition of the `messages` method to bind the
contents of the message list:

```scala:src/main/scala/code/snippet/Chat.scala
...

import net.liftweb.util.Helpers._

...
def messages = {
"li *" #> messageEntries
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Examples in this form are frequently confusing to me when reading getting started guides. Can we present the entire Chat object with imports in each of these since it's fairly self contains?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually want the compiled output here to produce the whole thing with highlighted changes… I have some more thoughts here. Bit off a little more than I could chew the first time I tried to do this, but I think there are some small things we can do to get to that point quickly.

...
```

In the previous section, we mentioned that Lift snippets can return
`(NodeSeq)=>NodeSeq` functions. That is what's happening here: Lift's CSS
selector transforms are actually functions that take a `NodeSeq` and return a
`NodeSeq`, constructed using an easy-to-read syntax.

What we do in this particular transformation is select all ``li``s. We then
specify that we want to transform them by replacing their contents (`*`) by
whatever is on the right. The right side, however, is a list, in this case of
``String``s. When there's a list on the right side of a transformation, Lift
repeats the matched element or elements once for each entry in the list, and
binds the contents of each element in turn.

Let's start up Lift and see what's going on. In your terminal, enter the
directory of the chat app and start up the application:

```
$ sbt
> jetty:start
[info] Compiling 4 Scala sources to /Users/Shadowfiend/github/lift-example/target/scala-2.9.2/classes...
[info] jetty-8.1.7.v20120910
[info] NO JSP Support for /, did not find org.apache.jasper.servlet.JspServlet
[info] started o.e.j.w.WebAppContext{/,[file:/Users/Shadowfiend/github/lift-example/src/main/webapp/]}
[info] started o.e.j.w.WebAppContext{/,[file:/Users/Shadowfiend/github/lift-example/src/main/webapp/]}
[info] Started [email protected]:8080
[success] Total time: 4 s, completed Oct 6, 2013 2:31:01 PM
>
```

Once you see the success message, point your browser to
`http://localhost:8080/`. You should see an empty chat list, since currently
there are no message entries. To fix this, we're going to add a chat message
every time we render the message list:

```scala:src/main/scala/code/snippet/Chat.scala
...
def messages = {
messageEntries :+= "It is now " + formattedTimeNow
"li *" #> messageEntries
}
...
```

Let's recompile and restart the server:

```
> jetty:stop
[info] stopped o.e.j.w.WebAppContext{/,[file:/Users/Shadowfiend/github/lift-example/src/main/webapp/]}
[success] Total time: 0 s, completed Oct 6, 2013 2:36:48 PM
> container:start
[info] Compiling 1 Scala source to /Users/Shadowfiend/github/lift-example/target/scala-2.9.2/classes...
[info] jetty-8.1.7.v20120910
[info] NO JSP Support for /, did not find org.apache.jasper.servlet.JspServlet
[info] started o.e.j.w.WebAppContext{/,[file:/Users/Shadowfiend/github/lift-example/src/main/webapp/]}
[info] started o.e.j.w.WebAppContext{/,[file:/Users/Shadowfiend/github/lift-example/src/main/webapp/]}
[info] Started [email protected]:8080
```

Now if you pull up the page you'll see something that doesn't look quite right.
The markup we're producing should look something like:

```
<li>It is now 13:25 UTC</li>
<li>It is now 13:25 UTC</li>
<li>It is now 13:25 UTC</li>
<li>It is now 13:25 UTC</li>
```

If you reload the page, you'll get something like:

```
<li>It is now 13:25 UTC</li>
<li>It is now 13:25 UTC</li>
<li>It is now 13:25 UTC</li>
<li>It is now 13:25 UTC</li>
<li>It is now 13:26 UTC</li>
<li>It is now 13:26 UTC</li>
<li>It is now 13:26 UTC</li>
<li>It is now 13:26 UTC</li>
```

What's causing all the repetition? Well, remember when we described what the
CSS Selector Transform was doing, we said we “select all ``li``s”. We also said
that the list on the right side means “Lift repeats the matched element **or
elements**”. So we select all the ``li``s, but in the template there are 4, so
that the template when viewed alone (say, for a user test, or when a frontend
developer is editing it) has some content in it. How do we bridge the two
without getting nasty in our HTML?

Lift lets us tag the extra elements with a class `clearable`:

```html:src/main/webapp/index.html
...
<li>Hi!</li>
<li class="clearable">Oh, hey there.</li>
<li class="clearable">How are you?</li>
<li class="clearable">Good, you?</li>
...
```

Then, in our snippet, we can use a special transform called `ClearClearable`,
which will remove all of the tagged elements before we start transforming the
template:

```scala:src/main/scala/code/snippet/Chat.scala
...
def messages = {
messageEntries :+= "It is now " + formattedTimeNow

ClearClearable &
"li *" #> messageEntries
}
...
```

Notice that we combine the two CSS selector transforms here by using `&`. You
can chain together as many CSS selector transforms as you want this way, as long
as they don't modify the same parts of the same element. We'll deal with that
limitation link:13-who-knows[a little later] footnote:[This is because CSS
selector transforms are optimized for speed, and pass through the nodes a
single time to make all of the transformations happen.].
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably also worth adding a footnote here regarding indentation rules, since any Scala developer passing through will likely wonder why we don't represent this as:

ClearClearable &
  "li *" #> messageEntries

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to punt on this… Not sure that note really belongs in a beginner tutorial. I've also been rethinking the general principle, although I like the look of same-indentation. Mostly because if you consider auto-indentation tools your friend, they can actually catch the common error of forgetting an &... Anyway, I don't have a strong thought there for now, but I think we can get away with not mentioning it in the tutorial.


Now if we restart the server and look at the results, we'll see the right thing
happening: one entry per message, and every time we reload the page we get a
new entry.

Now that we've got the list of messages rendering, it's time to get into the
bread and butter of web development: link:5-basic-forms.adoc[forms].
Loading