Skip to content
This repository has been archived by the owner on Apr 3, 2019. It is now read-only.

Commit

Permalink
Merge pull request #1682 from mozilla/issue-358
Browse files Browse the repository at this point in the history
COPPA fix

I think the keyboard accessibility could be better, but this is a vast improvement over what we have now. No reason to hold this up. I will file a follow on bug for the keyboard stuff.

Nicely done @johngruen, I learned a few things about Javascript Date objects from you.

r+.
  • Loading branch information
Shane Tomlinson committed Sep 23, 2014
2 parents 717482f + 4fcd17f commit 04cfc89
Show file tree
Hide file tree
Showing 8 changed files with 496 additions and 83 deletions.
6 changes: 4 additions & 2 deletions app/scripts/lib/auth-errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ function () {
YEAR_OF_BIRTH_REQUIRED: 1012,
UNUSABLE_IMAGE: 1013,
NO_CAMERA: 1014,
URL_REQUIRED: 1015
URL_REQUIRED: 1015,
BIRTHDAY_REQUIRED: 1016
};

var CODE_TO_MESSAGES = {
Expand Down Expand Up @@ -91,7 +92,8 @@ function () {
1012: t('Year of birth required'),
1013: t('A usable image was not found'),
1014: t('Could not initialize camera'),
1015: t('Valid URL required')
1015: t('Valid URL required'),
1016: t('Valid birthday required')
};

return {
Expand Down
8 changes: 7 additions & 1 deletion app/scripts/require_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ require.config({
sinon: '../bower_components/sinon/index',
speedTrap: '../bower_components/speed-trap/dist/speed-trap',
md5: '../bower_components/JavaScript-MD5/js/md5',
canvasToBlob: '../bower_components/blueimp-canvas-to-blob/js/canvas-to-blob'
canvasToBlob: '../bower_components/blueimp-canvas-to-blob/js/canvas-to-blob',
moment: '../bower_components/moment/moment'
},
config: {
moment: {
noGlobal: true
}
},
packages: [
{ name: 'draggable',
Expand Down
30 changes: 29 additions & 1 deletion app/scripts/templates/sign_up.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

</div>

<div class="select-row-wrapper">
<div class="select-row-wrapper" id="year-picker">
<div class="select-row">
<select id="fxa-age-year" {{#shouldFocusYear}}autofocus{{/shouldFocusYear}}>
<option id="fxa-none" value="none">{{#t}}Year of birth{{/t}}</option>
Expand All @@ -62,6 +62,34 @@
</div>
</div>

<div class="select-row-wrapper hidden" id="month-date-picker">

<div class="select-row half-width">
<select id="fxa-age-month">
<option id="fxa-month-none" value="none">{{#t}}Month{{/t}}</option>
<option id="fxa-month-0" value="0">{{#t}}January{{/t}}</option>
<option id="fxa-month-1" value="1">{{#t}}February{{/t}}</option>
<option id="fxa-month-2" value="2">{{#t}}March{{/t}}</option>
<option id="fxa-month-3" value="3">{{#t}}April{{/t}}</option
<option id="fxa-month-4" value="4">{{#t}}May{{/t}}</option>
<option id="fxa-month-5" value="5">{{#t}}June{{/t}}</option>
<option id="fxa-month-6" value="6">{{#t}}July{{/t}}</option>
<option id="fxa-month-7" value="7">{{#t}}August{{/t}}</option>
<option id="fxa-month-8" value="8">{{#t}}September{{/t}}</option>
<option id="fxa-month-9" value="9">{{#t}}October{{/t}}</option>
<option id="fxa-month-10" value="10">{{#t}}November{{/t}}</option>
<option id="fxa-month-11" value="11">{{#t}}December{{/t}}</option>
</select>
</div>

<div class="select-row half-width disabled">
<select id="fxa-age-date" disabled="true">
<option id="fxa-day-none" value="none">{{#t}}Day{{/t}}</option>
</select>
</div>

</div>

<div class="privacy-links">
{{#t}}By proceeding, I agree to the <a id="fxa-tos" href="/legal/terms">Terms of Service</a> and <a id="fxa-pp" href="/legal/privacy">Privacy Notice</a> of Firefox cloud services.{{/t}}
</div>
Expand Down
180 changes: 147 additions & 33 deletions app/scripts/views/sign_up.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@

define([
'underscore',
'p-promise',
'lib/promise',
'views/base',
'views/form',
'stache!templates/sign_up',
'lib/session',
'views/mixins/password-mixin',
'lib/auth-errors',
'lib/strings',
'views/mixins/password-mixin',
'views/mixins/service-mixin'
],
function (_, p, BaseView, FormView, Template, Session, PasswordMixin, AuthErrors, ServiceMixin) {
function (_, p, BaseView, FormView, Template, Session, AuthErrors,
Strings, PasswordMixin, ServiceMixin) {
var t = BaseView.t;

function selectAutoFocusEl(email, password) {
Expand All @@ -28,16 +30,11 @@ function (_, p, BaseView, FormView, Template, Session, PasswordMixin, AuthErrors
}

var now = new Date();

// If COPPA says 13, why 14 here? To make UX simpler, we only ask
// for their year of birth, we do not ask for month and day.
// To make this safe and ensure we do not let *any* 12 year olds pass,
// we are saying that it is acceptable for some 13 year olds to be
// caught in the snare.
// This is written on 2014-01-16. 13 years ago is 2001-01-16. Somebody born
// in 2001-01-15 is now 13. Somebody born 2001-01-17 is still only 12.
// To avoid letting the 12 year old in, add an extra year.
var TOO_YOUNG_YEAR = now.getFullYear() - 14;
var CUTOFF_AGE = {
year: now.getFullYear() - 13,
month: now.getMonth(),
date: now.getDate()
};

var View = FormView.extend({
template: Template,
Expand All @@ -61,15 +58,15 @@ function (_, p, BaseView, FormView, Template, Session, PasswordMixin, AuthErrors
},

// afterRender fucnction to handle select-row hack (issue 822)
afterRender: function() {
afterRender: function () {
var select = this.$el.find('.select-row select');
select.focus(function(){
select.parent().addClass('select-focus');
select.focus(function (){
$(this).parent().addClass('select-focus');
});
select.blur(function(){
select.blur(function (){
select.parent().removeClass('select-focus');
});
select.change(function(){
select.change(function (){
select.parent().removeClass('invalid-row');
});

Expand All @@ -80,7 +77,11 @@ function (_, p, BaseView, FormView, Template, Session, PasswordMixin, AuthErrors

events: {
'change .show-password': 'onPasswordVisibilityChange',
'keydown #fxa-age-year': 'submitOnEnter'
'keydown #fxa-age-year': 'submitOnEnter',
'keydown #fxa-age-month': 'submitOnEnter',
'keydown #fxa-age-date': 'submitOnEnter',
'change #fxa-age-year': 'onUserYearSelect',
'change #fxa-age-month': 'onUserMonthSelect'
},

context: function () {
Expand Down Expand Up @@ -118,39 +119,89 @@ function (_, p, BaseView, FormView, Template, Session, PasswordMixin, AuthErrors
},

isValidEnd: function () {
return this._validateYear();
if (! this._validateYear()) {
return false;
}

if (this._getYear() === CUTOFF_AGE.year) {
return this._validateMonthAndDate();
}

return true;
},

showValidationErrorsEnd: function () {
if (! this._validateYear()) {
//next two lines deal with ff30's select list regression
var selectRow = this.$el.find('.select-row');
selectRow.addClass('invalid-row');

this.showValidationError('#fxa-age-year', AuthErrors.toError('YEAR_OF_BIRTH_REQUIRED'));
var selectYearRow = $('#fxa-age-year').parent();
selectYearRow.addClass('invalid-row');

this.showValidationError('#fxa-age-year',
AuthErrors.toError('YEAR_OF_BIRTH_REQUIRED'));
} else if (this._getYear() === CUTOFF_AGE.year &&
! this._validateMonthAndDate()) {
var selectMonthDateRow =
this.$('#fxa-age-month, #fxa-age-date').parent();
selectMonthDateRow.addClass('invalid-row');

this.showValidationError('#fxa-age-month',
AuthErrors.toError('BIRTHDAY_REQUIRED'));
}
},

submit: function () {
if (! this._isUserOldEnough()) {
return this._cannotCreateAccount();
}
var self = this;
return p()
.then(function () {
if (! self._isUserOldEnough()) {
return self._cannotCreateAccount();
}

return self._createAccount();
});
},

return this._createAccount();
_getSelectedUserAge: function () {
var self = this;
return {
year: self._getYear(),
month: self._getMonth(),
date: self._getDate()
};
},

_validateYear: function () {
return ! isNaN(this._getYear());
},

_validateMonthAndDate: function () {
return ! (isNaN(this._getMonth()) || isNaN(this._getDate()));
},

_getYear: function () {
return this.$('#fxa-age-year').val();
return parseInt(this.$('#fxa-age-year').val(), 10);
},

_getMonth: function () {
return parseInt(this.$('#fxa-age-month').val(), 10);
},

_isUserOldEnough: function () {
var year = parseInt(this._getYear(), 10);
_getDate: function () {
return parseInt(this.$('#fxa-age-date').val(), 10);
},

_isUserOldEnough: function (userAge) {
userAge = userAge || this._getSelectedUserAge();
if (userAge.year < CUTOFF_AGE.year) {
return true;
} else if (userAge.year === CUTOFF_AGE.year &&
userAge.month < CUTOFF_AGE.month) {
return true;
}

return year <= TOO_YOUNG_YEAR;
return (userAge.year === CUTOFF_AGE.year &&
userAge.month === CUTOFF_AGE.month &&
userAge.date <= CUTOFF_AGE.date);
},

_cannotCreateAccount: function () {
Expand Down Expand Up @@ -198,7 +249,70 @@ function (_, p, BaseView, FormView, Template, Session, PasswordMixin, AuthErrors
});
},

onSignUpSuccess: function(accountData) {
onUserYearSelect: function () {
if (this._getYear() === CUTOFF_AGE.year) {
this._toggleDatePicker();
}
},

onUserMonthSelect: function () {
var datePickerEl = this.$('#fxa-age-date');
var selectedYear = this._getYear();
var selectedMonth = this._getMonth();
var selectedDate = this._getDate();

if (isNaN(selectedMonth)) {
this._disableDatePicker(datePickerEl);
} else {
this._enableDatePicker(datePickerEl);
}

var daysInMonth = this._daysInMonth(selectedYear, selectedMonth);
this._updateDatePickerValues(datePickerEl, daysInMonth);

if (this._isValidDateForMonth(selectedDate, daysInMonth)) {
datePickerEl.val(selectedDate);
}
},

//if the user changes from march to february (or similar),
//we need to reset out-of-bounds dates, or keep in-bounds dates
_isValidDateForMonth: function (date, daysInMonth) {
return (! isNaN(date)) && date <= daysInMonth;
},

_disableDatePicker: function (datePickerEl) {
datePickerEl.attr('disabled', 'true');
datePickerEl.parent().addClass('disabled');
},

_enableDatePicker: function (datePickerEl) {
datePickerEl.removeAttr('disabled');
datePickerEl.parent().removeClass('disabled');
},

_updateDatePickerValues: function (datePickerEl, days) {
var defaultValue = datePickerEl.children(':eq(0)');
datePickerEl.empty();
datePickerEl.append(defaultValue);
for (var i = 1; i <= days; i++) {
var optionHtml = Strings.interpolate(
'<option id="fxa-day-%s" value="%s">%s</option>', [i, i, i]);
datePickerEl.append(optionHtml);
}
},

_daysInMonth: function (year, month) {
return new Date(year, month + 1, 0).getDate();
},

_toggleDatePicker: function () {
this.$('#year-picker').addClass('hidden');
this.$('#month-date-picker').removeClass('hidden');
this.focus('#fxa-age-month');
},

onSignUpSuccess: function (accountData) {
// user was pre-verified, just send them to the signup complete screen.
if (accountData.verified) {
this.navigate('signup_complete');
Expand Down
23 changes: 23 additions & 0 deletions app/styles/_general.scss
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,7 @@ label:focus ~ .input-help-focused {
*/
.select-row-wrapper {
position: relative;
height: 45px;
}

.select-row {
Expand All @@ -494,13 +495,35 @@ label:focus ~ .input-help-focused {
transition-property: border-color;
width: 100%;

&.half-width {
float:left;
width: 48%;
&:first-child {
margin-right:4%;
}
select {
padding: 9px 10% 9px $input-left-right-padding * .5;
width:94%;
}
}

&:hover {
background-image: image-url('ddarrow_active.png');
border-color: $input-row-hover-border-color;

select {
color: $input-text-color;
}

&.disabled {
background: $content-background-color image-url('ddarrow_inactive.png') 96% center no-repeat;
border: 1px solid $input-row-border-color;
cursor: default;

select {
color: $input-placeholder-color;
}
}
}

select {
Expand Down

0 comments on commit 04cfc89

Please sign in to comment.