···134134 require f
135135end
136136137137+# and sinatra_more form and render helpers
138138+require "#{App.root}/lib/sinatra_more/markup_plugin"
139139+require "#{App.root}/lib/sinatra_more/render_plugin"
140140+137141# and controllers, binding each's helper
138142(
139143 [ "#{App.root}/app/controllers/application_controller.rb" ] +
+41
lib/sinatra_more/markup_plugin.rb
···11+#
22+# sinatra_more
33+# Copyright (c) 2009 Nathan Esquenazi
44+#
55+# Permission is hereby granted, free of charge, to any person obtaining
66+# a copy of this software and associated documentation files (the
77+# "Software"), to deal in the Software without restriction, including
88+# without limitation the rights to use, copy, modify, merge, publish,
99+# distribute, sublicense, and/or sell copies of the Software, and to
1010+# permit persons to whom the Software is furnished to do so, subject to
1111+# the following conditions:
1212+#
1313+# The above copyright notice and this permission notice shall be
1414+# included in all copies or substantial portions of the Software.
1515+#
1616+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1717+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1818+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1919+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
2020+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
2121+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
2222+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2323+#
2424+2525+require File.dirname(__FILE__) + '/support_lite'
2626+require File.dirname(__FILE__) + '/markup_plugin/form_builder/abstract_form_builder'
2727+require File.dirname(__FILE__) + '/markup_plugin/form_builder/standard_form_builder'
2828+Dir[File.dirname(__FILE__) + '/markup_plugin/*.rb'].each {|file| load file }
2929+3030+module SinatraMore
3131+ module MarkupPlugin
3232+ def self.registered(app)
3333+ app.set :default_builder, 'StandardFormBuilder'
3434+ app.helpers OutputHelpers
3535+ app.helpers TagHelpers
3636+ app.helpers AssetTagHelpers
3737+ app.helpers FormHelpers
3838+ app.helpers FormatHelpers
3939+ end
4040+ end
4141+end
···11+#
22+# sinatra_more
33+# Copyright (c) 2009 Nathan Esquenazi
44+#
55+# Permission is hereby granted, free of charge, to any person obtaining
66+# a copy of this software and associated documentation files (the
77+# "Software"), to deal in the Software without restriction, including
88+# without limitation the rights to use, copy, modify, merge, publish,
99+# distribute, sublicense, and/or sell copies of the Software, and to
1010+# permit persons to whom the Software is furnished to do so, subject to
1111+# the following conditions:
1212+#
1313+# The above copyright notice and this permission notice shall be
1414+# included in all copies or substantial portions of the Software.
1515+#
1616+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1717+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1818+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1919+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
2020+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
2121+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
2222+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2323+#
2424+2525+module SinatraMore
2626+ module AssetTagHelpers
2727+2828+ # Creates a div to display the flash of given type if it exists
2929+ # flash_tag(:notice, :class => 'flash', :id => 'flash-notice')
3030+ def flash_tag(kind, options={})
3131+ flash_text = flash[kind]
3232+ return '' if flash_text.blank?
3333+ options.reverse_merge!(:class => 'flash')
3434+ content_tag(:div, flash_text, options)
3535+ end
3636+3737+ # Creates a link element with given name, url and options
3838+ # link_to 'click me', '/dashboard', :class => 'linky'
3939+ # link_to('/dashboard', :class => 'blocky') do ... end
4040+ # parameters: name, url='javascript:void(0)', options={}, &block
4141+ def link_to(*args, &block)
4242+ if block_given?
4343+ url, options = (args[0] || 'javascript:void(0);'), (args[1] || {})
4444+ options.reverse_merge!(:href => url)
4545+ link_content = capture_html(&block)
4646+ result_link = content_tag(:a, link_content, options)
4747+ block_is_template?(block) ? concat_content(result_link) : result_link
4848+ else
4949+ name, url, options = args.first, (args[1] || 'javascript:void(0);'), (args[2] || {})
5050+ options.reverse_merge!(:href => url)
5151+ content_tag(:a, name, options)
5252+ end
5353+ end
5454+5555+ # Creates a mail link element with given name and caption
5656+ # mail_to "me@demo.com" => <a href="mailto:me@demo.com">me@demo.com</a>
5757+ # mail_to "me@demo.com", "My Email" => <a href="mailto:me@demo.com">My Email</a>
5858+ def mail_to(email, caption=nil, mail_options={})
5959+ html_options = mail_options.slice!(:cc, :bcc, :subject, :body)
6060+ mail_query = Rack::Utils.build_query(mail_options).gsub(/\+/, '%20').gsub('%40', '@')
6161+ mail_href = "mailto:#{email}"; mail_href << "?#{mail_query}" if mail_query.present?
6262+ link_to (caption || email), mail_href, html_options
6363+ end
6464+6565+ # Creates a meta element with the content and given options
6666+ # meta_tag "weblog,news", :name => "keywords" => <meta name="keywords" content="weblog,news">
6767+ # meta_tag "text/html; charset=UTF-8", :http-equiv => "Content-Type" => <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
6868+ def meta_tag(content, options={})
6969+ options.reverse_merge!("content" => content)
7070+ tag(:meta, options)
7171+ end
7272+7373+ # Creates an image element with given url and options
7474+ # image_tag('icons/avatar.png')
7575+ def image_tag(url, options={})
7676+ options.reverse_merge!(:src => image_path(url))
7777+ tag(:img, options)
7878+ end
7979+8080+ # Returns a stylesheet link tag for the sources specified as arguments
8181+ # stylesheet_link_tag 'style', 'application', 'layout'
8282+ def stylesheet_link_tag(*sources)
8383+ options = sources.extract_options!.symbolize_keys
8484+ sources.collect { |sheet| stylesheet_tag(sheet, options) }.join("\n")
8585+ end
8686+8787+ # javascript_include_tag 'application', 'special'
8888+ def javascript_include_tag(*sources)
8989+ options = sources.extract_options!.symbolize_keys
9090+ sources.collect { |script| javascript_tag(script, options) }.join("\n")
9191+ end
9292+9393+ # Returns the path to the image, either relative or absolute
9494+ def image_path(src)
9595+ src.gsub!(/\s/, '')
9696+ src =~ %r{^\s*(/|http)} ? src : File.join('/images', src)
9797+ end
9898+9999+ protected
100100+101101+ # stylesheet_tag('style', :media => 'screen')
102102+ def stylesheet_tag(source, options={})
103103+ options = options.dup.reverse_merge!(:href => stylesheet_path(source), :media => 'screen', :rel => 'stylesheet', :type => 'text/css')
104104+ tag(:link, options)
105105+ end
106106+107107+ # javascript_tag 'application', :src => '/javascripts/base/application.js'
108108+ def javascript_tag(source, options={})
109109+ options = options.dup.reverse_merge!(:src => javascript_path(source), :type => 'text/javascript', :content => "")
110110+ tag(:script, options)
111111+ end
112112+113113+ def javascript_path(source)
114114+ return source if source =~ /^http/
115115+ result_path = "/javascripts/#{source.gsub(/.js$/, '')}"
116116+ result_path << ".js" unless source =~ /\.js\w{2,4}$/
117117+ stamp = File.exist?(result_path) ? File.mtime(result_path) : Time.now.to_i
118118+ "#{result_path}?#{stamp}"
119119+ end
120120+121121+ def stylesheet_path(source)
122122+ return source if source =~ /^http/
123123+ result_path = "/stylesheets/#{source.gsub(/.css$/, '')}"
124124+ result_path << ".css" unless source =~ /\.css\w{2,4}$/
125125+ stamp = File.exist?(result_path) ? File.mtime(result_path) : Time.now.to_i
126126+ "#{result_path}?#{stamp}"
127127+ end
128128+ end
129129+end
···11+#
22+# sinatra_more
33+# Copyright (c) 2009 Nathan Esquenazi
44+#
55+# Permission is hereby granted, free of charge, to any person obtaining
66+# a copy of this software and associated documentation files (the
77+# "Software"), to deal in the Software without restriction, including
88+# without limitation the rights to use, copy, modify, merge, publish,
99+# distribute, sublicense, and/or sell copies of the Software, and to
1010+# permit persons to whom the Software is furnished to do so, subject to
1111+# the following conditions:
1212+#
1313+# The above copyright notice and this permission notice shall be
1414+# included in all copies or substantial portions of the Software.
1515+#
1616+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1717+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1818+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1919+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
2020+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
2121+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
2222+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2323+#
2424+2525+class AbstractFormBuilder
2626+ attr_accessor :template, :object
2727+2828+ def initialize(template, object)
2929+ @template = template
3030+ @object = build_object(object)
3131+ raise "FormBuilder template must be initialized!" unless template
3232+ raise "FormBuilder object must be not be nil value. If there's no object, use a symbol instead! (i.e :user)" unless object
3333+ end
3434+3535+ # f.error_messages
3636+ def error_messages(options={})
3737+ @template.error_messages_for(@object, options)
3838+ end
3939+4040+ # f.label :username, :caption => "Nickname"
4141+ def label(field, options={})
4242+ options.reverse_merge!(:caption => "#{field.to_s.titleize}: ")
4343+ @template.label_tag(field_id(field), options)
4444+ end
4545+4646+ # f.hidden_field :session_id, :value => "45"
4747+ def hidden_field(field, options={})
4848+ options.reverse_merge!(:value => field_value(field), :id => field_id(field))
4949+ @template.hidden_field_tag field_name(field), options
5050+ end
5151+5252+ # f.text_field :username, :value => "(blank)", :id => 'username'
5353+ def text_field(field, options={})
5454+ options.reverse_merge!(:value => field_value(field), :id => field_id(field))
5555+ @template.text_field_tag field_name(field), options
5656+ end
5757+5858+ # f.text_area :summary, :value => "(enter summary)", :id => 'summary'
5959+ def text_area(field, options={})
6060+ options.reverse_merge!(:value => field_value(field), :id => field_id(field))
6161+ @template.text_area_tag field_name(field), options
6262+ end
6363+6464+ # f.password_field :password, :id => 'password'
6565+ def password_field(field, options={})
6666+ options.reverse_merge!(:value => field_value(field), :id => field_id(field))
6767+ @template.password_field_tag field_name(field), options
6868+ end
6969+7070+ # f.select :color, :options => ['red', 'green'], :include_blank => true
7171+ # f.select :color, :collection => @colors, :fields => [:name, :id]
7272+ def select(field, options={})
7373+ options.reverse_merge!(:id => field_id(field), :selected => field_value(field))
7474+ @template.select_tag field_name(field), options
7575+ end
7676+7777+ # f.check_box :remember_me, :value => 'true', :uncheck_value => '0'
7878+ def check_box(field, options={})
7979+ unchecked_value = options.delete(:uncheck_value) || '0'
8080+ options.reverse_merge!(:id => field_id(field), :value => '1')
8181+ options.merge!(:checked => true) if values_matches_field?(field, options[:value])
8282+ html = hidden_field(field, :value => unchecked_value, :id => nil)
8383+ html << @template.check_box_tag(field_name(field), options)
8484+ end
8585+8686+ # f.radio_button :gender, :value => 'male'
8787+ def radio_button(field, options={})
8888+ options.reverse_merge!(:id => field_id(field, options[:value]))
8989+ options.merge!(:checked => true) if values_matches_field?(field, options[:value])
9090+ @template.radio_button_tag field_name(field), options
9191+ end
9292+9393+ # f.file_field :photo, :class => 'avatar'
9494+ def file_field(field, options={})
9595+ options.reverse_merge!(:id => field_id(field))
9696+ @template.file_field_tag field_name(field), options
9797+ end
9898+9999+ # f.submit "Update", :class => 'large'
100100+ def submit(caption="Submit", options={})
101101+ @template.submit_tag caption, options
102102+ end
103103+104104+ # f.simage_submitubmit "buttons/submit.png", :class => 'large'
105105+ def image_submit(source, options={})
106106+ @template.image_submit_tag source, options
107107+ end
108108+109109+ protected
110110+111111+ # Returns the known field types for a formbuilder
112112+ def self.field_types
113113+ [:hidden_field, :text_field, :text_area, :password_field, :file_field, :radio_button, :check_box, :select]
114114+ end
115115+116116+ # Returns the object's models name
117117+ # => user_assignment
118118+ def object_name
119119+ object.is_a?(Symbol) ? object : object.class.to_s.underscore.gsub('/', '-')
120120+ end
121121+122122+ # Returns true if the value matches the value in the field
123123+ # field_has_value?(:gender, 'male')
124124+ def values_matches_field?(field, value)
125125+ value.present? && (field_value(field).to_s == value.to_s || field_value(field).to_s == 'true')
126126+ end
127127+128128+ # Returns the value for the object's field
129129+ # field_value(:username) => "Joey"
130130+ def field_value(field)
131131+ @object && @object.respond_to?(field) ? @object.send(field) : ""
132132+ end
133133+134134+ # Returns the name for the given field
135135+ # field_name(:username) => "user[username]"
136136+ def field_name(field)
137137+ "#{object_name}[#{field}]"
138138+ end
139139+140140+ # Returns the id for the given field
141141+ # field_id(:username) => "user_username"
142142+ # field_id(:gender, :male) => "user_gender_male"
143143+ def field_id(field, value=nil)
144144+ value.blank? ? "#{object_name}_#{field}" : "#{object_name}_#{field}_#{value}"
145145+ end
146146+147147+ # explicit_object is either a symbol or a record
148148+ # Returns a new record of the type specified in the object
149149+ def build_object(object_or_symbol)
150150+ object_or_symbol.is_a?(Symbol) ? object_class(object_or_symbol).new : object_or_symbol
151151+ end
152152+153153+ # Returns the class type for the given object
154154+ def object_class(explicit_object)
155155+ explicit_object.is_a?(Symbol) ? explicit_object.to_s.classify.constantize : explicit_object.class
156156+ end
157157+end
···11+#
22+# sinatra_more
33+# Copyright (c) 2009 Nathan Esquenazi
44+#
55+# Permission is hereby granted, free of charge, to any person obtaining
66+# a copy of this software and associated documentation files (the
77+# "Software"), to deal in the Software without restriction, including
88+# without limitation the rights to use, copy, modify, merge, publish,
99+# distribute, sublicense, and/or sell copies of the Software, and to
1010+# permit persons to whom the Software is furnished to do so, subject to
1111+# the following conditions:
1212+#
1313+# The above copyright notice and this permission notice shall be
1414+# included in all copies or substantial portions of the Software.
1515+#
1616+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1717+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1818+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1919+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
2020+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
2121+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
2222+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2323+#
2424+2525+class StandardFormBuilder < AbstractFormBuilder
2626+2727+ # text_field_block(:username, { :class => 'long' }, { :class => 'wide-label' })
2828+ # text_area_block(:summary, { :class => 'long' }, { :class => 'wide-label' })
2929+ # password_field_block(:password, { :class => 'long' }, { :class => 'wide-label' })
3030+ # file_field_block(:photo, { :class => 'long' }, { :class => 'wide-label' })
3131+ # check_box_block(:remember_me, { :class => 'long' }, { :class => 'wide-label' })
3232+ # select_block(:color, :options => ['green', 'black'])
3333+ (self.field_types - [ :hidden_field, :radio_button ]).each do |field_type|
3434+ class_eval <<-EOF
3535+ def #{field_type}_block(field, options={}, label_options={})
3636+ label_options.reverse_merge!(:caption => options.delete(:caption)) if options[:caption]
3737+ field_html = label(field, label_options)
3838+ field_html << #{field_type}(field, options)
3939+ @template.content_tag(:p, field_html)
4040+ end
4141+ EOF
4242+ end
4343+4444+ # submit_block("Update")
4545+ def submit_block(caption, options={})
4646+ submit_html = self.submit(caption, options)
4747+ @template.content_tag(:p, submit_html)
4848+ end
4949+5050+ # image_submit_block("submit.png")
5151+ def image_submit_block(source, options={})
5252+ submit_html = self.image_submit(source, options)
5353+ @template.content_tag(:p, submit_html)
5454+ end
5555+end
+299
lib/sinatra_more/markup_plugin/form_helpers.rb
···11+#
22+# sinatra_more
33+# Copyright (c) 2009 Nathan Esquenazi
44+#
55+# Permission is hereby granted, free of charge, to any person obtaining
66+# a copy of this software and associated documentation files (the
77+# "Software"), to deal in the Software without restriction, including
88+# without limitation the rights to use, copy, modify, merge, publish,
99+# distribute, sublicense, and/or sell copies of the Software, and to
1010+# permit persons to whom the Software is furnished to do so, subject to
1111+# the following conditions:
1212+#
1313+# The above copyright notice and this permission notice shall be
1414+# included in all copies or substantial portions of the Software.
1515+#
1616+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1717+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1818+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1919+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
2020+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
2121+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
2222+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2323+#
2424+2525+require_relative "./form_builder/abstract_form_builder"
2626+require_relative "./form_builder/standard_form_builder"
2727+2828+module SinatraMore
2929+ module FormHelpers
3030+ # Constructs a form for object using given or default form_builder
3131+ # form_for :user, '/register' do |f| ... end
3232+ # form_for @user, '/register', :id => 'register' do |f| ... end
3333+ def form_for(object, url, settings={}, &block)
3434+ builder_class = configured_form_builder_class(settings[:builder])
3535+ form_html = capture_html(builder_class.new(self, object), &block)
3636+ form_tag(url, settings) { form_html }
3737+ end
3838+3939+ # Constructs form fields for an object using given or default form_builder
4040+ # Used within an existing form to allow alternate objects within one form
4141+ # fields_for @user.assignment do |assignment| ... end
4242+ # fields_for :assignment do |assigment| ... end
4343+ def fields_for(object, settings={}, &block)
4444+ builder_class = configured_form_builder_class(settings[:builder])
4545+ fields_html = capture_html(builder_class.new(self, object), &block)
4646+ concat_content fields_html
4747+ end
4848+4949+ # Constructs a form without object based on options
5050+ # form_tag '/register' do ... end
5151+ def form_tag(url, options={}, &block)
5252+ options.reverse_merge!(:method => 'post', :action => url)
5353+ if options.delete(:multipart)
5454+ options[:enctype] = "multipart/form-data"
5555+ end
5656+ inner_form_html = hidden_form_method_field(options[:method]) +
5757+ capture_html(&block)
5858+ if options[:method].to_sym != :get
5959+ inner_form_html = hidden_field_tag(Rack::Csrf.field,
6060+ :value => Rack::Csrf.csrf_token(env)) + inner_form_html
6161+ end
6262+ concat_content content_tag('form', inner_form_html, options)
6363+ end
6464+6565+ # Constructs a field_set to group fields with given options
6666+ # field_set_tag("Office", :class => 'office-set')
6767+ # parameters: legend_text=nil, options={}
6868+ def field_set_tag(*args, &block)
6969+ options = args.extract_options!
7070+ legend_text = args[0].is_a?(String) ? args.first : nil
7171+ legend_html = legend_text.blank? ? '' : content_tag(:legend, legend_text)
7272+ field_set_content = legend_html + capture_html(&block)
7373+ concat_content content_tag('fieldset', field_set_content, options)
7474+ end
7575+7676+ # Constructs list html for the errors for a given object
7777+ # error_messages_for @user
7878+ def error_messages_for(record, options={})
7979+ if record.blank? || record.errors.none?
8080+ return ""
8181+ end
8282+ options.reverse_merge!(:header_message =>
8383+ "The #{record.class.to_s.downcase} could not be saved!")
8484+ error_messages = record.errors.full_messages
8585+ error_items = error_messages.collect{|er| content_tag(:li, er) }.
8686+ join("\n")
8787+ error_html = content_tag(:p, options.delete(:header_message))
8888+ error_html << content_tag(:ul, error_items, :class => 'errors-list')
8989+ content_tag(:div, error_html, :class => 'field-errors')
9090+ end
9191+9292+ # Constructs a label tag from the given options
9393+ # label_tag :username, :class => 'long-label'
9494+ # label_tag :username, :class => 'long-label' do ... end
9595+ def label_tag(name, options={}, &block)
9696+ options.reverse_merge!(:caption => "#{name.to_s.titleize}: ",
9797+ :for => name)
9898+ caption_text = options.delete(:caption)
9999+ if block_given? # label with inner content
100100+ label_content = caption_text + capture_html(&block)
101101+ concat_content(content_tag(:label, label_content, options))
102102+ else # regular label
103103+ content_tag(:label, caption_text, options)
104104+ end
105105+ end
106106+107107+ # Constructs a hidden field input from the given options
108108+ # hidden_field_tag :session_key, :value => "__secret__"
109109+ def hidden_field_tag(name, options={})
110110+ options.reverse_merge!(:name => name)
111111+ input_tag(:hidden, options)
112112+ end
113113+114114+ # Constructs a text field input from the given options
115115+ # text_field_tag :username, :class => 'long'
116116+ def text_field_tag(name, options={})
117117+ options.reverse_merge!(:name => name)
118118+ input_tag(:text, options)
119119+ end
120120+121121+ # Constructs a text area input from the given options
122122+ # text_area_tag :username, :class => 'long', :value => "Demo?"
123123+ def text_area_tag(name, options={})
124124+ options.reverse_merge!(:name => name)
125125+ content_tag(:textarea, options.delete(:value).to_s, options)
126126+ end
127127+128128+ # Constructs a password field input from the given options
129129+ # password_field_tag :password, :class => 'long'
130130+ def password_field_tag(name, options={})
131131+ options.reverse_merge!(:name => name)
132132+ input_tag(:password, options)
133133+ end
134134+135135+ # Constructs a check_box from the given options
136136+ # options = [['caption', 'value'], ['Green', 'green1'],
137137+ # ['Blue', 'blue1'], ['Black', "black1"]]
138138+ # options = ['option', 'red', 'yellow' ]
139139+ # select_tag(:favorite_color, :options => ['red', 'yellow'],
140140+ # :selected => 'green1')
141141+ # select_tag(:country, :collection => @countries,
142142+ # :fields => [:name, :code])
143143+ def select_tag(name, options={})
144144+ options.reverse_merge!(:name => name)
145145+ collection, fields = options.delete(:collection), options.delete(:fields)
146146+ if collection
147147+ options[:options] = options_from_collection(collection, fields)
148148+ end
149149+ if groups = options.delete(:groups)
150150+ selected = options.delete(:selected)
151151+ select_options_html = grouped_options_for_select(groups, selected)
152152+ if options.delete(:include_blank)
153153+ select_options_html = options_for_select([ "" ], selected) +
154154+ select_options_html
155155+ end
156156+ else
157157+ if options.delete(:include_blank)
158158+ options[:options].unshift('')
159159+ end
160160+ select_options_html = options_for_select(options.delete(:options),
161161+ options.delete(:selected))
162162+ end
163163+ if options[:multiple]
164164+ options.merge!(:name => "#{options[:name]}[]")
165165+ end
166166+ content_tag(:select, select_options_html, options)
167167+ end
168168+169169+ # Constructs a check_box from the given options
170170+ # check_box_tag :remember_me, :value => 'Yes'
171171+ def check_box_tag(name, options={})
172172+ options.reverse_merge!(:name => name, :value => '1')
173173+ input_tag(:checkbox, options)
174174+ end
175175+176176+ # Constructs a radio_button from the given options
177177+ # radio_button_tag :remember_me, :value => 'true'
178178+ def radio_button_tag(name, options={})
179179+ options.reverse_merge!(:name => name)
180180+ input_tag(:radio, options)
181181+ end
182182+183183+ # Constructs a file field input from the given options
184184+ # file_field_tag :photo, :class => 'long'
185185+ def file_field_tag(name, options={})
186186+ options.reverse_merge!(:name => name)
187187+ input_tag(:file, options)
188188+ end
189189+190190+ # Constructs a submit button from the given options
191191+ # submit_tag "Create", :class => 'success'
192192+ def submit_tag(caption="Submit", options={})
193193+ options.reverse_merge!(:value => caption)
194194+ input_tag(:submit, options)
195195+ end
196196+197197+ # Constructs a button input from the given options
198198+ # button_tag "Cancel", :class => 'clear'
199199+ def button_tag(caption, options = {}, &block)
200200+ options.reverse_merge!(:value => caption)
201201+ content_tag(:button, caption, options, &block)
202202+ end
203203+204204+ # Constructs a submit button from the given options
205205+ # submit_tag "Create", :class => 'success'
206206+ def image_submit_tag(source, options = {})
207207+ options.reverse_merge!(:src => image_path(source))
208208+ input_tag(:image, options)
209209+ end
210210+211211+ protected
212212+213213+ # Returns an array of option items for a select field based on the given
214214+ # collection fields is an array containing the fields to display from each
215215+ # item in the collection
216216+ def options_from_collection(collection, fields)
217217+ if collection.blank?
218218+ return ''
219219+ end
220220+ collection.collect{|item| [ item.send(fields.first),
221221+ item.send(fields.last) ] }
222222+ end
223223+224224+ # Returns the options tags for a select based on the given option items
225225+ def options_for_select(option_items, selected_values = [])
226226+ if option_items.blank?
227227+ return ''
228228+ end
229229+ if !selected_values.is_a?(Array)
230230+ selected_values = [selected_values].compact
231231+ end
232232+ option_items.collect{|caption, value|
233233+ value ||= caption
234234+ selected = selected_values.find{|v|
235235+ v.to_s.match(/^(#{value}|#{caption})$/)
236236+ }
237237+ content_tag(:option, caption, :value => value, :selected => !!selected)
238238+ }.join("\n")
239239+ end
240240+241241+ # Returns the options tags for a select based on the given option items
242242+ def grouped_options_for_select(grouped_option_items, selected_values = [])
243243+ if grouped_option_items.blank?
244244+ return ''
245245+ end
246246+ if !selected_values.is_a?(Array)
247247+ selected_values = [selected_values].compact
248248+ end
249249+250250+ grouped_option_items.collect{|group|
251251+ content_tag(:optgroup,
252252+ group[1].collect{|caption, value|
253253+ value ||= caption
254254+ selected = selected_values.find{|v|
255255+ v.to_s.match(/^(#{value}|#{caption})$/)
256256+ }
257257+ content_tag(:option, caption, :value => value,
258258+ :selected => !!selected)
259259+ }.join("\n"),
260260+ :label => group[0])
261261+ }.join("\n")
262262+ end
263263+264264+ # returns the hidden method field for 'put' and 'delete' forms
265265+ # Only 'get' and 'post' are allowed within browsers;
266266+ # 'put' and 'delete' are just specified using hidden fields with form
267267+ # action still 'put'.
268268+ # hidden_form_method_field('delete') =>
269269+ # <input name="_method" value="delete" />
270270+ def hidden_form_method_field(desired_method)
271271+ if desired_method.to_s.match(/get|post/)
272272+ return ''
273273+ end
274274+ original_method = desired_method.dup
275275+ desired_method.replace('post')
276276+ hidden_field_tag(:_method, :value => original_method)
277277+ end
278278+279279+ # Returns the FormBuilder class to use based on all available setting
280280+ # sources
281281+ # If explicitly defined, returns that, otherwise returns defaults
282282+ # configured_form_builder_class(nil) => StandardFormBuilder
283283+ def configured_form_builder_class(explicit_builder = nil)
284284+ default_builder = if self.respond_to?(:settings) &&
285285+ self.settings.respond_to?(:default_builder)
286286+ self.settings.default_builder
287287+ else
288288+ nil
289289+ end
290290+291291+ configured_builder = explicit_builder || default_builder ||
292292+ 'StandardFormBuilder'
293293+ if configured_builder.is_a?(String)
294294+ configured_builder = configured_builder.constantize
295295+ end
296296+ configured_builder
297297+ end
298298+ end
299299+end
+97
lib/sinatra_more/markup_plugin/format_helpers.rb
···11+#
22+# sinatra_more
33+# Copyright (c) 2009 Nathan Esquenazi
44+#
55+# Permission is hereby granted, free of charge, to any person obtaining
66+# a copy of this software and associated documentation files (the
77+# "Software"), to deal in the Software without restriction, including
88+# without limitation the rights to use, copy, modify, merge, publish,
99+# distribute, sublicense, and/or sell copies of the Software, and to
1010+# permit persons to whom the Software is furnished to do so, subject to
1111+# the following conditions:
1212+#
1313+# The above copyright notice and this permission notice shall be
1414+# included in all copies or substantial portions of the Software.
1515+#
1616+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1717+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1818+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1919+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
2020+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
2121+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
2222+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2323+#
2424+2525+module SinatraMore
2626+ module FormatHelpers
2727+2828+ # Returns escaped text to protect against malicious content
2929+ def escape_html(text)
3030+ Rack::Utils.escape_html(text)
3131+ end
3232+ alias h escape_html
3333+ alias sanitize_html escape_html
3434+3535+ # Returns escaped text to protect against malicious content
3636+ # Returns blank if the text is empty
3737+ def h!(text, blank_text = ' ')
3838+ return blank_text if text.nil? || text.empty?
3939+ h text
4040+ end
4141+4242+4343+ # Smart time helper which returns relative text representing times for recent dates
4444+ # and absolutes for dates that are far removed from the current date
4545+ # time_in_words(10.days.ago) => '10 days ago'
4646+ def time_in_words(date)
4747+ date = date.to_date
4848+ date = Date.parse(date, true) unless /Date.*/ =~ date.class.to_s
4949+ days = (date - Date.today).to_i
5050+5151+ return 'today' if days >= 0 and days < 1
5252+ return 'tomorrow' if days >= 1 and days < 2
5353+ return 'yesterday' if days >= -1 and days < 0
5454+5555+ return "in #{days} days" if days.abs < 60 and days > 0
5656+ return "#{days.abs} days ago" if days.abs < 60 and days < 0
5757+5858+ return date.strftime('%A, %B %e') if days.abs < 182
5959+ return date.strftime('%A, %B %e, %Y')
6060+ end
6161+ alias time_ago time_in_words
6262+6363+ # Returns relative time in words referencing the given date
6464+ # relative_time_ago(Time.now) => 'about a minute ago'
6565+ def relative_time_ago(from_time)
6666+ distance_in_minutes = (((Time.now - from_time.to_time).abs)/60).round
6767+ case distance_in_minutes
6868+ when 0..1 then 'about a minute'
6969+ when 2..44 then "#{distance_in_minutes} minutes"
7070+ when 45..89 then 'about 1 hour'
7171+ when 90..1439 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
7272+ when 1440..2439 then '1 day'
7373+ when 2440..2879 then 'about 2 days'
7474+ when 2880..43199 then "#{(distance_in_minutes / 1440).round} days"
7575+ when 43200..86399 then 'about 1 month'
7676+ when 86400..525599 then "#{(distance_in_minutes / 43200).round} months"
7777+ when 525600..1051199 then 'about 1 year'
7878+ else "over #{(distance_in_minutes / 525600).round} years"
7979+ end
8080+ end
8181+8282+ # Used in xxxx.js.erb files to escape html so that it can be passed to javascript from sinatra
8383+ # escape_javascript("<h1>Hey</h1>")
8484+ def escape_javascript(html_content)
8585+ return '' unless html_content
8686+ javascript_mapping = { '\\' => '\\\\', '</' => '<\/', "\r\n" => '\n', "\n" => '\n' }
8787+ javascript_mapping.merge("\r" => '\n', '"' => '\\"', "'" => "\\'")
8888+ escaped_string = html_content.gsub(/(\\|<\/|\r\n|[\n\r"'])/) { javascript_mapping[$1] }
8989+ "\"#{escaped_string}\""
9090+ end
9191+9292+ alias js_escape escape_javascript
9393+ alias js_escape_html escape_javascript
9494+ alias escape_for_javascript escape_javascript
9595+9696+ end
9797+end
+122
lib/sinatra_more/markup_plugin/output_helpers.rb
···11+#
22+# sinatra_more
33+# Copyright (c) 2009 Nathan Esquenazi
44+#
55+# Permission is hereby granted, free of charge, to any person obtaining
66+# a copy of this software and associated documentation files (the
77+# "Software"), to deal in the Software without restriction, including
88+# without limitation the rights to use, copy, modify, merge, publish,
99+# distribute, sublicense, and/or sell copies of the Software, and to
1010+# permit persons to whom the Software is furnished to do so, subject to
1111+# the following conditions:
1212+#
1313+# The above copyright notice and this permission notice shall be
1414+# included in all copies or substantial portions of the Software.
1515+#
1616+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1717+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1818+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1919+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
2020+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
2121+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
2222+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2323+#
2424+2525+module SinatraMore
2626+ module OutputHelpers
2727+ # Captures the html from a block of template code for erb or haml
2828+ # capture_html(&block) => "...html..."
2929+ def capture_html(*args, &block)
3030+ if self.respond_to?(:is_haml?) && is_haml?
3131+ block_is_haml?(block) ? capture_haml(*args, &block) : block.call
3232+ elsif has_erb_buffer?
3333+ result_text = capture_erb(*args, &block)
3434+ result_text.present? ? result_text : (block_given? && block.call(*args))
3535+ else # theres no template to capture, invoke the block directly
3636+ block.call(*args)
3737+ end
3838+ end
3939+4040+ # Outputs the given text to the templates buffer directly
4141+ # concat_content("This will be output to the template buffer in erb or haml")
4242+ def concat_content(text="")
4343+ if self.respond_to?(:is_haml?) && is_haml?
4444+ haml_concat(text)
4545+ elsif has_erb_buffer?
4646+ erb_concat(text)
4747+ else # theres no template to concat, return the text directly
4848+ text
4949+ end
5050+ end
5151+5252+ # Returns true if the block is from an ERB or HAML template; false otherwise.
5353+ # Used to determine if html should be returned or concatted to view
5454+ # block_is_template?(block)
5555+ def block_is_template?(block)
5656+ block && (block_is_erb?(block) || (self.respond_to?(:block_is_haml?) && block_is_haml?(block)))
5757+ end
5858+5959+ # Capture a block of content to be rendered at a later time.
6060+ # Your blocks can also receive values, which are passed to them by <tt>yield_content</tt>
6161+ # content_for(:name) { ...content... }
6262+ # content_for(:name) { |name| ...content... }
6363+ def content_for(key, &block)
6464+ content_blocks[key.to_sym] << block
6565+ end
6666+6767+ # Render the captured content blocks for a given key.
6868+ # You can also pass values to the content blocks by passing them
6969+ # as arguments after the key.
7070+ # yield_content :include
7171+ # yield_content :head, "param1", "param2"
7272+ def yield_content(key, *args)
7373+ blocks = content_blocks[key.to_sym]
7474+ return nil if blocks.empty?
7575+ blocks.map { |content|
7676+ capture_html(*args, &content)
7777+ }.join
7878+ end
7979+8080+ protected
8181+8282+ # Retrieves content_blocks stored by content_for or within yield_content
8383+ # content_blocks[:name] => ['...', '...']
8484+ def content_blocks
8585+ @content_blocks ||= Hash.new {|h,k| h[k] = [] }
8686+ end
8787+8888+ # Used to capture the html from a block of erb code
8989+ # capture_erb(&block) => '...html...'
9090+ def capture_erb(*args, &block)
9191+ erb_with_output_buffer { block_given? && block.call(*args) }
9292+ end
9393+9494+ # Concats directly to an erb template
9595+ # erb_concat("Direct to buffer")
9696+ def erb_concat(text)
9797+ @_out_buf << text if has_erb_buffer?
9898+ end
9999+100100+ # Returns true if an erb buffer is detected
101101+ # has_erb_buffer? => true
102102+ def has_erb_buffer?
103103+ !@_out_buf.nil?
104104+ end
105105+106106+ # Used to determine if a block is called from ERB.
107107+ # NOTE: This doesn't actually work yet because the variable __in_erb_template
108108+ # hasn't been defined in ERB. We need to find a way to fix this.
109109+ def block_is_erb?(block)
110110+ has_erb_buffer? || block && eval('defined? __in_erb_template', block)
111111+ end
112112+113113+ # Used to direct the buffer for the erb capture
114114+ def erb_with_output_buffer(buf = '') #:nodoc:
115115+ @_out_buf, old_buffer = buf, @_out_buf
116116+ yield
117117+ @_out_buf
118118+ ensure
119119+ @_out_buf = old_buffer
120120+ end
121121+ end
122122+end
+78
lib/sinatra_more/markup_plugin/tag_helpers.rb
···11+#
22+# sinatra_more
33+# Copyright (c) 2009 Nathan Esquenazi
44+#
55+# Permission is hereby granted, free of charge, to any person obtaining
66+# a copy of this software and associated documentation files (the
77+# "Software"), to deal in the Software without restriction, including
88+# without limitation the rights to use, copy, modify, merge, publish,
99+# distribute, sublicense, and/or sell copies of the Software, and to
1010+# permit persons to whom the Software is furnished to do so, subject to
1111+# the following conditions:
1212+#
1313+# The above copyright notice and this permission notice shall be
1414+# included in all copies or substantial portions of the Software.
1515+#
1616+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1717+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1818+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1919+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
2020+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
2121+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
2222+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2323+#
2424+2525+module SinatraMore
2626+ module TagHelpers
2727+ # Creates an html input field with given type and options
2828+ # input_tag :text, :class => "test"
2929+ def input_tag(type, options = {})
3030+ options.reverse_merge!(:type => type)
3131+ tag(:input, options)
3232+ end
3333+3434+ # Creates an html tag with given name, content and options
3535+ # content_tag(:p, "hello", :class => 'light')
3636+ # content_tag(:p, :class => 'dark') do ... end
3737+ # parameters: content_tag(name, content=nil, options={}, &block)
3838+ def content_tag(*args, &block)
3939+ name = args.first
4040+ options = args.extract_options!
4141+ tag_html = block_given? ? capture_html(&block) : args[1]
4242+ tag_result = tag(name, options.merge(:content => tag_html))
4343+ block_is_template?(block) ? concat_content(tag_result) : tag_result
4444+ end
4545+4646+ # Creates an html tag with the given name and options
4747+ # tag(:br, :style => 'clear:both')
4848+ # tag(:p, :content => "hello", :class => 'large')
4949+ def tag(name, options={})
5050+ content = options.delete(:content)
5151+ identity_tag_attributes.each { |attr| options[attr] = attr.to_s if options[attr] }
5252+ html_attrs = options.collect { |a, v| v.blank? ? nil : "#{a}=\"#{v}\"" }.compact.join(" ")
5353+ base_tag = (!html_attrs.blank? ? "<#{name} #{html_attrs}" : "<#{name}")
5454+ base_tag << (content ? ">#{content}</#{name}>" : " />")
5555+ end
5656+5757+ def meta_tag(name, content, options = {})
5858+ html_attrs = options.collect{|a,v|
5959+ if v.blank?
6060+ nil
6161+ else
6262+ "#{Rack::Utils.escape_html(a)}=\"#{Rack::Utils.escape_html(v)}\""
6363+ end
6464+ }.compact.join(" ")
6565+6666+ "<meta name=\"#{Rack::Utils.escape_html(name)}\" " <<
6767+ "content=\"#{Rack::Utils.escape_html(content)}\" " <<
6868+ (html_attrs.blank? ? "" : html_attrs << " ") << "/>"
6969+ end
7070+7171+ protected
7272+7373+ # Returns a list of attributes which can only contain an identity value (i.e selected)
7474+ def identity_tag_attributes
7575+ [:checked, :disabled, :selected, :multiple]
7676+ end
7777+ end
7878+end
+34
lib/sinatra_more/render_plugin.rb
···11+#
22+# sinatra_more
33+# Copyright (c) 2009 Nathan Esquenazi
44+#
55+# Permission is hereby granted, free of charge, to any person obtaining
66+# a copy of this software and associated documentation files (the
77+# "Software"), to deal in the Software without restriction, including
88+# without limitation the rights to use, copy, modify, merge, publish,
99+# distribute, sublicense, and/or sell copies of the Software, and to
1010+# permit persons to whom the Software is furnished to do so, subject to
1111+# the following conditions:
1212+#
1313+# The above copyright notice and this permission notice shall be
1414+# included in all copies or substantial portions of the Software.
1515+#
1616+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1717+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1818+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1919+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
2020+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
2121+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
2222+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2323+#
2424+2525+require File.dirname(__FILE__) + '/support_lite'
2626+Dir[File.dirname(__FILE__) + '/render_plugin/**/*.rb'].each {|file| load file }
2727+2828+module SinatraMore
2929+ module RenderPlugin
3030+ def self.registered(app)
3131+ app.helpers RenderHelpers
3232+ end
3333+ end
3434+end
+85
lib/sinatra_more/render_plugin/render_helpers.rb
···11+#
22+# sinatra_more
33+# Copyright (c) 2009 Nathan Esquenazi
44+#
55+# Permission is hereby granted, free of charge, to any person obtaining
66+# a copy of this software and associated documentation files (the
77+# "Software"), to deal in the Software without restriction, including
88+# without limitation the rights to use, copy, modify, merge, publish,
99+# distribute, sublicense, and/or sell copies of the Software, and to
1010+# permit persons to whom the Software is furnished to do so, subject to
1111+# the following conditions:
1212+#
1313+# The above copyright notice and this permission notice shall be
1414+# included in all copies or substantial portions of the Software.
1515+#
1616+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1717+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1818+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1919+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
2020+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
2121+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
2222+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2323+#
2424+2525+module SinatraMore
2626+ module RenderHelpers
2727+ # Renders a erb template based on the relative path
2828+ # erb_template 'users/new'
2929+ def erb_template(template_path, options={})
3030+ render_template template_path, options.merge(:template_engine => :erb)
3131+ end
3232+3333+ # Renders a haml template based on the relative path
3434+ # haml_template 'users/new'
3535+ def haml_template(template_path, options={})
3636+ render_template template_path, options.merge(:template_engine => :haml)
3737+ end
3838+3939+ # Renders a template from a file path automatically determining rendering engine
4040+ # render_template 'users/new'
4141+ # options = { :template_engine => 'haml' }
4242+ def render_template(template_path, options={})
4343+ template_engine = options.delete(:template_engine) || resolve_template_engine(template_path)
4444+ render template_engine.to_sym, template_path.to_sym, options
4545+ end
4646+4747+ # Partials implementation which includes collections support
4848+ # partial 'photo/_item', :object => @photo
4949+ # partial 'photo/_item', :collection => @photos
5050+ def partial(template, options={})
5151+ options.reverse_merge!(:locals => {}, :layout => false)
5252+ path = template.to_s.split(File::SEPARATOR)
5353+ object_name = path[-1].to_sym
5454+ path[-1] = "_#{path[-1]}"
5555+ template_path = File.join(path)
5656+ raise 'Partial collection specified but is nil' if options.has_key?(:collection) && options[:collection].nil?
5757+ if collection = options.delete(:collection)
5858+ options.delete(:object)
5959+ counter = 0
6060+ collection.collect do |member|
6161+ counter += 1
6262+ options[:locals].merge!(object_name => member, "#{object_name}_counter".to_sym => counter)
6363+ render_template(template_path, options.merge(:layout => false))
6464+ end.join("\n")
6565+ else
6666+ if member = options.delete(:object)
6767+ options[:locals].merge!(object_name => member)
6868+ end
6969+ render_template(template_path, options.merge(:layout => false))
7070+ end
7171+ end
7272+ alias render_partial partial
7373+7474+ private
7575+7676+ # Returns the template engine (i.e haml) to use for a given template_path
7777+ # resolve_template_engine('users/new') => :haml
7878+ def resolve_template_engine(template_path)
7979+ resolved_template_path = File.join(self.settings.views, template_path.to_s + ".*")
8080+ template_file = Dir[resolved_template_path].first
8181+ raise "Template path '#{template_path}' could not be located in views!" unless template_file
8282+ template_engine = File.extname(template_file)[1..-1].to_sym
8383+ end
8484+ end
8585+end
+45
lib/sinatra_more/support_lite.rb
···11+#
22+# sinatra_more
33+# Copyright (c) 2009 Nathan Esquenazi
44+#
55+# Permission is hereby granted, free of charge, to any person obtaining
66+# a copy of this software and associated documentation files (the
77+# "Software"), to deal in the Software without restriction, including
88+# without limitation the rights to use, copy, modify, merge, publish,
99+# distribute, sublicense, and/or sell copies of the Software, and to
1010+# permit persons to whom the Software is furnished to do so, subject to
1111+# the following conditions:
1212+#
1313+# The above copyright notice and this permission notice shall be
1414+# included in all copies or substantial portions of the Software.
1515+#
1616+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1717+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1818+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1919+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
2020+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
2121+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
2222+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2323+#
2424+2525+# This is for adding specific methods that are required by sinatra_more if activesupport isn't required
2626+2727+require 'yaml' unless defined?(YAML)
2828+2929+unless String.method_defined?(:titleize) && Hash.method_defined?(:slice)
3030+ require 'active_support/core_ext/kernel'
3131+ require 'active_support/core_ext/array'
3232+ require 'active_support/core_ext/hash'
3333+ require 'active_support/core_ext/module'
3434+ require 'active_support/core_ext/class'
3535+ require 'active_support/deprecation'
3636+ require 'active_support/inflector'
3737+end
3838+3939+unless String.method_defined?(:blank?)
4040+ begin
4141+ require 'active_support/core_ext/object/blank'
4242+ rescue LoadError
4343+ require 'active_support/core_ext/blank'
4444+ end
4545+end