Commit 9e2c5ab4 by asadiqbal

WL-399 Functional and UI changes

parent a6da7159
......@@ -25,7 +25,9 @@ var CourseDetails = Backbone.Model.extend({
course_image_asset_path: '', // the full URL (/c4x/org/course/num/asset/filename)
pre_requisite_courses: [],
entrance_exam_enabled : '',
entrance_exam_minimum_score_pct: '50'
entrance_exam_minimum_score_pct: '50',
learning_info: [],
instructor_info: {}
},
validate: function(newattrs) {
......
......@@ -35,7 +35,11 @@ define([
entrance_exam_enabled : '',
entrance_exam_minimum_score_pct: '50',
license: null,
language: ''
language: '',
learning_info: [],
instructor_info: {
'instructors': []
}
},
mockSettingsPage = readFixtures('mock/mock-settings-page.underscore');
......
// Backbone Application View: Instructor Information
define([ // jshint ignore:line
'jquery',
'underscore',
'backbone',
'gettext',
'js/utils/templates',
"js/models/uploads",
"js/views/uploads"
],
function ($, _, Backbone, gettext, TemplateUtils, FileUploadModel, FileUploadDialog) {
'use strict';
var InstructorInfoView = Backbone.View.extend({
events : {
'click .remove-instructor-data': 'removeInstructor',
'click .action-upload-instructor-image': "uploadInstructorImage"
},
initialize: function(options) {
// Set up the initial state of the attributes set for this model instance
_.bindAll(this, 'render');
this.template = this.loadTemplate('course-instructor-details');
this.model = options.model;
},
loadTemplate: function(name) {
// Retrieve the corresponding template for this model
return TemplateUtils.loadTemplate(name);
},
render: function() {
// Assemble the render view for this model.
$("span.course-instructor-details-fields").empty();
var self = this;
var instructors = this.model.get('instructor_info')['instructors'];
$.each(instructors, function( index, data ) {
$(self.el).append(self.template({
data: data,
index: index,
instructors: instructors.length
}));
});
},
removeInstructor: function(event) {
/*
* Remove course Instructor fields.
* */
event.preventDefault();
var index = event.currentTarget.getAttribute('data-index'),
existing_info = _.clone(this.model.get('instructor_info'));
existing_info['instructors'].splice(index, 1);
this.model.set('instructor_info', existing_info);
this.model.trigger("change:instructor_info", this.model );
this.render();
},
uploadInstructorImage: function(event) {
/*
* Upload instructor image.
* */
event.preventDefault();
var index = event.currentTarget.getAttribute('data-index'),
info = _.clone(this.model.get('instructor_info')),
instructor = info.instructors[index];
var upload = new FileUploadModel({
title: gettext("Upload instructor image."),
message: gettext("Files must be in JPEG or PNG format."),
mimeTypes: ['image/jpeg', 'image/png']
});
var self = this;
var modal = new FileUploadDialog({
model: upload,
onSuccess: function(response) {
var options = {
'instructor_image_asset_path': response.asset.url
};
instructor.image = options.instructor_image_asset_path;
self.model.set('instructor_info', info);
self.model.trigger("change:instructor_info");
self.render();
}
});
modal.show();
}
});
return InstructorInfoView;
});
// Backbone Application View: Course Learning Information
define([ // jshint ignore:line
'jquery',
'underscore',
'backbone',
'gettext',
'js/utils/templates'
],
function ($, _, Backbone, gettext, TemplateUtils) {
'use strict';
var LearningInfoView = Backbone.View.extend({
events: {
'click .delete-course-learning-info': "removeLearningInfo"
},
initialize: function(options) {
// Set up the initial state of the attributes set for this model instance
_.bindAll(this, 'render');
this.template = this.loadTemplate('course-settings-learning-fields');
this.model = options.model;
},
loadTemplate: function(name) {
// Retrieve the corresponding template for this model
return TemplateUtils.loadTemplate(name);
},
render: function() {
// rendering for this model
$("li.course-settings-learning-fields").empty();
var self = this;
var learning_information = this.model.get('learning_info');
$.each(learning_information, function( index, info ) {
$(self.el).append(self.template({index: index, info: info, info_count: learning_information.length }));
});
},
removeLearningInfo: function(event) {
/*
* Remove course learning fields.
* */
event.preventDefault();
var index = event.currentTarget.getAttribute('data-index'),
existing_info = _.clone(this.model.get('learning_info'));
existing_info.splice(index, 1);
this.model.set('learning_info', existing_info);
this.render();
}
});
return LearningInfoView;
});
define(["js/views/validation", "codemirror", "underscore", "jquery", "jquery.ui", "js/utils/date_utils", "js/models/uploads",
"js/views/uploads", "js/views/license", "js/models/license",
define(["js/views/validation", "codemirror", "underscore", "jquery", "jquery.ui", "js/utils/date_utils",
"js/models/uploads", "js/views/uploads", "js/views/license", "js/models/license",
"common/js/components/views/feedback_notification", "jquery.timepicker", "date", "gettext",
'edx-ui-toolkit/js/utils/string-utils'],
"js/views/learning_info", "js/views/instructor_info", "edx-ui-toolkit/js/utils/string-utils"],
function(ValidatingView, CodeMirror, _, $, ui, DateUtils, FileUploadModel,
FileUploadDialog, LicenseView, LicenseModel, NotificationView,
timepicker, date, gettext, StringUtils) {
timepicker, date, gettext, LearningInfoView, InstructorInfoView, StringUtils) {
var DetailsView = ValidatingView.extend({
// Model class is CMS.Models.Settings.CourseDetails
......@@ -21,7 +21,9 @@ var DetailsView = ValidatingView.extend({
// would love to move to a general superclass, but event hashes don't inherit in backbone :-(
'focus :input' : "inputFocus",
'blur :input' : "inputUnfocus",
'click .action-upload-image': "uploadImage"
'click .action-upload-image': "uploadImage",
'click .add-course-learning-info': "addLearningFields",
'click .add-course-instructor-info': "addInstructorFields"
},
initialize : function(options) {
......@@ -43,6 +45,8 @@ var DetailsView = ValidatingView.extend({
this.listenTo(this.model, 'invalid', this.handleValidationError);
this.listenTo(this.model, 'change', this.showNotificationBar);
this.listenTo(this.model, 'change:instructor_info', this.showNotificationBar);
this.listenTo(this.model, 'change:learning_info', this.showNotificationBar);
this.selectorToField = _.invert(this.fieldToSelectorMap);
// handle license separately, to avoid reimplementing view logic
this.licenseModel = new LicenseModel({"asString": this.model.get('license')});
......@@ -60,6 +64,16 @@ var DetailsView = ValidatingView.extend({
closeIcon: true
}).show();
}
this.learning_info_view = new LearningInfoView({
el: $(".course-settings-learning-fields"),
model: this.model
});
this.instructor_info_view = new InstructorInfoView({
el: $(".course-instructor-details-fields"),
model: this.model
});
},
render: function() {
......@@ -126,6 +140,8 @@ var DetailsView = ValidatingView.extend({
}
this.licenseView.render();
this.learning_info_view.render();
this.instructor_info_view.render();
return this;
},
......@@ -146,7 +162,36 @@ var DetailsView = ValidatingView.extend({
'course_image_asset_path': 'course-image-url',
'pre_requisite_courses': 'pre-requisite-course',
'entrance_exam_enabled': 'entrance-exam-enabled',
'entrance_exam_minimum_score_pct': 'entrance-exam-minimum-score-pct'
'entrance_exam_minimum_score_pct': 'entrance-exam-minimum-score-pct',
'course_settings_learning_fields': 'course-settings-learning-fields',
'add_course_learning_info': 'add-course-learning-info',
'add_course_instructor_info': 'add-course-instructor-info',
'course_learning_info': 'course-learning-info'
},
addLearningFields: function() {
/*
* Add new course learning fields.
* */
var existing_info = _.clone(this.model.get('learning_info')),
info = '';
existing_info[existing_info.length] = info;
this.model.set('learning_info', existing_info);
this.learning_info_view.render();
},
addInstructorFields: function() {
/*
* Add new course instructor fields.
* */
var existing_info = _.clone(this.model.get('instructor_info')),
index = existing_info.instructors.length,
data = JSON.parse('{"name": "","title": "","organization": "","image": "","bio": ""}');
existing_info.instructors[index] = data;
this.model.set('instructor_info', existing_info);
this.model.trigger("change:instructor_info");
this.instructor_info_view.render();
},
updateTime : function(e) {
......@@ -164,7 +209,29 @@ var DetailsView = ValidatingView.extend({
$(e.currentTarget).attr('title', currentTimeText);
},
updateModel: function(event) {
var value;
var index = event.currentTarget.getAttribute('data-index');
switch (event.currentTarget.id) {
case 'course-learning-info-' + index:
value = $(event.currentTarget).val();
var learning_info = _.clone(this.model.get('learning_info'));
learning_info[index] = value;
this.model.set('learning_info', learning_info);
break;
case 'course-instructor-name-' + index:
case 'course-instructor-title-' + index:
case 'course-instructor-organization-' + index:
case 'course-instructor-bio-' + index:
value = $(event.currentTarget).val();
var field = event.currentTarget.getAttribute('data-field'),
instructor_info = _.clone(this.model.get('instructor_info'));
instructor_info['instructors'][index][field] = value;
this.model.set('instructor_info', instructor_info);
this.model.trigger("change:instructor_info");
break;
case 'course-language':
this.setField(event);
break;
case 'course-image-url':
this.setField(event);
var url = $(event.currentTarget).val();
......
......@@ -838,6 +838,184 @@
}
}
.add-new-data {
background-color: $green;
border-color: $green;
color: $white;
display: inline-block;
padding: 10px 20px;
border-radius: 3px;
margin-bottom: 10px;
&:hover{
background-color: $green-l1;
border-color: $green-l1;
box-shadow: 0 2px 1px rgba(0,0,0,0.2);
}
&.add-course-learning-info {
margin-top: 10px;
}
}
&.course-learning-info {
.list-input {
.course-settings-learning-fields {
.field {
.input-learning-info {
width: 88%;
display: inline-block;
}
.input-learning-info-stretch {
width: 100%;
display: inline-block;
}
input[type="button"] {
width: 10%;
margin-left: 1%;
font-size: 14px;
display: inline-block;
}
}
}
}
}
&.instructor-types {
.list-input {
.course-instructor-details-fields {
.field {
width: 30%;
&.field-course-instructor-bio {
width: 95%;
}
&.current-instructor-image {
width: 100%;
text-align: left;
padding: 0;
.action-upload-instructor-image {
float: right;
width: 20%;
border-radius: 5px;
background-color: $white;
border: 1px solid lighten($blue, 24%);
padding: 10px 20px;
color: lighten($blue, 24%);
font-weight: 600;
font-size: 12px;
margin-top: 5px;
&:hover {
background-color: lighten($blue, 24%);
color: $white;
}
}
.wrapper-instructor-image {
margin: 15px auto;
}
.new-instructor-image-url {
width: 65%;
display: inline-block;
}
}
}
}
&:last-child {
margin-bottom: 0;
}
}
.field-group {
@include clearfix();
width: flex-grid(9, 9);
margin-bottom: ($baseline*1.5);
border-bottom: 1px solid $gray-l5;
padding-bottom: ($baseline*1.5);
&:last-child {
border: none;
padding-bottom: 0;
}
.field {
display: inline-block;
vertical-align: top;
width: flex-grid(3, 6);
margin-bottom: ($baseline/2);
margin-right: flex-gutter();
}
// specific fields - course image
.field-course-instructor-image {
margin-bottom: ($baseline/2);
padding: ($baseline/2) $baseline;
background: $gray-l5;
text-align: center;
.wrapper-instructor-image {
display: block;
width: 375px;
height: 200px;
overflow: hidden;
margin: 0 auto;
border: 1px solid $gray-l4;
box-shadow: 0 1px 1px $shadow-l1;
padding: ($baseline/2);
background: $white;
}
.instructor-image {
display: block;
width: 100%;
min-height: 100%;
}
.msg {
@extend %t-copy-sub2;
display: block;
margin-top: ($baseline/2);
color: $gray-l3;
}
}
.field-course-instructor-name,
.field-course-instructor-title,
.field-course-instructor-bio,
.field-course-instructor-organization {
width: flex-grid(2, 4);
}
.wrapper-input {
@include clearfix();
width: flex-grid(9,9);
.input {
float: left;
width: flex-grid(6,9);
margin-right: flex-gutter();
}
.action-upload-image {
@extend %ui-btn-flat-outline;
float: right;
width: flex-grid(2,9);
margin-top: ($baseline/4);
padding: ($baseline/2) $baseline;
}
}
}
.actions {
float: left;
width: flex-grid(9, 9);
.new-button {
@extend %btn-primary-green;
}
.delete-button {
margin: 0;
}
}
}
// specific fields - advanced settings
&.advanced-policies {
......
<li class="field-group">
<div class="field text field-course-instructor-name">
<label for="course-instructor-name-<%- index %>"><%= gettext("Name") %></label>
<input type="text" class="long" id="course-instructor-name-<%- index %>" value="<%- data['name'] %>" data-index=<%- index %> data-field="name" placeholder="<%= gettext('Instructor Name') %>" />
<span class="tip tip-stacked"><%= gettext("Please provide a instructor name")%></span>
</div>
<div class="field text field-course-instructor-title">
<label for="course-instructor-title-<%- index %>"><%= gettext("Title") %></label>
<input type="text" class="long" id="course-instructor-title-<%- index %>" value="<%- data['title'] %>" data-index=<%- index %> data-field="title" placeholder="<%= gettext('Instructor Title') %>" />
<span class="tip tip-stacked"><%= gettext("Please provide a instructor title")%></span>
</div>
<div class="field text field-course-instructor-organization">
<label for="course-instructor-organization-<%- index %>"><%= gettext("Organization") %></label>
<input type="text" class="long" id="course-instructor-organization-<%- index %>" value = "<%- data['organization'] %>" data-index=<%- index %> data-field="organization" placeholder="<%= gettext('Organization Name') %>" />
<span class="tip tip-stacked"><%= gettext("Please provide a valid organization name")%></span>
</div>
<div class="field text field-course-instructor-bio">
<label for="course-instructor-bio-<%- index %>"><%= gettext("Bio") %></label>
<textarea class="short text" id="course-instructor-bio-<%- index %>" data-index=<%- index %> data-field="bio" placeholder="<%= gettext('Instructor Bio Details') %>" ><%- data['bio'] %></textarea>
<span class="tip tip-stacked"><%= gettext("Please provide a instructor bio details")%></span>
</div>
<div class="field image field-course-instructor-image current-instructor-image">
<label for="course-instructor-image-<%- index %>"><%= gettext("Image") %></label>
<span class="wrapper-instructor-image">
<img class="instructor-image" id="instructor-image" src="<%= data['image']%>" alt="<%= gettext('Instructor Image') %>" />
</span>
<input type="text" dir="ltr" class="long new-instructor-image-url" id="course-instructor-image-<%- index %>" value="<%- data['image'] %>" data-field="image" placeholder="<%= gettext('Instructor Image URL') %>" autocomplete="off" />
<button type="button" class="action action-upload-instructor-image" data-index=<%- index %>><%= gettext("Upload Image") %></button>
<span class="tip tip-stacked"><%= gettext("Please provide a valid path and name to your image (Note: only JPEG or PNG format supported)")%></span>
</div>
<% if (instructors > 1 ) { %>
<div class="actions">
<a href="#" class="button delete-button standard remove-item remove-instructor-data" data-index=<%- index %>><span class="delete-icon"></span><%= gettext("Delete") %></a>
</div>
<% } %>
</li>
\ No newline at end of file
<div class="field text" id="fields-course-learning-info-<%- index %>">
<label for= "course-learning-info-<%- index %>"> <%- gettext("What You'll Learn") %></label>
<input type="text" class=<% if (info_count > 1) { %>"input-learning-info" <% } else { %> "input-learning-info-stretch" <% } %> id="course-learning-info-<%- index %>" value="<%- info %>" data-index=<%- index %>>
<% if (info_count > 1 ) { %>
<input type="button" value='Delete' class="button delete-button standard remove-item delete-course-learning-info" data-index=<%- index %>>
<% } %>
</div>
\ No newline at end of file
......@@ -107,4 +107,36 @@
</li>
</ol>
</section>
<section class="group-settings course-learning-info">
<header>
<h2 class="title-2">Course Learning Information</h2>
<span class="tip">Provide the course learning details</span>
</header>
<ol class="list-input enum">
<li class="course-settings-learning-fields">
<div class="field text" id="fields-course-learning-info-0">
<label for="course-learning-info-0" class=""> What You'll Learn</label>
<input type="text" class="input-learning-info-stretch" id="course-learning-info-0">
</div>
</li>
<li>
<a href="javascript:void(0)" class="add-course-learning-info">Add Learning Details</a></li>
</ol>
</section>
<section class="group-settings instructor-types">
<header>
<h2 class="title-2">Course Instructors Information</h2>
<span class="tip">Information for course instructors</span>
</header>
<ol class="list-input enum">
<span class="course-instructor-details-fields">
</span>
<li>
<a href="javascript:void(0)" class="add-course-instructor-info add-new-data">
Add Instructor Details</a>
</li>
</ol>
</section>
</form>
......@@ -14,7 +14,7 @@
%>
<%block name="header_extras">
% for template_name in ["basic-modal", "modal-button", "upload-dialog", "license-selector"]:
% for template_name in ["basic-modal", "modal-button", "upload-dialog", "license-selector", "course-settings-learning-fields", "course-instructor-details"]:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="js/${template_name}.underscore" />
</script>
......@@ -386,10 +386,38 @@ CMS.URL.UPLOAD_ASSET = '${upload_asset_url}'
<span class="tip tip-stacked">${_("Enter your YouTube video's ID (along with any restriction parameters)")}</span>
</div>
</li>
% endif
% endif
</ol>
</section>
<hr class="divide" />
<section class="group-settings course-learning-info">
<header>
<h2 class="title-2">${_("Course Learning Information")}</h2>
<span class="tip">${_("Provide the course learning details")}</span>
</header>
<ol class="list-input enum">
<li class="course-settings-learning-fields"></li>
<li>
<a href="javascript:void(0)" class="action action-primary button new-button add-new-data add-course-learning-info">
<i class="icon fa fa-plus icon-inline"></i>${_("Add Learning Details")}</a>
</li>
</ol>
</section>
<hr class="divide" />
<section class="group-settings instructor-types">
<header>
<h2 class="title-2">${_("Course Instructors Information")}</h2>
<span class="tip">${_("Information for course instructors")}</span>
</header>
<ol class="list-input enum">
<span class="course-instructor-details-fields"></span>
<li>
<a href="javascript:void(0)" class="action action-primary button new-button add-course-instructor-info add-new-data">
<i class="icon fa fa-plus icon-inline"></i>${_("Add Instructor Details")}</a>
</li>
</ol>
</section>
% if about_page_editable or is_prerequisite_courses_enabled or is_entrance_exams_enabled:
<hr class="divide" />
......
......@@ -774,6 +774,30 @@ class CourseFields(object):
scope=Scope.settings
)
learning_info = List(
display_name=_("Course Learning Information"),
help=_("Specify what student can learn from the course."),
default=[''],
scope=Scope.settings
)
instructor_info = Dict(
display_name=_("Course Instructor"),
help=_("Enter the details for Course Instructor"),
default={
"instructors": [
{
"name": "",
"title": "",
"organization": "",
"image": "",
"bio": "",
},
]
},
scope=Scope.settings
)
class CourseModule(CourseFields, SequenceModule): # pylint: disable=abstract-method
"""
......
......@@ -217,4 +217,6 @@ class AdvancedSettingsPage(CoursePage):
'enable_proctored_exams',
'enable_timed_exams',
'enable_subsection_gating',
'learning_info',
'instructor_info'
]
......@@ -66,6 +66,8 @@ class CourseDetails(object):
'50'
) # minimum passing score for entrance exam content module/tree,
self.self_paced = None
self.learning_info = []
self.instructor_info = []
@classmethod
def fetch_about_attribute(cls, course_key, attribute):
......@@ -100,6 +102,8 @@ class CourseDetails(object):
course_details.course_image_asset_path = course_image_url(descriptor)
course_details.language = descriptor.language
course_details.self_paced = descriptor.self_paced
course_details.learning_info = descriptor.learning_info
course_details.instructor_info = descriptor.instructor_info
# Default course license is "All Rights Reserved"
course_details.license = getattr(descriptor, "license", "all-rights-reserved")
......@@ -226,6 +230,14 @@ class CourseDetails(object):
descriptor.license = jsondict['license']
dirty = True
if 'learning_info' in jsondict:
descriptor.learning_info = jsondict['learning_info']
dirty = True
if 'instructor_info' in jsondict:
descriptor.instructor_info = jsondict['instructor_info']
dirty = True
if 'language' in jsondict and jsondict['language'] != descriptor.language:
descriptor.language = jsondict['language']
dirty = True
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment