diff --git a/lms/static/js/spec/student_account/account_settings_factory_spec.js b/lms/static/js/spec/student_account/account_settings_factory_spec.js
index 58c1554250..fd417507c2 100644
--- a/lms/static/js/spec/student_account/account_settings_factory_spec.js
+++ b/lms/static/js/spec/student_account/account_settings_factory_spec.js
@@ -108,6 +108,11 @@ define(['backbone',
request = requests[1];
expect(request.method).toBe('GET');
+ expect(request.url).toBe('/user_api/v1/preferences/time_zones/?country_code=1');
+ AjaxHelpers.respondWithJson(requests, Helpers.TIME_ZONE_RESPONSE);
+
+ request = requests[2];
+ expect(request.method).toBe('GET');
expect(request.url).toBe(Helpers.USER_PREFERENCES_API_URL);
AjaxHelpers.respondWithError(requests, 500);
@@ -126,6 +131,7 @@ define(['backbone',
Helpers.expectSettingsSectionsButNotFieldsToBeRendered(accountSettingsView);
AjaxHelpers.respondWithJson(requests, Helpers.createAccountSettingsData());
+ AjaxHelpers.respondWithJson(requests, Helpers.TIME_ZONE_RESPONSE);
AjaxHelpers.respondWithJson(requests, Helpers.createUserPreferencesData());
Helpers.expectLoadingIndicatorIsVisible(accountSettingsView, false);
@@ -141,6 +147,7 @@ define(['backbone',
var accountSettingsView = createAccountSettingsPage();
AjaxHelpers.respondWithJson(requests, Helpers.createAccountSettingsData());
+ AjaxHelpers.respondWithJson(requests, Helpers.TIME_ZONE_RESPONSE);
AjaxHelpers.respondWithJson(requests, Helpers.createUserPreferencesData());
AjaxHelpers.respondWithJson(requests, {}); // Page viewed analytics event
diff --git a/lms/static/js/spec/student_account/account_settings_fields_spec.js b/lms/static/js/spec/student_account/account_settings_fields_spec.js
index ca19620440..c69958b75a 100644
--- a/lms/static/js/spec/student_account/account_settings_fields_spec.js
+++ b/lms/static/js/spec/student_account/account_settings_fields_spec.js
@@ -45,7 +45,74 @@ define(['backbone',
);
});
- it('sends request to /i18n/setlang/ after changing language preference in LanguagePreferenceFieldView', function() {
+ it('update time zone dropdown after country dropdown changes', function() {
+ var baseSelector = '.u-field-value > select';
+ var groupsSelector = baseSelector + '> optgroup';
+ var groupOptionsSelector = groupsSelector + '> option';
+
+ var timeZoneData = FieldViewsSpecHelpers.createFieldData(AccountSettingsFieldViews.TimeZoneFieldView, {
+ valueAttribute: 'time_zone',
+ groupOptions: [{
+ groupTitle: gettext('All Time Zones'),
+ selectOptions: FieldViewsSpecHelpers.SELECT_OPTIONS
+ }],
+ persistChanges: true,
+ required: true
+ });
+ var countryData = FieldViewsSpecHelpers.createFieldData(AccountSettingsFieldViews.DropdownFieldView, {
+ valueAttribute: 'country',
+ options: [['KY', 'Cayman Islands'], ['CA', 'Canada'], ['GY', 'Guyana']],
+ persistChanges: true
+ });
+
+ var countryChange = {country: 'GY'};
+ var timeZoneChange = {time_zone: 'Pacific/Kosrae'};
+
+ var timeZoneView = new AccountSettingsFieldViews.TimeZoneFieldView(timeZoneData).render();
+ var countryView = new AccountSettingsFieldViews.DropdownFieldView(countryData).render();
+
+ requests = AjaxHelpers.requests(this);
+
+ timeZoneView.listenToCountryView(countryView);
+
+ // expect time zone dropdown to have single subheader ('All Time Zones')
+ expect(timeZoneView.$(groupsSelector).length).toBe(1);
+ expect(timeZoneView.$(groupOptionsSelector).length).toBe(3);
+ expect(timeZoneView.$(groupOptionsSelector)[0].value).toBe(FieldViewsSpecHelpers.SELECT_OPTIONS[0][0]);
+
+ // change country
+ countryView.$(baseSelector).val(countryChange[countryData.valueAttribute]).change();
+ FieldViewsSpecHelpers.expectAjaxRequestWithData(requests, countryChange);
+ AjaxHelpers.respondWithJson(requests, {success: 'true'});
+
+ AjaxHelpers.expectRequest(
+ requests,
+ 'GET',
+ '/user_api/v1/preferences/time_zones/?country_code=GY'
+ );
+ AjaxHelpers.respondWithJson(requests, [
+ {time_zone: 'America/Guyana', description: 'America/Guyana (ECT, UTC-0500)'},
+ {time_zone: 'Pacific/Kosrae', description: 'Pacific/Kosrae (KOST, UTC+1100)'}
+ ]);
+
+ // expect time zone dropdown to have two subheaders (country/all time zone sub-headers) with new values
+ expect(timeZoneView.$(groupsSelector).length).toBe(2);
+ expect(timeZoneView.$(groupOptionsSelector).length).toBe(5);
+ expect(timeZoneView.$(groupOptionsSelector)[0].value).toBe('America/Guyana');
+
+ // select time zone option from option
+ timeZoneView.$(baseSelector).val(timeZoneChange[timeZoneData.valueAttribute]).change();
+ FieldViewsSpecHelpers.expectAjaxRequestWithData(requests, timeZoneChange);
+ AjaxHelpers.respondWithJson(requests, {success: 'true'});
+ timeZoneView.render();
+
+ // expect time zone dropdown to have three subheaders (currently selected/country/all time zones)
+ expect(timeZoneView.$(groupsSelector).length).toBe(3);
+ expect(timeZoneView.$(groupOptionsSelector).length).toBe(6);
+ expect(timeZoneView.$(groupOptionsSelector)[0].value).toBe('Pacific/Kosrae');
+ });
+
+ it('sends request to /i18n/setlang/ after changing language in LanguagePreferenceFieldView', function() {
requests = AjaxHelpers.requests(this);
var selector = '.u-field-value > select';
diff --git a/lms/static/js/spec/student_account/helpers.js b/lms/static/js/spec/student_account/helpers.js
index 49e9bd44fa..0bfc695ef1 100644
--- a/lms/static/js/spec/student_account/helpers.js
+++ b/lms/static/js/spec/student_account/helpers.js
@@ -49,6 +49,11 @@ define(['underscore'], function(_) {
['3', 'Option 3']
];
+ var TIME_ZONE_RESPONSE = [{
+ time_zone: 'America/Guyana',
+ description: 'America/Guyana (ECT, UTC-0500)'
+ }];
+
var IMAGE_MAX_BYTES = 1024 * 1024;
var IMAGE_MIN_BYTES = 100;
@@ -123,6 +128,7 @@ define(['underscore'], function(_) {
createAccountSettingsData: createAccountSettingsData,
createUserPreferencesData: createUserPreferencesData,
FIELD_OPTIONS: FIELD_OPTIONS,
+ TIME_ZONE_RESPONSE: TIME_ZONE_RESPONSE,
expectLoadingIndicatorIsVisible: expectLoadingIndicatorIsVisible,
expectLoadingErrorIsVisible: expectLoadingErrorIsVisible,
expectElementContainsField: expectElementContainsField,
diff --git a/lms/static/js/student_account/views/account_settings_factory.js b/lms/static/js/student_account/views/account_settings_factory.js
index d9d464f3e2..fc92c267da 100644
--- a/lms/static/js/student_account/views/account_settings_factory.js
+++ b/lms/static/js/student_account/views/account_settings_factory.js
@@ -20,7 +20,7 @@
) {
var accountSettingsElement, userAccountModel, userPreferencesModel, aboutSectionsData,
accountsSectionData, ordersSectionData, accountSettingsView, showAccountSettingsPage,
- showLoadingError, orderNumber;
+ showLoadingError, orderNumber, getUserField, userFields, timeZoneDropdownField, countryDropdownField;
accountSettingsElement = $('.wrapper-account-settings');
@@ -110,7 +110,7 @@
})
},
{
- view: new AccountSettingsFieldViews.DropdownFieldView({
+ view: new AccountSettingsFieldViews.TimeZoneFieldView({
model: userPreferencesModel,
required: true,
title: gettext('Time Zone'),
@@ -120,7 +120,10 @@
'time zone here, course dates, including assignment deadlines, are displayed in ' +
'Coordinated Universal Time (UTC).'
),
- options: fieldsData.time_zone.options,
+ groupOptions: [{
+ groupTitle: gettext('All Time Zones'),
+ selectOptions: fieldsData.time_zone.options
+ }],
persistChanges: true
})
}
@@ -169,6 +172,19 @@
}
];
+ // set TimeZoneField to listen to CountryField
+ getUserField = function(list, search) {
+ return _.find(list, function(field) {
+ return field.view.options.valueAttribute === search;
+ }).view;
+ };
+ userFields = _.find(aboutSectionsData, function(section) {
+ return section.title === gettext('Basic Account Information');
+ }).fields;
+ timeZoneDropdownField = getUserField(userFields, 'time_zone');
+ countryDropdownField = getUserField(userFields, 'country');
+ timeZoneDropdownField.listenToCountryView(countryDropdownField);
+
accountsSectionData = [
{
title: gettext('Linked Accounts'),
diff --git a/lms/static/js/student_account/views/account_settings_fields.js b/lms/static/js/student_account/views/account_settings_fields.js
index 4262fa7330..1e350b22cc 100644
--- a/lms/static/js/student_account/views/account_settings_fields.js
+++ b/lms/static/js/student_account/views/account_settings_fields.js
@@ -76,6 +76,67 @@
});
}
+ }),
+ TimeZoneFieldView: FieldViews.DropdownFieldView.extend({
+ fieldTemplate: field_dropdown_account_template,
+
+ initialize: function(options) {
+ this.options = _.extend({}, options);
+ _.bindAll(this, 'listenToCountryView', 'updateCountrySubheader', 'replaceOrAddGroupOption');
+ this._super(options); // eslint-disable-line no-underscore-dangle
+ },
+
+ listenToCountryView: function(view) {
+ this.listenTo(view.model, 'change:country', this.updateCountrySubheader);
+ },
+
+ updateCountrySubheader: function(user) {
+ var view = this;
+ $.ajax({
+ type: 'GET',
+ url: '/user_api/v1/preferences/time_zones/',
+ data: {country_code: user.attributes.country},
+ success: function(data) {
+ var countryTimeZones = $.map(data, function(timeZoneInfo) {
+ return [[timeZoneInfo.time_zone, timeZoneInfo.description]];
+ });
+ view.replaceOrAddGroupOption(
+ 'Country Time Zones',
+ countryTimeZones
+ );
+ view.render();
+ }
+ });
+ },
+
+ updateValueInField: function() {
+ var options;
+ if (this.modelValue()) {
+ options = [[this.modelValue(), this.displayValue(this.modelValue())]];
+ this.replaceOrAddGroupOption(
+ 'Currently Selected Time Zone',
+ options
+ );
+ }
+ this._super(); // eslint-disable-line no-underscore-dangle
+ },
+
+ replaceOrAddGroupOption: function(title, options) {
+ var groupOption = {
+ groupTitle: gettext(title),
+ selectOptions: options
+ };
+
+ var index = _.findIndex(this.options.groupOptions, function(group) {
+ return group.groupTitle === gettext(title);
+ });
+ if (index >= 0) {
+ this.options.groupOptions[index] = groupOption;
+ } else {
+ this.options.groupOptions.unshift(groupOption);
+ }
+ }
+
}),
PasswordFieldView: FieldViews.LinkFieldView.extend({
fieldType: 'button',
diff --git a/lms/static/js/views/fields.js b/lms/static/js/views/fields.js
index ccf44340f1..c67f185711 100644
--- a/lms/static/js/views/fields.js
+++ b/lms/static/js/views/fields.js
@@ -369,7 +369,8 @@
},
initialize: function(options) {
- _.bindAll(this, 'render', 'optionForValue', 'fieldValue', 'displayValue', 'updateValueInField', 'saveValue');
+ _.bindAll(this, 'render', 'optionForValue', 'fieldValue', 'displayValue', 'updateValueInField',
+ 'saveValue', 'createGroupOptions');
this._super(options);
this.listenTo(this.model, 'change:' + this.options.valueAttribute, this.updateValueInField);
@@ -385,7 +386,7 @@
titleVisible: this.options.titleVisible !== undefined ? this.options.titleVisible : true,
iconName: this.options.iconName,
showBlankOption: (!this.options.required || !this.modelValueIsSet()),
- selectOptions: this.options.options,
+ groupOptions: this.createGroupOptions(),
message: this.helpMessage
}));
this.delegateEvents();
@@ -407,7 +408,17 @@
},
optionForValue: function(value) {
- return _.find(this.options.options, function(option) { return option[0] === value; });
+ var options = [];
+ if (_.isUndefined(this.options.groupOptions)) {
+ return _.find(this.options.options, function(option) { return option[0] === value; });
+ } else {
+ _.each(this.options.groupOptions, function(groupOption) {
+ options = options.concat(groupOption.selectOptions);
+ });
+ return _.find(options, function(option) {
+ return option[0] === value;
+ });
+ }
},
fieldValue: function() {
@@ -483,6 +494,14 @@
if (this.editable !== 'never') {
this.$('.u-field-value select').prop('disabled', disable);
}
+ },
+
+ createGroupOptions: function() {
+ return !(_.isUndefined(this.options.groupOptions)) ? this.options.groupOptions :
+ [{
+ groupTitle: null,
+ selectOptions: this.options.options
+ }];
}
});
diff --git a/lms/templates/fields/field_dropdown.underscore b/lms/templates/fields/field_dropdown.underscore
index 8f0c61f5b2..097365c9dc 100644
--- a/lms/templates/fields/field_dropdown.underscore
+++ b/lms/templates/fields/field_dropdown.underscore
@@ -23,8 +23,13 @@
<% if (showBlankOption) { %>
<% } %>
- <% _.each(selectOptions, function(selectOption) { %>
-
+ <% _.each(groupOptions, function(groupOption) { %>
+ <% if (groupOption.groupTitle != null) { %>
+