hobo 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. data/LICENSE.txt +22 -0
  2. data/README.txt +18 -0
  3. data/bin/hobo +81 -0
  4. data/hobo_files/plugin/CHANGES.txt +963 -0
  5. data/hobo_files/plugin/LICENSE.txt +22 -0
  6. data/hobo_files/plugin/README +4 -0
  7. data/hobo_files/plugin/Rakefile +11 -0
  8. data/hobo_files/plugin/generators/hobo/hobo_generator.rb +37 -0
  9. data/hobo_files/plugin/generators/hobo/templates/application.dryml +2 -0
  10. data/hobo_files/plugin/generators/hobo/templates/guest.rb +31 -0
  11. data/hobo_files/plugin/generators/hobo_front_controller/USAGE +11 -0
  12. data/hobo_files/plugin/generators/hobo_front_controller/hobo_front_controller_generator.rb +90 -0
  13. data/hobo_files/plugin/generators/hobo_front_controller/templates/controller.rb +51 -0
  14. data/hobo_files/plugin/generators/hobo_front_controller/templates/functional_test.rb +18 -0
  15. data/hobo_files/plugin/generators/hobo_front_controller/templates/helper.rb +2 -0
  16. data/hobo_files/plugin/generators/hobo_front_controller/templates/index.dryml +43 -0
  17. data/hobo_files/plugin/generators/hobo_front_controller/templates/login.dryml +44 -0
  18. data/hobo_files/plugin/generators/hobo_front_controller/templates/search.dryml +18 -0
  19. data/hobo_files/plugin/generators/hobo_front_controller/templates/signup.dryml +45 -0
  20. data/hobo_files/plugin/generators/hobo_model/USAGE +26 -0
  21. data/hobo_files/plugin/generators/hobo_model/hobo_model_generator.rb +38 -0
  22. data/hobo_files/plugin/generators/hobo_model/templates/fixtures.yml +11 -0
  23. data/hobo_files/plugin/generators/hobo_model/templates/migration.rb +13 -0
  24. data/hobo_files/plugin/generators/hobo_model/templates/model.rb +24 -0
  25. data/hobo_files/plugin/generators/hobo_model/templates/unit_test.rb +10 -0
  26. data/hobo_files/plugin/generators/hobo_model_controller/USAGE +30 -0
  27. data/hobo_files/plugin/generators/hobo_model_controller/hobo_model_controller_generator.rb +43 -0
  28. data/hobo_files/plugin/generators/hobo_model_controller/templates/controller.rb +5 -0
  29. data/hobo_files/plugin/generators/hobo_model_controller/templates/functional_test.rb +18 -0
  30. data/hobo_files/plugin/generators/hobo_model_controller/templates/helper.rb +2 -0
  31. data/hobo_files/plugin/generators/hobo_model_controller/templates/view.rhtml +2 -0
  32. data/hobo_files/plugin/generators/hobo_rapid/hobo_rapid_generator.rb +51 -0
  33. data/hobo_files/plugin/generators/hobo_rapid/templates/hobo_rapid.js +436 -0
  34. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/default_mapping.rb +11 -0
  35. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/banner.gif +0 -0
  36. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_bodytop.gif +0 -0
  37. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_corner_01.gif +0 -0
  38. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_corner_02.gif +0 -0
  39. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_corner_03.gif +0 -0
  40. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_corner_04.gif +0 -0
  41. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_shadow_bottom.gif +0 -0
  42. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_shadow_left.gif +0 -0
  43. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_shadow_right.gif +0 -0
  44. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_shadow_top.gif +0 -0
  45. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/header_blue.gif +0 -0
  46. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/header_dblue.gif +0 -0
  47. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/header_green.gif +0 -0
  48. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/header_purple.gif +0 -0
  49. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/header_red.gif +0 -0
  50. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/logo.gif +0 -0
  51. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/spinner.gif +0 -0
  52. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/txt_list_img_dblue.gif +0 -0
  53. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/txt_list_img_green.gif +0 -0
  54. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/txt_list_img_purple.gif +0 -0
  55. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/txt_list_img_red.gif +0 -0
  56. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_corner_01.gif +0 -0
  57. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_corner_02.gif +0 -0
  58. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_corner_03.gif +0 -0
  59. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_corner_04.gif +0 -0
  60. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_shadow_bottom.gif +0 -0
  61. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_shadow_left.gif +0 -0
  62. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_shadow_right.gif +0 -0
  63. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_shadow_top.gif +0 -0
  64. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/stylesheets/application.css +390 -0
  65. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/views/application.dryml +104 -0
  66. data/hobo_files/plugin/generators/hobo_user_model/USAGE +26 -0
  67. data/hobo_files/plugin/generators/hobo_user_model/hobo_user_model_generator.rb +38 -0
  68. data/hobo_files/plugin/generators/hobo_user_model/templates/fixtures.yml +11 -0
  69. data/hobo_files/plugin/generators/hobo_user_model/templates/migration.rb +15 -0
  70. data/hobo_files/plugin/generators/hobo_user_model/templates/model.rb +58 -0
  71. data/hobo_files/plugin/generators/hobo_user_model/templates/unit_test.rb +10 -0
  72. data/hobo_files/plugin/init.rb +44 -0
  73. data/hobo_files/plugin/lib/action_view_extensions/base.rb +14 -0
  74. data/hobo_files/plugin/lib/active_record/has_many_association.rb +54 -0
  75. data/hobo_files/plugin/lib/active_record/has_many_through_association.rb +22 -0
  76. data/hobo_files/plugin/lib/active_record/table_definition.rb +34 -0
  77. data/hobo_files/plugin/lib/extensions.rb +245 -0
  78. data/hobo_files/plugin/lib/extensions/test_case.rb +130 -0
  79. data/hobo_files/plugin/lib/hobo.rb +353 -0
  80. data/hobo_files/plugin/lib/hobo/HtmlString +3 -0
  81. data/hobo_files/plugin/lib/hobo/authenticated_user.rb +106 -0
  82. data/hobo_files/plugin/lib/hobo/authentication_support.rb +108 -0
  83. data/hobo_files/plugin/lib/hobo/composite_model.rb +66 -0
  84. data/hobo_files/plugin/lib/hobo/controller.rb +134 -0
  85. data/hobo_files/plugin/lib/hobo/controller_helpers.rb +135 -0
  86. data/hobo_files/plugin/lib/hobo/core.rb +475 -0
  87. data/hobo_files/plugin/lib/hobo/define_tags.rb +56 -0
  88. data/hobo_files/plugin/lib/hobo/dryml.rb +161 -0
  89. data/hobo_files/plugin/lib/hobo/dryml/dryml_builder.rb +126 -0
  90. data/hobo_files/plugin/lib/hobo/dryml/tag_module.rb +9 -0
  91. data/hobo_files/plugin/lib/hobo/dryml/taglib.rb +57 -0
  92. data/hobo_files/plugin/lib/hobo/dryml/template.rb +586 -0
  93. data/hobo_files/plugin/lib/hobo/dryml/template_environment.rb +302 -0
  94. data/hobo_files/plugin/lib/hobo/dryml/template_handler.rb +19 -0
  95. data/hobo_files/plugin/lib/hobo/generator.rb +25 -0
  96. data/hobo_files/plugin/lib/hobo/html_string.rb +3 -0
  97. data/hobo_files/plugin/lib/hobo/lazy_hash.rb +28 -0
  98. data/hobo_files/plugin/lib/hobo/mapping_tags.rb +262 -0
  99. data/hobo_files/plugin/lib/hobo/markdown_string.rb +7 -0
  100. data/hobo_files/plugin/lib/hobo/model.rb +391 -0
  101. data/hobo_files/plugin/lib/hobo/model_controller.rb +676 -0
  102. data/hobo_files/plugin/lib/hobo/model_queries.rb +92 -0
  103. data/hobo_files/plugin/lib/hobo/model_support.rb +44 -0
  104. data/hobo_files/plugin/lib/hobo/password_string.rb +3 -0
  105. data/hobo_files/plugin/lib/hobo/predicate_dispatch.rb +78 -0
  106. data/hobo_files/plugin/lib/hobo/proc_binding.rb +32 -0
  107. data/hobo_files/plugin/lib/hobo/rapid.rb +447 -0
  108. data/hobo_files/plugin/lib/hobo/static_tags +92 -0
  109. data/hobo_files/plugin/lib/hobo/text.rb +3 -0
  110. data/hobo_files/plugin/lib/hobo/textile_string.rb +13 -0
  111. data/hobo_files/plugin/lib/hobo/undefined.rb +41 -0
  112. data/hobo_files/plugin/lib/hobo/undefined_access_error.rb +5 -0
  113. data/hobo_files/plugin/lib/hobo/where_fragment.rb +23 -0
  114. data/hobo_files/plugin/lib/rexml.rb +345 -0
  115. data/hobo_files/plugin/tags/core.dryml +6 -0
  116. data/hobo_files/plugin/tags/rapid.dryml +177 -0
  117. data/hobo_files/plugin/tags/rapid_editing.dryml +168 -0
  118. data/hobo_files/plugin/tags/rapid_navigation.dryml +95 -0
  119. data/hobo_files/plugin/tags/rapid_pages.dryml +175 -0
  120. data/hobo_files/plugin/tasks/environments.rake +19 -0
  121. data/hobo_files/plugin/tasks/hobo_tasks.rake +4 -0
  122. data/hobo_files/plugin/test/hobo_dryml_template_test.rb +7 -0
  123. data/hobo_files/plugin/test/hobo_test.rb +7 -0
  124. data/hobo_files/plugin/uninstall.rb +1 -0
  125. metadata +206 -0
@@ -0,0 +1,92 @@
1
+ module Hobo
2
+
3
+ class ModelQueries
4
+
5
+ def initialize(model)
6
+ @model = model
7
+ end
8
+
9
+
10
+ attr_reader :model
11
+
12
+ def not_(fragment)
13
+ WhereFragment.new("not (#{fragment.to_sql})")
14
+ end
15
+
16
+
17
+ def is_in(association)
18
+ association = Hobo.object_from_dom_id(association) if association.is_a? String
19
+ refl = association.proxy_reflection
20
+ raise HoboError.new("association #{refl.name} is not a collection of #{model.name.pluralize}") unless
21
+ refl.klass == model and refl.macro == :has_many
22
+
23
+ WhereFragment.new(_association_finder_sql(association))
24
+ end
25
+
26
+
27
+ def not_in(association)
28
+ not_(is_in(association))
29
+ end
30
+
31
+
32
+ def method_missing(name, *args)
33
+ check_column = proc do |col|
34
+ raise HoboError.new("no such column '#{col}' in query") unless
35
+ model.columns.every(:name).include? col
36
+ end
37
+
38
+ m, field = *name.to_s.match(/^(.*)_is$/)
39
+ if m
40
+ if (refl = model.reflections[field.to_sym]) and refl.macro == :belongs_to
41
+ field = refl.primary_key_name
42
+ val = args[0] && args[0].id
43
+ raise HoboError.new("don't use self in query blocks") if val == self
44
+ else
45
+ check_column[field]
46
+ val = args[0]
47
+ end
48
+ return (if val.nil?
49
+ WhereFragment.new("#{field} IS NULL")
50
+ else
51
+ WhereFragment.new("#{field} = ?", val)
52
+ end)
53
+ end
54
+
55
+ m, field = *name.to_s.match(/^(.*)_contains$/)
56
+ if m
57
+ check_column[field]
58
+ return WhereFragment.new("#{field} like ?", "%#{args[0]}%")
59
+ end
60
+
61
+ m, field = *name.to_s.match(/^(.*)_starts$/)
62
+ if m
63
+ check_column[field]
64
+ return WhereFragment.new("#{field} like ?", "#{args[0]}%")
65
+ end
66
+
67
+ m, field = *name.to_s.match(/^(.*)_ends$/)
68
+ if m
69
+ check_column[field]
70
+ return WhereFragment.new("#{field} like ?", "%#{args[0]}")
71
+ end
72
+
73
+ return WhereFragment.new(@model.send(name, *args))
74
+ end
75
+
76
+ def _association_finder_sql(assoc)
77
+ refl = assoc.proxy_reflection
78
+ if refl.through_reflection
79
+ conditions = assoc.send(:construct_conditions)
80
+ from = assoc.send(:construct_from)
81
+ joins = assoc.send(:construct_joins)
82
+
83
+ table = "#{model.table_name}"
84
+ "id in (select #{table}.id from #{table} #{joins} where #{conditions})"
85
+ else
86
+ Object.instance_method(:instance_variable_get).bind(assoc).call("@finder_sql")
87
+ end
88
+ end
89
+
90
+ end
91
+
92
+ end
@@ -0,0 +1,44 @@
1
+ module Hobo
2
+
3
+ module ModelSupport
4
+
5
+ # This module provides methods common to both Hobo::Model and Hobo::CompositeModel
6
+
7
+ def self.included(base)
8
+ base.extend(ClassMethods) if base.is_a? Class
9
+ end
10
+
11
+ module ClassMethods
12
+
13
+ def delegate_and_compose(*methods)
14
+ options = methods.pop
15
+ unless options.is_a?(Hash) && to = options[:to]
16
+ raise ArgumentError, ("Delegation needs a target. Supply an options hash " +
17
+ "with a :to key as the last argument (e.g. delegate :hello, :to => :greeter).")
18
+ end
19
+ use = options[:use]
20
+
21
+ methods.each do |method|
22
+ module_eval(<<-EOS, "(__COMPOSED_DELEGATION__)", 1)
23
+ def #{method}
24
+ @__#{method}_result__ ||= begin
25
+ obj = #{to}.__send__(#{method.inspect})
26
+ return nil if obj.nil?
27
+
28
+ if obj.nil?
29
+ nil
30
+ elsif obj.is_a?(Array)
31
+ obj.map {|o| self.compose_with(o, #{use.inspect})}
32
+ else
33
+ self.compose_with(obj, #{use.inspect})
34
+ end
35
+ end
36
+ end
37
+ EOS
38
+ end
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,3 @@
1
+ class Hobo::PasswordString < String
2
+
3
+ end
@@ -0,0 +1,78 @@
1
+ module Hobo::PredicateDispatch
2
+
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+
9
+ def default_method_for(method)
10
+ "_default_#{method}"
11
+ end
12
+
13
+ def defp(name, predicate=nil, &block)
14
+ ivar = "@@#{name}_predicates"
15
+ default_method_name = default_method_for(name).to_sym
16
+ methods = ivar.in?(class_variables) && class_variable_get(ivar)
17
+
18
+ if !methods
19
+ # It's the first defp for this name...
20
+
21
+ methods = class_variable_set(ivar, [])
22
+
23
+ if name.to_s.in?(instance_methods) and predicate
24
+ # There's already a normal method with this name - it
25
+ # becomes the default.
26
+ alias_method default_method_name, name
27
+ end
28
+
29
+ end
30
+
31
+ if predicate
32
+ pname = "#{name}_p#{methods.length}"
33
+ methods << [pname, predicate.arity == 1, block.arity == 2]
34
+ define_method(pname, block)
35
+ define_method(pname + "_predicate", &predicate)
36
+ else
37
+ # It's the default - if there's already one, overwrite it
38
+ define_method(default_method_name, block)
39
+ end
40
+
41
+ module_eval <<-END, __FILE__, __LINE__+1
42
+ def #{name}(options={}, &block)
43
+ methods = #{ivar}
44
+ for method_name, predicate_wants_options, wants_block in methods
45
+ pred = method_name + "_predicate"
46
+ if (predicate_wants_options ? send(pred, options) : send(pred))
47
+ return (if wants_block
48
+ send(method_name, options, block)
49
+ else
50
+ send(method_name, options)
51
+ end)
52
+ end
53
+ end
54
+ if respond_to?(:#{default_method_name})
55
+ if method(:#{default_method_name}).arity == 2
56
+ #{default_method_name}(options, block)
57
+ else
58
+ #{default_method_name}(options)
59
+ end
60
+ end
61
+ end
62
+ END
63
+ end
64
+
65
+ alias :p :proc
66
+
67
+ def predicate_method?(method_name)
68
+ "@@#{method_name}_predicates".in?(class_variables)
69
+ end
70
+
71
+ def predicate_has_default?(method_name)
72
+ default_method_for(method_name).in?(instance_methods)
73
+ end
74
+
75
+ end
76
+
77
+
78
+ end
@@ -0,0 +1,32 @@
1
+ module Hobo
2
+
3
+ class ProcBinding
4
+
5
+ def self.eval(proc, self_, locals)
6
+ ProcBinding.new(self_, locals).instance_eval(&proc)
7
+ end
8
+
9
+ def initialize(self_, locals)
10
+ @self_ = self_
11
+ @locals = locals
12
+ locals.symbolize_keys!
13
+ end
14
+
15
+ def method_missing(name, *args, &block)
16
+ if @locals.has_key?(name)
17
+ @locals[name]
18
+ else
19
+ @self_.send(name, *args, &block)
20
+ end
21
+ end
22
+
23
+ end
24
+
25
+ (Object.instance_methods +
26
+ Object.private_instance_methods +
27
+ Object.protected_instance_methods).each do |m|
28
+ ProcBinding.send(:undef_method, m) unless
29
+ %w{initialize method_missing send instance_eval}.include?(m) || m.starts_with?('_')
30
+ end
31
+
32
+ end
@@ -0,0 +1,447 @@
1
+ module Hobo::Rapid
2
+
3
+ include Hobo::DefineTags
4
+
5
+ TYPE_NAMES = {
6
+ Hobo::HtmlString => :html,
7
+ Hobo::Text => :textarea,
8
+ TrueClass => :boolean,
9
+ FalseClass => :boolean,
10
+ Date => :date,
11
+ Time => :datetime,
12
+ Hobo::PasswordString => :password_string,
13
+ Fixnum => :integer,
14
+ BigDecimal => :integer,
15
+ Float => :float,
16
+ String => :string
17
+ }
18
+
19
+ def options_for_hobo_ajax(options)
20
+ js_options = build_callbacks(options)
21
+
22
+ js_options['asynchronous'] = false if options[:type] == :synchronous
23
+ js_options['method'] = method_option_to_s(options[:method]) if options[:method]
24
+ js_options['evalScripts'] = false if options[:script] == false
25
+ js_options['form'] = options[:form] if options[:form]
26
+ js_options['params'] = make_params_js(options[:params]) if options[:params]
27
+ js_options['resultUpdate'] = js_result_updates(options[:result_update]) if options[:result_update]
28
+ js_options['resetForm'] = false if options[:reset_form] == false
29
+ js_options['refocusForm'] = false if options[:refocus_form] == false
30
+
31
+ js_options.empty? ? nil : options_for_javascript(js_options)
32
+ end
33
+
34
+
35
+ def js_updates(updates)
36
+ return '[]' unless updates
37
+ updates = [updates] unless updates.is_a? Array
38
+ '[' + comma_split(updates).map{|u| js_str(u)}.join(', ') + ']'
39
+ end
40
+
41
+
42
+ def js_result_updates(updates)
43
+ return '[]' unless updates
44
+ updates = [updates] unless updates.is_a? Array
45
+ pairs = comma_split(updates).omap{split(/\s*=\s*/)}
46
+ '[' + pairs.map{|p| "{id: #{js_str(p[0])}, result: #{js_str(p[1])}}"}.join(", ") + ']'
47
+ end
48
+
49
+
50
+ def ajax_updater(url_or_form, message, update, options={})
51
+ options ||= {}
52
+ target = if url_or_form == :post_form
53
+ target = "this"
54
+ else
55
+ js_str(url_or_form)
56
+ end
57
+ js_options = options_for_hobo_ajax(options)
58
+ args = [target, js_str(message || "..."), js_updates(update), js_options].compact
59
+
60
+ confirm = options.delete(:confirm)
61
+
62
+ func = "Hobo.ajaxRequest(#{args * ', '})"
63
+ if confirm
64
+ "if (confirm(#{js_str(confirm)})) { #{func} }"
65
+ else
66
+ func
67
+ end
68
+ end
69
+
70
+
71
+ def a_or_an(word)
72
+ if word =~ /^[aeiouh]/
73
+ "an #{word}"
74
+ else
75
+ "a #{word}"
76
+ end
77
+ end
78
+
79
+
80
+ def no_break(s)
81
+ s = new_context { yield } if block_given?
82
+ s.gsub(' ', '&nbsp;')
83
+ end
84
+
85
+
86
+ def_tag :if_current_user_in do
87
+ if_(:q => current_user_in?(this)) { tagbody.call }
88
+ end
89
+
90
+
91
+ def_tag :edit, :in_place do
92
+ if can_view_this?
93
+ if not can_edit_this?
94
+ show
95
+ elsif this_parent.new_record? or in_place == false
96
+ form_field(options)
97
+ else
98
+ editor(options)
99
+ end
100
+ end
101
+ end
102
+
103
+
104
+ def type_name(klass)
105
+ TYPE_NAMES[klass]
106
+ end
107
+
108
+
109
+ def_tag :form_field do
110
+ raise HoboError.new("Not allowed to edit") unless can_edit_this?
111
+ if this_type.respond_to?(:macro)
112
+ if this_type.macro == :belongs_to
113
+ belongs_to_field(options)
114
+ elsif this_type.macro == :has_many
115
+ has_many_field(options)
116
+ end
117
+
118
+ else
119
+ tag = type_name(this_type).to_s + "_field"
120
+ if respond_to?(tag)
121
+ options[:name] ||= param_name_for_this
122
+ tag_src = send(tag, options)
123
+ if this_parent.errors[this_field]
124
+ "<div class='field_with_errors'>#{tag_src}</div>"
125
+ else
126
+ tag_src
127
+ end
128
+ else
129
+ raise HoboError, ("No form field tag for #{this_field}:#{this_type} (this=#{this.inspect})")
130
+ end
131
+ end
132
+ end
133
+
134
+ def_tag :has_many_field do
135
+ raise NotImplementedError, "form field for has_many associations not implemented"
136
+ end
137
+
138
+ def_tag :belongs_to_field do
139
+ belongs_to_menu_field(options)
140
+ end
141
+
142
+ def_tag :textarea_field, :name do
143
+ text_area_tag(name, this, options)
144
+ end
145
+
146
+
147
+ def_tag :boolean_field, :name do
148
+ check_box_tag(name, '1', this, options)
149
+ end
150
+
151
+ def_tag :password_string_field, :name do
152
+ password_field_tag(name, this)
153
+ end
154
+
155
+ def_tag :html_field, :name do
156
+ text_area_tag(name, this, add_classes(options, :tiny_mce))
157
+ end
158
+
159
+ def_tag :date_field do
160
+ select_date(this || Time.now, :prefix => param_name_for_this)
161
+ end
162
+
163
+ def_tag :datetime_field do
164
+ select_datetime(this || Time.now, :prefix => param_name_for_this)
165
+ end
166
+
167
+ def_tag :integer_field, :name do
168
+ text_field_tag(name, this, options)
169
+ end
170
+
171
+ def_tag :float_field, :name do
172
+ text_field_tag(name, this, options)
173
+ end
174
+
175
+ def_tag :string_field, :name do
176
+ text_field_tag(name, this, options)
177
+ end
178
+
179
+ def_tag :editor do
180
+ raise HoboError.new("Not allowed to edit") unless can_edit_this?
181
+
182
+ if this_type.respond_to?(:macro)
183
+ if this_type.macro == :belongs_to
184
+ belongs_to_editor(options)
185
+ else
186
+ has_many_editor(options)
187
+ end
188
+ else
189
+ tag = type_name(this_type).to_s + "_editor"
190
+ if respond_to?(tag)
191
+ send(tag, options)
192
+ else
193
+ raise HoboError.new("<editor> not implemented for #{this.class.name}\##{this_field} " +
194
+ "(#{this.inspect}:#{this_type})")
195
+ end
196
+ end
197
+ end
198
+
199
+
200
+ def_tag :has_many_editor do
201
+ # TODO: Implement
202
+ object_link(options)
203
+ end
204
+
205
+
206
+ def in_place_editor(kind, tag, options)
207
+ opts = add_classes(options, kind, editor_class).merge(:hobo_model_id => this_field_dom_id)
208
+
209
+ update = opts.delete(:update)
210
+ blank_message = opts.delete(:blank_message) || "(click to edit)"
211
+
212
+ display = show(:no_span => true)
213
+ opts[:hobo_blank_message] = blank_message
214
+ display = blank_message if display.blank?
215
+ opts[:hobo_update] = update if update
216
+ content_tag(tag, display, opts)
217
+ end
218
+
219
+
220
+ def_tag :string_editor do
221
+ in_place_editor "in_place_textfield_bhv", :span, options
222
+ end
223
+
224
+ def_tag :textarea_editor do
225
+ in_place_editor "in_place_textarea_bhv", :div, options
226
+ end
227
+
228
+ def_tag :html_editor do
229
+ in_place_editor "in_place_html_textarea_bhv", :div, options
230
+ end
231
+
232
+ def_tag :belongs_to_editor do
233
+ belongs_to_menu_editor(options)
234
+ end
235
+
236
+ def_tag :datetime_editor do
237
+ string_editor(options)
238
+ end
239
+
240
+ def_tag :date_editor do
241
+ string_editor(options)
242
+ end
243
+
244
+ def_tag :integer_editor do
245
+ in_place_editor "in_place_textfield_bhv", :span, options
246
+ end
247
+
248
+ def_tag :float_editor do
249
+ in_place_editor "in_place_textfield_bhv", :span, options
250
+ end
251
+
252
+ def_tag :password_string_editor do
253
+ raise HoboError, "passwords cannot be edited in place"
254
+ end
255
+
256
+ def_tag :boolean_editor do
257
+ boolean_checkbox_editor(options)
258
+ end
259
+
260
+
261
+ AJAX_ATTRS = [:before, :success, :failure, :complete, :type, :method,
262
+ :script, :form, :params, :confirm,
263
+ :reset_form, :refocus_form]
264
+
265
+
266
+ def_tag :update_button, :label, :message, :attrs, :update, :params do
267
+ raise HoboError.new("no update specified") unless update
268
+ message2 = message || label
269
+ func = ajax_updater(object_url(this), message2, update,
270
+ :params => { this.class.name.underscore => attrs }.merge(params || {}),
271
+ :method => :put)
272
+ tag :input, add_classes(options.merge(:type =>'button', :onclick => func, :value => label),
273
+ "button_input update_button update_#{this.class.name.underscore}_button")
274
+ end
275
+
276
+
277
+ def_tag :delete_button, :label, :message, :update, :ajax, :else, :image, :confirm, :fade do
278
+ fade2 = fade.nil? ? true : fade
279
+ if can_delete?(this)
280
+ opts = options.merge(if image
281
+ { :type => "image", :src => "#{urlb}/images/#{image}" }
282
+ else
283
+ { :type => "button" }
284
+ end)
285
+ label2 = label || "Remove"
286
+ confirm2 = confirm || "Are you sure?"
287
+
288
+ add_classes!(opts,
289
+ image ? "image_button_input" : "button_input",
290
+ "delete_button delete_#{this.class.name.underscore}_button")
291
+ url = object_url(this, "destroy")
292
+ if ajax == false
293
+ opts[:confirm] = confirm2
294
+ button_to(label2, url, opts)
295
+ else
296
+ opts[:value] = label2
297
+ opts[:onclick] = "Hobo.removeButton(this, '#{url}', #{js_updates(update)}, #{fade2 ? 'true' : 'false'})"
298
+ tag(:input, opts)
299
+ end
300
+ else
301
+ else_
302
+ end
303
+ end
304
+
305
+
306
+ def_tag :create_button, :model, :update, :attrs, :label, :message, :else do
307
+ raise HoboError.new("no update specified") unless update
308
+ params = attrs || {}
309
+ if model
310
+ new = (model.is_a?(String) ? model.constantize : model).new(params)
311
+ else
312
+ raise HoboError.new("invalid context for <create_button>") unless Hobo.simple_has_many_association?(this)
313
+ params[this.proxy_reflection.primary_key_name] = this.proxy_owner.id
314
+ new = this.new(params)
315
+ end
316
+ if can_create?(new)
317
+ label2 = label || "New #{new.class.name.titleize}"
318
+ message2 = message || label2
319
+ class_name = new.class.name.underscore
320
+ func = ajax_updater(object_url(new.class), message2, update,
321
+ ({:params => { class_name => params }} unless params.empty?))
322
+ tag :input, add_classes(options.merge(:type =>'button', :onclick => func, :value => label2),
323
+ "button_input create_button create_#{class_name}_button")
324
+ else
325
+ else_
326
+ end
327
+ end
328
+
329
+
330
+ def_tag :remote_method_button, :method, :update, :result_update, :params, :label, :message do
331
+ ajax_options, html_options = options.partition_hash(AJAX_ATTRS)
332
+
333
+ message2 = message || method.titleize
334
+ func = ajax_updater(object_url(this) + "/#{method}", message2, update,
335
+ ajax_options.merge(:params => params, :result_update => result_update))
336
+ html_options.update(:type =>'button', :onclick => "var e = this; " + func, :value => label)
337
+ tag(:input, add_classes(html_options, "button_input remote_method_button #{method}_button"))
338
+ end
339
+
340
+
341
+ def_tag :hobo_rapid_javascripts, :tiny_mce do
342
+ res = javascript_include_tag("hobo_rapid")
343
+ res += "<script>"
344
+ unless Hobo.all_controllers.empty?
345
+ res += "var controllerNames = {" +
346
+ Hobo.all_controllers.map {|c| "#{c.singularize}: '#{c}'"}.join(', ') +
347
+ "}; "
348
+ end
349
+ res += "urlBase = '#{urlb}'; hoboPartPage = '#{view_name}'</script>"
350
+
351
+ if tiny_mce
352
+ res += javascript_include_tag("tiny_mce/tiny_mce_src") + %{
353
+ <script type="text/javascript">
354
+ tinyMCE.init({ mode: "textareas", editor_selector: "tiny_mce",
355
+ plugins: 'save',
356
+ theme_advanced_buttons1 : "bold, italic, separator, " +
357
+ "bullist, outdent, indent, separator, " +
358
+ "undo, redo, separator, link, unlink",
359
+ theme_advanced_buttons2 : "",
360
+ theme_advanced_buttons3 : ""
361
+ });
362
+ </script>}
363
+ end
364
+ res
365
+ end
366
+
367
+
368
+ def_tag :object_form, :message, :update, :hidden_fields, :url do
369
+ ajax_options, html_options = options.partition_hash(AJAX_ATTRS)
370
+
371
+ url2 = url || object_url(this)
372
+ if update
373
+ # add an onsubmit to convert to an ajax form if `update` is given
374
+ function = ajax_updater(:post_form, message, update, ajax_options)
375
+ html_options[:onsubmit] = [html_options[:onsubmit],
376
+ "#{function}; return false;"].compact.join("; ")
377
+ end
378
+
379
+ body, field_names = with_form_context{ tagbody.call }
380
+ body = body.to_s
381
+
382
+ hiddens = case hidden_fields
383
+ when nil
384
+ []
385
+ when '*'
386
+ this.class.column_names - ['type']
387
+ else
388
+ comma_split(hidden_fields)
389
+ end
390
+ pname = this.class.name.underscore
391
+ hidden_tags = hiddens.map do |h|
392
+ val = this.send(h)
393
+ name = "#{pname}[#{h}]"
394
+ hidden_field_tag(name, val.to_s) if val and name.not_in?(field_names)
395
+ end
396
+ hidden_tags << hidden_field_tag("_method", "PUT") unless this.respond_to?(:new_record?) and this.new_record?
397
+
398
+ html_options[:method] = "post"
399
+ body_with_hiddens = hidden_tags.compact.join("\n") + body
400
+
401
+ form_class = this.new_record? ? "new_#{this.class.name.underscore}" : this.class.name.underscore
402
+
403
+ content_tag("form", body_with_hiddens, add_classes(html_options, form_class).merge(:action => url2))
404
+ end
405
+
406
+
407
+ def_tag :create_form, :model, :in, :update, :message, :hidden_fields do
408
+ hiddens = hidden_fields
409
+ if model
410
+ obj = (model.is_a?(String) ? model.constantize : model).new
411
+ else
412
+ raise HoboError.new("cannot create object in #{in_.inspect}") unless this.is_a? Array
413
+ obj = this.new
414
+ refl = this.proxy_reflection
415
+ if refl.macro == :has_many and !refl.through_reflection
416
+ hiddens ||= [refl.primary_key_name]
417
+ end
418
+ end
419
+ m = message || "New #{obj.class.name.titleize}"
420
+ options = add_classes(options, "create_#{obj.class.underscore.name}_form")
421
+ object_form(obj, options.merge(:message => m, :update => update, :hidden_fields => hiddens)) do
422
+ tagbody.call
423
+ end
424
+ end
425
+
426
+ def_tag :remote_method_form, :method, :message, :update, :result_update do
427
+ ajax_options, html_options = options.partition_hash(AJAX_ATTRS)
428
+
429
+ url = object_url(this, method)
430
+ if update || result_update || !ajax_options.empty?
431
+ # add an onsubmit to convert to an ajax form
432
+
433
+ function = ajax_updater(:post_form, message, update, ajax_options.merge(:result_update => result_update))
434
+ html_options[:onsubmit] = [html_options[:onsubmit],
435
+ "var e = this; #{function}; return false;"].compact.join("; ")
436
+ end
437
+
438
+ add_classes!(html_options, "#{this.class.name.underscore}_#{method}_form")
439
+ html_options[:method] = "post"
440
+ content_tag("form", tagbody.call, html_options.merge(:action => url))
441
+ end
442
+
443
+ def editor_class
444
+ "#{this_parent.class.name.underscore}_#{this_field}_editor"
445
+ end
446
+
447
+ end