¶ ↑
Markaby (Markup as Ruby)Markaby is a very short bit of code for writing HTML pages in pure Ruby. It is an alternative to ERb which weaves the two languages together. Also a replacement for templating languages which use primitive languages that blend with HTML.
¶ ↑
Install itJust do what everyone else does:
# in Gemfile: gem 'markaby', '>= 0.9'
then bundle install:
bundle install
¶ ↑
Using Markaby with Rails 4/5+:Install the gem (using bundler), then:
# in config/initializers/markaby.rb: require 'markaby/rails' Markaby::Rails::TemplateHandler.register!({ tagset: Markaby::HTML5, indent: 2, })
Name your templates with an html.mab extension. You’ll also want to configure your text editor to see .mab as ruby.
Here’s how you’d do that for Atom:
-
Install the file-types module:
apm install file-types
-
in your config: Atom -> Config:
"*": "file-types": "\\.mab$": "source.ruby"
Now that’s some chunky bacon!
¶ ↑
Using Markaby in helpers:Just call Markaby::Builder with a block as below.
You can also require ‘markaby/kernel_method’ to make it even easier:
# my_helper.rb: require 'markaby/kernel_method' # or put this in an initializer module MyHelper # note - you can also use Markaby::Builder.new { }.to_s def chunky_bacon mab do p "Chunky Bacon!" end end end
¶ ↑
Using Markaby with Sinatra (1.0+)get '/foo' do mab :my_template # my_template.mab in the sinatra view path end
If you are looking for sinatra support pre 0.7, see github.com/sbfaulkner/sinatra-markaby
¶ ↑
Using Markaby with other frameworksTilt has a Markaby module, so in principle, any web framework that supports Tilt will also support Markaby. See the appropriate tilt documentation:
¶ ↑
Using Markaby as a Ruby classMarkaby is flaming easy to call from your Ruby classes.
require 'markaby' mab = Markaby::Builder.new mab.html do head { title "Boats.com" } body do h1 "Boats.com has great deals" ul do li "$49 for a canoe" li "$39 for a raft" li "$29 for a huge boot that floats and can fit 5 people" end end end puts mab.to_s
Markaby::Builder.new does take two arguments for passing in variables and a helper object. You can also affix the block right on to the class.
See Markaby::Builder for all of that.
instance_eval
¶ ↑
A Note About The Markaby::Builder class is different from the normal Builder class, since it uses instance_eval
when running blocks. This cleans up the appearance of the Markaby code you write. If instance_eval
was not used, the code would look like this:
mab = Markaby::Builder.new mab.html do mab.head { mab.title "Boats.com" } mab.body do mab.h1 "Boats.com has great deals" end end puts mab.to_s
So, the advantage is the cleanliness of your code. The disadvantage is that the block will run inside the Markaby::Builder object’s scope. This means that inside these blocks, self
will be your Markaby::Builder object. When you use instance variables in these blocks, they will be instance variables of the Markaby::Builder object.
This doesn’t affect Rails users, but when used in regular Ruby code, it can be a bit disorienting. You are recommended to put your Markaby code in a module where it won’t mix with anything.
¶ ↑
The Six Steps of MarkabyIf you dive right into Markaby, it’ll probably make good sense, but you’re likely to run into a few kinks. Why not review these six steps and commit them memory so you can really know what you’re doing?
¶ ↑
1. Element ClassesElement classes may be added by hooking methods onto container elements:
div.entry do h2.entryTitle 'Son of WebPage' div.entrySection %{by Anthony} div.entryContent 'Okay, once again, the idea here is ...' end
Which results in:
<div class="entry"> <h2 class="entryTitle">Son of WebPage</h2> <div class="entrySection">by Anthony</div> <div class="entryContent">Okay, once again, the idea here is ...</div> </div>
Alternatively you can define the class as an attribute on your element - see below.
¶ ↑
2. Element IDsIDs may be added by the use of bang methods:
div.page! do div.content! do h1 "A Short Short Saintly Dog" end end
Which results in:
<div id="page"> <div id="content"> <h1>A Short Short Saintly Dog</h1> </div> </div>
Alternatively you can define the ID as an attribute on your element - see below.
¶ ↑
3. Validate Your XHTML 1.0 OutputIf you’d like Markaby to help you assemble valid XHTML documents, you can use the html5
, xhtml_transitional
or xhtml_strict
methods in place of the normal html
tag.
html5 do head { ... } body { ... } end
This will add the XML instruction and the doctype tag to your document (for xhtml_strict and xhtml_transitional). Also, a character set meta tag will be placed inside your head
tag.
Now, since Markaby knows which doctype you’re using, it checks a big list of valid tags and attributes before printing anything.
>> div styl: "padding: 10px" do >> img src: "samorost.jpg" >> end InvalidHtmlError: no such attribute `styl'
Markaby will also make sure you don’t use the same element ID twice!
¶ ↑
4. Escape or No Escape?Markaby uses a simple convention for escaping stuff: if a string is an argument, it gets escaped. If the string is in a block, it doesn’t.
This is handy if you’re using something like RedCloth or RDoc inside an element. Pass the string back through the block and it’ll skip out of escaping.
div.comment { RedCloth.new(str).to_html }
But, if we have some raw text that needs escaping, pass it in as an argument:
div.comment raw_str
One caveat: if you have other tags inside a block, the string passed back will be ignored.
div.comment do div.author "_why" div.says "Torpedoooooes!" "<div>Silence.</div>" end
The final div above won’t appear in the output. You can’t mix tag modes like that, friend.
¶ ↑
5. Auto-stringificationIf you end up using any of your Markaby “tags” as a string, the tag won’t be output. It’ll be up to you to add the new string back into the HTML output.
This means if you call to_s
, you’ll get a string back.
div.title { "Rock Bottom" + span(" by Robert Wyatt").to_s }
But, when you’re adding strings in Ruby, to_s
happens automatically.
div.title { "Rock Bottom" + span(" by Robert Wyatt") }
Interpolation works fine.
div.title { "Rock Bottom #{span(" by Robert Wyatt")}" }
And any other operation you might perform on a string.
div.menu! \ ['5.gets', 'bits', 'cult', 'inspect', '-h'].map do |category| link_to category end. join( " | " )
tag!
Method¶ ↑
6. The If you need to force a tag at any time, call tag!
with the tag name followed by the possible arguments and block. The CssProxy won’t work with this technique.
tag! :select, :id => "country_list" do countries.each do |country| tag! :option, country end end
If you wish to register your own, specialist, tags, you can install a TagHandler
¶ ↑
Some other notes, so you aren’t confused:¶ ↑
On using different tagsets:Because of the ways various frameworks sub-render templates, to use a different tagset in a rendered sub template, you may need to set it at the top of your sub-template:
self.tagset = Markaby::HTML5 # or Markaby::Transitional, Markaby::XHTMLStrict, Markaby::XHTMLFrameset
Note, this is only necessary if you were rendering, say, a one off page as html transitional but had the default engine as html5.
¶ ↑
Defining attributes on elementsIf you do not use the CssProxy
(div.entry
to define the class, div.page!
to define the ID), then you can pass a hash to your element to define any arbitrary attributes.
div id: "page-123", class: "entry" do div style: "display: inline-block;" do p "Have you noticed this book is basically written by a lunatic?" end end
Will result in
<div id="page-123" class="entry"> <div style="display: inline-block;"> <p>Have you noticed this book is basically written by a lunatic?</p> </div> </div>
If you pass a hash to your attribute definition, Markaby will expand the hash entries. This is useful for data attributes (for example, if you are using a Stimulus controller), or aria attributes. Any attributes will have underscores replaced with dashes when the hash is expanded.
div data: { controller: "something" } do div do h1(data: { something_target: "title" }) { "There goes my pickup" } end end
Will result in
<div data-controller="something"> <div> <h1 data-something-target="title">There goes my pickup</h1> </div> </div>
¶ ↑
Custom elements and web componentsIf you are using the HTML5 tagset (which is the default), and your document has custom-elements defined, you can create those in the same way as standard elements.
Unlike standard elements, there is no validity checking for your attributes.
article do my_custom_panel variant: "primary" do p "Our careers are so over" end end
Results in
<article> <my-custom-panel variant="primary"> <p>Our careers are so over</p> </my-custom-panel> </article>
¶ ↑
CreditsMarkaby is a work of immense hope by Tim Fletcher and why the lucky stiff. It is maintained by joho, spox, and smtlaissezfaire. Thank you for giving it a whirl.
Markaby is inspired by the HTML library within cgi.rb. Hopefully it will turn around and take some cues.
¶ ↑
Patches from contributors:aredridel (Aria Stewart - [email protected])
- Make exceptions inherit from StandardError (f259c0)