manatee 0.0.1.pre1

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 (58) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +23 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.mdown +55 -0
  5. data/Rakefile +7 -0
  6. data/app/assets/javascripts/manatee.js +1 -0
  7. data/app/assets/javascripts/manatee/helpers.js +4 -0
  8. data/app/assets/javascripts/manatee/helpers/asset_tag.jsh.coffee +138 -0
  9. data/app/assets/javascripts/manatee/helpers/asset_url.jsh.coffee +90 -0
  10. data/app/assets/javascripts/manatee/helpers/csrf.jsh.coffee +5 -0
  11. data/app/assets/javascripts/manatee/helpers/date.jsh.coffee +54 -0
  12. data/app/assets/javascripts/manatee/helpers/debug.jsh.coffee +2 -0
  13. data/app/assets/javascripts/manatee/helpers/form.jsh.coffee +25 -0
  14. data/app/assets/javascripts/manatee/helpers/form_builder.jsh.coffee +24 -0
  15. data/app/assets/javascripts/manatee/helpers/form_options.jsh.coffee +267 -0
  16. data/app/assets/javascripts/manatee/helpers/form_tag.jsh.coffee +204 -0
  17. data/app/assets/javascripts/manatee/helpers/javascript.jsh.coffee +15 -0
  18. data/app/assets/javascripts/manatee/helpers/number.jsh.coffee +58 -0
  19. data/app/assets/javascripts/manatee/helpers/sanitize.jsh.coffee +5 -0
  20. data/app/assets/javascripts/manatee/helpers/tag.jsh.coffee +58 -0
  21. data/app/assets/javascripts/manatee/helpers/text.jsh.coffee +12 -0
  22. data/app/assets/javascripts/manatee/helpers/translation.jsh.coffee +7 -0
  23. data/app/assets/javascripts/manatee/helpers/url.jsh.coffee +36 -0
  24. data/app/assets/javascripts/manatee/helpers/util.jsh.coffee +41 -0
  25. data/app/assets/javascripts/manatee/rails_helpers.js +2 -0
  26. data/app/assets/javascripts/manatee/rails_helpers/routes.js.erb +195 -0
  27. data/app/assets/javascripts/manatee/rails_routes.js +1 -0
  28. data/app/assets/javascripts/manatee/renderer.js.erb +53 -0
  29. data/app/assets/javascripts/manatee_railsless.js +1 -0
  30. data/lib/manatee.rb +87 -0
  31. data/lib/manatee/config.rb +51 -0
  32. data/lib/manatee/handler.rb +45 -0
  33. data/lib/manatee/rails.rb +6 -0
  34. data/lib/manatee/rails/extensions.rb +23 -0
  35. data/lib/manatee/rails/handler.rb +16 -0
  36. data/lib/manatee/rails/hash_visitor.rb +35 -0
  37. data/lib/manatee/rails/helper.rb +9 -0
  38. data/lib/manatee/rails/resolver.rb +70 -0
  39. data/lib/manatee/rails/routes_compiler.rb +34 -0
  40. data/lib/manatee/sprockets.rb +8 -0
  41. data/lib/manatee/sprockets/jsh_processor_2x.rb +32 -0
  42. data/lib/manatee/sprockets/jsh_processor_3x.rb +31 -0
  43. data/lib/manatee/version.rb +3 -0
  44. data/manatee.gemspec +32 -0
  45. data/test/example/renderer.js.erb +8 -0
  46. data/test/example/translations.en.yml +410 -0
  47. data/test/example/views/index.jst.eco +12 -0
  48. data/test/helpers/asset_tag_test.rb +175 -0
  49. data/test/helpers/asset_url_test.rb +349 -0
  50. data/test/helpers/form_options_test.rb +718 -0
  51. data/test/helpers/form_tag_test.rb +387 -0
  52. data/test/helpers/javascript_test.rb +39 -0
  53. data/test/helpers/number_test.rb +111 -0
  54. data/test/helpers/tag_test.rb +71 -0
  55. data/test/support/dom_assertion.rb +49 -0
  56. data/test/support/view_test.rb +91 -0
  57. data/test/test_helper.rb +17 -0
  58. metadata +213 -0
@@ -0,0 +1,24 @@
1
+
2
+ # _to_partial_path
3
+ # button
4
+ # check_box
5
+ # collection_check_boxes
6
+ # collection_radio_buttons
7
+ # collection_select
8
+ # date_select
9
+ # datetime_select
10
+ # emitted_hidden_id?
11
+ # fields_for
12
+ # file_field
13
+ # grouped_collection_select
14
+ # hidden_field
15
+ # label
16
+ # multipart=
17
+ # new
18
+ # radio_button
19
+ # select
20
+ # submit
21
+ # time_select
22
+ # time_zone_select
23
+ # to_model
24
+ # to_partial_path
@@ -0,0 +1,267 @@
1
+ optionsNullSelector = (value) ->
2
+ false
3
+
4
+ optionsStringSelectorBuilder = (_reference) ->
5
+ reference = _reference
6
+ (value) ->
7
+ value.toString() && reference == value.toString()
8
+
9
+ optionsArraySelectorBuilder = (_reference)->
10
+ reference = []
11
+ for index, ref_value of _reference
12
+ reference.push ref_value.toString()
13
+ (value) ->
14
+ value && reference.indexOf(value.toString()) > -1
15
+
16
+ disabledAndSelector = (options, selected_selector, disabled_selector) ->
17
+ options['selected'] = true if selected_selector(options['value'])
18
+ options['disabled'] = true if disabled_selector(options['value'])
19
+
20
+ stringOption = (value, selected_selector, disabled_selector) ->
21
+ options = { value: value }
22
+ disabledAndSelector options, selected_selector, disabled_selector
23
+ @contentTag 'option', @htmlEscape(value), @htmlEscapeAttributes(options)
24
+
25
+ selectorForOptions = (reference) ->
26
+ reference = reference.toString() if typeof reference == 'number' || reference == true || reference == false
27
+
28
+ return [ reference, optionsNullSelector ] if typeof reference == 'function'
29
+ return [ optionsNullSelector, optionsNullSelector ] if reference == null || reference == undefined
30
+ return [ optionsStringSelectorBuilder(reference), optionsNullSelector ] if typeof reference == 'string'
31
+ return [ optionsArraySelectorBuilder(reference), optionsNullSelector ] if reference instanceof Array
32
+
33
+ [ selectorForOptions( reference['selected'] )[0], selectorForOptions( reference['disabled'] )[0] ]
34
+
35
+ collectionValueForSelector = (_selector, value_method) ->
36
+ selector = _selector
37
+ (value) ->
38
+ selector value[value_method]
39
+
40
+ selectorForOptionsFromCollection = (reference, _value_method) ->
41
+ value_method = _value_method
42
+ reference = reference.toString() if typeof reference == 'number' || reference == true || reference == false
43
+
44
+ return [ reference, optionsNullSelector ] if typeof reference == 'function'
45
+ return [ optionsNullSelector, optionsNullSelector ] if reference == null || reference == undefined
46
+ return [ collectionValueForSelector(optionsStringSelectorBuilder(reference), value_method), optionsNullSelector ] if typeof reference == 'string'
47
+ return [ collectionValueForSelector(optionsArraySelectorBuilder(reference), value_method), optionsNullSelector ] if reference instanceof Array
48
+
49
+ [ selectorForOptionsFromCollection( reference['selected'], _value_method )[0], selectorForOptionsFromCollection( reference['disabled'], _value_method )[0] ]
50
+
51
+ collectionFetcherBlock = (_accessor_method) ->
52
+ accessor_method = _accessor_method
53
+ if typeof accessor_method == 'function'
54
+ accessor_method
55
+ else
56
+ (object) ->
57
+ object[accessor_method]
58
+
59
+ extractNamePrefixAndMethod = ((object, method_or_prefix_and_method) ->
60
+ [ prefix, method ] = if typeof method_or_prefix_and_method == 'string'
61
+ class_name = object['_class'] || object['_type'] || object['class'] || object['type']
62
+ if class_name
63
+ class_name = class_name() if typeof class_name == 'function'
64
+ [ @underscore(class_name), method_or_prefix_and_method ]
65
+ else
66
+ [ null, method_or_prefix_and_method ]
67
+ else
68
+ method_or_prefix_and_method
69
+
70
+ name = if prefix then prefix + '[' + method + ']' else method
71
+ [ name, prefix, method ]).bind(this)
72
+
73
+ multipleHiddenInput = ((name, options) ->
74
+ multiple_hidden_input = if options['multiple']
75
+ name = name + '[]' if name[-2..-1] != '[]'
76
+ if options['include_hidden'] == true || options['include_hidden'] == undefined
77
+ @tag 'input', type: 'hidden', name: name, value: '', disabled: options['disabled']
78
+ else
79
+ ''
80
+ else
81
+ ''
82
+ options['include_hidden'] = undefined
83
+ [ name, multiple_hidden_input ]).bind(this)
84
+
85
+
86
+
87
+ helper 'optionsForSelect', (container, selectors) ->
88
+ return container.toString() if typeof container == 'string' || typeof value == 'number' || value == true || value == false
89
+ return container().toString() if typeof container == 'function'
90
+
91
+ [ selected_selector, disabled_selector ] = selectorForOptions selectors
92
+
93
+ unless container instanceof Array
94
+ array_container = []
95
+ for index, value of container
96
+ array_container.push [ index, value ]
97
+ container = array_container
98
+
99
+ select_options = ''
100
+ for index, value of container
101
+ value = value.toString() if typeof value == 'number' || value == true || value == false
102
+ value = '' if value == null
103
+
104
+ if typeof value == 'string'
105
+ select_options += stringOption.call(this, value, selected_selector, disabled_selector)
106
+ if value instanceof Array
107
+ [ content, value_option, options ] = if value.length == 3
108
+ [ value[0], value[1], @_clone(value[2]) ]
109
+ else if value.length == 2
110
+ if typeof value[1] == 'object'
111
+ [ value[0], value[0], @_clone(value[1]) ]
112
+ else
113
+ [ value[0], value[1], {} ]
114
+ else
115
+ [ value[0], value[0], {} ]
116
+
117
+ content = content.toString() if typeof content == 'number' || content == true || content == false
118
+ content ||= ''
119
+
120
+ value_option = value_option.toString() if typeof value_option == 'number' || value_option == true || value_option == false
121
+ options['value'] ||= value_option || content
122
+
123
+ disabledAndSelector options, selected_selector, disabled_selector
124
+ select_options += @contentTag 'option', @htmlEscape(content), @htmlEscapeAttributes(options)
125
+
126
+ select_options
127
+
128
+ helper 'optionsFromCollectionForSelect', (collection, value_method, text_method, selectors) ->
129
+ [ selected_selector, disabled_selector ] = selectorForOptionsFromCollection selectors, value_method
130
+ selectors = { selected: [], disabled: [] }
131
+
132
+ text_fetcher = collectionFetcherBlock text_method
133
+ value_fetcher = collectionFetcherBlock value_method
134
+
135
+ container = []
136
+ for index, object of collection
137
+ options = if object instanceof Array && object.length == 3
138
+ object[2]
139
+ else
140
+ {}
141
+ value = value_fetcher object
142
+ container.push [ text_fetcher(object), value, options ]
143
+ selectors['selected'].push(value) if selected_selector(object)
144
+ selectors['disabled'].push(value) if disabled_selector(object)
145
+
146
+ @optionsForSelect container, selectors
147
+
148
+ helper 'groupedOptionsForSelect', (grouped_options, selectors, options = {}) ->
149
+ [ selected_selector, disabled_selector ] = selectorForOptions selectors
150
+
151
+ options_for_select = ''
152
+ if options['prompt']
153
+ prompt = if typeof options['prompt'] == 'string' || options['prompt'] == 'number'
154
+ options['prompt'].toString()
155
+ else
156
+ @translate 'helpers.select.prompt', default: 'Please select'
157
+ options_for_select += @contentTag( 'option', @htmlEscape(prompt), { value: '' } )
158
+
159
+ for index, options_in_group of grouped_options
160
+ [ options_in_group, optgroup_options ] = if options['divider']
161
+ if grouped_options instanceof Array
162
+ [ options_in_group, { label: options['divider'] } ]
163
+ else
164
+ if grouped_options instanceof Array
165
+ optgroup_options = options_in_group[2] || {}
166
+ optgroup_options['label'] = options_in_group[0]
167
+ [ options_in_group[1], optgroup_options ]
168
+ else
169
+ [ options_in_group, { label: index } ]
170
+
171
+ options_for_select += @contentTag('optgroup', @optionsForSelect(options_in_group, selectors), @htmlEscapeAttributes(optgroup_options) )
172
+
173
+ options_for_select
174
+
175
+ helper 'optionGroupsFromCollectionForSelect', (collection, group_method, group_label_method, option_key_method, option_value_method, selectors) ->
176
+ group_fetcher = collectionFetcherBlock group_method
177
+ group_label_fetcher = collectionFetcherBlock group_label_method
178
+
179
+ options_for_select = ''
180
+ for index, object of collection
181
+ options_for_select += @contentTag('optgroup', @optionsFromCollectionForSelect(group_fetcher(object), option_key_method, option_value_method, selectors), { label: @htmlEscape( group_label_fetcher object ) } )
182
+
183
+ options_for_select
184
+
185
+ # EXPLAIN: When object is a Ruby object, two things can happen:
186
+ # 1. If it don't responds to method [], class if infered by .class method
187
+ # 2. If it responds to method [], class is infered by object['_class'] || object['_type'] || object['class'] || object['type'] in that order
188
+ #
189
+ # IMPORTANT: Removed 'required' option, it seems too much cryptic to be used
190
+ # TODO: Add index option
191
+ helper 'selectForObject', (object, method_or_prefix_and_method, choices_or_options, options_or_choices) ->
192
+ [ name, prefix, method ] = extractNamePrefixAndMethod object, method_or_prefix_and_method
193
+
194
+ [ choices, options ] = if typeof options_or_choices == 'function'
195
+ [ options_or_choices, @_clone( choices_or_options || {} ) ]
196
+ else
197
+ [ choices_or_options, @_clone( options_or_choices || {} ) ]
198
+
199
+ [ name, multiple_hidden_input ] = multipleHiddenInput name, options
200
+
201
+ # reference = options['selected'] || object[method]
202
+ # options['selected'] = undefined
203
+ # pre_selector = selectorForOptions( reference )[0]
204
+ # selector = (value) ->
205
+ # selected = pre_selector value
206
+ # # Welcome black magic!
207
+ # options['prompt'] = undefined if selected
208
+ # selected
209
+
210
+ selector = if options['selected'] == null
211
+ undefined
212
+ else
213
+ reference = options['selected'] || object[method]
214
+ pre_selector = selectorForOptions( reference )[0]
215
+ (value) ->
216
+ selected = pre_selector value
217
+ # Welcome black magic!
218
+ options['prompt'] = undefined if selected
219
+ selected
220
+ options['selected'] = undefined
221
+
222
+ if typeof choices == 'object'
223
+ unless choices instanceof Array
224
+ array_choices = []
225
+ for index, value of choices
226
+ array_choices.push [ index, value ]
227
+ choices = array_choices
228
+
229
+ if choices[1] instanceof Array
230
+ choices_options = {}
231
+ choices_options['divider'] = options['divider'] unless options['divider'] == undefined
232
+ options['divider'] = undefined
233
+ return multiple_hidden_input + @selectTag(name, @groupedOptionsForSelect(choices, selector, choices_options), options)
234
+
235
+ multiple_hidden_input + @selectTag(name, @optionsForSelect(choices, selector), options)
236
+
237
+ helper 'collectionSelectForObject', (object, method_or_prefix_and_method, collection, value_method, text_method, options)->
238
+ [ name, prefix, method ] = extractNamePrefixAndMethod object, method_or_prefix_and_method
239
+
240
+ options = if options != undefined then @_clone(options) else {}
241
+
242
+ [ name, multiple_hidden_input ] = multipleHiddenInput name, options
243
+
244
+ selector = if options['selected'] == null
245
+ undefined
246
+ else
247
+ reference = options['selected'] || object[method]
248
+ pre_selector = selectorForOptionsFromCollection( reference, value_method )[0]
249
+ (value) ->
250
+ selected = pre_selector value
251
+ # Welcome black magic!
252
+ options['prompt'] = undefined if selected
253
+ selected
254
+ options['selected'] = undefined
255
+
256
+ choices = @optionsFromCollectionForSelect collection, value_method, text_method, selector
257
+ multiple_hidden_input + @selectTag(name, choices, options)
258
+
259
+ # TODO: Finish form_options helpers
260
+ # collection_check_boxes
261
+ # collection_radio_buttons
262
+ # collection_select
263
+ # grouped_collection_select
264
+
265
+ # TODO: Think about implementing or not time zone helpers
266
+ # time_zone_options_for_select
267
+ # time_zone_select
@@ -0,0 +1,204 @@
1
+ sanitizeToId = (name) ->
2
+ name = name.toString()
3
+ name = name[0..-3] if name[-2..-1] == '[]'
4
+ name.replace(/\]/g, '').replace /[^-a-zA-Z0-9:.]/g, '_'
5
+
6
+ humanizeToLabel = (name) ->
7
+ sanitized = name.toString().replace(/\]/g, '').replace /[^-a-zA-Z0-9:.]/g, ' '
8
+ sanitized[0].toUpperCase() + sanitized.slice(1);
9
+
10
+ fieldTagBuilder = (_type) ->
11
+ type = _type
12
+ (name, value = null, options = {}) ->
13
+ html_options = @_clone options
14
+ html_options['type'] = type
15
+ html_options['name'] = name
16
+ html_options['id'] = sanitizeToId(name) if html_options['id'] == undefined
17
+ html_options['value'] ||= value if value
18
+ @tag 'input', html_options
19
+
20
+ for i, type of ['color', 'date', 'datetime', 'email', 'file', 'hidden', 'month', 'number', 'password', 'range', 'search', 'text', 'time', 'url', 'week']
21
+ helper (type+'FieldTag'), fieldTagBuilder.call(this, type)
22
+
23
+ helper 'datetimeLocalFieldTag', fieldTagBuilder.call(this, 'datetime-local')
24
+ helper 'telephoneFieldTag', fieldTagBuilder.call(this, 'tel')
25
+
26
+ alias 'phoneFieldTag', 'telephoneFieldTag'
27
+
28
+ helper 'buttonTag', (content_or_options = null, options_or_content = {}) ->
29
+ [content, options] = @_contentOrOptions content_or_options, options_or_content
30
+
31
+ content = content.call(this) if typeof content == 'function'
32
+ options['name'] ||= 'button'
33
+ options['type'] ||= 'submit'
34
+ @contentTag 'button', (content || 'Button'), options
35
+
36
+ helper 'checkBoxTag', (name, value = 1, checked = false, options = {}) ->
37
+ html_options = @_clone options
38
+ html_options['type'] = 'checkbox'
39
+ html_options['name'] = name
40
+ html_options['value'] = value
41
+ html_options['checked'] = 'checked' if checked
42
+ html_options['id'] = sanitizeToId(name) if html_options['id'] == undefined
43
+ @tag 'input', html_options
44
+ alias 'checkboxTag', 'checkBoxTag'
45
+
46
+ helper 'fieldSetTag', (legend = null, content_or_options = '', options_or_content = {}) ->
47
+ [content, options] = if typeof legend == 'function'
48
+ content_and_options = [ legend, {} ]
49
+ legend = null
50
+ content_and_options
51
+ else
52
+ @_contentOrOptions content_or_options, options_or_content, ''
53
+
54
+ content = content.call(this) if typeof content == 'function'
55
+ content = @contentTag('legend', legend) + content if legend && legend != ''
56
+ @contentTag 'fieldset', content, options
57
+
58
+ alias 'fieldsetTag', 'fieldSetTag'
59
+
60
+ buildFormOptions = (url, options) ->
61
+ method = (options['method'] || 'post').toLowerCase()
62
+ options['action'] = url
63
+ options['method'] = if method == 'get' then 'get' else 'post'
64
+ options['accept-charset'] ||= 'UTF-8'
65
+
66
+ if options['multipart']
67
+ options['enctype'] = 'multipart/form-data'
68
+ options['multipart'] = undefined
69
+
70
+ options['data-remote'] = 'true' if options['remote']
71
+ options['remote'] = undefined
72
+
73
+ enforce_utf8 = options['enforce_utf8'] == undefined || options['enforce_utf8']
74
+ options['enforce_utf8'] = undefined
75
+
76
+ authenticity_token = if ( @_context.protectFromForgery && options['authenticity_token'] == undefined ) || options['authenticity_token']
77
+ if options['authenticity_token'] == undefined || options['authenticity_token'] == true then @csrfToken else options['authenticity_token']
78
+ else
79
+ false
80
+ options['authenticity_token'] = undefined
81
+
82
+ [ method, enforce_utf8, authenticity_token ]
83
+
84
+ buildFormContent = (content, method, enforce_utf8, authenticity_token) ->
85
+ form_content = ''
86
+
87
+ method_enforcer = method != 'get' && method != 'post'
88
+ form_content += '<div style="display:none">'
89
+ if enforce_utf8 || authenticity_token || method_enforcer
90
+ form_content += @utf8EnforcerTag() if enforce_utf8
91
+ form_content += @methodHiddenTag(method) if method_enforcer
92
+ form_content += @authenticityTokenTag(authenticity_token) if authenticity_token
93
+ form_content += '</div>'
94
+
95
+ content = content.call(this) if typeof content == 'function'
96
+
97
+ form_content + content
98
+
99
+ helper 'formTag', (url = '/', content_or_options, options_or_content) ->
100
+ [content, options] = if typeof url == 'function'
101
+ content_and_options = [url, {}]
102
+ url = '/'
103
+ content_and_options
104
+ else
105
+ @_contentOrOptions content_or_options, options_or_content, ''
106
+
107
+ [ method, enforce_utf8, authenticity_token ] = buildFormOptions.call this, url, options
108
+
109
+ @contentTag 'form', buildFormContent.call(this, content, method, enforce_utf8, authenticity_token), options
110
+
111
+ helper 'imageSubmitTag', (source, options = {}) ->
112
+ options = @_clone options
113
+ options['src'] ||= @imagePath source
114
+ options['type'] ||= 'image'
115
+ options['alt'] ||= @imageAlt source
116
+ @tag 'input', options
117
+
118
+ helper 'labelTag', (name, content_or_options = null, options_or_content = {}) ->
119
+ [content, options] = if typeof name == 'function'
120
+ content_and_options = [name, {}]
121
+ name = null
122
+ content_and_options
123
+ else
124
+ @_contentOrOptions content_or_options, options_or_content
125
+
126
+ content ||= humanizeToLabel name
127
+ options['for'] ||= sanitizeToId(name) if name
128
+ @contentTag 'label', content, options
129
+
130
+ helper 'radioButtonTag', (name, value, checked = false, options = {}) ->
131
+ options = @_clone options
132
+ options['type'] = 'radio'
133
+ options['name'] = name
134
+ options['value'] = value
135
+ options['checked'] = 'checked' if checked
136
+ options['id'] ||= sanitizeToId(name) + '_' + sanitizeToId(value)
137
+ @tag 'input', options
138
+
139
+ helper 'selectTag', (name, option_tags = '', options = {}) ->
140
+ options = @_clone options
141
+
142
+ if options['include_blank']
143
+ blank_content = if options['include_blank'] == true then '' else @htmlEscape( options['include_blank'] )
144
+ option_tags = @contentTag('option', blank_content, value: '') + option_tags
145
+ options['include_blank'] = undefined
146
+
147
+ if options['prompt']
148
+ prompt_content = if options['prompt'] == true then @translate('helpers.select.prompt', default: 'Please select') else @htmlEscape( options['prompt'] )
149
+ option_tags = @contentTag('option', prompt_content, value: '') + option_tags
150
+ options['prompt'] = undefined
151
+
152
+ html_name = if options['multiple']
153
+ unless name.toString()[-2..-1] == '[]'
154
+ name + '[]'
155
+ else
156
+ name
157
+
158
+ options['multiple'] = undefined if options['multiple'] == false
159
+
160
+ options['id'] = sanitizeToId(name) if options['id'] == undefined
161
+ options['name'] ||= name
162
+ @contentTag 'select', option_tags, options
163
+
164
+ helper 'submitTag', (value = "Save changes", options = {}) ->
165
+ options = @_clone options
166
+ options['type'] ||= 'submit'
167
+ options['name'] ||= 'commit'
168
+ options['value'] ||= value
169
+ @tag 'input', options
170
+
171
+ handleSizeAttribute = (options) ->
172
+ if typeof options['size'] == 'string'
173
+ size = options['size'].toLowerCase()
174
+ if (/\d+X\d+/i).test(size)
175
+ xIndex = size.lastIndexOf('x');
176
+ options['cols'] ||= size.substring 0, xIndex
177
+ options['rows'] ||= size.substring xIndex+1
178
+ options['size'] = undefined
179
+
180
+ helper 'textAreaTag', (name, content_or_options = '', options_or_content = {}) ->
181
+ [content, options] = @_contentOrOptions content_or_options, options_or_content
182
+ handleSizeAttribute options
183
+
184
+ content = content.call(this) if typeof content == 'function'
185
+ content ||= ''
186
+
187
+ if typeof options['escape'] == 'undefined' || options['escape']
188
+ content = @htmlEscape content
189
+ options['escape'] = undefined
190
+
191
+ options['id'] = sanitizeToId(name) if options['id'] == undefined
192
+ options['name'] ||= name
193
+ @contentTag 'textarea', "\n"+content, options
194
+
195
+ alias 'textareaTag', 'textAreaTag'
196
+
197
+ helper 'authenticityTokenTag', (authenticity_token) ->
198
+ @tag 'input', type: 'hidden', name: @_context.requestForgeryProtectionToken, value: authenticity_token
199
+
200
+ helper 'methodHiddenTag', (method) ->
201
+ @tag 'input', type: 'hidden', name: '_method', value: method
202
+
203
+ helper 'utf8EnforcerTag', () ->
204
+ @tag 'input', type: 'hidden', name: 'utf8', value: "&#x2713;"