cuca 0.05 → 0.06

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.
@@ -1,35 +1,109 @@
1
1
  # == FormWidget
2
2
  #
3
- # To implement a form inherit this class and overwrite:
3
+ # The FormWidget helps you to build and validate html forms.
4
4
  #
5
- # * form - generate your form (as if it was the output method of a widget)
6
- # - Use @form_name for your html form name
7
- # - Use @submit_name for your submit button
8
- # * validate - to validate a posted form
9
- # - write @form_errors['element_name'] on errors (to handle them with FormErrors Widget)
10
- # * setup - Define initial values for your form
5
+ # Every form has a name and some optional options per instance.
11
6
  #
12
- # A form will call {form_name}_submit(result) on the CONTROLLER if the form was submitted
13
- # and validation passed.
14
- # If you don't want this, overwrite on_submit
7
+ # = Implementation
15
8
  #
16
- # A form will use instance variables for form values
9
+ # To implement your form, derive a class from FormWidget and implement these methods:
10
+ #
11
+ # * form - To generate your form
12
+ # - Use @form_name to name your form
13
+ # - Use @submit_name to name your submit button
14
+ # * validate(variables) - OPTIONAL
15
+ # - Validate the result of the form (variables == hash with values)
16
+ # - Write @form_errors['element_name'] = 'Error message' on all errors
17
+ # * setup - OPTIONAL (setup additional stuff after object initialization)
18
+ # * before_validate(variables)
19
+ # - rewrite fields or compose real fields out of virtual fields
20
+ # * on_submit - OPTIONAL
21
+ # - Do stuff once submitted
22
+ # - By default this method will call formname_submit on the controller
23
+ #
24
+ # = Usage
25
+ #
26
+ # On a controller, call with
27
+ #
28
+ # MyForm('form_name', :option=>:value ..)
29
+ #
30
+ #
31
+ # = Example
32
+ #
33
+ # class EmailFormWidget < ARFormWidget
34
+ # include Cuca::FormElements
35
+ #
36
+ # # euser and edomain will complse email_address
37
+ # def before_validate(variables)
38
+ # variables['email_address'] = variables['euser'] + '@' + variables['edomain']
39
+ # variables.delete('euser')
40
+ # variables.delete('edomain')
41
+ # variables
42
+ # end
43
+ #
44
+ # def validate(var)
45
+ # @form_errors['euser'] = 'Invalid Format' \
46
+ # unless var['email_address'] =~ /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,6}$/
47
+ # end
48
+ #
49
+ # # create a custom layout using FormElement module
50
+ # def form
51
+ # mab do
52
+ # FormErrors(@form_errors)
53
+ # fe_formstart
54
+ # text "Email Address" ; fe_text('euser'); text '@' ; fe_text('edomain')
55
+ # fe_submit
56
+ # fe_formend
57
+ # end
58
+ # end
59
+ # end
60
+ #
61
+ # On the controller
62
+ #
63
+ # class EmailAddController < ApplicationController
64
+ #
65
+ # def email_add_submit(var)
66
+ # mab { text "You entered #{var.inspect}" }
67
+ # end
68
+ #
69
+ # def run
70
+ # mab { EmailForm('email_add', :default_values => { :euser => 'your-name', :edomain => 'domain.com' } }
71
+ # end
72
+ # end
73
+ #
74
+
17
75
  class FormWidget < Cuca::Widget
18
76
 
77
+ # Returns true if this form was posted
19
78
  def posted?
20
79
  return (request_method == 'POST' && !params[@submit_name].nil?)
21
80
  end
81
+
82
+ # Returns true if a form with that name was posted
83
+ def self.posted?(form_name)
84
+ w = Cuca::Widget.new
85
+ return (w.request_method == 'POST' && !w.params['submit_' + form_name].nil?)
86
+ end
87
+
88
+ # get form params and return them as hash. If form hasn't been posted yet
89
+ # it will get the variables from the options[:default_values]
90
+ def get_form_variables
91
+ var = @options[:default_values] || {}
92
+ params.each_pair { |k,v| var[k] = v } if posted?
93
+ @variables = {}
94
+ var.each_pair { |k,v| @variables[k.to_s] = v } # this allows is to pass symbols to default_values
95
+ @variables
96
+ end
22
97
 
23
- # get from params and set instance variables
24
- def load_variables
25
- params.each_pair { |k,v| instance_variable_set('@'+k, v) }
98
+ # returns the default value for a variable.
99
+ def get_default_variable(var)
100
+ @options[:default_values].each_pair { |k,v| return v if k.to_s == var.to_s }
101
+ nil
26
102
  end
27
103
 
28
- # get from params and return them as hash
29
- def get_variables
30
- r = {}
31
- params.each_pair { |k,v| r[k] = v }
32
- return r
104
+ # accessor to current values of a variable by name. This is for posted an un-posted forms.
105
+ def v
106
+ @variables
33
107
  end
34
108
 
35
109
  # Overwrite this method to setup initial values
@@ -37,49 +111,54 @@ class FormWidget < Cuca::Widget
37
111
  def setup
38
112
  end
39
113
 
40
-
41
- # Create your form by overwriting this demo
114
+ # Create your form by overwriting this
42
115
  # Name your submit button @submit_name, so the form can detect if it
43
- # is submitted or not.
116
+ # is submitted or not. You can use FormElements module to get access to some
117
+ # helper functions to build really fast forms.
44
118
  def form
45
119
  end
46
120
 
47
- # Overwrite this method with your validation code
48
- # @form_errors hash with error messages
49
- def validate
121
+ # this will get called with the request_parameters as arguments for you to filter.
122
+ # You can use this to create new fields and to get rid of virtual ones.
123
+ def before_validate(variables)
124
+ variables
125
+ end
126
+
127
+
128
+
129
+ # Overwrite this method with your validation code.
130
+ # Fill up @form_errors hash with error messages.
131
+ def validate(variables)
50
132
  end
51
133
 
52
134
  # If form is validated we call on_submit. Default behaviour is to call
53
- # {form_name}_submit(result) on the CONTROLLER.
135
+ # {form_name}_submit(raw_result, [rewritten_result]) on the CONTROLLER.
54
136
  def on_submit
55
- controller.send(@form_name+'_submit', get_variables) unless controller.nil?
56
- clear
57
- form
137
+ met = @form_name+"_submit"
138
+ if controller.method(met.intern).arity == 2 then
139
+ controller.send(met, @variables, @before_validate_variables) unless controller.nil?
140
+ else
141
+ controller.send(met, @variables) unless controller.nil?
142
+ end
58
143
  end
59
144
 
60
145
  # options can be used for form specific stuff
61
146
  # we only use :post_to to set @post_to atm
62
147
  def output(form_name, options = {})
148
+ @options = options
149
+ @post_to = @options[:post_to] || cgi.path_info
63
150
  @form_name = form_name
64
-
65
151
  @submit_name = 'submit_'+@form_name
66
152
  @form_errors = {}
67
153
 
68
- @options = options
69
-
70
154
  setup
71
-
72
- @post_to = @options[:post_to] || cgi.path_info
73
-
74
-
75
- # form
155
+
156
+ get_form_variables
76
157
 
77
158
  if posted? then
78
- load_variables
79
- validate
159
+ @before_validate_variables = before_validate(request_parameters.dup)
160
+ validate(@before_validate_variables)
80
161
  if @form_errors.empty? then
81
- # What was that thought about?
82
- # clear # submitted forms should not have any content that might have been generated
83
162
  return on_submit
84
163
  else
85
164
  form
@@ -90,52 +169,3 @@ class FormWidget < Cuca::Widget
90
169
  end
91
170
  end
92
171
 
93
-
94
-
95
- require 'cuca/generator/markaby'
96
-
97
- class TestFormWidget < FormWidget
98
-
99
- include Cuca::Generator::Markaby
100
-
101
- def setup
102
- @demo_input = "enter something"
103
- end
104
-
105
- def make_tips
106
- @form_errors.each_pair do |k,v|
107
- hints[:tips] ||= []
108
- hints[:tips] << { :id => k, :title => "Error in form", :text => v}
109
- end
110
- end
111
-
112
-
113
- def validate
114
- # validate form return true/false
115
- if @demo_input != 'demo' then
116
- @form_errors['demo_input'] = "I said please enter 'demo' but you entered #{@demo_input}"
117
- end
118
- make_tips
119
- true
120
- end
121
-
122
- def form
123
- mab {
124
-
125
- FormErrors(@form_errors)
126
-
127
- form( :action=>@post_to, :method=>'post') { # :name=>@form_name,
128
- text "This is a demo form, overwrite 'form' method. Enter 'demo'"
129
- br
130
- E('demo_input', true) { input(:type => 'text', :value => @demo_input, :name=>'demo_input'); text @error_text }
131
- br
132
- br
133
- input(:type => 'submit', :value => 'save', :name => @submit_name)
134
- }
135
- }
136
- end
137
-
138
- end
139
-
140
-
141
- # end
@@ -0,0 +1,135 @@
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
+
37
+ def fe_textarea(name, attribs = {})
38
+ a = { :rows => 5, :cols => 50 }.merge(attribs)
39
+ "<textarea name='#{name}' #{a2p(a)}>#{get_value(name, attribs)}</textarea>\n"
40
+ end
41
+
42
+ # build a form start tag
43
+ def fe_formstart(attribs = {})
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
+
56
+ # this is to build a select box, example:
57
+ # fe_select('gender', [['f', 'female'],['m','Male']]) or
58
+ # fe_select('gender', ['f','m'])
59
+ def fe_select(name, options, attribs = {})
60
+ r = "<select name='#{name}' #{a2p(attribs)}>\n"
61
+ options.each do |o|
62
+ ov = o.instance_of?(Array) ? o[0] : o
63
+ sel = ''
64
+ sel = ' selected' if v[name] == ov
65
+ if o.instance_of?(Array) then
66
+ r+="<option value='#{o[0]}'#{sel}>#{o[1]}</option>\n"
67
+ else
68
+ r+="<option value='#{o}'#{sel}>#{o}</option>\n"
69
+ end
70
+ end
71
+ r+="</select>\n"
72
+ end
73
+
74
+ def fe_bool(name,attribs = {})
75
+ r = ''
76
+ attribs = attribs.dup
77
+ truename = attribs[:true] || 'true'
78
+ falsename = attribs[:false] || 'false'
79
+ trueval = attribs.has_key?(:trueval) ? attribs[:trueval] : 't'
80
+ falseval = attribs.has_key?(:falseval) ? attribs[:falseval] : 'f'
81
+ $stderr.puts "#{name}: #{v[name].inspect} #{trueval.inspect} #{falseval.inspect}"
82
+ attribs.delete(:true)
83
+ attribs.delete(:false)
84
+ attribs.delete(:trueval)
85
+ attribs.delete(:falseval)
86
+
87
+
88
+ 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"
91
+ r << "</select>\n"
92
+ end
93
+
94
+
95
+ def fe_submit(attribs = {})
96
+ a = { :value => 'Submit', :name=>@submit_name }.merge(attribs)
97
+ "<input type='submit' #{a2p(a)}>\n"
98
+ end
99
+
100
+
101
+ def fe_formend
102
+ "</form>\n"
103
+ end
104
+
105
+
106
+ def fe_datetime(name, attribs = {})
107
+ require 'date'
108
+ begin
109
+ val = v[name].instance_of?(Time) ? v[name] : DateTime.parse(v[name] || 'now')
110
+ rescue ArgumentError
111
+ val = DateTime.now
112
+ end
113
+ value = "#{val.year}/#{twodig(val.month)}/#{twodig(val.day)} #{val.hour}:#{val.min}"
114
+ "<input type='text' name='#{name}' value='#{value}' #{a2p(attribs)}>\n"
115
+ end
116
+
117
+ def fe_date(name, attribs = {})
118
+ require 'date'
119
+ if v[name].instance_of?(Date) then
120
+ value = "#{v[name].year}/#{twodig(v[name].month)}/#{twodig(v[name].day)}"
121
+ end
122
+ if v[name].nil? || (v[name].instance_of?(String) && v[name].empty?) then
123
+ value = ''
124
+ end
125
+ if value.nil? then
126
+ val = Date.today
127
+ value = "#{val.year}/#{twodig(val.month)}/#{twodig(val.day)}"
128
+ end
129
+
130
+ "<input type='text' name='#{name}' value='#{value}' #{a2p(attribs)}> (yyyy/mm/dd)\n"
131
+ end
132
+
133
+
134
+ end
135
+ end
@@ -19,12 +19,12 @@ require 'active_record'
19
19
  class DBListWidget < BaseList
20
20
  def columns
21
21
  # $stderr.puts " Getting Columns: #{@columns}"
22
- @columns
22
+ @columns.delete_if { |c| !c[:display] }
23
23
  end
24
24
 
25
25
  # returns :query field by :id (only :id is defined in the QueryDef)
26
26
  def wc_query_field(field_id)
27
- @columns.each do |c|
27
+ @columns.each do |c|
28
28
  if c[:id] == field_id then
29
29
  return c[:query]
30
30
  end
@@ -65,6 +65,7 @@ class DBListWidget < BaseList
65
65
  end
66
66
  res
67
67
  end
68
+
68
69
 
69
70
  def query(query_def)
70
71
  findstuff = {:conditions => where_clause(query_def) }
@@ -72,7 +73,7 @@ class DBListWidget < BaseList
72
73
  findstuff[:offset] = query_def.range.first
73
74
  findstuff[:limit] = query_def.range.last-query_def.range.first+1
74
75
  findstuff[:joins] = @joins || nil
75
- sel = @columns.collect do |c|
76
+ sel = @query_columns.collect do |c|
76
77
  ret = c.has_key?(:query) ? "#{c[:query]} as #{c[:id]}" : c[:id]
77
78
  ret = nil if c[:query] == false
78
79
  ret
@@ -80,8 +81,10 @@ class DBListWidget < BaseList
80
81
  findstuff[:select] = sel.compact.join(',')
81
82
  $stderr.puts "Find-Stuff: #{findstuff.inspect}"
82
83
  @data = @model_class.find(:all, findstuff)
83
- @data = normalize_result(@data)
84
+ $stderr.puts "data: #{@data}"
85
+ @additional_data = @data.dup
84
86
 
87
+ @data = normalize_result(@data)
85
88
  @total_rows= @model_class.count(:conditions => where_clause(query_def), :joins => @joins)
86
89
 
87
90
  # $stderr.puts "Query: #{@data.inspect} - #{query_def.order_by.inspect}"
@@ -116,8 +119,10 @@ class DBListWidget < BaseList
116
119
  setup
117
120
  fixup_columns
118
121
  # $stderr.puts @columns.inspect
119
- @columns.freeze
122
+ # @columns.freeze
120
123
  @extra_conditions.freeze
124
+ @query_columns = @columns.dup
125
+ @columns = @columns.delete_if { |c| !c[:display] } # don't display columns without a 'display'
121
126
  super(list_name)
122
127
  end
123
128
  end
@@ -29,10 +29,14 @@ class BaseList < Cuca::Widget
29
29
  res
30
30
  end
31
31
 
32
- def rewrite_field(row, field_id, content)
32
+ def rewrite_field(row, field_id, content, additional_content = nil)
33
33
  # $stderr.puts "REwrite field(#{row.inspect}, #{field_id.inspect}, #{content.inspect} - hooks #{@rewrite_hooks.inspect}"
34
34
  return content unless @rewrite_hooks[field_id]
35
- return @rewrite_hooks[field_id].call(row2hash(row), content)
35
+ if @rewrite_hooks[field_id].arity == 2 then
36
+ return @rewrite_hooks[field_id].call(row2hash(row), content)
37
+ else
38
+ return @rewrite_hooks[field_id].call(row2hash(row), content, additional_content)
39
+ end
36
40
  end
37
41
 
38
42
 
@@ -52,12 +56,11 @@ class BaseList < Cuca::Widget
52
56
  # @data = [['a','b', 12], ...]
53
57
  # @total_rows = 123
54
58
  def query(query_definition)
55
- return
59
+ return
56
60
  end
57
61
 
58
- # TODO: Load from Session?
59
62
  def load_query_definition
60
- qd = QueryDef.new(@list_name)
63
+ qd = QueryDef.new(@list_name, (@query_defaults || {}))
61
64
  qd.from_params(params)
62
65
  if request_method == 'POST' then
63
66
  qd.range = nil # reset pagination if someone submitted a filter
@@ -105,43 +108,39 @@ class BaseList < Cuca::Widget
105
108
  @rows_per_page = (@query_def.range.last - @query_def.range.first) + 1
106
109
  @total_pages = (@total_rows / @rows_per_page).to_i
107
110
  @total_pages = @total_pages + 1 unless ((@total_rows % @rows_per_page) == 0)
108
-
109
- pl = '' ; i = 0
110
- (0..(@total_pages-1)).each do |p|
111
- range = i..(i+@rows_per_page-1)
112
- if range.include?(@query_def.range.first+1) then
113
- pl<< "#{(p+1).to_s} "
114
- else
115
- pl << "#{SLinkWidget.new(:args=>['',(p+1).to_s,@query_def.to_h('range', range)]).to_s} "
116
- end
117
- i+=@rows_per_page
118
- end
111
+
112
+ pl = ''
113
+ (0..(@total_pages-1)).map do |e|
114
+ { :idx =>e,
115
+ :range=>((e*@rows_per_page)..(e*@rows_per_page+@rows_per_page-1)) }
116
+ end.map do |e|
117
+ rt = @rows_per_page * 2
118
+ r = false
119
+ r = true if e[:idx] == 0
120
+ r = true if e[:idx] == (@total_pages - 1)
121
+ r = true if ((e[:range].min - rt)..(e[:range].max + rt)).include?(@query_def.range.first+1)
122
+ e[:display] = r
123
+ e[:hit] = e[:range].include?(@query_def.range.first+1)
124
+ e
125
+ end.each do |p|
126
+ if p[:display] && !p[:hit]then
127
+ pl << "#{SLinkWidget.new(:args=>['',(p[:idx]+1).to_s,@query_def.to_h('range', p[:range])]).to_s} "
128
+ elsif p[:hit] then
129
+ pl << "<b>#{p[:idx]+1}</b> "
130
+ else
131
+ pl << '... ' unless pl[-4..-1] == '... '
132
+ end
133
+ end
119
134
 
120
135
  return pl
121
136
 
122
- #### The above should be a bit faster that this mab version:
123
- # return mabtext do
124
- # i = 0
125
- # (0..(@total_pages-1)).each do |p|
126
- # range = i..(i+@rows_per_page-1)
127
- # $stderr.puts "#{range.inspect} include? #{@query_def.range.first+1}"
128
- # if range.include?(@query_def.range.first+1) then
129
- # text((p+1).to_s + " ")
130
- # else
131
- # Link('', @query_def.to_h('range', range)) { ((p+1).to_s) }
132
- # SLink('',(p+1).to_s, @query_def.to_h('range', range))
133
- # text " "
134
- # end
135
- # i+=@rows_per_page
136
- # end
137
- # end
138
137
  end
139
138
 
140
139
  private
141
140
  def erb_template
142
141
  <<'ENDTEMPLATE'
143
142
  <div class='list'>
144
- <%= @paginate %>
143
+ [ <%= @paginate %> ]
145
144
  <table width>
146
145
  <tr>
147
146
 
@@ -181,10 +180,12 @@ end
181
180
  </tr>
182
181
  </form>
183
182
 
184
- <% @data.each do |row| %>
183
+ <% @additional_data ||= [] %>
184
+ <% @data.each_index do |row_idx| %>
185
185
  <tr>
186
- <% row.each_index do |field_idx| %>
187
- <td> <%= rewrite_field(row, @columns[field_idx][:id], row[field_idx]) %> </td>
186
+ <% @data[row_idx].each_index do |field_idx| %>
187
+ <% add_data = @additional_data[row_idx] || nil %>
188
+ <td> <%= rewrite_field(@data[row_idx], @columns[field_idx][:id], @data[row_idx][field_idx], add_data) %> </td>
188
189
  <% end %>
189
190
  </tr>
190
191
  <% end %>
@@ -195,35 +196,3 @@ ENDTEMPLATE
195
196
 
196
197
  end
197
198
 
198
-
199
- # This was the old markaby template for the listwidget. I find it less readable so will not
200
- # continue working on it.
201
- #
202
- #
203
- # mab {
204
- # div.list do
205
- # text @paginate
206
- # table do
207
- # tr do
208
- # @columns.each { |c| td { @query_def.order_by == c[:id] ? text(c[:display]) : Link('', @query_def.to_h('order_by',
209
- # end
210
- # form(:name=>"#{@list_name}_form", :method=>'POST') do
211
- # tr do
212
- # @columns.each { |c|
213
- # td { @ftag = "#{@list_name}_filter_#{c[:id]}"
214
- # input(:type => 'text', :name => @ftag, :value => @query_def.filters[c[:id]])
215
- # input(:type=>'submit',:value=>'ok') if (@columns.last[:id] == c[:id])
216
- # }
217
- # }
218
- # end
219
- # end
220
- # @data.each do |row|
221
- # tr do
222
- # row.each_index do |field_idx|
223
- # td { text rewrite_field(row, @columns[field_idx][:id], row[field_idx]) }
224
- # end
225
- # end
226
- # end # data.each
227
- # end # table.do
228
- # end # div.list
229
- # } #mab
@@ -63,7 +63,7 @@ class QueryDef
63
63
 
64
64
  #getter
65
65
  if (ATTRIBS.include?(met)) then
66
- return @data[met] || ATTRIBS_DEFAULTS[met].dup
66
+ return @data[met] || @attribs_defaults[met].dup
67
67
  end
68
68
 
69
69
  #setter
@@ -94,7 +94,7 @@ class QueryDef
94
94
  return ATTRIBS_DECODE[name].call(value)
95
95
  rescue
96
96
  # $stderr.puts "Decoding failed: #{name} - #{value}: #{$!}"
97
- return ATTRIBS_DEFAULTS[name].dup
97
+ return @attribs_defaults[name].dup
98
98
  end
99
99
  else
100
100
  return value
@@ -140,7 +140,7 @@ class QueryDef
140
140
  if v then
141
141
  @data[a] = ev_dec(a,v)
142
142
  else
143
- @data[a] = ATTRIBS_DEFAULTS[a].dup
143
+ @data[a] = @attribs_defaults[a].dup
144
144
  end
145
145
  # $stderr.puts "\n*** ENDFROM_PARAMS: #{@data.inspect}"
146
146
  end
@@ -162,7 +162,8 @@ class QueryDef
162
162
  return self
163
163
  end
164
164
 
165
- def initialize(list_name)
165
+ def initialize(list_name, default_attribs = {})
166
+ @attribs_defaults = ATTRIBS_DEFAULTS.merge(default_attribs)
166
167
  @list_name = list_name
167
168
  @data = {}
168
169
  end