<!-- Tags that define higher level interactive 'widgets' --> <!-- An enhanced version of Rapid's `<table>` that has support for column sorting, searching and pagination. This tag calls `<table merge-params>`, so the parameters for `<table>` are also available. An [worked example](/tutorials/agility#improve_the_project_page_with_a_searchable_sortable_table) of this tag is available in the [Agility Tutorial](/tutorials/agility) --> <def tag="table-plus" attrs="sort-field, sort-direction, sort-columns" > <% sort_field ||= @sort_field; sort_direction ||= @sort_direction; sort_columns ||= {} %> <% sort_columns['this'] ||= this.member_class.try.name_attribute %> <div class="table-plus" merge-attrs="&attributes - attrs_for(:with_fields) - attrs_for(:table)"> <div class="header" param="header"> <div class="search"> <form param="search-form" method="get" action=""> <hidden-fields for-query-string skip="page, search"/> <span><t key="hobo.table_plus.search">Search</t></span> <input class="search" type="search" name="search" value="¶ms[:search]"/> <submit label="&I18n.t('hobo.table_plus.submit_label', :default=>'Go')" class="search-button" param="search-submit"/> </form> </div> </div> <table merge-attrs="&attributes & (attrs_for(:table) + attrs_for(:with_fields))" empty merge-params> <field-heading-row:> <with-field-names merge-attrs="&all_attributes & attrs_for(:with_fields)"> <% col = sort_columns[scope.field_path] || scope.field_path sort = sort_field == col && sort_direction == 'asc' ? "-#{col}" : col sort_url = url_for(params.merge(:sort => sort) - [:page]) col_heading_name = this.member_class.try.human_attribute_name(scope.field_name, :default=> scope.field_name.titleize) %> <th param="#{scope.field-name}-heading"> <a href="&sort_url" class="column-sort" param="#{scope.field-name}-heading-link"><%= col_heading_name %></a> <if test="&col == sort_field"> <do param="up-arrow" if="&sort_direction == 'desc'">↑</do> <do param="down-arrow" if="&sort_direction == 'asc'">↓</do> </if> </th> </with-field-names> <th if="&all_parameters[:controls]" class="controls"></th> </field-heading-row> </table> <empty-collection-message param="empty-message"/> <page-nav param if="&this.respond_to?(:page_count) || this.respond_to?(:total_pages)"/> </div> </def> <!-- An enhanced version of Rapid's `<collection>` tag that supports drag-and-drop re-ordering. Each item in the collection has a `<div class="ordering-handle" param="handle">` added, which can be used to drag the item up and down. ### Attributes - `sortable-options` - a hash of options to pass to the `sortable_elemnt` helper. Default are: { :constraint => :vertical, :overlap => :vertical, :scroll => :window, :handle => 'ordering-handle', :complete => [visual_effect(:highlight, attributes[:id])] } ### Controller support This tag assumes the controller has a `reorder` action and the model has a `position_column` method. This action is added automatically by Hobo's model-controller if the model declares `acts_as_list`. See also [Drag and Drop Reordering](/manual/controllers#drag_and_drop_reordering) in the [Controllers and Routing](/manual/controllers) chapter of the manual. --> <def tag="sortable-collection" attrs="sortable-options"><% singular_name = this.member_class.name.underscore attributes[:id] ||= "#{singular_name}_ordering" route_method = subsite ? "#{subsite}_reorder_#{singular_name.pluralize}_url" : "reorder_#{singular_name.pluralize}_url" reorder_url = send(route_method) %> <collection class="sortable" merge> <item: id="#{singular_name}_#{this.id}" param> <div class="ordering-handle" param="handle" if="&can_edit?(this.position_column)">↑<br/>↓</div> <do param="default"><card param/></do> </item:> </collection> <%= if Dryml.last_if opts = { :url => reorder_url, :constraint => :vertical, :overlap => :vertical, :scroll => :window, :handle => 'ordering-handle', :complete => [visual_effect(:highlight, attributes[:id])] } opts.update(sortable_options) if sortable_options sortable_element attributes[:id], opts end # FIXME: Make unobstrusive %> </def> <!-- Deprecated: use collection-preview instead. --> <def tag="preview-with-more"><collection-preview merge/></def> <!-- Captures the common pattern of a list of "the first few" cards, along with a link to the rest. --> <def tag="collection-preview" attrs="name"> <% model_class = this.member_class name ||= model_class.name.downcase.pluralize -%> <section class="#{name.dasherize} collection-preview" param="default"> <h3 param="heading"> <ht key="#{name.to_s.tableize}.collection.heading" count="&this.size"> <do param="heading-content"><%= name.pluralize.titleize %></do> </ht> </h3> <a with="&model_class" action="new" if="&can_create?(model_class.new)" param="new-link"> <ht key="#{model_class.to_s.tableize}.actions.new">New <%= model_class.model_name.human %></ht> </a> <collection param/> <unless test="&this.empty?"> <a param="show-all"><ht key="#{model_class.to_s.tableize}.actions.show_all">Show all <%= name.pluralize.titleize %>...</ht></a> </unless> </section> </def> <!-- Renders a gravatar (see [gravatar.com](http://gravatar.com)) image in side a link to `this`. Requires `this` to have an `email_address` field. Normally called with a user record in context. ### Attributes - `size` - Size in pixels of the image. Defaults to 80. - `rating` - The rating allowed. Defaults to 'g'. See [gravatar.com](http://gravatar.com) for information on ratings. --> <def tag="gravatar" attrs="size, rating"> <% size ||= 80; rating ||= 'g'; digest = Digest::MD5.hexdigest(this.email_address) -%> <a class="gravatar"><img class="gravatar" src="http://www.gravatar.com/avatar/#{digest}?s=#{size}&r=#{rating}" merge-attrs/></a> </def> <!-- Provides an ajax-powered *find-as-you-type* live search field which is hooked up to Hobo's site-side search feature. At the moment this tag is not very flexible. It is not easy to use if for anything other than Hobo's site-wide search. --> <def tag="live-search"> <div class="search"> <label for="search-field"><t key="hobo.live_search.label">Search</t></label><input type="search" class="live-search"/> <spinner id="search-spinner"/> </div> <section class="hidden" id="search-results-panel"> <h2><t key="hobo.live_search.results_label">Search Results</t></h2><div param="close-button"><t key="hobo.live_search.close_button">close</t></div> <section id="search-results"> </section> </section> </def> <!-- A `<select>` menu intended to act as a filter for index pages. ### Example Filtering on state is a common use. Here's the dryml for Order: <filter-menu param-name="state" options="&Order::Lifecycle.states.keys" /> And the controller action: def index # always validate data given in URL's!!! params[:state]=nil unless Order::Lifecycle.states.include?(params[:state]._?.to_sym) finder = params[:state] ? Order.send(params[:state]) : Order hobo_index finder end See [Filtering stories by status](/tutorials/agility#filtering_stories_by_status) in the [Agility Tutorial](/tutorials/agility) for an example. ### Attributes - `param-name` - the name of the HTTP parameter to use for the filter - `options` - an array of options or an array of arrays (useful for localized apps) for the menu. It can be omitted if you provide the options as an array or array of arrays in the locale file. - `no-filter` - The text of the first option which indicates no filter is in effect. Defaults to 'All' ### I18n It lookups the options attributes in filter_menu.#{param_name}.options. The passed options are used as a default in case the lookup fails. Besides the "tags.filter_menu.default.no_filter" key is used as default of the attribute "no-filter" (or "All" if no default is found) ### I18n Example es: tags: filter_menu: period: no_filter: Todos PerĂodos options: - [ "Hoy", "today" ] - [ "Ayer", "yesterday" ] TIME_PERIODS = %w[today yesterday] <t-filter-menu param-name="period" options="&TIME_PERIODS" no-filter="All Periods"/> with I18n.locale == :es <select name="period"> <option value="">Todos PerĂodos</option> <option value="today">Hoy</option> <option value="yesterday">Ayer</option> </select> with I18n.locale == :en (i.e no locale file) <select name="period"> <option value="">All Periods</option> <option value="today">today</option> <option value="yesterday">yesterday</option> </select> --> <def tag="filter-menu" attrs="param-name, options, no-filter, id"> <% opt = I18n.t("tags.filter_menu.#{param_name}.options", :default=>'') options = opt unless opt.blank? no_filter = I18n.t("tags.filter_menu.#{param_name}.no_filter", :default=>[:"tags.filter_menu.default.no_filter", no_filter, "All"]) %> <form action="&request.fullpath" method="get" class="filter-menu" merge-attrs="id"> <div> <% opt = options.first.kind_of?(Array) ? options.*.last : options selected = opt.detect {|o| o.to_s==params[param_name.gsub('-', '_')] } %> <select-menu name="¶m_name" options="&options" selected="&selected" first-option="&no_filter" key="¶m_name" merge-params/> </div> </form> </def>