hobo_rapid 1.4.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. data/README.markdown +5 -0
  2. data/VERSION +1 -0
  3. data/app/controllers/dev_controller.rb +25 -0
  4. data/app/helpers/hobo_rapid_helper.rb +197 -0
  5. data/hobo_rapid.gemspec +26 -0
  6. data/lib/hobo_rapid/railtie.rb +6 -0
  7. data/lib/hobo_rapid.rb +13 -0
  8. data/taglibs/buttons/buttons.dryml +1 -0
  9. data/taglibs/buttons/create_button.dryml +40 -0
  10. data/taglibs/buttons/delete-button.dryml +75 -0
  11. data/taglibs/buttons/remote_method_button.dryml +34 -0
  12. data/taglibs/buttons/transition_button.dryml +71 -0
  13. data/taglibs/buttons/transition_link.dryml +22 -0
  14. data/taglibs/buttons/update_button.dryml +29 -0
  15. data/taglibs/cache/cache.dryml +1 -0
  16. data/taglibs/cache/nested_cache.dryml +332 -0
  17. data/taglibs/cards/card.dryml +7 -0
  18. data/taglibs/cards/cards.dryml +1 -0
  19. data/taglibs/cards/search_card.dryml +4 -0
  20. data/taglibs/editors/click_editor.dryml +60 -0
  21. data/taglibs/editors/editors.dryml +27 -0
  22. data/taglibs/editors/live_editor.dryml +37 -0
  23. data/taglibs/forms/error_messages.dryml +21 -0
  24. data/taglibs/forms/form.dryml +101 -0
  25. data/taglibs/forms/formlet.dryml +48 -0
  26. data/taglibs/forms/forms.dryml +5 -0
  27. data/taglibs/forms/submit.dryml +14 -0
  28. data/taglibs/hobo_rapid.dryml +14 -0
  29. data/taglibs/html/a.dryml +156 -0
  30. data/taglibs/html/aside.dryml +5 -0
  31. data/taglibs/html/doctype.dryml +39 -0
  32. data/taglibs/html/empty_tag.dryml +23 -0
  33. data/taglibs/html/footer.dryml +5 -0
  34. data/taglibs/html/header.dryml +5 -0
  35. data/taglibs/html/html.dryml +18 -0
  36. data/taglibs/html/if_ie.dryml +11 -0
  37. data/taglibs/html/image.dryml +11 -0
  38. data/taglibs/html/javascript.dryml +12 -0
  39. data/taglibs/html/section.dryml +12 -0
  40. data/taglibs/html/section_group.dryml +11 -0
  41. data/taglibs/html/stylesheet.dryml +5 -0
  42. data/taglibs/html/table.dryml +174 -0
  43. data/taglibs/i18n/ht.dryml +48 -0
  44. data/taglibs/i18n/human_attribute_name +18 -0
  45. data/taglibs/i18n/human_collection_name.dryml +69 -0
  46. data/taglibs/i18n/i18n.dryml +1 -0
  47. data/taglibs/i18n/model_name_human.dryml +16 -0
  48. data/taglibs/i18n/t.dryml +12 -0
  49. data/taglibs/inputs/after_submit.dryml +32 -0
  50. data/taglibs/inputs/check_many.dryml +25 -0
  51. data/taglibs/inputs/collection_input.dryml +10 -0
  52. data/taglibs/inputs/hidden_field.dryml +48 -0
  53. data/taglibs/inputs/hot_input.dryml +50 -0
  54. data/taglibs/inputs/input.dryml +76 -0
  55. data/taglibs/inputs/input_all.dryml +14 -0
  56. data/taglibs/inputs/input_for.dryml +38 -0
  57. data/taglibs/inputs/input_for_date.dryml +86 -0
  58. data/taglibs/inputs/input_for_enum_string.dryml +26 -0
  59. data/taglibs/inputs/input_many.dryml +131 -0
  60. data/taglibs/inputs/inputs.dryml +1 -0
  61. data/taglibs/inputs/name_one.dryml +74 -0
  62. data/taglibs/inputs/or_cancel.dryml +8 -0
  63. data/taglibs/inputs/select_input.dryml +13 -0
  64. data/taglibs/inputs/select_many.dryml +58 -0
  65. data/taglibs/inputs/select_menu.dryml +23 -0
  66. data/taglibs/inputs/select_one.dryml +46 -0
  67. data/taglibs/inputs/sortable_input_many.dryml +31 -0
  68. data/taglibs/inputs/sti_type_input.dryml +6 -0
  69. data/taglibs/lists/collection.dryml +26 -0
  70. data/taglibs/lists/empty_collection_message.dryml +13 -0
  71. data/taglibs/lists/feckless_fieldset.dryml +94 -0
  72. data/taglibs/lists/field_list.dryml +21 -0
  73. data/taglibs/lists/field_list_v1.dryml +64 -0
  74. data/taglibs/lists/labelled_item_list.dryml +11 -0
  75. data/taglibs/lists/lists.dryml +2 -0
  76. data/taglibs/lists/with_fields.dryml +98 -0
  77. data/taglibs/pages/account.dryml +30 -0
  78. data/taglibs/pages/account_disabled.dryml +19 -0
  79. data/taglibs/pages/flash_message.dryml +23 -0
  80. data/taglibs/pages/forgot_password.dryml +62 -0
  81. data/taglibs/pages/login.dryml +57 -0
  82. data/taglibs/pages/not_found_page.dryml +18 -0
  83. data/taglibs/pages/page_nav.dryml +17 -0
  84. data/taglibs/pages/pages.dryml +12 -0
  85. data/taglibs/pages/permission_denied_page.dryml +24 -0
  86. data/taglibs/plus/collection_preview.dryml +23 -0
  87. data/taglibs/plus/filter_menu.dryml +103 -0
  88. data/taglibs/plus/gravatar.dryml +12 -0
  89. data/taglibs/plus/live_search.dryml +40 -0
  90. data/taglibs/plus/plus.dryml +1 -0
  91. data/taglibs/plus/sortable_collection.dryml +36 -0
  92. data/taglibs/plus/table_plus.dryml +63 -0
  93. data/taglibs/views/a_or_an.dryml +10 -0
  94. data/taglibs/views/collection_name.dryml +28 -0
  95. data/taglibs/views/comma_list.dryml +2 -0
  96. data/taglibs/views/count.dryml +99 -0
  97. data/taglibs/views/links_for_collection.dryml +2 -0
  98. data/taglibs/views/name.dryml +30 -0
  99. data/taglibs/views/nil_view.dryml +10 -0
  100. data/taglibs/views/record_flags.dryml +5 -0
  101. data/taglibs/views/type_name.dryml +24 -0
  102. data/taglibs/views/view.dryml +79 -0
  103. data/taglibs/views/view_for.dryml +42 -0
  104. data/taglibs/views/views.dryml +1 -0
  105. data/taglibs/views/you.dryml +150 -0
  106. data/vendor/assets/javascripts/hobo_rapid.js +1 -0
  107. data/vendor/assets/stylesheets/feckless-fieldset.css +40 -0
  108. data/vendor/assets/stylesheets/hobo-rapid.css +94 -0
  109. data/vendor/assets/stylesheets/hobo_rapid.css +1 -0
  110. metadata +174 -0
@@ -0,0 +1,16 @@
1
+ <!-- Wrapper around ActiveModel::Name#human
2
+
3
+ #### Attributes
4
+
5
+ - model - (optional) should be a model class or a record object (default to this)
6
+ - count - (optional) used to pick the inflected string for the model. It should be an integer.
7
+ -->
8
+ <def tag="model-name-human" attrs="model, count"><%=
9
+ model ||= this
10
+ model = model.class unless model.kind_of? Class
11
+ # prepare symbolized attributes for merging
12
+ attrs = {}
13
+ all_attributes.each_pair{|k,v| attrs[k.to_sym] = v}
14
+ model.model_name.human( attrs )
15
+ %>
16
+ </def>
@@ -0,0 +1,12 @@
1
+ <!-- Simple wrapper around I18n.t.
2
+
3
+ The tag content is used as the :default option. It is overridden by an explicit 'default' attribute.
4
+ There is a default :count => 1.
5
+
6
+ ### Attributes
7
+
8
+ - key: the key to lookup
9
+ - all the attributes accepted by the wrapped method -->
10
+ <fakedef tag="t" attrs="key">
11
+ <!-- actually defined in i18n.rb -->
12
+ </fakedef>
@@ -0,0 +1,32 @@
1
+ <!--
2
+ Used inside a form to specify where to redirect after successful submission. This works by inserting a hidden field called `after_submit` which is used by Hobo if present to perform a redirect after the form submission.
3
+
4
+ ### Usage
5
+
6
+ Use the `stay-here` attribute to remain on the current page:
7
+
8
+ <form>
9
+ <after-submit stay-here/>
10
+ ...
11
+ </form>
12
+
13
+ Use the `go-back` option to return to the page in `session[:previous_uri]`:
14
+
15
+ <form>
16
+ <after-submit go-back/>
17
+ ...
18
+ </form>
19
+
20
+ Use the `uri` option to specify a redirect location:
21
+
22
+ <form>
23
+ <after-submit uri="/admin"/>
24
+ ...
25
+ </form>
26
+ -->
27
+ <def tag="after-submit" attrs="uri, stay-here, go-back"><%
28
+ uri = "stay-here" if stay_here
29
+ uri = session[:previous_uri] if go_back
30
+ -%>
31
+ <input type="hidden" value="&params[:after_submit] || uri" name="after_submit" if="&uri"/>
32
+ </def>
@@ -0,0 +1,25 @@
1
+ <!-- Renders a `<ul>` list of checkboxes, one for each of the potential targt in a `has_many` association. The user can check the items they wish to have associated. A typical use might be selecting categories for a blog post.
2
+
3
+ ### Attributes
4
+
5
+ - `options` - an array of models that may be added to the collection
6
+ - `disabled` - if true, sets the disabled flag on all check boxes.
7
+
8
+ -->
9
+ <def tag="check-many" attrs="options, disabled"><%
10
+ collection = this
11
+ param_name = param_name_for_this
12
+ options ||= begin
13
+ conditions = ActiveRecord::Associations::BelongsToAssociation.new(this_parent, this_field_reflection).send(:conditions)
14
+ this_field_reflection.klass.all(:conditions => conditions, :limit => 100).select {|x| can_view?(x)}
15
+ end
16
+ -%>
17
+ <ul class="check-many" param="default" merge-attrs>
18
+ <input type="hidden" name="#{param_name}[]" value=""/><% # ensure all items are removed when nothing checked
19
+ %>
20
+ <li repeat="&options" param>
21
+ <input id="#{dom_id(this, :check_many)}" type="checkbox" name="#{param_name}[]" value="@#{this.id}" checked="&this.in?(collection)" disabled="&disabled"/>
22
+ <label for="#{dom_id(this, :check_many)}"><name param/></label>
23
+ </li>
24
+ </ul>
25
+ </def>
@@ -0,0 +1,10 @@
1
+ <!-- This tag is called by `<input>` when the context is a `has_many :through` collection. By default a `<select-many>`
2
+ is used, but this can be customised on a per-type basis. For example, say you would like the `<check-many>` tag used to
3
+ edit collections a `Category` model in your application:
4
+
5
+ <def tag="collection-input" for="Category"><check-many merge/></def>
6
+ -->
7
+ <def tag="collection-input" polymorphic></def>
8
+
9
+ <!-- The default `<collection-input>` - calls `<select-many>` -->
10
+ <def tag="collection-input" for="ActiveRecord::Base"><select-many merge/></def>
@@ -0,0 +1,48 @@
1
+ <%# creates hidden inputs for the specified fields.
2
+
3
+ ### Attributes
4
+
5
+ - `fields`: the fields to create hidden inputs for. If neither fields nor for-query-string is specified, all fields except for `type`, `created_at` and `updated_at` are used.
6
+
7
+ - `for-query-string`: creates hidden inputs from the query string
8
+ parameters of our original request. AJAX parameters are skipped.
9
+ %>
10
+ <def tag="hidden-fields" attrs="fields, skip, for-query-string"><%=
11
+ pairs = if for_query_string
12
+ if for_query_string.blank? || for_query_string==true
13
+ query_parameters_filtered
14
+ else
15
+ query_parameters_filtered(:only => comma_split(for_query_string))
16
+ end
17
+ else
18
+ hiddens = case fields
19
+ when '*', nil
20
+ this.class.column_names - ['type', 'created_at', 'updated_at']
21
+ else
22
+ comma_split(fields)
23
+ end
24
+ hiddens.map do |field|
25
+ val = this.send(field)
26
+ param_name = param_name_for(form_field_path + [field])
27
+ [param_name, val] unless val.nil? ||
28
+ field.to_sym.in?(this.class.attr_protected) ||
29
+ (this.new_record? && val == this.class.column(field).default)
30
+ end.compact
31
+ end
32
+ skip = comma_split skip
33
+ pairs.to_a.reject { |p| p.first.in?(skip) }.map { |n, v|
34
+ hidden_field_tag(n, v.to_s) if v && n.not_in?(scope.form_field_names)
35
+ }.compact.safe_join("\n".html_safe)
36
+ %></def>
37
+
38
+
39
+ <%# a simple wrapper around hidden_field_tag.
40
+ %>
41
+ <def tag="hidden-field">
42
+ <%= hidden_field_tag(param_name_for_this, this, deunderscore_attributes(attributes)) %>
43
+ </def>
44
+
45
+ <!-- Renders an `<input type='hidden'>` for the `id` field of the current context -->
46
+ <def tag="hidden-id-field">
47
+ <if:id><input type="hidden" name="#{param_name_for_this}" value="#{this}" /></if>
48
+ </def>
@@ -0,0 +1,50 @@
1
+ <!--
2
+ This tag wraps an input with some ajax that refreshes a part when the input is changed.
3
+
4
+ An example of when this is useful is when a form is rendered differently depending on what country is selected. In this example, we'll change the label of the region to state or province or whatever is appropriate for the country.
5
+
6
+ <field-list: replace>
7
+ <do part="shipping">
8
+ <field-list fields="address,city,region,country,postal_code">
9
+ <country-view:><hot-input ajax /><country-view:>
10
+ <region-label:><%= this_parent.country._?.region_label %><region-label:>
11
+ <field-list>
12
+ </do>
13
+ <field-list:>
14
+
15
+ The actual input is exposed as the default parameter, so it can be customized:
16
+
17
+ <hot-input ajax selector="select">
18
+ <combobox>
19
+ <select-one limit="5000"/>
20
+ </combobox>
21
+ </hot-input>
22
+
23
+ Note also the selector option, which is useful for complex, combined inputs like a combobox. A single change in a complex input may actually result in change events being fired on multiple fundamental HTML elements, so we use the selector to only watch a single input.
24
+
25
+ The hot-input must be inside a form, and the form's context must be the same as the page's context. You can work around this limitation by using custom controller code.
26
+
27
+ ### Attributes
28
+
29
+ This tag supports the [standard form AJAX attributes](/api_taglibs/rapid_forms), such as update, message, spinner-next-to, etc.
30
+
31
+ In particular, the part to update *must* be specified using either the `ajax`, `update` or `updates` attribute.
32
+
33
+ * path: the path to send the AJAX request to. If not specified, the current page path is used.
34
+ * method: the HTTP method for the request. Defaults to 'GET'.
35
+ * events: the javascript event(s) to trigger on. Default is 'change'.
36
+ * selector: filter for the events. Default is null.
37
+
38
+ Note that `event` and `selector` are passed straight to `$.on()` See [its documentation](http://api.jquery.com/on/) for more details.
39
+
40
+ -->
41
+ <def tag="hot-input" attrs="path, method, events, selector" >
42
+ <%
43
+ attributes['message'] ||= 'Loading...'
44
+ ajax_attrs, html_attrs = attributes.partition_hash(HoboRapidHelper::AJAX_ATTRS)
45
+ add_data_rapid!(html_attrs, "hot-input", :ajax_attrs => ajax_attrs, :path => path || request.path, :method => method || 'GET', :events => events || 'change', :selector => selector)
46
+ %>
47
+ <span class="hot-input" param="default" merge-attrs="&html_attrs">
48
+ <input merge param />
49
+ </span>
50
+ </def>
@@ -0,0 +1,76 @@
1
+ <!--
2
+ Provides an editable control tailored to the type of the object in context. `<input>` tags should be used within a
3
+ `<form>`. `<input>` is a _polymorphic_ tag which means that there are a variety of definitions, each one written for a
4
+ particular type. For example there are inputs for `text`, `boolean`, `password`, `date`, `datetime`, `integer`,
5
+ `float`, `string` and more.
6
+
7
+ ### Usage
8
+
9
+ The tag behaves as a regular HTML input if the type attribute is given:
10
+
11
+ <input type="text" name="my_input"/> -> Output is exactly as provided, untouched by Rapid
12
+
13
+ If no type attribute is given then the _context_ is used. For example if the context is a blog post:
14
+
15
+ <input:title/> ->
16
+ <input id="blog_post[name]" class="string blog-post-name" type="text" value="My Blog Post" name="blog_post[name]"/>
17
+
18
+ <input:created_at/> ->
19
+ <select id="blog_post_created_at_year" name="blog_post[created_at][year]">...</select>
20
+ <select id="blog_post_created_at_month" name="blog_post[created_at][month]">...</select>
21
+ <select id="blog_post_created_at_day" name="blog_post[created_at][day]">...</select>
22
+
23
+ <input:description/> ->
24
+ <textarea class="text blog-post-description" id="blog_post[description]" name="blog_post[description]">...</textarea>
25
+
26
+ If the context is a `belongs_to` association, the `<select-one>` tag is used.
27
+
28
+ If the context is a `has_many :through` association, the polymorphic `<collection-input>` tag is used.
29
+
30
+ ### Attributes
31
+
32
+ - no-edit: control what happens if `can_edit?` is false. Can be one of:
33
+
34
+ - view: render the current value using the `<view>` tag
35
+ - disable: render the input as normal, but add HTML's `disabled` attribute
36
+ - skip: render nothing at all
37
+ - ignore: render the input normally. That is, don't even perform the edit check.
38
+ -->
39
+ <def tag="input" attrs="no-edit"><%=
40
+ if attributes[:type]
41
+ element :input, deunderscore_attributes(attributes), nil, true, true
42
+ else
43
+ no_edit ||= :view
44
+ no_edit = no_edit.to_sym
45
+ no_edit_permission = !can_edit? unless no_edit == :ignore
46
+ if no_edit_permission && no_edit == :view
47
+ view
48
+ elsif no_edit_permission && no_edit == :skip
49
+ ""
50
+ else
51
+ attrs = add_classes(attributes, type_id.dasherize, type_and_field.dasherize)
52
+ attrs[:name] ||= param_name_for_this
53
+ attrs[:disabled] = true if no_edit_permission && no_edit == :disable
54
+ the_input = if (refl = this_field_reflection)
55
+ if refl.macro == :belongs_to
56
+ call_polymorphic_tag('input', attrs) or select_one(attrs)
57
+ elsif refl.macro == :has_many
58
+ if refl.options[:through]
59
+ collection_input(attrs)
60
+ else
61
+ input_many(attrs)
62
+ end
63
+ end
64
+ else
65
+ call_polymorphic_tag('input', attrs) or
66
+ (call_polymorphic_tag('input', HoboFields.to_class(this_type::COLUMN_TYPE), attrs) if defined?(this_type::COLUMN_TYPE)) or
67
+ raise Hobo::Error, ("No input tag for #{this_field}:#{this_type} (this=#{this.inspect})")
68
+ end
69
+ unless this_parent.errors[this_field].empty?
70
+ "<span class='field-with-errors'>#{the_input}</span>".html_safe
71
+ else
72
+ the_input
73
+ end
74
+ end
75
+ end
76
+ %></def>
@@ -0,0 +1,14 @@
1
+ <!-- Renders a sub-section of a form with fields for every record in a `has_many` association. This is similar to `<input-many>` except there is no ability to add and remove items (i.e. no (+) and (-) buttons).
2
+ -->
3
+ <def tag="input-all">
4
+ <% association_fkey = this_field_reflection.foreign_key -%>
5
+ <ul class="input-all #{this_field.dasherize}">
6
+ <li repeat class="#{'record-with-errors' unless this.errors.empty?}">
7
+ <set-scoped form-field-names="&[]">
8
+ <hidden-id-field/>
9
+ <do param="default"/>
10
+ <hidden-fields skip="&association_fkey"/>
11
+ </set-scoped>
12
+ </li>
13
+ </ul>
14
+ </def>
@@ -0,0 +1,38 @@
1
+ <!-- A `<textarea>` input -->
2
+ <def tag="input" for="text" attrs="name">
3
+ <%= text_area_tag(name, this, deunderscore_attributes(attributes)) %>
4
+ </def>
5
+
6
+ <!-- A checkbox plus a hidden-field. The hidden field trick comes from Rails - it means that when the checkbox is not checked, the parameter name is still submitted, with a '0' value (the value is '1' when the checkbox is checked) -->
7
+ <def tag="input" for="boolean" attrs="name">
8
+ <%= unless attributes[:disabled]
9
+ cb_tag = check_box_tag(name, '1', this, deunderscore_attributes(attributes))
10
+ cb_hidden_tag = hidden_field_tag(name, '0', :id => nil)
11
+ cb_hidden_tag + cb_tag
12
+ end %>
13
+ </def>
14
+
15
+ <!-- A password input - `<input type='password'>` -->
16
+ <def tag="input" for="password" attrs="name">
17
+ <%= password_field_tag(name, this, deunderscore_attributes(attributes)) %>
18
+ </def>
19
+
20
+ <!-- An `<input type='text'>` input. -->
21
+ <def tag="input" for="integer" attrs="name">
22
+ <%= text_field_tag(name, this, deunderscore_attributes(attributes)) %>
23
+ </def>
24
+
25
+ <!-- An `<input type='text'>` input. -->
26
+ <def tag="input" for="BigDecimal" attrs="name">
27
+ <%= text_field_tag(name, this, deunderscore_attributes(attributes)) %>
28
+ </def>
29
+
30
+ <!-- An `<input type='text'>` input. -->
31
+ <def tag="input" for="float" attrs="name">
32
+ <%= text_field_tag(name, this, deunderscore_attributes(attributes)) %>
33
+ </def>
34
+
35
+ <!-- An `<input type='text'>` input. -->
36
+ <def tag="input" for="string" attrs="name">
37
+ <%= text_field_tag(name, this, deunderscore_attributes(attributes)) %>
38
+ </def>
@@ -0,0 +1,86 @@
1
+ <!-- A date picker, using the `select_date` helper from Rails
2
+
3
+ ### Attributes
4
+
5
+ - all the options of select_date and date_select are passed to the select_date Rails
6
+ helper
7
+
8
+ All the other attributes are passed to the `select_date` helper as the html-options hash.
9
+
10
+ The menus default to the current date if the current value is nil.
11
+
12
+ Examples:
13
+
14
+ - override the input for date tag in a form
15
+
16
+ <my-special-date-view:>
17
+ <select-date start-year="&1940" order="day,month,year" />
18
+ </my-special-date-view:>
19
+
20
+ - override the input tag application-wide
21
+
22
+ <def tag='input' for='date'>
23
+ <select-date merge order="day,month,year" start-year="&1940" />
24
+ </def>
25
+
26
+ -->
27
+ <def tag="select-date" attrs="use-month-numbers, use-short-month, add-month-numbers, use-month-names, date-separator, start-year, end-year, discard-day, discard-month, discard-year, order, include-blank, default, disabled, prompt, prefix">
28
+ <% order = order.nil? ? [:year, :month, :day] : comma_split(order).*.to_sym -%>
29
+ <%= select_date(this || current_time,
30
+ (all_attributes - attributes.keys).reverse_merge(:prefix => param_name_for_this).merge(:order => order),
31
+ attributes - [:name]) %>
32
+ </def>
33
+
34
+
35
+ <%# Selects the `<datepicker>` as the default input for date. To
36
+ choose the older `<select-date>` input, add this to your
37
+ application.dryml:
38
+
39
+ <def tag='input' for='date'>
40
+ <select-date merge/>
41
+ </def>
42
+ %>
43
+ <def tag="input" for="Date">
44
+ <datepicker merge/>
45
+ </def>
46
+
47
+ <!-- A date/time picker, using the `select_time` helper from Rails
48
+
49
+ ### Attributes
50
+
51
+ - include-seconds, time-separator, prompt and prefix are passed to the select_time helper as options
52
+
53
+ All the other attributes are passed to the `select_time` helper as the html-options hash.
54
+
55
+ The menus default to the current time if the current value is nil.
56
+
57
+ -->
58
+ <def tag="input" for="time" attrs="include-seconds, time-separator, prompt, prefix">
59
+ <%= select_time( this || current_time,
60
+ (all_attributes - attributes.keys).reverse_merge(:prefix => param_name_for_this),
61
+ attributes - [:name] ) %>
62
+ </def>
63
+
64
+
65
+ <!-- A date/time picker, using the `select_datetime` helper from Rails
66
+
67
+ ### Attributes
68
+
69
+ - order: The order of the year, month and date menus. A comma separated string or an array. Default: "year, month,
70
+ day, hour, minute"
71
+ - date-separator, discard-type, prompt and prefix are passed to the select_date helper as options
72
+
73
+ All the other attributes are passed to the `select_date` helper as the html-options hash.
74
+
75
+ The menus default to the current time if the current value is nil.
76
+
77
+ -->
78
+ <def tag="input" for="datetime" attrs="order, date-separator, discard-type, prompt, prefix">
79
+ <% if ! order.nil?
80
+ order = comma_split(order).*.to_sym
81
+ attributes.merge!(:order => order)
82
+ end -%>
83
+ <%= select_datetime(this || current_time,
84
+ (all_attributes - attributes.keys).reverse_merge(:prefix => param_name_for_this).merge(:order => order),
85
+ attributes - [:name] ) %>
86
+ </def>
@@ -0,0 +1,26 @@
1
+ <!-- A `<select>` menu containing the values of an 'enum string'.
2
+
3
+ ### Attributes
4
+
5
+ - `labels` - A hash that gives custom labels for the values of the enum.
6
+ Any values that do not have corresponding keys in this hash will get `value.titleize` as the label.
7
+ - `titleize` - Set to false to have the value itself (rather than `value.titleize`) be the default label. Default: true
8
+ - `first-option` - a string to be used for an extra option in the first position. E.g. "Please choose..."
9
+ - `first-value` - the value to be used with the `first-option`. Typically not used, meaning the option has a blank value.
10
+
11
+ -->
12
+ <def tag="input" for="HoboFields::Types::EnumString" attrs="labels, titleize, first-option, first-value"><%
13
+ labels ||= {}
14
+ labels = HashWithIndifferentAccess.new(labels)
15
+ titleize = true if titleize.nil?
16
+ options = this_type.values.map {|v|
17
+ [I18n.t("activerecord.attributes.#{this_parent.class.to_s.downcase}/#{this_field}s.#{v}",
18
+ :default => titleize ? v.titleize : v),
19
+ v]
20
+ }
21
+ %>
22
+ <select name="#{param_name_for_this}" merge-attrs>
23
+ <option value="#{first_value}" unless="&first_option.nil?"><first-option/></option>
24
+ <%= options_for_select(options, this) %>
25
+ </select>
26
+ </def>
@@ -0,0 +1,131 @@
1
+ <!-- Creates a sub-section of the form which the user can repeat using (+) and (-) buttons, in order to allow an entire `has_many` collection to be created/edited in a single form.
2
+
3
+ This tag is very different from tags like `<select-many>` and `<check-many>` in that:
4
+
5
+ - Those tags are used to *choose existing records* to include in the association, while `<input-many>` is used to actually create or edit the records in the association.
6
+
7
+ ### Example
8
+
9
+ Say you are creating a new `Category` in your online shop, and you want to create some initial products *in the same form*, you can add the following to your form:
10
+
11
+ <input-many:products fields="name, price"/>
12
+
13
+ which is the same as
14
+
15
+ <input-many:products><field-list fields="name, price"/></input-many>
16
+
17
+ The body of the tag will be repeated for each of the current records in the collection, or will just appear once (with blank fields) if the colleciton is empty.
18
+
19
+ ### Attributes
20
+
21
+ - fields: If you do not specify any content for the input-many, a `<field-list>` is rendered. This attribute is passed through to the `<field-list>`
22
+
23
+ - skip: Passed through to the `<field-list>`. If not specified, it defaults to the parent association.
24
+
25
+ - `minimum`: the minimum number of items in the collection. Currently only '0' and '1' are supported values. The default is '0'.
26
+
27
+ - `template`: the default values for new items. Normally this functionality is better provided by Model.new, but it's here if you need it.
28
+
29
+ - `add-hook`, `remove-hook`: javascript hooks which work similarly to the events described below.
30
+
31
+ ### Events
32
+
33
+ - `rapid:add`: fired after the element is inserted
34
+
35
+ - `rapid:remove`: fired before the element is inserted. Returning false will cancel the removal.
36
+
37
+ - `rapid:change`: fired after an element has been removed or inserted.
38
+
39
+ - `hide`, `show`: passed to jquery-ui's hide and show functions.
40
+
41
+ Example javascript:
42
+
43
+ var last_added;
44
+ var last_removed;
45
+ $(document).ready(function() {
46
+ $('.stories').on('rapid:add', function(ev) {
47
+ last_added = this;
48
+ });
49
+ $('.stories').on('rapid:remove', function(ev) {
50
+ last_removed = this;
51
+ if(!confirm("really?")) return false;
52
+ });
53
+ });
54
+
55
+ ### Example
56
+
57
+ Say you are creating a new `Category` in your online shop, and you want to create some initial products *in the same form*, you can add the following to your form:
58
+
59
+ <input-many:products fields="name, price" />
60
+
61
+ You'll often want to provide the `item` parameter:
62
+
63
+ <input-many:products><item:><field-list fields="name, price" /></item:></input-many>
64
+
65
+ A fully worked up example of nested input-many's may be found in [agility/jquery-test](http://github.com/tablatom/agility/blob/jquery-test/app/views/projects/nested_has_many_test.dryml)
66
+ -->
67
+ <def tag="input-many" attrs="minimum, fields, skip, more-skip, template, add-hook, remove-hook, hide, show" polymorphic >
68
+ <%
69
+ # helper function to create id's on buttons to facilitate testing
70
+ def underize(s)
71
+ s.gsub(/\[/,"_").gsub(/\]/,"")
72
+ end
73
+ %>
74
+ <set empty="&this.empty?"/>
75
+ <% template ||= this.try.new_candidate || this.member_class.new -%>
76
+ <% minimum ||= 0 ; minimum = minimum.to_i -%>
77
+ <% skip ||= this.proxy_reflection.klass.reflect_on_all_associations.detect {|p| p.primary_key_name==this.proxy_reflection.primary_key_name}.try.name.to_s if this.respond_to? :proxy_reflection -%>
78
+ <% skip += ",#{more_skip}" if more_skip -%>
79
+ <% js_attrs = all_attributes.slice(:minimum, :add_hook, :remove_hook, :hide, :show) %>
80
+ <% js_attrs[:prefix] = param_name_for_this %>
81
+ <ul class="input-many #{this_field.dasherize}" data-rapid="#{data_rapid('input-many', js_attrs)}" merge-attrs>
82
+ <fake-field-context fake-field="-1" context="&template">
83
+ <li class="input-many-li input-many-template" id="#{underize param_name_for_this}">
84
+ <div class="input-many-item" param="default">
85
+ <field-list param merge-attrs="fields" skip="&skip" />
86
+ </div>
87
+ <div class="buttons">
88
+ <button param="remove-item" id="#{underize param_name_for_this}_remove">-</button>
89
+ <button param="add-item" id="#{underize param_name_for_this}_add">+</button>
90
+ </div>
91
+ </li>
92
+ </fake-field-context>
93
+ <li class="input-many-li empty #{'hidden' unless this.empty? and minimum==0}" id="#{underize param_name_for_this}_-1_empty">
94
+ <!-- HACK way to signal an empty collection to the controller -->
95
+ <input type="hidden" class="empty-input" id="#{underize param_name_for_this}" name="#{param_name_for_this}" value="" disabled="&(!this.empty? || minimum>0)" />
96
+ <fake-field-context fake-field="-1" context="&template">
97
+ <div param="empty-message">
98
+ <ht key="#{this.class.to_s.underscore}.collection.empty_message">
99
+ No <%= this.class.name.titleize.downcase.pluralize %>.
100
+ </ht>
101
+ </div>
102
+ <div class="buttons">
103
+ <button param="remove-item" class="hidden" id="#{underize param_name_for_this}_remove">-</button>
104
+ <button param="add-item" id="#{underize param_name_for_this}_add">+</button>
105
+ </div>
106
+ </fake-field-context>
107
+ </li>
108
+ <fake-field-context fake-field="0" context="&template">
109
+ <li class="input-many-li" if="&(this_parent.empty? && minimum>0)" id="#{underize param_name_for_this}">
110
+ <div class="input-many-item" param="default">
111
+ <field-list param merge-attrs="fields" skip="&skip" />
112
+ </div>
113
+ <div class="buttons">
114
+ <button param="remove-item" class="hidden" id="#{underize param_name_for_this}_remove">-</button>
115
+ <button param="add-item" id="#{underize param_name_for_this}_add">+</button>
116
+ </div>
117
+ </li>
118
+ </fake-field-context>
119
+ <li repeat class="input-many-li #{'record-with-errors' unless this.errors.empty?}" id="#{underize param_name_for_this}">
120
+ <error-messages without-heading class="sub-record"/>
121
+ <hidden-id-field/>
122
+ <div class="input-many-item" param="default">
123
+ <field-list param merge-attrs="fields" skip="&skip" />
124
+ </div>
125
+ <div class="buttons">
126
+ <button param="remove-item" class="#{'hidden' if this_parent.length<=minimum}" id="#{underize param_name_for_this}_remove">-</button>
127
+ <button param="add-item" class="#{'hidden' if not last_item?}" id="#{underize param_name_for_this}_add">+</button>
128
+ </div>
129
+ </li>
130
+ </ul>
131
+ </def>
@@ -0,0 +1 @@
1
+ <%# Simple or complicated input widgets for use inside forms %>
@@ -0,0 +1,74 @@
1
+ <!-- An `<input type="text">` with auto-completion. Allows the user to chose the target of a `belongs_to` association by name.
2
+
3
+ This tag relies on an autocompleter being defined in a controller. A simple example:
4
+
5
+ <form with="&ProjectMembership.new">
6
+ <name-one:user>
7
+ </form>
8
+
9
+ class ProjectMembership < ActiveRecord::Base
10
+ hobo_model
11
+ belongs_to :user
12
+ end
13
+
14
+ class User < ActiveRecord::Base
15
+ hobo_user_model
16
+ has_many :project_memberships, :accessible => true, :dependent => :destroy
17
+ end
18
+
19
+ class UsersController < ApplicationController
20
+ autocomplete
21
+ end
22
+
23
+ The route used by the autocompleter looks something like `/users/complete_name`. The first part of this route is specified by the `complete-target` attribute, and the second part is specified by the `completer` attribute.
24
+
25
+ `complete-target` specifies the controller for the route. It can be specified by either supplying a model class or a model. If a model is supplied, the id of the model is passed as a parameter to the controller. (`?id=7`, for example) The default for this attribute is the class of the context. In other words, the class that contains the `has_many / has_one`, not the class with the `belongs_to`.
26
+
27
+ `completer` specifies the action for the route. `name-one` prepends `complete_` to the value given here. This should be exactly the same as the first parameter to `autocomplete` in your controller. As an example: `autocomplete :email_address` would correspond to `completer="email_address"`. The default for this attribute is the name field for the model being searched, which is usually `name`, but not always.
28
+
29
+ The query string is passed to the controller in the `query` parameter. (`?query=hello` for example).
30
+
31
+ For more information on how to customize the controller, see the [controller manual](http://cookbook.hobocentral.net/manual/controllers#autocompleters)
32
+
33
+ Here's a more complex example. This used to be a part of [agility](http://cookbook.hobocentral.net/tutorials/agility) until it was simplified.
34
+
35
+ class ProjectsController < ApplicationController
36
+ autocomplete :new_member_name do
37
+ project = find_instance
38
+ hobo_completions :name, User.without_project(project).is_not(project.owner)
39
+ end
40
+ end
41
+
42
+ Note that this was added to the projects controller, rather than the users controller as in the first example. You can read this as: create an auto-complete action called `new_member_name` that finds users that are not already members of the project, and not the owner of the project, and completes the :name field.
43
+
44
+ <name-one:user complete-target="&@project" completer="new_member_name"/>
45
+
46
+ We're using an object as the complete-target rather than a class. This allows the `find_instance` in our controller action to function.
47
+
48
+ There's another example of `<name-one>` use in the [recipes](http://cookbook.hobocentral.net/recipes/36-using-a-name-one-one-a).
49
+
50
+ ### Attributes:
51
+
52
+ - `complete-target`, `completer`: see above
53
+ - `min-chars`: The minimum number of characters that must be entered in the input field before an Ajax request is made.
54
+ - `nil-value`: If there is no current value, this text will appear greyed out inside the control, and will disappear on focus.
55
+
56
+ ### Note:
57
+
58
+ If you wish to set `min-chars` to 0, you will require this [patch to controls.js](http://github.com/bryanlarsen/scriptaculous/commit/3915b7b). 'controls.js' was added to your project via the rails generator, not via Hobo.
59
+
60
+ -->
61
+ <def tag="name-one" attrs="complete-target, completer, min-chars, nil-value"><%
62
+ complete_target ||= this_field_reflection.klass
63
+ completer ||= (complete_target.is_a?(Class) ? complete_target : complete_target.class).name_attribute
64
+ min_chars ||= 1
65
+ value = name(:no_wrapper => true, :if_present => true)
66
+ -%>
67
+ <wrap tag="span" class="field-with-errors" when="&!this_parent.nil? && !this_parent.errors[this_field].empty?">
68
+ <input type="text" name="#{param_name_for_this}"
69
+ class="autocompleter #{type_and_field._?.dasherize} #{css_data :complete_on, typed_id(complete_target), completer} #{css_data :min_chars, min_chars} #{'nil-value' if value==''}"
70
+ value="#{value=='' ? nil_value : value}"
71
+ merge-attrs/>
72
+ <div class="completions-popup" style="display:none"></div>
73
+ </wrap>
74
+ </def>