<!-- Core Rapid tags and tags that don't belong anywhere else. -->

<!-- Renders a table with one row per field, where each row contains a `<th>` with the field name, and a `<td>` with (by default)
     a `<view>` of the field.
         
### Attributes

 - fields: Comma separated list of field names to display. Defaults to the fields returned by the `standard_fields` helper. That is, all fields apart from IDs and timestamps.
 
 - force-all: All non-viewable fields will be skipped unless this attribute is given
 
 - skip: Comma separated list of fields to exclude

 - tag: The name of a tag to use inside the `<td>` to display the value. Defaults to `view`
 
 - no-edit: Controls the behavior of `<input>` tags when the user doesn't have edit permission for a field.
   - view: render the current value using the `<view>` tag
   - disable: render the input as normal, but add HTML's `disabled` attribute
   - skip: render nothing at all. This will omit the entire row (including the label)
   - ignore: render the input normally. That is, don't even perform the edit check.

### Example

    <field-list fields="first-name, last-name, city">
      <first-name-label:>Given Name</first-name-label:>
      <last-name-label:>Family Name</last-name-label:>
      <city-view:><name-one/></city-view:>
    </field-list>

-->
<def tag="field-list" attrs="tag, no-edit">
  <% tag ||= scope.in_form ? "input" : "view"; no_edit ||= "skip" %>
  <labelled-item-list merge-attrs="&attributes - attrs_for(:with_fields)">
    <with-fields merge-attrs="&attributes & attrs_for(:with_fields)">
    <% field_name = this_field_name 
       input_attrs = {:no_edit => no_edit} if tag == "input"
    -%>
      <labelled-item unless="&tag == 'input' && no_edit == 'skip' && !can_edit?">
        <item-label param="#{this_field.to_s.sub('?', '')}-label" unless="&field_name.blank?">
          <do param="label"><%= field_name %></do>
        </item-label>
        <item-value param="#{this_field.to_s.sub('?', '')}-view" colspan="&2 if field_name.blank?">
          <do param="view"><call-tag tag="&tag" param="#{this_field.to_s.sub('?', '')}-tag" merge-attrs="&input_attrs"/></do>
          <div param="input-help" if="&tag.to_sym == :input && !this_field_help.blank?"><%= this_field_help %></div>
        </item-value>
      </labelled-item>
    </with-fields>
  </labelled-item-list>
</def>

<!-- Used to render nil values. By default renders "(Not Available)"
  
### Usage

Redefine in your app to have nil values displayed differently, e.g.:

    <def tag="nil-view">-</def>
  
-->
<def tag="nil-view"><%= scope.nil_view || "(Not Available)" %></def>

<!--
`<table>` is extended in Rapid to provide a shorthand way to output a set of fields for a given collection. This is enabled using the `field` attribute (without the `field` attribute this is just the regular HTML `<table>` tag)

### Usage

If the context is an array of blog posts...

    <table fields="name, created_at, description"/>

This will output a header row containing "Name", "Created At" and "Description" followed by a row for each record in the collection. By default, the `<view/>` tag is called for each field in the row. This can be altered with the `field-tag` attribute, e.g.

    <table fields="name, created_at, description" field-tag="input"/>

This will use `<input/>` as the tag in each table cell instead of `<view/>`

### Additional Notes

* `<table>` provides parameters based on the names of the fields which can be used to further customise the output. For each field a heading parameter is provided, e.g. name-heading, created-at-heading, description-heading. These can be used to customise the headings:

        <table fields="name, created_at, description">
          <created-at-heading:>Creation Date</created-at-heading:>
        </table>
* Similarly, "view" parameters are provided as an additional way to customise the table cells of the table body, e.g. `name-view`, `created-at-view`, `description-view`:

        <table fields="name, created_at, description">
          <created-at-view:><view format="%d %B %Y"/></created-at-view:>
        </table>
* By adding an empty `control` parameter, the default control column is enable adding an edit link and delete button for each table row:

        <table fields="name, created_at, description">
          <controls:/>
        </table>
    The controls can be further customised using the "edit-link" and "delete-button" parameters or by providing completely new content for the control column, e.g.

        <table fields="name, created_at, description">
          <controls:>my controls!</controls:>
        </table>
-->
<def tag="table" attrs="fields, field-tag, empty">
  <if test="&!(fields || all_parameters.tr?)">
    <%= element("table", attributes, all_parameters.default) %>
  </if>
  <else>
    <% field_tag ||= "view" %>
    <unless test="&this.empty? && !empty">
      <% element "table", attributes - attrs_for(:with_fields) do %>
      <thead if="&all_parameters[:thead] || fields" param>
        <tr param="field-heading-row">
          <with-field-names merge-attrs="&all_attributes & attrs_for(:with_fields)">
            <th param="#{scope.field_name}-heading"><%= this.member_class.try.view_hints.try.field_name(scope.field_name) if scope %></th>
          </with-field-names>
          <th if="&all_parameters[:controls]" class="controls"/>
        </tr>
      </thead>
      <tbody param>
        <repeat>
          <tr param if="&can_view?"
              class="#{scope.even_odd} #{this_type.name.underscore} #{model_id_class}">
            <if test="&fields">
              <with-fields merge-attrs="&all_attributes & attrs_for(:with_fields)" force-all>
                <td param="#{this_field.to_s.sub('?', '').gsub('.', '-')}-view"><call-tag tag="&field_tag"/></td>
              </with-fields>
              <td class="controls" param="controls" if="&all_parameters[:controls]">
                <a param="edit-link" action="edit" if="&can_edit?"><ht key="hobo.action.edit">Edit</ht></a>
                <delete-button param/>
              </td>
            </if>
          </tr>
        </repeat>      
      </tbody>
      <tfoot if="&all_parameters[:tfoot]" param/>
      <% end %>
    </unless>
  </else>
</def>

<!--
Provides a short hand way of displaying images in public/images

### Usage

    <image src="hobo.png"/> -> <img src="/images/hobo.png"/>
    <image src="blog/funny.jpg" alt="Funny Scene"/> -> <img src="/images/blog/funny.jpg" alt="Funny Scene"/>
-->
<def tag="image" attrs="src">
  <img src="#{base_url}/images/#{src}" merge-attrs/>
</def>


<!-- Renders an ajax-progress 'spinner' using `spinner.gif` from the current theme, with a `class='hidden'` -->
<def tag="spinner">
  <img src="#{base_url}/hobothemes/#{Hobo.current_theme}/images/spinner.gif" class="hidden" merge-attrs/>
</def>


<!-- Renders some standard JavaScript code that various features of the Rapid library rely on. This tag would typicallu be called from your `<page>` tag. The default Rapid pages include this already. -->
<def tag="hobo-rapid-javascripts"><%=
   res = 'var hoboParts = {};'
   # FIXME: This should interrogate the model-router - not the models
   unless Hobo::Model.all_models.empty?
     # Tell JS code how to pluralize names, unless they follow the simple rule
     names = Hobo::Model.all_models.map do |m|
                                          m = m.name.underscore
                                          "#{m}: '#{m.pluralize}'" unless m.pluralize == m + 's'
                                        end.compact
     res << "var pluralisations = {#{names * ', '}}; "
   end
   base = [base_url, subsite].compact.join("/")
   res << "urlBase = '#{base}'; hoboPagePath = '#{view_name}'"
   if protect_against_forgery?
     res << "; formAuthToken = { name: '#{request_forgery_protection_token}', value: '#{form_authenticity_token}' }"
   end
   res
%></def>

<!-- Renders the name of the current context using a variety of methods.
  
### Details

 - Equivalent to `<nil-view>` if `this` is nil
 - Equivalent to `<count>` if `this` is an Array
 - Equivalent to `<type-name>` if `this` is a class
 - If the context has a `name_attribute` defined, equivalent to `<view:abc/>` (where `abc` is the name attribute)
 - Finally falls back to `this.to_s` (html escaped), but only if the user has view permission for `this`

### Attributes

 - if-present: if given, nothing at all will be rendered for nil values (as opposed to rendering `<nil-view>`)
  
-->
<def tag="name" attrs="if-present"><%= 
  if this.nil?
    nil_view unless if_present
  else
    if this.is_a?(Array)
      count
    elsif this.is_a?(Class)
      type_name(attributes)
    elsif (name_attr = this.class.try.name_attribute) && can_view?(this, name_attr)
      view(merge_attrs(attributes, {:field => name_attr}))
    elsif can_view?(this)
      h this.to_s
    end
  end
%></def>


<!-- Renders a human readable version of the type of the context
  
### Details

 - If `this` is already a class, the name of that class is used
 - Otherwise, first `this.member_class` (for collections), then `this.class` are tried
 - By default the name is titleised and singular.
 
### Attributes

 - plural: pluralise the name
 - lowercase: render the name in all lower case
 - dasherize: render the name in lower case with dashes instead of spaces. 

-->
<def tag="type-name" attrs="plural, lowercase, dasherize"><%=
  type ||= (this if this.is_a?(Class)) || this.try.member_class || this.class
  
  name = type.respond_to?(:view_hints) ? type.view_hints.model_name : type.name
  name = dasherize ? name.underscore.dasherize : name.titleize
  name = name.pluralize if plural
  name = name.downcase if lowercase
  name
%></def>
     

<!-- Renders a human readable name of a collection
  
### Details

 - Uses `this.origin_attribute` as the name.
 - Falls back to `<type-name>` otherwise.
 - By default the name is titleised and plural.
 
### Attributes

 - singular: singularise the name
 - lowercase: render the name in all lower case
 - dasherize: render the name in lower case with dashes instead of spaces. 

-->
<def tag="collection-name" attrs="singular, lowercase, dasherize"><%=
  if (attr = this.try.origin_attribute)
    name = attr.to_s
    name = dasherize ? name.underscore.dasherize : name.titleize
    name = name.singularize if singular
    name = name.downcase if lowercase
    name
  else
    type_name(:plural => !singular, :lowercase => lowercase, :dasherize => dasherize)
  end
%></def>

<!--
`<a>` is extended in Rapid to automatically provide URLs for Hobo model routes

### Usage

The tag behaves as a regular HTML link or anchor if either the href or name attribute is given:

    <a href="/admin">Admin</a> -> Output is exactly as provided, untouched by Rapid

If no href or name is given then the _context_ is used to determine the link URL.
The helper method `object_url` is used to construct the URL using restful routing:

If the context is a class then the link will be an index page:

    <a with="&BlogPost">My Blog</a> -> <a href="/blog_posts">My Blog</a>

If the context is a hobo model instance then the link will be a show page:

    <% blog_post = BlogPost.find(1) %>
    <a with="&blog_post">My Blog Post</a> -> <a href="/blog_posts/1">My Blog Post</a>

An action can be provided for an alternative show page:

    <a with="&blog_post" action="edit">Edit Post</a> -> <a href="/blog_posts/1/edit">Edit Post</a>

Or a new page if the context is a class:
  
    <a with="&BlogPost" action="new">New Blog Post</a> -> <a href="/blog_posts/new">New Blog Post</a>

### Additional Features

* If the constructed route does not exist then the link will not be created, but the content of the link will still be output. E.g. when `/blog_posts` does not exist (because the hobo model controller does not exist or the index action is disabled):

        <a with="&BlogPost">My Blog</a> -> My Blog

    when the show action `/blog_posts/:id` does not exist:

        <a with="&blog_post">My Blog Post</a> -> My Blog Post
* If no content text is provided then `<a>` will use the name method on the context to provide the text. E.g.

        <a with="&blog_post"/> -> <a href="/blog_posts/1">My First Blog Post</a>`
        <a with="&BlogPost"/> -> <a href="/blog_posts">Blog Posts</a>`
* If `action="new"` then `<a>` will check that the current user has permission to create the object
* Several useful classes are added automatically to the output `<a>`.

### Attributes

* action:  If "new", triggers the special behaviour listed above.  Otherwise, contains the action to be performed on the context.   If neither `action` nor `method` are specified, the action will be "index" or "show", as appropriate.

* to:  Use this item as the target instead of the current context.

* params, query-params:  These are appended to the target as a query string after a "?".

* href, name:  If either of these attributes are present, the smart features of this tag are turned off.

* format: this adds ".#{format}" to the end of the url

* subsite: routes the URL using the subsite

* force: overrides the permission check if `action` is "new"

* method: "get", "put", "post" or "delete".  "get" is the default

-->
<def tag="a" attrs="action, to, params, query-params, href, format, subsite, force"><%=
  content = parameters.default
   
  params = self.query_params.merge(params || HashWithIndifferentAccess.new) if query_params
  
  if href || attributes[:name]
    # Regular link
    href += "?" + params.map { |n, v| "#{n}=#{v}" }.join('&') if !params.blank?
    element(:a, attributes.update(:href => href), content)
  else
    target = to || this
  
    if target.nil?
      Hobo::Dryml.last_if = false
      nil_view
    elsif action == "new"
      # Link to a new object form
      new_record = target.new
      new_record.set_creator(current_user)
      href = object_url(target, "new", params._?.merge(:subsite => subsite))
      
      if href && (force || can_create?(new_record))
        new_class_name = if target.respond_to?(:proxy_reflection)
                           target.proxy_reflection.klass.name
                         else
                           target.name
                         end

        add_classes!(attributes, "new-#{new_class_name.underscore}-link")
        content = "New #{new_class_name.titleize}" if content.blank?
        Hobo::Dryml.last_if = true
        element(:a, attributes.update(:href => href), content)
      else
        Hobo::Dryml.last_if = false
        ""
      end
    else
      # Link to an existing object

      content = name if content.blank?
      
      href = object_url(target, action, (params || {}).merge(:subsite => subsite))
      if href.nil?
        # This target is registered with ModelRouter as not linkable
        content
      else
        css_class = target.try.origin_attribute || target.class.name.underscore.dasherize
        add_classes!(attributes, "#{css_class}-link")
      
        href.sub!(/\?|$/, ".#{format}\\0") unless format.blank?
      
        # Set default link text if none given
        element(:a, attributes.update(:href => href), content)
      end
    end
  end
%></def>

<!--
Provides a read-only view tailored to the type of the object being viewed. `<view>` is a _polymorphic_ tag which means that there are a variety of definitions, each one written for a particular type. For example there are views for `Date`, `Time`, `Numeric`, `String` and `Boolean`. The type specific view is enclosed in a wrapper tag (typically a `<span>` or `<div>`) with some useful classes automatically added.

### Usage

Assuming the context is a blog post...

* Viewing a DateTime field:

        <view:created_at/> -> <span class="view blog-post-created-at">June 09, 2008 15:36</span>
* Viewing a String field:

        <view:title/> -> <span class="view blog-post-title">My First Blog Post</span>
* Viewing an Integer field:

        <view:comment_count/> -> <span class="view blog-post-comment-count">4</span>
* Viewing the blog post itself results in a link to the blog post (using Rapid's `<a>` tag):

        <view/> -> <span class="view model:blog-post-1"><a href="/blog_posts/1">My First Blog Post</a></span>

### Additional Notes

* The wrapper tag is `<span>` unless the field type is `Text` (different to `String`) where it is `<div>`. Use the `inline` or `block` attributes to force a `<span>` or a `<div>`, e.g.

        <view:body/> -> <div class="view blog-post-body">This is my blog post body</div>

        <view:body inline/> -> <span class="view blog-post-body">This is my blog post body</span>

        <view:created_at block/> -> <div class="view blog-post-created-at">June 09, 2008 15:36</div>
* Use the `no-wrapper` attribute to remove the wrapper tag completely. e.g.

        <view:created_at no-wrapper/> -> June 09, 2008 15:36
-->
<def tag="view" attrs="inline, block, if-blank, no-wrapper, truncate"><%=
  raise HoboError, "view of non-viewable field '#{this_field}' of #{this_parent.typed_id rescue this_parent}" unless
    can_view?
   
  res = if this.nil? && if_blank.nil?
          this_type.is_a?(Class) && this_type <= String ? "" : nil_view
        elsif (refl = this_field_reflection) && refl.macro == :has_many
          collection_view(attributes)
        else
          view_tag = find_polymorphic_tag("view")
          
          if view_tag == "view" # i.e. it didn't find a type specific tag
            if this.respond_to?(:to_html)
              this.to_html(scope.xmldoctype)
            else
              this.to_s
            end
          else
            attrs = add_classes(attributes, "view", type_and_field._?.dasherize, model_id_class)

            view_attrs = attrs_for(view_tag)
            the_view = send(view_tag, attrs & view_attrs)
            
            the_view = if_blank if if_blank && the_view.blank?

            truncate = 30 if truncate == true
            the_view = self.truncate(the_view, :length => truncate.to_i) if truncate
            the_view = the_view.strip
            
            if no_wrapper
              the_view
            else
              wrapper = if inline
                          :span
                        elsif block || this_type <= HoboFields::Text
                          :div
                        else
                          :span
                        end
              element(wrapper, attrs - view_attrs, the_view)
            end
          end
        end
   Hobo::Dryml.last_if = !res.blank?
   res
%></def>

<!-- `<view>` calls this tag when called for a `has_many` collection. By default calls `<links-for-collection/>` -->
<def tag="collection-view" polymorphic><links-for-collection merge-attrs/></def>

<!-- Renders a comma separated list of links (`<a>`), or "(none)" if the list is empty -->
<def tag="links-for-collection"><%= this.empty? ? "(none)" : context_map { a }.join(", ") %></def>

<!-- Renders `this.to_s(:long)`, or `this.strftime(format)` if the `format` attribute is given -->
<def tag="view" for="Date" attrs="format"><%= this && (format ? this.strftime(format) : this.to_s(:long)) %></def>

<!-- Renders `this.to_s(:long)`, or `this.strftime(format)` if the `format` attribute is given -->
<def tag="view" for="Time" attrs="format"><%= this && (format ? this.strftime(format) : this.to_s(:long)) %></def>

<!-- Renders `this.to_s(:long)`, or `this.strftime(format)` if the `format` attribute is given -->
<def tag="view" for="ActiveSupport::TimeWithZone" attrs="format"><%= this && (format ? this.strftime(format) : this.to_s(:long)) %></def>

<!-- Renders `this.to_s`, or `format % this` if the `format` attribute is given -->
<def tag="view" for="Numeric" attrs="format"><%= format ? format % this : this.to_s %></def>

<!-- Renders `this` with HTML escaping and newlines replaced with `<br>` tags -->
<def tag="view" for="string"><%= 
  if !(this.class == String) && this.respond_to?(:to_html) # workaround for Maruku which adds String#to_html : (
    this.to_html(scope.xmldoctype)
  else
    h(this).gsub("\n", "<br#{scope.xmldoctype ? ' /' : ''}>") 
  end
%></def>

<!-- Renders 'Yes' for true and 'No' for false -->
<def tag="view" for="boolean"><%= this ? 'Yes' : 'No' %></def>

<!-- Renders a link (`<a>`) to `this` -->
<def tag="view" for="ActiveRecord::Base"><a merge-attrs/></def>


<!--
A convenience tag used to output a count and a correctly pluralised label. Works with any kind of collection such as an `ActiveRecord` association or an array.

### Usage

    <count:comments/> -> <span class="count">1 Comment</span>

    <count:viewings/> -> <span class="count">3 Viewings</span>

The label can be customised using the `label` attribute, e.g.

    <count:comments label="blog post comment"/> -> <span class="count">12 blog post comments</span>

### Additional Notes

* Use the `prefix` attribute to insert words before the count. If the prefix is "are" or "is" then it will be pluralised if needed:

        There <count:comments prefix="are"/> -> There <span class="count">is 1 Comment</span>
        There <count:viewings prefix="are"/> -> There <span class="count">are 3 Viewings</span>
* Use the `lowercase` attribute to force the generated label to be lowercase:

        <count:comments lowercase/> -> <span class="count">1 comment</span>
* Use the `if-any` attribute to output nothing if the count is zero. This can be followed by an `<else>` tag to handle the empty case:

        <count:comments if-any/><else>There are no comments</else>
-->
<def tag="count" attrs="label, prefix, if-any, lowercase"><span class="count"><%=
  raise Exception.new("asked for count of a string") if this.is_a?(String)
   
  c = this.try.to_int || this.try.total_entries || (this.try.loaded? && this.try.length) || this.try.count || this.try.length

  label ||= if this.is_a?(Class)
              this.view_hints.model_name
            elsif (attr = this.try.origin_attribute)
              this.member_class.view_hints.field_name(attr).singularize
            else
              this.member_class.view_hints.model_name
            end.titleize

  label = label.downcase if lowercase
   
  Hobo::Dryml.last_if = c > 0 if if_any
  if if_any && c == 0
    ""
  else
    main = label.blank? ? c : pluralize(c, label)
   
    if prefix.in? %w(are is)
      prefix = c == 1 ? "is" : "are"
    end

    (prefix ? "#{prefix} #{main}" : main.to_s)

  end
%></span></def>


<!-- Renders a `<link rel="Stylesheet" type="text/css">` to include the default stylesheet for the selected theme (select with `<set-theme>`). Included in the default pages. 
-->
<def tag="theme-stylesheet" attrs="name">
  <% name ||= Hobo.current_theme -%>
  <link href="#{base_url}/hobothemes/#{Hobo.current_theme}/stylesheets/#{name}.css"
        media="all" rel="Stylesheet" type="text/css" merge/>
</def>

<!-- Convenience tag to help with the common situation where you need to address the current user as "you", and refer to other users by name

### Usage

The context should be a user object. If `this == current_user` the "you" form is rendered, otherwise the form with the user's name:

 - `<you have/> new mail` -> "you have new mail" or "Jim has new mail"
 - `<you are/> now an admin` -> "you are now an admin" or "Jim is now an admin"
 - `<you do/>n't want to go there` -> "you don't want to go there" or "Jim doesn't want to go there"

### Attributes

 - titleize: render "You" instead of "you"
  
-->
<def tag="you" attrs="have, are, do, titleize"><if test="&this == current_user"><%= "#{titleize ? 'Y' : 'y'}ou#{' have' if have}#{' are' if are}#{' do' if do_}" %></if><else><do param="default"><name/><%= "#{' has' if have}#{' is' if are}#{' does' if do_}" %></do></else></def>

<!-- Equivalent to `<you titleize/>`. Yes it's an abuse of Ruby naming conventions, but it's so cute : ) -->
<def tag="You"><you merge titleize/></def>

<!--  Similar to `<you>`, but renders "Your" or "Fred's" -->
<def tag="your">
  <if test="&this == current_user">your</if>
  <else><do param="default"><%= n = name; n.ends_with?('s') ? "#{n}'" : "#{n}'s" %></do></else>
</def>

<!-- Capitalised versin of `<your>` -->
<def tag="Your">
  <if test="&this == current_user">Your</if>
  <else><do param="default"><%= n = name; n.ends_with?('s') ? "#{n}'" : "#{n}'s" %></do></else>
</def>

<!-- Deprecated. It's harder than you think to do this (e.g. an umbrealla, an user)
-->
<def tag="a-or-an" attrs="word"><%=
  (word =~ /^[aeiou]/i ? "an " : "a ") + word
%></def>


<!-- Capitalised version of `<a-or-an>` -->
<def tag="A-or-An" attrs="word"><%=
  (word =~ /^[aeiou]/i ? "An " : "A ") + word
%></def>


<!-- Renders a collection of string joined with ", ", or some other string passed in the `join` attribute -->
<def tag="comma-list" attrs="join"><%= this.join(join || ", ") %></def>


<!-- Development mode only - a menu to change the `current_user` -->
<def tag="dev-user-changer">
  <set user="&Hobo::User.default_user_model"/>
  <select-menu if="&user && RAILS_ENV == 'development'"
               first-option="Guest" options="&user.all(:limit => 30).*.login"
               onchange="location.href = '#{dev_support_path}/set_current_user?login=' + this.options[this.selectedIndex].value"
               selected="#{current_user.login}"
               class="dev-user-changer"/>
</def>