Subsecciones

Vistas

Writing a program that spits out HTML is often more difficult than you might imagine. Although programming languages are better at creating text than they used to be (some of us remember character handling in Fortran and standard Pascal), creating and concatenating string constructs is still painful. If there isn't much to do, it isn't too bad, but a whole HTML page is a lot of text manipulation.

With static HTML pages - those that don't change from request to request - you can use nice WYSIWG editors. Even those of us who like raw text editors find it easier to just type in the text and tags rather than fiddle with string concatenation in a programming language.

Of course the issue is with dynamic Web pages - those that take the results of something like database queries and embed them into the HTML. The page looks different with each result, and as a result regular HTML editors aren't up to the job.

The best way to work is to compose the dynamic Web page as you do a static page but put in markers that can be resolved into calls to gather dynamic information. Since the static part of the page acts as a template for the particular response, I call this a Template View.

Martin Fowler

Views in Sinatra are HTML templates that can optionally contain data passed from the application.

There are two ways to work with views in Sinatra: inline templates and external templates.

Véase en GitHub sinatra-up-and-running/tree/master/chapter2/views.

Templates Inline

Templates may be defined at the end of the source file. En este ejemplo trabajamos con varios templates inline en diferentes ficheros:

[~/sinatra/sinatraupandrunning/chapter2/views(master)]$ cat example2-14.rb 
require 'sinatra/base'

class App < Sinatra::Base
  enable :inline_templates
  get '/index' do 
    puts "Visiting #{request.url}"
    erb :index
  end
end

require './another'
__END__
@@index
<!DOCTYPE html>
<html> 
  <head>
    <meta charset="UTF-8">
    <title>Inline template</title> 
  </head>
  <body> 
    <h1>Worked!</h1>
  </body> 
</html>

En este fichero tenemos un segundo template inline:

[~/sinatra/sinatraupandrunning/chapter2/views(master)]$ cat another.rb 
class App 
  enable :inline_templates
  get '/' do
    erb :another
  end
end

__END__
@@another
<!DOCTYPE html>
<html> 
  <head>
    <meta charset="UTF-8">
    <title>Separated file</title> 
  </head>
  <body> 
    <h1>Inside another!</h1>
  </body> 
</html>

Este es nuestro config.ru:

[~/sinatra/sinatraupandrunning/chapter2/views(master)]$ cat config.ru 
require './example2-14'

run App
Para simplificar las cosas hemos hecho un Rakefile:
[~/sinatra/sinatraupandrunning/chapter2/views(master)]$ cat Rakefile 
task :default => :server

desc "run server"
task :server do
  sh "rackup"
end

desc "make a get / request via curl"
task :root do
  sh "curl -v localhost:9292"
end

desc "make a get /index request via curl"
task :index do 
  sh "curl -v localhost:9292/index"
end

El resultado de la ejecución es:

[~/sinatra/sinatra-up-and-running/chapter2/views/inline_templates(master)]$ curl http://localhost:9292/index
<!DOCTYPE html>
<html> 
  <head>
    <meta charset="UTF-8">
    <title>Inline template</title> 
  </head>
  <body> 
    <h1>Worked!</h1>
  </body> 
</html>
[~/sinatra/sinatra-up-and-running/chapter2/views/inline_templates(master)]$ curl http://localhost:9292/
<!DOCTYPE html>
<html> 
  <head>
    <meta charset="UTF-8">
    <title>Separated file</title> 
  </head>
  <body> 
    <h1>Inside another!</h1>
  </body> 
</html>

Named Templates

Templates may also be defined using the top-level template method:

template :layout do
  "%html\n  =yield\n"
end

template :index do
  '%div.title Hello World!'
end

get '/' do
  haml :index
end
If a template named layout exists, it will be used each time a template is rendered.

You can individually disable layouts by passing :layout => false or disable them by default via set :haml, :layout => false:

get '/' do
  haml :index, :layout => !request.xhr?
end

Templates Externos

$ ls
Rakefile               example2-16.rb         views
config.ru

$ cat example2-16.rb 
require 'sinatra/base'

class App < Sinatra::Base
  get '/index' do 
    puts "Visiting #{request.url}"
    erb :index
  end
end

$ cat views/index.erb 
<!DOCTYPE html>
<html> 
  <head>
    <meta charset="UTF-8">
    <title>Inline template</title> 
  </head>
  <body> 
    <h1>Worked!</h1>
  </body> 
</html>

$ cat config.ru 
require './example2-16'

run App

$ cat Rakefile 
task :default => :server

desc "run server"
task :server do
  sh "rackup"
end

desc "make a get / request via curl"
task :root do
  sh "curl -v localhost:9292"
end

desc "make a get /index request via curl"
task :index do 
  sh "curl -v localhost:9292/index"
end

$ rake server
rackup
>> Thin web server (v1.5.1 codename Straight Razor)
>> Maximum connections set to 1024
>> Listening on 0.0.0.0:9292, CTRL+C to stop
Visiting http://localhost:9292/index
127.0.0.1 - - [03/Jul/2013 22:30:16] "GET /index HTTP/1.1" 200 157 0.0774

$ rake index
curl -v localhost:9292/index
* About to connect() to localhost port 9292 (#0)
*   Trying 127.0.0.1... connected
* Connected to localhost (127.0.0.1) port 9292 (#0)
> GET /index HTTP/1.1
> User-Agent: curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21.4 OpenSSL/0.9.8x zlib/1.2.5
> Host: localhost:9292
> Accept: */*
> 
< HTTP/1.1 200 OK
< Content-Type: text/html;charset=utf-8
< Content-Length: 157
< X-XSS-Protection: 1; mode=block
< X-Content-Type-Options: nosniff
< X-Frame-Options: SAMEORIGIN
< Connection: keep-alive
< Server: thin 1.5.1 codename Straight Razor
< 
<!DOCTYPE html>
<html> 
  <head>
    <meta charset="UTF-8">
    <title>Inline template</title> 
  </head>
  <body> 
    <h1>Worked!</h1>
  </body> 
</html>

* Connection #0 to host localhost left intact
* Closing connection #0

Templates Externos en Subcarpetas

Véase en GitHub sinatra-up-and-running/tree/master/chapter2/views/external_view_files/external_in_subfolders

$ ls
Rakefile  app.rb    config.ru views

$ cat app.rb 
require 'sinatra/base'

class App < Sinatra::Base
  get '/:user/profile' do |user|
    @user = user
    erb '/user/profile'.to_sym
  end

  get '/:user/help' do |user|
    @user = user
    erb :'/user/help'
  end
end

$ cat views/user/profile.erb 
<!DOCTYPE html>
<html> 
  <head>
    <meta charset="UTF-8">
    <title>Profile Template</title> 
  </head>
  <body> 
    <h1>Profile of <%= @user %></h1>
     <%= params %>
  </body> 
</html>

$ cat views/user/help.erb 
<!DOCTYPE html>
<html> 
  <head>
    <meta charset="UTF-8">
    <title>HELP Template</title> 
  </head>
  <body> 
    <h1>Help for user <%= @user %></h1>
     <pre>
       <%= params %>
     </pre>
  </body> 
</html>

$ cat config.ru 
require './app'

run App

$ cat Rakefile 
PORT = 9292
task :default => :server

desc "run server"
task :server do
  sh "rackup"
end

desc "make a get /pepe/profile request via curl"
task :profile, :name do |t, h|
  user = h['name'] || 'pepe'
  sh "curl -v localhost:#{PORT}/#{user}/profile"
end

desc "make a get /pepe/help request via curl"
task :help do 
  sh "curl -v localhost:#{PORT}/pepe/help"
end

$ rake server
rackup
>> Thin web server (v1.5.1 codename Straight Razor)
>> Maximum connections set to 1024
>> Listening on 0.0.0.0:9292, CTRL+C to stop
127.0.0.1 - - [03/Jul/2013 21:40:04] "GET /Pedro/profile HTTP/1.1" 200 227 0.1077

$ rake profile[Pedro]
curl -v localhost:9292/Pedro/profile
* About to connect() to localhost port 9292 (#0)
*   Trying 127.0.0.1... connected
* Connected to localhost (127.0.0.1) port 9292 (#0)
> GET /Pedro/profile HTTP/1.1
> User-Agent: curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21.4 OpenSSL/0.9.8x zlib/1.2.5
> Host: localhost:9292
> Accept: */*
> 
< HTTP/1.1 200 OK
< Content-Type: text/html;charset=utf-8
< Content-Length: 227
< X-XSS-Protection: 1; mode=block
< X-Content-Type-Options: nosniff
< X-Frame-Options: SAMEORIGIN
< Connection: keep-alive
< Server: thin 1.5.1 codename Straight Razor
< 
<!DOCTYPE html>
<html> 
  <head>
    <meta charset="UTF-8">
    <title>Profile Template</title> 
  </head>
  <body> 
    <h1>Profile of Pedro</h1>
     {"splat"=>[], "captures"=>["Pedro"], "user"=>"Pedro"}
  </body> 
</html>


* Connection #0 to host localhost left intact
* Closing connection #0

Variables en las Vistas

Comunicación vía variables de instancia

Los templates se evaluan en el mismo contexto que los manejadores de las rutas. Las variables de instancia son accesibles directamente en los templates.

get '/:id' do
  @foo = Foo.find(params[:id])
  haml '%h1= @foo.name'
end
Veamos un ejemplo de comunicación via variables de instancia entre el manejador de la ruta y el template:

[~/sinatra/sinatra-views/passing_data_into_views(master)]$ ls
Rakefile        config.ru       via_instance.rb

[~/sinatra/sinatra-views/passing_data_into_views(master)]$ cat via_instance.rb 
require 'sinatra/base'


class App < Sinatra::Base
  get '/*' do |name|
    def some_template
       <<-'HAMLTEMP'
%ol
  - @foo.each do |item|
    %li 
      %i #{item}
HAMLTEMP
    end

    puts "*---***#{name}*---****"
    @foo = name.split('/')
    haml some_template
  end
end

[~/sinatra/sinatra-views/passing_data_into_views(master)]$ cat config.ru 
require './via_instance'

run App

[~/sinatra/sinatra-views/passing_data_into_views(master)]$ cat Rakefile 
task :default => :server

desc "run server"
task :server do
  sh "rackup"
end

desc "make a get /juan/leon/hernandez request via curl"
task :client do
  sh "curl -v localhost:9292/juan/leon/hernandez"
end

[~/sinatra/sinatraupandrunning/chapter2/views/passing_data_into_views(master)]$ rake server
rackup
>> Thin web server (v1.5.1 codename Straight Razor)
>> Maximum connections set to 1024
>> Listening on 0.0.0.0:9292, CTRL+C to stop
*---***juan/leon/hernandez*---****
127.0.0.1 - - [05/Jul/2013 17:06:05] "GET /juan/leon/hernandez HTTP/1.1" 200 109 0.3502

[~/sinatra/sinatra-views/passing_data_into_views(master)]$ rake client
curl -v localhost:9292/juan/leon/hernandez
* About to connect() to localhost port 9292 (#0)
*   Trying 127.0.0.1... connected
* Connected to localhost (127.0.0.1) port 9292 (#0)
> GET /juan/leon/hernandez HTTP/1.1
> User-Agent: curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21.4 OpenSSL/0.9.8x zlib/1.2.5
> Host: localhost:9292
> Accept: */*
> 
< HTTP/1.1 200 OK
< Content-Type: text/html;charset=utf-8
< Content-Length: 109
< X-XSS-Protection: 1; mode=block
< X-Content-Type-Options: nosniff
< X-Frame-Options: SAMEORIGIN
< Connection: keep-alive
< Server: thin 1.5.1 codename Straight Razor
< 
<ol>
  <li>
    <i>juan</i>
  </li>
  <li>
    <i>leon</i>
  </li>
  <li>
    <i>hernandez</i>
  </li>
</ol>
* Connection #0 to host localhost left intact
* Closing connection #0

Pasando variables a la vista explícitamente via un hash

También es posible pasar en la llamada un hash especificando las variables locales:

get '/:id' do
  foo = Foo.find(params[:id])
  haml '%h1= bar.name', :locals => { :bar => foo }
end
This is typically used when rendering templates as partials from within other templates.

Veamos un ejemplo:

$ ls
Rakefile    config.ru   via_hash.rb views

$ cat via_hash.rb
require 'sinatra/base'

class App < Sinatra::Base
  get '/*' do |name|
    def some_template
       <<-'ERBTEMP'
<ul><% name.each do |item| %>
      <li> <i> <%= item %> </i> </li>
    <% end %>
</ul>
ERBTEMP
    end # method some_template

    puts "*---***#{name}*---****"
    erb some_template, :locals => { :name => name.split('/')}
  end
end

$ cat views/layout.erb 
<!DOCTYPE html>
<html>
  <head>
        <title>Sinatra</title>
  </head>
  <body>
    <h1>Accesing variables in templates via a parameter hash</h1>
    <%= yield %>
  </body>
</html>

$ cat config.ru 
require './via_hash'

run App

$ cat Rakefile task :default => :server

desc "run server"
task :server do
  sh "rackup"
end

desc "make a get /juan/leon/hernandez request via curl"
task :client do
  sh "curl -v localhost:9292/juan/leon/hernandez"
end

$ rake serverrackup
>> Thin web server (v1.5.1 codename Straight Razor)
>> Maximum connections set to 1024
>> Listening on 0.0.0.0:9292, CTRL+C to stop
*---***juan/leon/hernandez*---****
127.0.0.1 - - [05/Jul/2013 17:50:20] "GET /juan/leon/hernandez HTTP/1.1" 200 290 0.0352

$ rake client
curl -v localhost:9292/juan/leon/hernandez
* About to connect() to localhost port 9292 (#0)
*   Trying 127.0.0.1... connected
* Connected to localhost (127.0.0.1) port 9292 (#0)
> GET /juan/leon/hernandez HTTP/1.1
> User-Agent: curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21.4 OpenSSL/0.9.8x zlib/1.2.5
> Host: localhost:9292
> Accept: */*
> 
< HTTP/1.1 200 OK
< Content-Type: text/html;charset=utf-8
< Content-Length: 290
< X-XSS-Protection: 1; mode=block
< X-Content-Type-Options: nosniff
< X-Frame-Options: SAMEORIGIN
< Connection: keep-alive
< Server: thin 1.5.1 codename Straight Razor
< 
<!DOCTYPE html>
<html>
  <head>
        <title>Sinatra</title>
  </head>
  <body>
    <h1>Accesing variables in templates via a parameter hash</h1>
    <ul>
      <li> <i> juan </i> </li>
    
      <li> <i> leon </i> </li>
    
      <li> <i> hernandez </i> </li>
    
</ul>

  </body>
</html>
* Connection #0 to host localhost left intact
* Closing connection #0

Opciones pasadas a los Métodos de los Templates

Options passed to the render method override options set via set.

Available Options:

  1. locals

    List of locals passed to the document. Handy with partials. Example:

    erb "<%= foo %>", :locals => {:foo => "bar"}
    
  2. default_encoding

    String encoding to use if uncertain. Defaults to

    settings.default_encoding.
    

  3. views

    Views folder to load templates from. Defaults to settings.views.

  4. layout

    Whether to use a layout (true or false), if it's a Symbol, specifies what template to use. Example:

    erb :index, :layout => !request.xhr?
    

  5. content_type

    Content-Type the template produces, default depends on template language.

  6. scope

    Scope to render template under.

    Defaults to the application instance.

    If you change this, instance variables and helper methods will not be available.

  7. layout_engine

    Template engine to use for rendering the layout.

    Useful for languages that do not support layouts otherwise.

    Defaults to the engine used for the template. Example:

    set :rdoc, :layout_engine => :erb
    

  8. layout_options

    Special options only used for rendering the layout. Example:

    set :rdoc, :layout_options => { :views => 'views/layouts' }
    

  9. Templates are assumed to be located directly under the ./views directory.

    To use a different views directory:

    set :views, settings.root + '/templates'
    

  10. One important thing to remember is that you always have to reference templates with symbols, even if they’re in a subdirectory (in this case, use: :'subdir/template' or 'subdir/template'.to_sym).

    You must use a symbol because otherwise rendering methods will render any strings passed to them directly.

Casiano Rodríguez León
2015-01-25