In the last article, we managed to build an app that allowed users to show off their HTML, CSS and JavaScript skills by creating ‘riddles’ – small snippets of HTML, CSS and JavaScript. As an added bonus it also allowed users to use SCSS for styling and Markdown for the HTML. In this part we’re going to add some improvements to the app to make it more like the sites it clones.

iFrames

The first thing that we’ll do is place each riddle in it’s own iframe. This means that the riddle will effectively be in it’s own HTML document. This has the advantage of making each riddle behave exactly as it would if it was a self contained web page, so users will be able to load external librares and web fonts which wasn’t possible before.

To implement an iframe, we need to change the show view so it looks like this:

@@show
h1.title== @riddle.title
#riddle
  iframe src="/#{@riddle.id}"

We need a route handler for the iframe. In the src attribute of the iframe we used the route “/#{@riddle.id}” which is basically just the id of the Riddle. We need this so we can find the Riddle in the database, here’s the route handler that we need:

get '/:id' do
  @riddle = Riddle.get(params[:id])
  slim :riddle, layout: false
end

This gets the Riddle that corresponds to the id given from the database. We then display a new view called ‘riddle’. Notice that the layout is set to be false, if it wasn’t then our layout would be displayed again in the iframe, which isn’t what we want. We want the iframe to have it’s own HTML head and body. These will go directly in the view, as you can see below:

@@riddle
doctype html
html lang="en"
  head
    title== @riddle.title
    meta charset="utf-8"
    style
      == scss @riddle.css
    script
      == @riddle.js
  body
    == markdown @riddle.html

As you can see, we’re now using inline style and script tags to display the CSS and JavaScript. This is to avoid having to hit the database each time. And because each riddle has its own CSS and JavaScript, using inline styles and scripts will be fine. This means that we won’t need routes for them, so we can remove the following route handlers:

get '/css/riddle/:id/styles.css' do
  riddle = Riddle.get(params[:id])
  scss "#riddle #{riddle.css}"
end

get '/js/riddle/:id/script.js' do
  riddle = Riddle.get(params[:id])
  content_type 'text/javascript'
  render :str, riddle.js, :layout => false
end

We also need to remove the following lines that link to them in our layout:

- if @riddle
  link rel="stylesheet" href="/css/riddle/#{@riddle.id}/styles.css"
  script src="/js/riddle/#{@riddle.id}/script.js"

If you test this out, it won’t really look any different from before, but now we’re placing all the code into an iframe. You might notice that the iframe by default looks really small. This can be solved by adding the following line to our @@styles view:

iframe {width: 100%; min-height: 600px; border: none; }

Editing Riddles

The next feature to add is allowing users to edit Riddles. If somebody sees something they like then it would be good to allow them to make some changes and alteration. First of all, let’s add a button that allows users to edit a riddle when they’re on a riddle page. We’ll put it in the layout, right next to the ‘New Riddle’ button, but we only want it to show if there is a riddle to actually edit. This code should do it:

- if @riddle && @riddle.id
  a.button href="/edit/riddle/#{@riddle.id}" Edit this Riddle

We need to check if there is a @riddle object, but also if it has an id. This will restrict it to only using riddles that have been saved as they are only allocated an id after being saved (there is a @riddle object on the ‘new’ page where we wouldn’t want an edit button). Now we can click to edit a riddle we need to actually implement allowing users to edit them.

The problem is, we don’t want to actually update the actual riddle itself … people won’t want their good work changed by somebody else! And we don’t want to get into all the hassle of having users. The solution is simple – just create a new riddle with using the the attributes of the riddle we want to edit. This can be donein DataMapper using the following code:

Riddle.new(riddle.attributes.merge(id: nil))

Notice that we want to set the id to nil as this is an auto-incrementing field and will be set automatically by the database when the new riddle is saved, giving it its own unique url. All we need to do to make this happen is add the following route handler to our code:

get '/edit/riddle/:id' do
  riddle = Riddle.get(params[:id])
  @riddle = Riddle.new(riddle.attributes.merge(id: nil))
  slim :new
end

This refers to the url that we used in the edit button’s link. First of all we get the riddle that we wish to edit from the database. Then we ‘clone’ it as a new unsaved object and then display the new view, which is simply the form. Unfortunately this will just show blank fields at the moment, so we need to make a small alteration to the form so that the fields are populated with any default values:

@@new
form action="/riddle" method="POST"
  label for="title" Title
  input#title name="riddle[title]" value="#{@riddle.title}"
  label for="html" HTML
  textarea#html cols=60 rows=10 name="riddle[html]"=@riddle.html
  label for="css" CSS
  textarea#css cols=60 rows=10 name="riddle[css]"=@riddle.css
  label for="js" JS
  textarea#js cols=60 rows=10 name="riddle[js]"=@riddle.js
  input.button type="submit" value="Save"

Start Your Engines

At the start of part 1, I mentioned that Sinatra uses Tilt to implement lots of different view engines. We haven’t really used this yet. It would be cool if we let users choose which engine they wanted to use to display the HTML, use a preprocessor for CSS, or use CoffeScript instead of JavaScript. Sinatra makes this unbelieavably easy to achieve. First of all we need to require the relevant gems for all the view engines we plan to use. Add the following to the top of main.rb:

require 'haml'
require 'RedCloth'
require "coffee-script"
require "v8"
require "liquid"
require "markaby"
require "less"

The RedCloth gem is used to process Textile and the v8 gem embeds the v8 javascript engine in Ruby, which is requried for CoffeeScript support. All the others are hopefully self-explanatory.

Now we need to modify our database to include some fields to store which view engines to use. To do this, we first need to update the Riddle class to include these new properties:

class Riddle
  include DataMapper::Resource
  property :id, Serial

  property :created_at, DateTime
  property :updated_at, DateTime

  property :title, String

  property :html, Text
  property :html_engine, String
  property :css, Text
  property :css_engine, String
  property :js, Text
  property :js_engine, String

  def title=(value)
    super(value.empty? ? "Yet another untitled Riddle" : value)
  end
end

DataMapper.finalize

To make sure these changes are made to our database, all we need to do is use the DataMapper’s powerful migrations feature using irb. Type the following into a terminal:

$ irb ruby-1.9.2-p180 :001 > require './main' ruby-1.9.2-p180 :001 > Riddle.auto_migrate! 

It is important to note that using the auto_migrate! method will destroy all of the riddles currently stored in the database. If you don’t want this to happen you can use the auto_upgrade! method instead.

Next we need to modify our form to show a dropdown menu that allows users to select which engine they want to use:

@@new
form action="/riddle" method="POST"
  label for="title" Title
  input#title name="riddle[title]" value="#{@riddle.title}"
  select name="riddle[html_engine]"
    option value="markdown" HTML
    option value="markdown" MARKDOWN
    option value="textile" TEXTILE
    option value="haml" HAML
    option value="slim" SLIM
    option value="erb" ERB
    option value="liquid" LIQUID
    option value="markaby" MARKABY
  textarea#html cols=60 rows=10 name="riddle[html]"=@riddle.html
  select name="riddle[css_engine]"
    option value="css" CSS
    option value="scss" SCSS
    option value="sass" SASS
    option value="less" LESS
  textarea#css cols=60 rows=10 name="riddle[css]"=@riddle.css
  select name="riddle[js_engine]"
    option value="javascript" JAVASCRIPT
    option value="coffee" COFFEESCRIPT
  textarea#js cols=60 rows=10 name="riddle[js]"=@riddle.js
  input.button type="submit" value="Save Riddle"

This is a bit long and painfully hard-coded, but does the job we want for now. What is important is the value attribute with every option. This will be the value that is stored in the database and it’s important because in most cases this is the same as the helper method that Sinatra uses to render a view using that particular engine. As you’ll see, this makes rendering a riddle using a particular engine extremely easy. All we need to do now is modify the iframe code in the riddle view to the following:

@@riddle
doctype html
html lang="en"
  head
    title== @riddle.title
    meta charset="utf-8"
    style
      - if @riddle.css_engine =="css"
        == @riddle.css
      - else
        == send(@riddle.css_engine, @riddle.css)
    script
      - if @riddle.js_engine == "javascript"
        == @riddle.js
      - else
        == send(@riddle.js_engine, @riddle.js)
  body
    == send(@riddle.html_engine, @riddle.html)

Let’s take a closer look at how we are rendering the HTML. The following line will use the html_engine property of the riddle to render the HTML using the correct engine:

== send(@riddle.html_engine, @riddle.html) 

The send method is used to invoke the method that is stored in the html_engine property. This is why the name of the values in the form was so important as they have to match the method names exactly. I also cheated a little bit as there isn’t an html helper method, but as I noted in part 1, markdown will just display raw HTML just fine anyway, which is why if somebody selects the HTML option, it is actually ‘markdown’ that is stored in the database field.

We have to do things a bit differently for the CSS and JavaScript. Sinatra doesn’t have a specific helper for CSS or JavaScript, but since they are already in <style> and <script> tags respectively we can just serve it straight if the user selects ‘css’ or ‘javascript’ from the menu. Otherwise, if they choose a CSS preprocessor or CoffeScript, then the value stored in the database field matches the name of the Sinatra helper, so we can use the send method in a similar way as when selecting the HTML engine, as you can see in the snippet below that shows how the CSS code is inserted into the style block:

style
  - if @riddle.css_engine =="css"
    == @riddle.css
  - else
    == send(@riddle.css_engine, @riddle.css)

Finishing Up

We’re virtually there! All we need to do is tidy up a bit and add a bit of style to make everything look a bit nicer. Here are the styles that I used:

@@styles
@import url(http://fonts.googleapis.com/css?family=Pacifico);
$purple:#639;
$green:#396;
body{ font: 13px/1.4 arial, sans-serif; }
header{ overflow: hidden; }
.logo{float:left;overflow: hidden;}
.logo a{ color: $purple; font: 64px/1 pacifico; text-decoration: none; &:hover{color:$green;}}
.title{ color: $green; font: 32px/1 pacifico; }
.button {text-decoration: none; font-weight: bold; padding: 4px 8px; border-radius: 10px; background: $green; color: white; border:none; &:hover{background:$purple;}}
header .button{ float:left; margin: 36px 10px 0;}
form label, input.button {display: block;}
form select {display: block;}
iframe {width: 100%; min-height: 600px; border: none; }

And that’s pretty much it! It works quite nicely and we’re still well under 160 lines of code (including views and CSS)! You can see all the code on GitHub repo and the finished app is running on Heroku.

Here are a few additions that could be made to make it that probably wouldn’t be too hard to accomplish:

  • Include the option to include some JavaScript libraries in a Riddle (this could easily be done by simply linking to a CDN link for each respective JS library).
  • Include a live preview of a riddle as the user types it out (this could be done using JQuery and some Ajax)
  • Post riddles as gists on github (This gem would help with that)
  • The ability to share and embed riddles (the use of iframes makes this very easy to do)
  • Code highlighting in the textareas
  • … and the styling could be nicer!

Feel free to fork the GitHub repo and add any of these features!

I hope you’ve found this useful and also found some useful things about how Sinatra works. Please leave any feedback or ideas in comments below.