govuk_elements_rails 0.2.1 → 0.2.2

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.
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