cuca 0.05 → 0.06

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