Crystal Cove 02: It’s a Viewtiful world

Rishav Sharan
4 min readJun 28, 2018

--

Howdy brave one!
If you are here, I am assuming that you have endured the Intro to the series.
Previous Article in the series: Crystal Cove 01: From 0 to Webapp
Next Article in the series: None (to be updated on next publish)

The commit in our repo till this point is here

Didja know that Crystal comes with its own built in templating engine? Now, this is a language which comes with the kitchen sink 😆 and then some.

“But Rishav, not using a templating language is too bare metal!”, you might say.
Damnit! We are gonna go metal. So metal that we get all shiny and chrome. No template engine. Not even ECR. We gonna rough it up. We play with pure strings. Like real men/women/others/apache helicopters.

Let’s see if we can hook up a simple view. But not just a template, we want to have a Layout and a page which is rendered in the layout.

But first, lets update our route to make use of our shiny views.

Add a new routes as;

when "/about/"    ctx.response.content_type = "text/html; charset=utf-8"    ctx.response.print Cove::Layout.render(Cove::Views.about)

The idea here is clear. Layout.render is a function which takes this “about” page as an input and renders it within itself.

Lets add a couple more routes;

when "/about/"    ctx.response.content_type = "text/html; charset=utf-8"    ctx.response.print Cove::Layout.render(Cove::Views.about)when "/"    ctx.response.content_type = "text/html; charset=utf-8"    ctx.response.print Cove::Layout.render(Cove::Views.home)else     ctx.response.content_type = "text/html; charset=utf-8"     ctx.response.print Cove::Layout.render("404! Bewarsies! This be wasteland!")end

We now have the two new routes; “/” and “/about”. And we have even updated the “else” clause to use our Layout to serve the404 warning.

Before we go any further, you may want to read up on the Heredocs section in the main Crystal Guide. Go on, you will need to know about it for our next section.
Done? what? “it is just a multi-line string”?

yep. HereDocs are just multiline strings. 😆
and that’s what we will use to construct our views.

Lets do the Layout file now. In src/cove/views/Layout.cr add this;

Didn’t I tell you, we are gonna be speeding up. 🙀

But, don’t worry about the code right now. We will go through it later.
At the top of the routes file, lets require our views folder as require “./views/**”

Now, Add some new files;
In src/cove/views/pages add About.cr as;

In src/cove/views/pages add Home.cr as;

So whats happening here is that when you call any page, it returns the html section as a string, which then is embedded in our Layout function, which in turn is returned as a string itself to our context.response.

Simple and elegant. :D

Let’s now add a reusable HTML component. Lets add a navbar on the top of our page.

In src/cove/views/componentsadd Navbar.cr as;

If you check the Layout file again, you will know that the Navbar will be rendered in the section where you have the #{Cove::Views.navbar} string.

Similarly, the page (About or Home) will be rendered in the place of #{page} string.

Now we should be looking at adding custom css and js to our webpage. Maybe add a public folder. Maybe buy a manor in the countryside. Have a relaxed life.
No. We don’t need no public folder! 😆
We are the cool kids. We do things the radical way.

We will instead use the same pattern to add our custom css and js to our webpage. Even this custom css/js is essentially a component for us and we can simply embed them in our Layout file. What this means is that while we get ease of maintaining a separate css/js file, during render everything is dumped into a single html file. This means that instead of having 3 http connections, my server needs to serve only 1 connection which saves the overall roundtrip time significantly.
So, lets add these files;
In src/cove/views/ add CSS.cr as;

In src/cove/views/ add JS.cr as;

That’s it. Throw your custom global JS here, custom global css there and you are ready to run.

To summarize, you can compose an HTML component as;

module Cove::Views  def self.my_component    html = <<-HTML    <script type="text/javascript">

var foo = array('placeholder1', 'placeholder2');
document.getElementById("fooHolder").innerHTML = foo.toString();
</script>

<p id="fooHolder"></p>
HTML endend

If there is any js/css specific to that component, just throw it in here. Your component is now fully reusable. There are some small caveats but we will tackle them later.

What we have achieved here is an amazingly powerful pattern. You can now write composable HTML where each component can be defined in its own separate file. On server request, the app will just stitch up the relevant HTML for you to view.

If there are operations that I need to do on the view, like have multiple similar components, or show/hide some bit conditionally, we use simple crystal code in our views. No fiddly template escape strings.

Lets take our baby for spin (as if you haven’t already 😻)

The Home page:

The About Page:

The other two links are currently non functional and will drop you on a 404 page.

Thank you for being a wonderful audience. The current state of the repo is here

--

--