manatee 0.0.1.pre1

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