Skip to content

Instantly share code, notes, and snippets.

@austintaylor
Created March 15, 2012 15:19
Show Gist options
  • Save austintaylor/2044757 to your computer and use it in GitHub Desktop.
Save austintaylor/2044757 to your computer and use it in GitHub Desktop.
A different approach to HTML templating
<h1><%= _('Profile') %></h1>
<%= form_for @profile, :url => user_profile_path(@profile), :html => {:class => 'profile'} do |f| %>
<%= render 'shared/errors', :model => @profile %>
<dl>
<dt><%= f.label :name, _('Name') %></dt>
<dd><%= f.text_field :name %></dt>
</dl>
<dl>
<dt><%= f.label :email, _('Email') %></dt>
<dd><%= f.text_field :email %></dt>
</dl>
<dl>
<dt><%= f.label :gender, _('Gender') %></dt>
<dd><%= f.select :gender, Profile::GENDERS %></dd>
</dl>
<fieldset class="services">
<% Profile::SERVICE_TYPES.each do |type| %>
<% f.fields_for :services do |ff| %>
<% ff.fields_for type, @profile.services.detect {|s| s.service_type == type } || @profile.services.build(:service_type => type) do |fff| %>
<dl id="<%= type %>_field">
<%= fff.hidden_field :id if fff.object.id %>
<%= fff.hidden_field :service_type %>
<dt><%= fff.label :url, _(type.titleize) %></dt>
<dd><%= fff.text_field :url %></dd>
</dl>
<% end %>
<% end %>
<% end %>
</fieldset>
<dl>
<dt><%= _('Options') %></dt>
<dd><%= f.check_box :public %><%= f.label :public, _('Show public profile') %></dd>
<dd><%= f.check_box :newsletter %><%= f.label :newsletter, _('Receive updates via email') %></dd>
<% if @profile.admin? %>
<dd><%= f.check_box :show_admin_status %><%= f.label :show_admin_status, _('Show admin status on profile') %></dd>
<% end %>
</dl>
<%= link_to _('Cancel'), user_profile_path(@profile) %>
<%= f.submit _('Save') %>
<% end %>
%h1= _('Profile')
= form_for @profile, :url => user_profile_path(@profile), :html => {:class => 'profile'} do |f|
= render 'shared/errors', :model => @profile
%dl
%dt= f.label :name, _('Name')
%dd= f.text_field :name
%dl
%dt= f.label :email, _('Email')
%dd= f.text_field :email
%dl
%dt= f.label :gender, _('Gender')
%dd= f.select :gender, Profile::GENDERS
%fieldset.services
- Profile::SERVICE_TYPES.each do |type|
- f.fields_for :services do |ff|
- ff.fields_for type, @profile.services.detect {|s| s.service_type == type } || @profile.services.build(:service_type => type) do |fff|
%dl{:id => "#{type}_field"}
= fff.hidden_field :id if fff.object.id
= fff.hidden_field :service_type
%dt= fff.label :url, _(type.titleize)
%dd= fff.text_field :url
%dl
%dt= _('Options')
%dd
= f.check_box :public
= f.label :public, _('Show public profile')
%dd
= f.check_box :newsletter
= f.label :newsletter, _('Receive updates via email')
- if @profile.admin?
%dd
= f.check_box :show_admin_status
= f.label :show_admin_status, _('Show admin status on profile')
= link_to _('Cancel'), user_profile_path(@profile)
= f.submit _('Save')
<h1 x-loc>Profile</h1>
<form class="profile" x-action="/user/profiles/{profile}" x-method-for="profile">
<div x-partial="shared/errors" x-yield="profile->model"/>
<dl>
<dt><label x-for x-loc>Name</label></dt>
<dd><input type="text" x-bind="profile.name"/></dt>
</dl>
<dl>
<dt><label x-for x-loc>Email</label></dt>
<dd><input type="text" x-bind="profile.email"/></dt>
</dl>
<dl>
<dt><label x-for x-loc>Gender</label></dt>
<dd><select x-bind="profile.gender" x-options="global.profile.genders"/></dd>
</dl>
<fieldset class="services" x-each="global.profile.service_types->type">
<dl x-id="{type}_field">
<input type="hidden" x-bind="profile.services[service_type=type].id" x-if-exists/>
<input type="hidden" x-bind="profile.services[service_type=type].service_type"/>
<dt><label x-for x-content="type.titleize" x-loc/></dt>
<dd><input type="text" x-bind="profile.services[service_type=type].url"/></dd>
</dl>
</fieldset>
<dl>
<dt x-loc>Options</dt>
<dd><input type="checkbox" x-bind="profile.public" x-boolean/><label x-for x-loc>Show public profile</label></dd>
<dd><input type="checkbox" x-bind="profile.newsletter" x-boolean/><label x-for x-loc>Receive updates via email</label></dd>
<dd x-if="profile.admin?"><input type="checkbox" x-bind="profile.show_admin_status" x-boolean/><label x-for x-loc>Show admin status on profile</label></dd>
</dl>
<a x-href="/user/profiles/{profile}" x-loc>Cancel</a>
<input type="submit" x-loc>Save</input>
</form>
@austintaylor
Copy link
Author

Some things to note:

  • There is no change to the html syntax outside of the x-attributes.
  • x-attributes can transform the dom in a variety of ways, and would be extensible by the application.
  • The syntax for the value of an x-attribute has a few variations, depending on a simple type system. This is not ruby, but it is designed to play nicely with ruby. Smart syntax highlighting would help here.
  • x-for can infer its value from structural context.
  • There is no distinction between locals and instance variables.
  • No, you can't call methods that take arguments.
  • I'm still trying to work some things out, such as how partials should interact with form scope.

Justification:

  • Designers who can code html are awesome, and it makes sense to give them something familiar.
  • String splicing is error-prone.
  • Indentation in long documents without end tags is error prone.
  • Closing tags are more helpful than end keywords.
  • There are cool vim plugins for editing html.
  • Rails's html helpers often make things longer than they would have been verbatim.
  • fields_for is complicated.
  • Url helper methods are almost always longer than the url they generate, and hard to remember.
  • Control structures make things unnecessarily nested.
  • Writing code in your views is bad and everybody knows it.
  • In general, this approach allows us to address the problems we have with html generation in web applications much more directly than string-based helper methods do (x-bind, x-for, x-loc, and x-boolean, in particular).

@ravinggenius
Copy link

Have you taken a look at Plates? This looks kind-of similar.

@austintaylor
Copy link
Author

@ravinggenius Cool. Sounds like it has some of the same motivations. It's not really the same idea, though (templating by DOM manipulation rules vs DSL embedded in special attributes). Batman.js is closer.

@aiwilliams
Copy link

I think, for me, it's difficult to have an opinion that is well informed enough without using it for a while in various situations. Also, there are some designers who love to work in the actual application codebase, enjoy a terse language like Slim, and can even be taught enough Ruby avoid being stalled when they need a data structure or helper method. Then there is the fact that some template languages work good in one place and not in another. Sorry I can't give you a more hot/cold response :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment