govuk_elements_rails 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 036d93e6fb397f955de23ed8adf114bc3b83b19a
4
- data.tar.gz: 770cb5fb1d025ee758af29f64d02377410d6dd59
3
+ metadata.gz: 85648682a9e22e34cd2e42af89725178d104b997
4
+ data.tar.gz: 827a92dcc7d95e073db4e2590bbe28ffbbb4ca22
5
5
  SHA512:
6
- metadata.gz: 7c48cce45e3f80d92e84edd21456db964a73e3018e24d175e8cab56ff7d37a013ff26aaab41be857ce7fb83a8b217722699c98f675d0fe802c125ef0652411a5
7
- data.tar.gz: aaea624fa297a9391ff7aad10b600ab65b200c9b51dc92741db5591c62c19bed291ea99fcdcbf46405ca241c1a5913d2c4a1bbbedb591983f9098b370774c934
6
+ metadata.gz: 17519e48bc499d28e907ac48025bb631b06581e87791f3086713ecd9208b69439d9cf8dd4f8a3d390e93e376a465df5faa3c2c8ac8225b0f6ac570ffab48eb87
7
+ data.tar.gz: e7c7b1fb30e9f189121ff0ea168c694237b3e8354ec007f3c55ed0f3923b2398908666ed73111657095f8a7ca321597c5a297e42278ccd8a6c29997ca8028d5c
data/README.md CHANGED
@@ -15,6 +15,19 @@ If you are installing from git, ensure you enable submodules like so:
15
15
 
16
16
  gem 'govuk_elements_rails', :git => "https://github.com/ministryofjustice/govuk_elements_rails.git", :submodules => true
17
17
 
18
+ If you are working on the gem itself, clone and download submodules like this:
19
+
20
+ git clone https://github.com/ministryofjustice/govuk_elements_rails.git
21
+ cd govuk_elements_rails
22
+ git submodule init
23
+ git submodule update
24
+
25
+ To add a javascript file to gem, create new symlink to govuk_elements file like this:
26
+
27
+ cd vendor/assets/javascripts/
28
+ ln -s ../../../govuk_elements/public/javascripts/application.js
29
+ ls -l
30
+
18
31
  ## Usage
19
32
 
20
33
  At the top of a Sass file in your Rails project you should use an `@import` rule
@@ -43,13 +56,6 @@ For example here are all the requires possible at present:
43
56
  //= require bind
44
57
  //= require selection-buttons
45
58
 
46
- ## Usage of GovukElementsFormBuilder
47
-
48
- To replace the default form builder with a version that generates GOV.UK Elements classed markup, set the following in
49
- config/application.rb:
50
-
51
- config.use_govuk_elements_form_builder = true
52
-
53
59
  ## Alternate ways to reuse GOV.UK Elements
54
60
 
55
61
  There are other alternate ways to include GOV.UK Elements files in a Rails
@@ -0,0 +1,316 @@
1
+
2
+ class LabellingFormBuilder < ActionView::Helpers::FormBuilder
3
+
4
+ include ActionView::Helpers::CaptureHelper
5
+ include ActionView::Helpers::TagHelper
6
+ include ActionView::Context
7
+
8
+ def text_field_row(attribute, options={})
9
+ row_input attribute, :text_field, options
10
+ end
11
+
12
+ def text_area_row(attribute, options={})
13
+ row_input attribute, :text_area, options
14
+ end
15
+
16
+ def moj_date_fieldset attribute, legend, options = {}, example_date = Date.today, explanatory_text = nil
17
+ df = MojDateFieldset.new(self, attribute, legend, options, example_date, explanatory_text)
18
+ df.emit
19
+ end
20
+
21
+ # produces an postcode picker widget - this will produce an address/street and postcode attributes for the given object type
22
+ # @param [String] attribute: the name of the attribute on Claim which is to have the street and postcode attributes (e.g. property, defendant_1)
23
+ # @param [Hash] options: options to control the way in which the potcode picker is displayed, and what html is generated in the form:
24
+ # :prefix - the prefix to be applied to each element in the form
25
+ # :postcode_attr - the name of the postcode attribute if not 'postcode'
26
+ # :address_attr - the name of the address attribute if not 'street'
27
+ # :name - the prefix of the name given to the street and postcode attributes if not 'claim['xxxx'] where xxxx is the attribute
28
+ # :street_hint - and html which is to be inserted as a hint above the street textarea
29
+ # :vc - list of valid countries: postcodes that return a country not in the supplied list will be marked as invalid.
30
+ # If not supplied or blank, all countries are valid. Countries should be joined by '+' and spaces in country names should be replaced
31
+ # by underscores, e.g "england+wales+channel_islands+isle_of_man"
32
+ # :button_label - the text to use on the Find UK Address button if not 'Find Uk Address'
33
+ #
34
+ def moj_postcode_picker attribute, options = {}
35
+ default_options = {
36
+ :prefix => "claim_#{attribute}",
37
+ :postcode_attr => :postcode,
38
+ :address_attr => :street,
39
+ :name => "claim[#{attribute}]"
40
+ }
41
+ options = default_options.merge(options)
42
+ mpp = MojPostcodePicker.new(self, options)
43
+ mpp.emit
44
+ end
45
+
46
+ # Defaults to "Yes" "No" labels on radio inputs
47
+ def radio_button_fieldset attribute, legend, options={}
48
+ translation_key = translation_key(attribute)
49
+ raise "TBD: #{translation_key} #{options[:choice]}" if options[:choice].is_a?(Hash)
50
+
51
+ virtual_pageview = options[:data] ? options[:data].delete('virtual-pageview') : nil
52
+ input_class = options.delete(:input_class)
53
+
54
+ set_class_and_id attribute, options
55
+
56
+ options[:choice] ||= [ 'Yes', 'No' ]
57
+
58
+ options_class = options[:class][/inline/] ? 'inline' : 'options'
59
+
60
+ data_reverse = options.delete(:toggle_fieldset) ? ' data-reverse="true"' : ''
61
+
62
+ fieldset_tag attribute, legend, options do
63
+ @template.surround("<div class='#{options_class}'#{data_reverse}>".html_safe, "</div>".html_safe) do
64
+ options[:choice].map do |choice|
65
+ radio_button_row(attribute, choice, virtual_pageview, input_class)
66
+ end.join("\n")
67
+ end
68
+ end
69
+ end
70
+
71
+ def row attribute, options={}
72
+ @template.haml_tag haml_tag_text('div option', attribute, options) do
73
+ yield
74
+ end
75
+ end
76
+
77
+ def error_for? attribute
78
+ # if @object.is_a?(Claim)
79
+ # subkey = "claim_#{attribute}_error"
80
+ # base_errors = error_message_for(:base)
81
+ # base_errors && base_errors.to_h.key?(subkey) && !base_errors.to_h[subkey].empty?
82
+ # else
83
+ attribute_errors = error_message_for(attribute)
84
+ attribute_errors && !attribute_errors.empty?
85
+ # end
86
+ end
87
+
88
+ def error_span attribute, options={}
89
+ @template.surround(error_span_open_tag(options), "</span>".html_safe) do
90
+ error_span_message(attribute)
91
+ end
92
+ end
93
+
94
+ # Creates key for lookup of translation text.
95
+ # E.g. translation_key(hearing, {:choice=>"No"}) when in possession form
96
+ # returns "claim.possession.hearing.no"
97
+ def translation_key attribute, options={}
98
+ key = "#{ parent_id.gsub('_','.') }.#{attribute}".squeeze('.')
99
+ key.gsub!(/\.\d+\./, '.')
100
+ if choice = options[:choice]
101
+ choice = 'na' if choice == ''
102
+ key += ".#{choice.downcase}"
103
+ end
104
+ key
105
+ end
106
+
107
+ def error_id_for attribute
108
+ field_id = "#{parent_id}_#{attribute}".squeeze('_')
109
+ "#{field_id}_error"
110
+ end
111
+
112
+ def parent_id
113
+ @object_name.to_s.tr('[]','_').squeeze('_')
114
+ end
115
+
116
+ def labelled_check_box attribute, label, yes='Yes', no='No', options={}
117
+ set_class_and_id attribute, options
118
+ hidden_input = check_box_input_hidden attribute, options, yes, no
119
+
120
+ labeled_input = label(attribute) do
121
+ check_box_input(attribute, options, yes, no) + label
122
+ end
123
+
124
+ list = [hidden_input]
125
+
126
+ if error_for?(attribute)
127
+ id = error_id_for(attribute)
128
+ labeled_input.sub!(%Q[id="#{id}"], %Q[id="#{id.sub('_error','')}"])
129
+ list << hidden_fullstop(options)
130
+ list << error_span(attribute)
131
+ end
132
+
133
+ list << labeled_input
134
+
135
+ list.join("\n").html_safe
136
+ end
137
+
138
+ def set_class_and_id attribute, options
139
+ options[:class] = css_for(attribute, options)
140
+ options[:id] = id_for(attribute) unless id_for(attribute).blank?
141
+ end
142
+
143
+ def fieldset_tag(attribute, legend_text, options = {}, &block)
144
+ fieldset = tag(:fieldset, options_for_fieldset(options), true)
145
+
146
+ fieldset.safe_concat legend_for(attribute, legend_text, options) unless legend_text.blank?
147
+
148
+ fieldset.concat(capture(&block)) if block_given?
149
+ fieldset.safe_concat("</fieldset>")
150
+ end
151
+
152
+ private
153
+
154
+ def fieldset attribute, options={}
155
+ options.delete(:id) unless options[:id].present?
156
+ @template.haml_tag haml_tag_text('fieldset', attribute, options) do
157
+ yield
158
+ end
159
+ end
160
+
161
+ def id_for attribute, default=nil
162
+ error_for?(attribute) ? error_id_for(attribute) : (default || '')
163
+ end
164
+
165
+ def label_content_for attribute, label, options={}
166
+ label ||= attribute.to_s.humanize
167
+ label = ["#{label}"]
168
+ hint = hint_span(options)
169
+ label << hint if hint
170
+ if error_for?(attribute)
171
+ last = label.pop
172
+ label << ( ends_with_punctuation?(last) ? last : (last + hidden_fullstop(options)) )
173
+ label << error_span(attribute, options)
174
+ end
175
+ label.join(" ").html_safe
176
+ end
177
+
178
+ def hint_span options
179
+ options[:hint] ? "<span class='hint block'>#{options[:hint]}</span>".html_safe : nil
180
+ end
181
+
182
+ def ends_with_punctuation? span
183
+ span[/\?<\/span/] || span[/\.<\/span>/] || span[/\.|\?$/]
184
+ end
185
+
186
+ def legend_for attribute, legend_text, options
187
+ label = label_content_for(attribute, legend_text, hint: options[:hint])
188
+ content_tag(:legend, label)
189
+ end
190
+
191
+ def hidden_fullstop options
192
+ '<span class="visuallyhidden">.</span>'.html_safe
193
+ end
194
+
195
+ def error_span_message attribute
196
+ if @object.is_a? Claim
197
+ error_message_for(:base).to_h["claim_#{attribute}_error"]
198
+ else
199
+ error_message_for(attribute)[0]
200
+ end
201
+ end
202
+
203
+ def error_span_open_tag options
204
+ " <span class='error#{error_classes(options)}'#{error_span_id(options)}>".html_safe
205
+ end
206
+
207
+ def error_span_id options
208
+ options.has_key?(:id)? " id='#{options[:id]}'" : nil
209
+ end
210
+
211
+ def error_classes options
212
+ ' visuallyhidden' if options[:hidden]
213
+ end
214
+
215
+ def options_for_fieldset options
216
+ options.delete(:class) if options[:class].blank?
217
+ options = {}.merge(options)
218
+ options.delete(:hint)
219
+ options.delete(:choice)
220
+ options.delete(:date_select_options)
221
+ options
222
+ end
223
+
224
+ def error_message_for symbol
225
+ @object.errors.messages[symbol]
226
+ end
227
+
228
+ def check_box_input attribute, options, yes, no
229
+ html = check_box(attribute, options, yes, no)
230
+ html.gsub!(/<[^<]*type="hidden"[^>]*>/,'')
231
+ html.html_safe
232
+ end
233
+
234
+ def check_box_input_hidden attribute, options, yes, no
235
+ html = check_box(attribute, options, yes, no)
236
+ html.gsub!(/.*(<[^<]*type="hidden"[^>]*>).*/, '\1')
237
+ html.html_safe
238
+ end
239
+
240
+ def radio_button_row attribute, choice, virtual_pageview, input_class
241
+ translation_key = translation_key(attribute, choice: choice)
242
+
243
+ translation = I18n.t(translation_key)
244
+ raise "translation missing: #{translation_key}" if translation[/translation missing/]
245
+ label = translation unless translation[/translation missing/]
246
+
247
+ options = {}
248
+ options.merge!(class: input_class) if input_class
249
+ options.merge!(data: { 'virtual_pageview' => virtual_pageview }) if virtual_pageview
250
+
251
+ input = radio_button(attribute, choice, options)
252
+
253
+ id = input[/id="([^"]+)"/,1]
254
+
255
+ @template.surround("<div class='option'>".html_safe, "</div>".html_safe) do
256
+ @template.surround("<label for='#{id}'>".html_safe, "</label>".html_safe) do
257
+ # errors = error_span(attribute, {hidden: true}) if error_for?(attribute)
258
+ [
259
+ # errors,
260
+ input,
261
+ label
262
+ ].compact.join("\n")
263
+ end
264
+ end
265
+ end
266
+
267
+ def css_for attribute, options
268
+ css = ''
269
+ css += " #{options[:class]}" if options[:class].present?
270
+ css += ' error' if error_for?(attribute)
271
+ css.strip
272
+ end
273
+
274
+ def haml_tag_text tag, attribute, options
275
+ tag += " #{css_for(attribute, options)}"
276
+ haml_tag_text = tag.squeeze(' ').strip.gsub(' ','.')
277
+ end
278
+
279
+ def row_input attribute, input_type, options
280
+ virtual_pageview = options[:data] ? options[:data].delete('virtual-pageview') : nil
281
+ css = "form-group #{css_for(attribute, options)}".strip
282
+ id = id_for(attribute).blank? ? '' : "id='#{id_for(attribute)}' "
283
+
284
+ @template.surround("<div #{id}class='#{css}'>".html_safe, "</div>".html_safe) do
285
+ input_options = options.merge(class: "form-control #{options[:input_class]}".strip)
286
+ input_options.merge!(data: {'virtual_pageview' => virtual_pageview}) if virtual_pageview
287
+ input_options.delete(:label)
288
+ input_options.delete(:input_class)
289
+
290
+ labelled_input attribute, input_type, input_options, options[:label]
291
+ end
292
+ end
293
+
294
+ def labelled_input attribute, input_type, input_options, label=nil
295
+ label = label(attribute, label_content_for(attribute, label), class: 'form-label')
296
+
297
+ if max_length = max_length(attribute)
298
+ input_options.merge!(maxlength: max_length)
299
+ end
300
+
301
+ value = send(input_type, attribute, input_options)
302
+
303
+ [ label, value ].join("\n").html_safe
304
+ end
305
+
306
+ def max_length attribute
307
+ if validator = validators(attribute).detect{|x| x.is_a?(ActiveModel::Validations::LengthValidator)}
308
+ validator.options[:maximum]
309
+ end
310
+ end
311
+
312
+ def validators attribute
313
+ @object.class.validators_on(attribute)
314
+ end
315
+
316
+ end
@@ -1,5 +1,5 @@
1
1
  module GovUKElementsRails
2
2
  def self.elements_version
3
- '0.2.1'
3
+ '0.2.2'
4
4
  end
5
5
  end
@@ -0,0 +1,134 @@
1
+ function ShowHideContent() {
2
+ var self = this;
3
+
4
+
5
+ self.escapeElementName = function(str) {
6
+ result = str.replace('[', '\\[').replace(']', '\\]')
7
+ return(result);
8
+ };
9
+
10
+ self.showHideRadioToggledContent = function () {
11
+ $(".block-label input[type='radio']").each(function () {
12
+
13
+ var $radio = $(this);
14
+ var $radioGroupName = $radio.attr('name');
15
+ var $radioLabel = $radio.parent('label');
16
+
17
+ var dataTarget = $radioLabel.attr('data-target');
18
+
19
+ // Add ARIA attributes
20
+
21
+ // If the data-target attribute is defined
22
+ if (dataTarget) {
23
+
24
+ // Set aria-controls
25
+ $radio.attr('aria-controls', dataTarget);
26
+
27
+ $radio.on('click', function () {
28
+
29
+ // Select radio buttons in the same group
30
+ $radio.closest('form').find(".block-label input[name=" + self.escapeElementName($radioGroupName) + "]").each(function () {
31
+ var $this = $(this);
32
+
33
+ var groupDataTarget = $this.parent('label').attr('data-target');
34
+ var $groupDataTarget = $('#' + groupDataTarget);
35
+
36
+ // Hide toggled content
37
+ $groupDataTarget.hide();
38
+ // Set aria-expanded and aria-hidden for hidden content
39
+ $this.attr('aria-expanded', 'false');
40
+ $groupDataTarget.attr('aria-hidden', 'true');
41
+ });
42
+
43
+ var $dataTarget = $('#' + dataTarget);
44
+ $dataTarget.show();
45
+ // Set aria-expanded and aria-hidden for clicked radio
46
+ $radio.attr('aria-expanded', 'true');
47
+ $dataTarget.attr('aria-hidden', 'false');
48
+
49
+ });
50
+
51
+ } else {
52
+ // If the data-target attribute is undefined for a radio button,
53
+ // hide visible data-target content for radio buttons in the same group
54
+
55
+ $radio.on('click', function () {
56
+
57
+ // Select radio buttons in the same group
58
+ $(".block-label input[name=" + self.escapeElementName($radioGroupName) + "]").each(function () {
59
+
60
+ var groupDataTarget = $(this).parent('label').attr('data-target');
61
+ var $groupDataTarget = $('#' + groupDataTarget);
62
+
63
+ // Hide toggled content
64
+ $groupDataTarget.hide();
65
+ // Set aria-expanded and aria-hidden for hidden content
66
+ $(this).attr('aria-expanded', 'false');
67
+ $groupDataTarget.attr('aria-hidden', 'true');
68
+ });
69
+
70
+ });
71
+ }
72
+
73
+ });
74
+ }
75
+ self.showHideCheckboxToggledContent = function () {
76
+
77
+ $(".block-label input[type='checkbox']").each(function() {
78
+
79
+ var $checkbox = $(this);
80
+ var $checkboxLabel = $(this).parent();
81
+
82
+ var $dataTarget = $checkboxLabel.attr('data-target');
83
+
84
+ // Add ARIA attributes
85
+
86
+ // If the data-target attribute is defined
87
+ if (typeof $dataTarget !== 'undefined' && $dataTarget !== false) {
88
+
89
+ // Set aria-controls
90
+ $checkbox.attr('aria-controls', $dataTarget);
91
+
92
+ // Set aria-expanded and aria-hidden
93
+ $checkbox.attr('aria-expanded', 'false');
94
+ $('#'+$dataTarget).attr('aria-hidden', 'true');
95
+
96
+ // For checkboxes revealing hidden content
97
+ $checkbox.on('click', function() {
98
+
99
+ var state = $(this).attr('aria-expanded') === 'false' ? true : false;
100
+
101
+ // Toggle hidden content
102
+ $('#'+$dataTarget).toggle();
103
+
104
+ // Update aria-expanded and aria-hidden attributes
105
+ $(this).attr('aria-expanded', state);
106
+ $('#'+$dataTarget).attr('aria-hidden', !state);
107
+
108
+ });
109
+ }
110
+
111
+ });
112
+ }
113
+ }
114
+
115
+ $(document).ready(function() {
116
+
117
+ // Turn off jQuery animation
118
+ jQuery.fx.off = true;
119
+
120
+ // Use GOV.UK selection-buttons.js to set selected
121
+ // and focused states for block labels
122
+ var $blockLabels = $(".block-label input[type='radio'], .block-label input[type='checkbox']");
123
+ new GOVUK.SelectionButtons($blockLabels);
124
+
125
+ // Details/summary polyfill
126
+ // See /javascripts/vendor/details.polyfill.js
127
+
128
+ // Where .block-label uses the data-target attribute
129
+ // to toggle hidden content
130
+ var toggleContent = new ShowHideContent();
131
+ toggleContent.showHideRadioToggledContent();
132
+ toggleContent.showHideCheckboxToggledContent();
133
+
134
+ });
@@ -24,16 +24,21 @@
24
24
 
25
25
  // Handle cross-modal click events
26
26
  function addClickEvent(node, callback) {
27
- var keydown = false;
28
- addEvent(node, 'keydown', function () {
29
- keydown = true;
27
+ // Prevent space(32) from scrolling the page
28
+ addEvent(node, 'keypress', function (e, target) {
29
+ if (e.keyCode === 32) {
30
+ if (e.preventDefault) {
31
+ e.preventDefault();
32
+ }
33
+ else { e.returnValue = false; }
34
+ }
30
35
  });
36
+ // When the key comes up - check if it is enter(13) or space(32)
31
37
  addEvent(node, 'keyup', function (e, target) {
32
- keydown = false;
33
- if (e.keyCode === 13) { callback(e, target); }
38
+ if (e.keyCode === 13 || e.keyCode === 32) { callback(e, target); }
34
39
  });
35
- addEvent(node, 'click', function (e, target) {
36
- if (!keydown) { callback(e, target); }
40
+ addEvent(node, 'mouseup', function (e, target) {
41
+ callback(e, target);
37
42
  });
38
43
  }
39
44
 
@@ -86,6 +91,9 @@
86
91
  details.__content.id = 'details-content-' + i;
87
92
  }
88
93
 
94
+ // Add ARIA role="group" to details
95
+ details.setAttribute('role', 'group');
96
+
89
97
  // Add role=button to summary
90
98
  details.__summary.setAttribute('role', 'button');
91
99
 
@@ -93,30 +101,69 @@
93
101
  details.__summary.setAttribute('aria-controls', details.__content.id);
94
102
 
95
103
  // Set tabindex so the summary is keyboard accessible
96
- details.__summary.setAttribute('tabindex', '0');
104
+ // details.__summary.setAttribute('tabindex', 0);
105
+ // http://www.saliences.com/browserBugs/tabIndex.html
106
+ details.__summary.tabIndex = 0;
97
107
 
98
108
  // Detect initial open/closed state
99
- var detailsAttr = details.hasAttribute('open');
100
- if (typeof detailsAttr !== 'undefined' && detailsAttr !== false) {
109
+
110
+ // Native support - has 'open' attribute
111
+ if (details.open === true) {
101
112
  details.__summary.setAttribute('aria-expanded', 'true');
102
113
  details.__content.setAttribute('aria-hidden', 'false');
103
- } else {
114
+ details.__content.style.display = 'block';
115
+ }
116
+
117
+ // Native support - doesn't have 'open' attribute
118
+ if (details.open === false) {
104
119
  details.__summary.setAttribute('aria-expanded', 'false');
105
120
  details.__content.setAttribute('aria-hidden', 'true');
106
121
  details.__content.style.display = 'none';
107
122
  }
108
123
 
124
+ // If this is not a native implementation
125
+ if (!details.__native) {
126
+
127
+ // Add an arrow
128
+ var twisty = document.createElement('i');
129
+
130
+ // Check for the 'open' attribute
131
+ // If open exists, but isn't supported it won't have a value
132
+ if (details.getAttribute('open') === "") {
133
+ details.__summary.setAttribute('aria-expanded', 'true');
134
+ details.__content.setAttribute('aria-hidden', 'false');
135
+ }
136
+
137
+ // If open doesn't exist - it will be null or undefined
138
+ if (details.getAttribute('open') == null || details.getAttribute('open') == "undefined" ) {
139
+ details.__summary.setAttribute('aria-expanded', 'false');
140
+ details.__content.setAttribute('aria-hidden', 'true');
141
+ details.__content.style.display = 'none';
142
+ }
143
+
144
+ }
145
+
109
146
  // Create a circular reference from the summary back to its
110
147
  // parent details element, for convenience in the click handler
111
148
  details.__summary.__details = details;
112
149
 
113
150
  // If this is not a native implementation, create an arrow
114
- // inside the summary, saving its reference as a summary property
151
+ // inside the summary
115
152
  if (!details.__native) {
153
+
116
154
  var twisty = document.createElement('i');
117
- twisty.className = 'arrow arrow-closed';
118
- twisty.appendChild(document.createTextNode('\u25ba'));
155
+
156
+ if (details.getAttribute('open') === "") {
157
+ twisty.className = 'arrow arrow-open';
158
+ twisty.appendChild(document.createTextNode('\u25bc'));
159
+ } else {
160
+ twisty.className = 'arrow arrow-closed';
161
+ twisty.appendChild(document.createTextNode('\u25ba'));
162
+ }
163
+
119
164
  details.__summary.__twisty = details.__summary.insertBefore(twisty, details.__summary.firstChild);
165
+ details.__summary.__twisty.setAttribute('aria-hidden', 'true');
166
+
120
167
  }
121
168
  }
122
169
 
@@ -124,7 +171,6 @@
124
171
  // Also update the arrow position
125
172
  function statechange(summary) {
126
173
 
127
- // Update aria-expanded attribute on click
128
174
  var expanded = summary.__details.__summary.getAttribute('aria-expanded') == 'true';
129
175
  var hidden = summary.__details.__content.getAttribute('aria-hidden') == 'true';
130
176
 
@@ -1,12 +1,13 @@
1
1
  (function () {
2
- "use strict"
2
+ "use strict";
3
3
  var root = this,
4
4
  $ = root.jQuery;
5
5
 
6
6
  if (typeof GOVUK === 'undefined') { root.GOVUK = {}; }
7
7
 
8
- var BaseButtons = function ($elms, opts) {
9
- this.$elms = $elms;
8
+ var SelectionButtons = function (elmsOrSelector, opts) {
9
+ var $elms;
10
+
10
11
  this.selectedClass = 'selected';
11
12
  this.focusedClass = 'focused';
12
13
  if (opts !== undefined) {
@@ -14,124 +15,97 @@
14
15
  this[optionName] = optionObj;
15
16
  }.bind(this));
16
17
  }
17
- this.setEventNames();
18
- this.getSelections();
19
- this.bindEvents();
18
+ if (typeof elmsOrSelector === 'string') {
19
+ $elms = $(elmsOrSelector);
20
+ this.selector = elmsOrSelector;
21
+ this.setInitialState($(this.selector));
22
+ } else {
23
+ this.$elms = elmsOrSelector;
24
+ this.setInitialState(this.$elms);
25
+ }
26
+ this.addEvents();
20
27
  };
21
- BaseButtons.prototype.setEventNames = function () {
22
- this.selectionEvents = 'click';
23
- this.focusEvents = 'focus blur';
28
+ SelectionButtons.prototype.addEvents = function () {
29
+ if (typeof this.$elms !== 'undefined') {
30
+ this.addElementLevelEvents();
31
+ } else {
32
+ this.addDocumentLevelEvents();
33
+ }
24
34
  };
25
- BaseButtons.prototype.markFocused = function ($elm, state) {
26
- var elmId = $elm.attr('id');
35
+ SelectionButtons.prototype.setInitialState = function ($elms) {
36
+ $elms.each(function (idx, elm) {
37
+ var $elm = $(elm);
27
38
 
39
+ if ($elm.is(':checked')) {
40
+ this.markSelected($elm);
41
+ }
42
+ }.bind(this));
43
+ };
44
+ SelectionButtons.prototype.markFocused = function ($elm, state) {
28
45
  if (state === 'focused') {
29
46
  $elm.parent('label').addClass(this.focusedClass);
30
47
  } else {
31
48
  $elm.parent('label').removeClass(this.focusedClass);
32
49
  }
33
50
  };
34
- BaseButtons.prototype.bindEvents = function () {
35
- var selectionEventHandler = this.markSelected.bind(this),
36
- focusEventHandler = this.markFocused.bind(this);
51
+ SelectionButtons.prototype.markSelected = function ($elm) {
52
+ var radioName;
37
53
 
38
- this.$elms
39
- .on(this.selectionEvents, function (e) {
40
- selectionEventHandler($(e.target));
41
- })
42
- .on(this.focusEvents, function (e) {
43
- var state = (e.type === 'focus') ? 'focused' : 'blurred';
44
-
45
- focusEventHandler($(e.target), state);
46
- });
47
- };
48
-
49
- var RadioButtons = function ($elms, opts) {
50
- BaseButtons.apply(this, arguments);
51
- };
52
- RadioButtons.prototype.setEventNames = function () {
53
- // some browsers fire the 'click' when the selected radio changes by keyboard
54
- this.selectionEvents = 'click change';
55
- this.focusEvents = 'focus blur';
56
- };
57
- RadioButtons.prototype.getSelections = function () {
58
- var selectionEventHandler = this.markSelected.bind(this);
59
-
60
- this.selections = {};
61
- $.each(this.$elms, function (index, elm) {
62
- var $elm = $(elm),
63
- radioName = $elm.attr('name');
64
-
65
- if (typeof this.selections[radioName] === 'undefined') {
66
- this.selections[radioName] = false;
67
- }
54
+ if ($elm.attr('type') === 'radio') {
55
+ radioName = $elm.attr('name');
56
+ $($elm[0].form).find('input[name="' + radioName + '"]')
57
+ .parent('label')
58
+ .removeClass(this.selectedClass);
59
+ $elm.parent('label').addClass(this.selectedClass);
60
+ } else { // checkbox
68
61
  if ($elm.is(':checked')) {
69
- selectionEventHandler($elm);
62
+ $elm.parent('label').addClass(this.selectedClass);
63
+ } else {
64
+ $elm.parent('label').removeClass(this.selectedClass);
70
65
  }
71
- }.bind(this));
72
- };
73
- RadioButtons.prototype.bindEvents = function () {
74
- BaseButtons.prototype.bindEvents.call(this);
75
- };
76
- RadioButtons.prototype.markSelected = function ($elm) {
77
- var radioName = $elm.attr('name'),
78
- $previousSelection = this.selections[radioName];
79
-
80
- if ($previousSelection) {
81
- $previousSelection.parent('label').removeClass(this.selectedClass);
82
66
  }
83
- $elm.parent('label').addClass(this.selectedClass);
84
- this.selections[radioName] = $elm;
85
67
  };
86
- RadioButtons.prototype.markFocused = function ($elm) {
87
- BaseButtons.prototype.markFocused.apply(this, arguments);
68
+ SelectionButtons.prototype.addElementLevelEvents = function () {
69
+ this.clickHandler = this.getClickHandler();
70
+ this.focusHandler = this.getFocusHandler({ 'level' : 'element' });
71
+
72
+ this.$elms
73
+ .on('click', this.clickHandler)
74
+ .on('focus blur', this.focusHandler);
88
75
  };
76
+ SelectionButtons.prototype.addDocumentLevelEvents = function () {
77
+ this.clickHandler = this.getClickHandler();
78
+ this.focusHandler = this.getFocusHandler({ 'level' : 'document' });
89
79
 
90
- var CheckboxButtons = function ($elms, opts) {
91
- BaseButtons.apply(this, arguments);
80
+ $(document)
81
+ .on('click', this.selector, this.clickHandler)
82
+ .on('focus blur', this.selector, this.focusHandler);
92
83
  };
93
- CheckboxButtons.prototype.setEventNames = function () {
94
- BaseButtons.prototype.setEventNames.call(this);
84
+ SelectionButtons.prototype.getClickHandler = function () {
85
+ return function (e) {
86
+ this.markSelected($(e.target));
87
+ }.bind(this);
95
88
  };
96
- CheckboxButtons.prototype.getSelections = function () {
97
- var selectionEventHandler = this.markSelected.bind(this);
89
+ SelectionButtons.prototype.getFocusHandler = function (opts) {
90
+ var focusEvent = (opts.level === 'document') ? 'focusin' : 'focus';
98
91
 
99
- this.$elms.each(function (idx, elm) {
100
- var $elm = $(elm);
92
+ return function (e) {
93
+ var state = (e.type === focusEvent) ? 'focused' : 'blurred';
101
94
 
102
- if ($elm.is(':checked')) {
103
- selectionEventHandler($elm);
104
- }
105
- });
95
+ this.markFocused($(e.target), state);
96
+ }.bind(this);
106
97
  };
107
- CheckboxButtons.prototype.bindEvents = function () {
108
- BaseButtons.prototype.bindEvents.call(this);
109
- };
110
- CheckboxButtons.prototype.markSelected = function ($elm) {
111
- if ($elm.is(':checked')) {
112
- $elm.parent('label').addClass(this.selectedClass);
98
+ SelectionButtons.prototype.destroy = function () {
99
+ if (typeof this.selector !== 'undefined') {
100
+ $(document)
101
+ .off('click', this.selector, this.clickHandler)
102
+ .off('focus blur', this.selector, this.focusHandler);
113
103
  } else {
114
- $elm.parent('label').removeClass(this.selectedClass);
115
- }
116
- };
117
- CheckboxButtons.prototype.markFocused = function ($elm) {
118
- BaseButtons.prototype.markFocused.apply(this, arguments);
119
- };
120
-
121
- root.GOVUK.RadioButtons = RadioButtons;
122
- root.GOVUK.CheckboxButtons = CheckboxButtons;
123
-
124
- var selectionButtons = function ($elms, opts) {
125
- var $radios = $elms.filter('[type=radio]'),
126
- $checkboxes = $elms.filter('[type=checkbox]');
127
-
128
- if ($radios) {
129
- new GOVUK.RadioButtons($radios, opts);
130
- }
131
- if ($checkboxes) {
132
- new GOVUK.CheckboxButtons($checkboxes, opts);
104
+ this.$elms
105
+ .off('click', this.clickHandler)
106
+ .off('focus blur', this.focusHandler);
133
107
  }
134
108
  };
135
109
 
136
- root.GOVUK.selectionButtons = selectionButtons;
110
+ root.GOVUK.SelectionButtons = SelectionButtons;
137
111
  }).call(this);
@@ -94,20 +94,51 @@
94
94
  color: $hm-government;
95
95
  }
96
96
 
97
+ // Fix grid layout within example boxes for IE7 and below
98
+ // where box-sizing isn't supported: http://caniuse.com/#search=box-sizing
99
+ @mixin example-box-column($width) {
100
+ width: (($site-width - $gutter) * $width) - $gutter;
101
+ }
102
+
103
+ @include ie-lte(7){
104
+
105
+ // Set example box width to 900px (removing left and right padding)
106
+ $example-box-width: $site-width - ($gutter * 2);
107
+ width: $example-box-width;
108
+
109
+ // Recalculate grid column widths
110
+ .column-quarter {
111
+ @include example-box-column( 1/4 );
112
+ }
113
+ .column-half {
114
+ @include example-box-column( 1/2 );
115
+ }
116
+ .column-third {
117
+ @include example-box-column( 1/3 );
118
+ }
119
+ .column-two-thirds {
120
+ @include example-box-column( 2/3 );
121
+ }
122
+
123
+ // Scale images to fit grid columns
124
+ img {
125
+ width: 100%;
126
+ }
127
+ }
128
+
97
129
  }
98
130
 
99
131
 
100
132
  // 1. Layout
101
133
  // ==========================================================================
102
134
 
103
- // Grid layout boxes
104
135
  .example-grid p {
105
136
  width: 100%;
106
137
  background: file-url("examples/grid.png") 0 0 repeat;
107
- min-height: 30px;
108
138
  margin-bottom: 0;
139
+ height: 30px;
109
140
  @include media(tablet) {
110
- min-height: 60px;
141
+ height: 60px;
111
142
  }
112
143
  overflow: hidden;
113
144
  text-indent: -999em;
@@ -331,3 +362,17 @@ $palette: (
331
362
  .example-button ul {
332
363
  padding-bottom: 0;
333
364
  }
365
+
366
+
367
+ // 9. Alpha beta banners
368
+ // ==========================================================================
369
+
370
+ // Alpha
371
+ .phase-banner-alpha {
372
+ @include phase-banner($state: alpha);
373
+ }
374
+
375
+ // Beta
376
+ .phase-banner-beta {
377
+ @include phase-banner($state: beta);
378
+ }
@@ -12,6 +12,12 @@
12
12
  vertical-align: top;
13
13
  }
14
14
 
15
+ // Fix unwanted button padding in Firefox
16
+ .button::-moz-focus-inner {
17
+ border: 0;
18
+ padding: 0;
19
+ }
20
+
15
21
  .button:focus {
16
22
  outline: 3px solid $yellow;
17
23
  }
@@ -31,6 +31,7 @@ details .summary {
31
31
  // Match fallback arrow spacing with -webkit default
32
32
  details .arrow {
33
33
  margin-right: 0.35em;
34
+ font-style: normal;
34
35
  }
35
36
 
36
37
  // Remove top margin from bordered panel
@@ -45,15 +45,19 @@ fieldset {
45
45
  // Labels
46
46
 
47
47
  // Form labels, or for legends styled to look like labels
48
+ .form-label,
49
+ .form-label-bold {
50
+ display: block;
51
+ color: $text-colour;
52
+ }
53
+
48
54
  .form-label {
49
55
  @include core-19;
50
- display: block;
51
56
  margin-bottom: 5px;
52
57
  }
53
58
 
54
59
  .form-label-bold {
55
60
  @include bold-24;
56
- display: block;
57
61
  margin-bottom: 0;
58
62
  .form-hint {
59
63
  @include core-19;
@@ -9,11 +9,11 @@
9
9
  @import "design-patterns/alpha-beta";
10
10
 
11
11
 
12
- // Page container
12
+ // Content
13
13
  // ==========================================================================
14
14
 
15
- // Page container wraps the entire site content block
16
- #page-container {
15
+ // Content wraps the entire site content block
16
+ #content {
17
17
  @extend %site-width-container;
18
18
  @extend %contain-floats;
19
19
  padding-bottom: $gutter;
@@ -10,13 +10,16 @@ ol {
10
10
  // Bulleted lists
11
11
  .list-bullet {
12
12
  list-style-type: disc;
13
- margin-left: 20px;
13
+ padding-left: 20px;
14
14
  }
15
15
 
16
16
  // Numbered lists
17
17
  .list-number {
18
18
  list-style-type: decimal;
19
- margin-left: 25px;
19
+ padding-left: 20px;
20
+ @include ie-lte(7) {
21
+ padding-left: 28px;
22
+ }
20
23
  }
21
24
 
22
25
  .list-bullet,
@@ -14,7 +14,7 @@ table {
14
14
  table th,
15
15
  table td {
16
16
  @include core-16;
17
- padding: em(14) em(20) em(10) 0;
17
+ padding: em(12, 16) em(20, 16) em(9, 16) 0;
18
18
 
19
19
  text-align: left;
20
20
  color: #0b0c0c;
@@ -24,3 +24,14 @@ table td {
24
24
  table th {
25
25
  font-weight: 700;
26
26
  }
27
+
28
+ // Right align headings for numeric content
29
+ table th.numeric {
30
+ text-align: right;
31
+ }
32
+
33
+ // Use tabular numbers for numeric table cells
34
+ table td.numeric {
35
+ @include core-16($tabular-numbers: true);
36
+ text-align: right;
37
+ }
@@ -45,7 +45,7 @@
45
45
  // To stack horizontally, use .inline on parent, to sit block labels next to each other
46
46
  .inline .block-label {
47
47
  clear: none;
48
- margin-right: $gutter-half;
48
+ margin-right: 10px;
49
49
  }
50
50
 
51
51
  // Selected and focused states
@@ -1,46 +1,39 @@
1
- // Date of birth
1
+ // Date pattern
2
2
 
3
- .form-date label {
4
- display: block;
5
- margin-bottom: 5px;
3
+ // Hide the 'spinner' for webkit
4
+ // and also for Firefox
5
+ input::-webkit-outer-spin-button,
6
+ input::-webkit-inner-spin-button {
7
+ -webkit-appearance: none;
8
+ margin: 0
6
9
  }
7
-
8
- .form-date .form-group {
9
- width: 50px;
10
- float: left;
11
- margin-right: 20px;
12
- margin-bottom: 0;
13
- clear: none;
10
+ input[type=number] {
11
+ -moz-appearance: textfield
14
12
  }
15
13
 
16
- .form-date .form-group-year {
17
- width: 70px;
18
- }
14
+ .form-date {
19
15
 
20
- .form-date .form-group input {
21
- width: 100%;
22
- }
16
+ .form-group {
17
+ float: left;
18
+ width: 50px;
23
19
 
20
+ margin-right: 20px;
21
+ margin-bottom: 0;
22
+ clear: none;
24
23
 
25
- // Use Modernizr to detect for touch events
26
- // Build: http://modernizr.com/download/#-touch-cssclasses-teststyles-prefixes
24
+ label {
25
+ display: block;
26
+ margin-bottom: 5px;
27
+ }
27
28
 
28
- // Hide date of birth
29
- .touch .form-date {
30
- display: none;
31
- }
29
+ input {
30
+ width: 100%;
31
+ }
32
32
 
33
- // Hide native date of birth
34
- .no-touch .native-date-picker {
35
- display: none;
36
- }
33
+ }
37
34
 
38
- // Show native date of birth
39
- .touch .native-date-picker {
40
- display: block;
41
- }
35
+ .form-group-year {
36
+ width: 70px;
37
+ }
42
38
 
43
- // Set a minimum height for date inputs
44
- .touch input[type="date"] {
45
- min-height: 36px;
46
39
  }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: govuk_elements_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob McKinnon
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-17 00:00:00.000000000 Z
11
+ date: 2015-03-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -50,8 +50,8 @@ dependencies:
50
50
  - - ">="
51
51
  - !ruby/object:Gem::Version
52
52
  version: 3.2.0
53
- description: A gem wrapper around SHA 2072e1790df2fb57f85c9df1158367ca8bc22bf3 govuk_elements
54
- (heads/master) that pulls stylesheet and javascript files into a Rails app.
53
+ description: A gem wrapper around SHA caadd5a3bf04d58ab61bab5ec8e00afdba818b18 govuk_elements
54
+ (remotes/origin/HEAD) that pulls stylesheet and javascript files into a Rails app.
55
55
  email: rob.mckinnon ~@nospam@~ digital.justice.gov.uk
56
56
  executables: []
57
57
  extensions: []
@@ -61,6 +61,7 @@ files:
61
61
  - LICENCE
62
62
  - README.md
63
63
  - app/builders/govuk_elements_form_builder.rb
64
+ - app/builders/labelling_form_builder.rb
64
65
  - lib/govuk_elements_rails.rb
65
66
  - lib/govuk_elements_rails/engine.rb
66
67
  - lib/govuk_elements_rails/version.rb
@@ -89,6 +90,7 @@ files:
89
90
  - vendor/assets/images/icons/player-icon-play.png
90
91
  - vendor/assets/images/icons/player-icon-rewind.png
91
92
  - vendor/assets/images/icons/player-icon-volume.png
93
+ - vendor/assets/javascripts/application.js
92
94
  - vendor/assets/javascripts/bind.js
93
95
  - vendor/assets/javascripts/details.polyfill.js
94
96
  - vendor/assets/javascripts/selection-buttons.js
@@ -140,7 +142,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
140
142
  version: '0'
141
143
  requirements: []
142
144
  rubyforge_project:
143
- rubygems_version: 2.2.2
145
+ rubygems_version: 2.4.5
144
146
  signing_key:
145
147
  specification_version: 4
146
148
  summary: A gem wrapper around http://github.com/alphagov/govuk_elements that pulls