cuca 0.06 → 0.07

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.
@@ -0,0 +1,42 @@
1
+ # A good context for generators. It will find and run widgets on method_missing.
2
+ #
3
+ class GeneratorContext
4
+ def get_bindings
5
+ binding
6
+ end
7
+
8
+ def initialize(assigns, base_object)
9
+ assigns.each_pair do |k,v|
10
+ instance_variable_set("@#{k}", v)
11
+ end
12
+ @base = base_object
13
+ end
14
+
15
+ def method_missing(sym, *args, &block )
16
+ class_name = sym.id2name
17
+
18
+
19
+ # 1st try to find method in the base widget
20
+ if @base.respond_to?(class_name.intern) then
21
+ return @base.send(class_name, *args, &block)
22
+ end
23
+ c = nil
24
+ # 2nd try to find a widget
25
+ if Object.const_defined?(class_name+'Widget') then
26
+ c = Object::const_get(class_name+'Widget')
27
+ else
28
+ # ...try to find in action namespace
29
+ mod = $app.urlmap.action_module
30
+ c = mod.const_get(class_name+'Widget') if mod.const_defined?(class_name+'Widget')
31
+ end
32
+
33
+ raise NameError.new "Undefined method: #{class_name}" unless c
34
+
35
+ widget = c.new({:args => args,
36
+ :assigns => @assigns },
37
+ &block)
38
+
39
+ return widget.to_s
40
+ end
41
+ end
42
+
data/lib/cuca/layout.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Cuca
2
2
 
3
- # === Layout
3
+ # = Layout
4
4
  # A Layout just behaves as a normal widget plus it will give you the @content_for_layout
5
5
  # instance variable with the content generated from the controller.
6
6
  #
@@ -0,0 +1,171 @@
1
+
2
+ require 'cgi/session'
3
+ require 'cgi/session/pstore'
4
+
5
+ require 'cuca/app'
6
+ require 'cuca/widget'
7
+ require 'cuca/sessionflash'
8
+ require 'cuca/sessionpage'
9
+
10
+ module Cuca
11
+
12
+
13
+ # == Description
14
+ # Session can be used to store stateful data. It is not loaded by default, to make
15
+ # use of it you must require this module and tell a controller to use a session.
16
+ #
17
+ # == Example Use (permanent data)
18
+ #
19
+ # Initialize (using a controller):
20
+ #
21
+ # class ApplicationController < Cuca::Controller
22
+ # use_session
23
+ # end
24
+ #
25
+ # Save and Load data (from any widgets e.g. controller):
26
+ #
27
+ # class IndexController < ApplicationController
28
+ #
29
+ # def run
30
+ # session[:stuff_to_remember] = "hello world"
31
+ # @stuff = session[:stuff_to_remember]
32
+ # end
33
+ # end
34
+ #
35
+ # == Flash Memory
36
+ #
37
+ # The flash memory can be used to store temporarily data for the current and
38
+ # next action. A typical example are messages after a post event, like:
39
+ #
40
+ # class LoginController < ApplicationController
41
+ # (...)
42
+ # def post
43
+ # if (params['username'] == 'root' && params['pass'] == 'stuff') then
44
+ # session.flash[:message] = "You are logged in"
45
+ # session[:username] = 'root'
46
+ # stop :redirect => 'index'
47
+ # end
48
+ # end
49
+ # end
50
+ #
51
+ # If you want to keep the flash memory for another cycle you can call:
52
+ #
53
+ # session.flash.keep
54
+ #
55
+ #
56
+ # == Page Memory
57
+ #
58
+ # Page memory is a container to store data only valid for the current action.
59
+ # It will be erased once you leave to a different action.
60
+ # Current request and query parameters (get/post) are automatically available in this
61
+ # container.
62
+ #
63
+ #
64
+ #
65
+ # == Configuration
66
+ #
67
+ # Session is using some values from App::Config :
68
+ #
69
+ # 'session_prefix'
70
+ # 'session_valid'
71
+ # 'session_key'
72
+ #
73
+ class Session
74
+ attr_reader :flash
75
+ attr_reader :page
76
+ attr_reader :cgi
77
+
78
+ private
79
+ def make_session
80
+ @sess = CGI::Session.new(@cgi, @session_parameters)
81
+ end
82
+
83
+ public
84
+ def reset
85
+ begin
86
+ @sess.delete
87
+ rescue
88
+ end
89
+ make_session
90
+ end
91
+
92
+
93
+ # returns true/false if a session exists
94
+ def exists?
95
+ begin
96
+ p = @session_parameters.clone
97
+ p['new_session'] = false
98
+ session = CGI::Session.new(cgi, p)
99
+ rescue ArgumentError
100
+ return false
101
+ end
102
+ return true
103
+ end
104
+
105
+ def initialize(cgi)
106
+ @cgi = cgi
107
+
108
+ @session_parameters = {
109
+ 'database_manager' => CGI::Session::PStore,
110
+ 'session_key' => App::config["session_key"],
111
+ 'session_path' => '/',
112
+ # 'new_session' => false,
113
+ 'session_expires' => Time.now + App::config["session_valid"].to_i,
114
+ 'prefix' => App::config["session_prefix"] }
115
+
116
+ make_session
117
+
118
+
119
+ @flash = SessionFlash.new(self)
120
+ @page = SessionPage.new(self)
121
+ end
122
+
123
+ def []=(key, value)
124
+ @sess[key] = value
125
+ end
126
+
127
+ def [](key)
128
+ return @sess[key]
129
+ end
130
+
131
+ def close
132
+ @sess.close
133
+ end
134
+
135
+ def update
136
+ @sess.update
137
+ end
138
+
139
+ def delete
140
+ @sess.delete
141
+ end
142
+
143
+
144
+ end
145
+
146
+ class Controller
147
+ # This will create filters that initialize the session before the action and
148
+ # close it afterwards.
149
+ def self.use_session
150
+ priority_before_filter('ses_initialize_session')
151
+ priority_after_filter('ses_close_session')
152
+ end
153
+
154
+ def ses_initialize_session
155
+ $session = Session.new($app.cgi)
156
+ end
157
+ def ses_close_session
158
+ $session.close
159
+ # $session = nil
160
+ end
161
+ end
162
+
163
+
164
+ class Widget
165
+ def session
166
+ $session
167
+ end
168
+ end
169
+
170
+ end
171
+
@@ -4,6 +4,7 @@ module Cuca
4
4
  # for the current action. Once you leave to another page it will
5
5
  # be erased. (see Cuca::Session)
6
6
  class SessionPage
7
+
7
8
  private
8
9
  def pagekey
9
10
  "Pk_#{$app.urlmap.script.gsub(/[\/\\]/, '_')}".intern
@@ -23,18 +24,28 @@ class SessionPage
23
24
  end
24
25
 
25
26
  def [](key)
26
- pagemem[pagekey][key] ? pagemem[pagekey][key] : nil
27
+ pagemem[pagekey][key]
27
28
  end
28
29
 
29
30
  def []=(key,value)
30
31
  pagemem[pagekey][key] = value
31
32
  end
32
-
33
+
33
34
  # remove a variable from page memory
34
35
  def delete(key)
35
36
  pagemem[pagekey].delete(key)
36
37
  end
37
38
 
39
+ # delete all from current page memory
40
+ def reset
41
+ pagemem[pagekey] = {}
42
+ end
43
+
44
+ # access to pagemem
45
+ def memory
46
+ pagemem[pagekey]
47
+ end
48
+
38
49
  private
39
50
  def expire
40
51
  pagemem.each_pair do |k,v|
@@ -28,6 +28,8 @@ class ARFormWidget < FormWidget
28
28
  # switch off fields on new records
29
29
  # * :diabled_on_update => ['field_name_1', 'field_name_2', ..]
30
30
  # switch off fields on existing records
31
+ # * :save_attribs => ['attr1', 'attr2']
32
+ # allow to call a setter even if it's not a db column
31
33
  # * .. options from FormWidgets ...
32
34
  def output(form_name, model, options = {})
33
35
  @model = model
@@ -35,8 +37,16 @@ class ARFormWidget < FormWidget
35
37
  @disabled_on_create = options[:disabled_on_create] || []
36
38
  @hidden_on_update = options[:hidden_on_update] || []
37
39
  @hidden_on_create = options[:hidden_on_create] || []
40
+ @save_attribs = options[:save_attribs] || []
41
+
42
+
43
+ setup if self.respond_to?(:setup) # you might want to use a method for setup
44
+
38
45
 
39
46
  options[:default_values] = model.attributes.merge(options[:default_values] || {})
47
+ @save_attribs.each do |sa|
48
+ options[:default_values][sa] = model.send(sa.intern) if model.respond_to?(sa.intern)
49
+ end
40
50
 
41
51
  super(form_name, options)
42
52
  end
@@ -73,8 +83,15 @@ class ARFormWidget < FormWidget
73
83
  @password_fields.each do |pwf|
74
84
  p.delete(pwf) if p[pwf].chomp.empty?
75
85
  end
76
-
77
- @model.attributes = p
86
+
87
+ # remove possible additional data that model doesn't support to
88
+ # p.delete_if { |k,v| !@mode.respond_to?("#{k}=") }
89
+
90
+ column_names = @model.class.columns.map { |c| c.name }
91
+ p.each do |k,v|
92
+ @model.send("#{k}=".intern, v) if (column_names.include?(k) && @model.respond_to?("#{k}=")) || @save_attribs.include?(k)
93
+ end
94
+ # @model.attributes = p
78
95
 
79
96
  return true if @model.valid?
80
97
 
@@ -71,7 +71,6 @@
71
71
  # end
72
72
  # end
73
73
  #
74
-
75
74
  class FormWidget < Cuca::Widget
76
75
 
77
76
  # Returns true if this form was posted
@@ -89,7 +88,7 @@ class FormWidget < Cuca::Widget
89
88
  # it will get the variables from the options[:default_values]
90
89
  def get_form_variables
91
90
  var = @options[:default_values] || {}
92
- params.each_pair { |k,v| var[k] = v } if posted?
91
+ params.each_pair { |k,v| var[k] = v } if posted? # request_method == 'POST'
93
92
  @variables = {}
94
93
  var.each_pair { |k,v| @variables[k.to_s] = v } # this allows is to pass symbols to default_values
95
94
  @variables
@@ -167,5 +166,9 @@ class FormWidget < Cuca::Widget
167
166
  form
168
167
  end
169
168
  end
169
+
170
+ def self.run(*args)
171
+ w = self.class.new
172
+ w.output(args)
173
+ end
170
174
  end
171
-
@@ -33,7 +33,6 @@ module FormElements
33
33
  fe_text(name, attribs)
34
34
  end
35
35
 
36
-
37
36
  def fe_textarea(name, attribs = {})
38
37
  a = { :rows => 5, :cols => 50 }.merge(attribs)
39
38
  "<textarea name='#{name}' #{a2p(a)}>#{get_value(name, attribs)}</textarea>\n"
@@ -41,7 +40,8 @@ module FormElements
41
40
 
42
41
  # build a form start tag
43
42
  def fe_formstart(attribs = {})
44
- a = {:name=>@form_name, :method=>'post', :action=>@post_to }.merge(attribs)
43
+ form_name = attribs[:form_name] || @form_name
44
+ a = {:name=>form_name, :method=>'post', :action=>@post_to }.merge(attribs)
45
45
  "<form #{a2p(a)}>\n"
46
46
  end
47
47
 
@@ -51,21 +51,46 @@ module FormElements
51
51
  attribs.delete(:showvalue)
52
52
  "<input type='password' name='#{name}' value='#{v}' #{a2p(attribs)}>\n"
53
53
  end
54
-
54
+
55
+ # :default_value is 'on' or 'off' for checkboxes
56
+ def fe_checkbox(name, attribs = {})
57
+ checker_name = "#{name}_checker"
58
+ newval = !!(request_parameters[name] || query_parameters[name])
59
+ posted = (request_parameters[checker_name] || query_parameters[checker_name])
60
+
61
+ # checkbox html element isn't sent to cgi if not selected
62
+ # so cuca's page variables won't work - this fixes it for most cases.
63
+ if (session.page && (posted && !newval)) then
64
+ session.page[name] = 'off'
65
+ end
66
+
67
+ checked = v[name] && (v[name].to_s != 'off')
68
+ checked = attribs[:default_value] == 'on' if (!posted and !v[name])
69
+
70
+ checkedval = checked ? ' CHECKED' : ''
71
+ "<input type='checkbox' name='#{name}' #{a2p(attribs)}#{checkedval}>"+
72
+ "<input type='hidden' name='#{checker_name}' value='1'>\n" # Needed to detect if element was submitted
73
+ end
74
+
75
+
55
76
 
56
77
  # this is to build a select box, example:
57
78
  # fe_select('gender', [['f', 'female'],['m','Male']]) or
79
+ # ...with options parameters...:
80
+ # fe_select('gender', [['f', 'female', { :id => 'f'} ],['m','Male', {:id=>'m'}]])
81
+ # ...simple...:
58
82
  # fe_select('gender', ['f','m'])
59
83
  def fe_select(name, options, attribs = {})
60
84
  r = "<select name='#{name}' #{a2p(attribs)}>\n"
61
85
  options.each do |o|
62
86
  ov = o.instance_of?(Array) ? o[0] : o
87
+ params = o[2].inject('') { |m,(k,v)| m << " #{k}='#{v}'" } rescue ''
63
88
  sel = ''
64
- sel = ' selected' if v[name] == ov
89
+ sel = ' selected' if get_value(name, attribs).to_s == ov.to_s
65
90
  if o.instance_of?(Array) then
66
- r+="<option value='#{o[0]}'#{sel}>#{o[1]}</option>\n"
91
+ r+="<option value='#{o[0]}'#{sel}#{params}>#{o[1]}</option>\n"
67
92
  else
68
- r+="<option value='#{o}'#{sel}>#{o}</option>\n"
93
+ r+="<option value='#{o}'#{sel}#{params}>#{o}</option>\n"
69
94
  end
70
95
  end
71
96
  r+="</select>\n"
@@ -86,8 +111,8 @@ end
86
111
 
87
112
 
88
113
  r << "\n<select name='#{name}' #{a2p(attribs)}>\n"
89
- r << "<option #{"selected" if v[name] == trueval} value='#{trueval}'>#{truename}</option>\n"
90
- r << "<option #{"selected" if v[name] == falseval} value='#{falseval}'>#{falsename}</option>\n"
114
+ r << "<option #{"selected" if get_value(name, attribs) == trueval} value='#{trueval}'>#{truename}</option>\n"
115
+ r << "<option #{"selected" if get_value(name, attribs) == falseval} value='#{falseval}'>#{falsename}</option>\n"
91
116
  r << "</select>\n"
92
117
  end
93
118
 
@@ -0,0 +1,157 @@
1
+ # mixin this module to build form elements
2
+
3
+ module Cuca
4
+ module FormElements
5
+
6
+ private
7
+ def a2p(attribs)
8
+ a = attribs.dup
9
+ a.delete(:default_value) # reserved attribute
10
+ a.inject([]) { |m,e| m << ((e[1].to_s != '') ? "#{e[0].to_s}='#{e[1].to_s}'" : e[0].to_s) }.join(' ')
11
+ end
12
+
13
+ private
14
+ def twodig(n)
15
+ n.to_s.length == 1 ? "0"+n.to_s : n.to_s
16
+ end
17
+
18
+ # this allows to pass a :default_value to the set of attribs
19
+ private
20
+ def get_value(name, attribs)
21
+ v[name] || (attribs[:default_value] || '')
22
+ end
23
+
24
+ def fe_text(name, attribs = {})
25
+ "<input type='text' name='#{name}' value='#{get_value(name, attribs)}' #{a2p(attribs)}>\n"
26
+ end
27
+
28
+ def fe_hidden(name, attribs = {})
29
+ "<input type='hidden' name='#{name}' value='#{get_value(name, attribs)}' #{a2p(attribs)}>\n"
30
+ end
31
+
32
+ def fe_int(name, attribs = {})
33
+ fe_text(name, attribs)
34
+ end
35
+
36
+ def fe_textarea(name, attribs = {})
37
+ a = { :rows => 5, :cols => 50 }.merge(attribs)
38
+ "<textarea name='#{name}' #{a2p(a)}>#{get_value(name, attribs)}</textarea>\n"
39
+ end
40
+
41
+ # build a form start tag
42
+ def fe_formstart(attribs = {})
43
+ form_name = attribs[:form_name] || @form_name
44
+ a = {:name=>form_name, :method=>'post', :action=>@post_to }.merge(attribs)
45
+ "<form #{a2p(a)}>\n"
46
+ end
47
+
48
+ # fe password doesn't show passwords content but default (unless :showvalue defined in attribs)
49
+ def fe_password(name, attribs = {})
50
+ v = attribs[:showvalue] ? get_value(name, attribs) : ''
51
+ attribs.delete(:showvalue)
52
+ "<input type='password' name='#{name}' value='#{v}' #{a2p(attribs)}>\n"
53
+ end
54
+
55
+ def fe_checkbox(name, attribs = {})
56
+ checker_name = "#{name}_checker"
57
+ newval = !!(request_parameters[name] || query_parameters[name])
58
+ posted = (request_parameters[checker_name] || query_parameters[checker_name])
59
+
60
+ # checkbox html element isn't sent to cgi if not selected
61
+ # so cuca's page variables won't work - this fixes it.
62
+ app.logger.info "TEST #{name} #{posted.inspect} #{newval.inspect}"
63
+
64
+ if (session.page && (posted && !newval)) then
65
+ app.logger.info "Setting OFF #{name} #{posted.inspect} #{newval.inspect}"
66
+ session.page[name] = 'off'
67
+ end
68
+
69
+ app.logger.info "Checkbox #{name} - #{get_value(name, attribs)}"
70
+ checked = v[name] != 'off'
71
+
72
+ checkedval = checked ? ' CHECKED' : ''
73
+ "<input type='checkbox' name='#{name}' #{a2p(attribs)}#{checkedval}>\n"
74
+ end
75
+
76
+
77
+
78
+ # this is to build a select box, example:
79
+ # fe_select('gender', [['f', 'female'],['m','Male']]) or
80
+ # fe_select('gender', ['f','m'])
81
+ def fe_select(name, options, attribs = {})
82
+ r = "<select name='#{name}' #{a2p(attribs)}>\n"
83
+ options.each do |o|
84
+ ov = o.instance_of?(Array) ? o[0] : o
85
+ sel = ''
86
+ sel = ' selected' if get_value(name, attribs).to_s == ov.to_s
87
+ if o.instance_of?(Array) then
88
+ r+="<option value='#{o[0]}'#{sel}>#{o[1]}</option>\n"
89
+ else
90
+ r+="<option value='#{o}'#{sel}>#{o}</option>\n"
91
+ end
92
+ end
93
+ r+="</select>\n"
94
+ end
95
+
96
+ def fe_bool(name,attribs = {})
97
+ r = ''
98
+ attribs = attribs.dup
99
+ truename = attribs[:true] || 'true'
100
+ falsename = attribs[:false] || 'false'
101
+ trueval = attribs.has_key?(:trueval) ? attribs[:trueval] : 't'
102
+ falseval = attribs.has_key?(:falseval) ? attribs[:falseval] : 'f'
103
+ $stderr.puts "#{name}: #{v[name].inspect} #{trueval.inspect} #{falseval.inspect}"
104
+ attribs.delete(:true)
105
+ attribs.delete(:false)
106
+ attribs.delete(:trueval)
107
+ attribs.delete(:falseval)
108
+
109
+
110
+ r << "\n<select name='#{name}' #{a2p(attribs)}>\n"
111
+ r << "<option #{"selected" if get_value(name, attribs) == trueval} value='#{trueval}'>#{truename}</option>\n"
112
+ r << "<option #{"selected" if get_value(name, attribs) == falseval} value='#{falseval}'>#{falsename}</option>\n"
113
+ r << "</select>\n"
114
+ end
115
+
116
+
117
+ def fe_submit(attribs = {})
118
+ a = { :value => 'Submit', :name=>@submit_name }.merge(attribs)
119
+ "<input type='submit' #{a2p(a)}>\n"
120
+ end
121
+
122
+
123
+ def fe_formend
124
+ "</form>\n"
125
+ end
126
+
127
+
128
+ def fe_datetime(name, attribs = {})
129
+ require 'date'
130
+ begin
131
+ val = v[name].instance_of?(Time) ? v[name] : DateTime.parse(v[name] || 'now')
132
+ rescue ArgumentError
133
+ val = DateTime.now
134
+ end
135
+ value = "#{val.year}/#{twodig(val.month)}/#{twodig(val.day)} #{val.hour}:#{val.min}"
136
+ "<input type='text' name='#{name}' value='#{value}' #{a2p(attribs)}>\n"
137
+ end
138
+
139
+ def fe_date(name, attribs = {})
140
+ require 'date'
141
+ if v[name].instance_of?(Date) then
142
+ value = "#{v[name].year}/#{twodig(v[name].month)}/#{twodig(v[name].day)}"
143
+ end
144
+ if v[name].nil? || (v[name].instance_of?(String) && v[name].empty?) then
145
+ value = ''
146
+ end
147
+ if value.nil? then
148
+ val = Date.today
149
+ value = "#{val.year}/#{twodig(val.month)}/#{twodig(val.day)}"
150
+ end
151
+
152
+ "<input type='text' name='#{name}' value='#{value}' #{a2p(attribs)}> (yyyy/mm/dd)\n"
153
+ end
154
+
155
+
156
+ end
157
+ end