hobo 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
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