Commit 7925ca1e by Anton Stupak

Merge pull request #1873 from edx/anton/transcripts-start-end-time

Video: Update behavior of start/end time fields.
parents bc625ab5 70518a46
...@@ -5,6 +5,8 @@ These are notable changes in edx-platform. This is a rolling list of changes, ...@@ -5,6 +5,8 @@ These are notable changes in edx-platform. This is a rolling list of changes,
in roughly chronological order, most recent first. Add your entries at or near in roughly chronological order, most recent first. Add your entries at or near
the top. Include a label indicating the component affected. the top. Include a label indicating the component affected.
Blades: Update behavior of start/end time fields. BLD-506.
Blades: Make LTI module not send grade_back_url if has_score=False. BLD-561. Blades: Make LTI module not send grade_back_url if has_score=False. BLD-561.
Blades: Show answer for imageresponse. BLD-21. Blades: Show answer for imageresponse. BLD-21.
......
...@@ -230,8 +230,18 @@ def open_tab(_step, tab_name): ...@@ -230,8 +230,18 @@ def open_tab(_step, tab_name):
@step('I set value "([^"]*)" to the field "([^"]*)"$') @step('I set value "([^"]*)" to the field "([^"]*)"$')
def set_value_transcripts_field(_step, value, field_name): def set_value_transcripts_field(_step, value, field_name):
field_id = '#' + world.browser.find_by_xpath('//label[text()="%s"]' % field_name.strip())[0]['for'] XPATH = '//label[text()="{name}"]'.format(name=field_name)
world.css_fill(field_id, value.strip()) SELECTOR = '#' + world.browser.find_by_xpath(XPATH)[0]['for']
element = world.css_find(SELECTOR).first
if element['type'] == 'text':
SCRIPT = '$("{selector}").val("{value}").change()'.format(
selector=SELECTOR,
value=value
)
world.browser.execute_script(SCRIPT)
assert world.css_has_value(SELECTOR, value)
else:
assert False, 'Incorrect element type.';
world.wait_for_ajax_complete() world.wait_for_ajax_complete()
......
...@@ -18,7 +18,6 @@ requirejs.config({ ...@@ -18,7 +18,6 @@ requirejs.config({
"jquery.iframe-transport": "xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.iframe-transport", "jquery.iframe-transport": "xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.iframe-transport",
"jquery.inputnumber": "xmodule_js/common_static/js/vendor/html5-input-polyfills/number-polyfill", "jquery.inputnumber": "xmodule_js/common_static/js/vendor/html5-input-polyfills/number-polyfill",
"jquery.immediateDescendents": "xmodule_js/common_static/coffee/src/jquery.immediateDescendents", "jquery.immediateDescendents": "xmodule_js/common_static/coffee/src/jquery.immediateDescendents",
"jquery.maskedinput": "xmodule_js/common_static/js/vendor/jquery.maskedinput.min",
"datepair": "xmodule_js/common_static/js/vendor/timepicker/datepair", "datepair": "xmodule_js/common_static/js/vendor/timepicker/datepair",
"date": "xmodule_js/common_static/js/vendor/date", "date": "xmodule_js/common_static/js/vendor/date",
"underscore": "xmodule_js/common_static/js/vendor/underscore-min", "underscore": "xmodule_js/common_static/js/vendor/underscore-min",
...@@ -98,10 +97,6 @@ requirejs.config({ ...@@ -98,10 +97,6 @@ requirejs.config({
deps: ["jquery"], deps: ["jquery"],
exports: "jQuery.fn.inputNumber" exports: "jQuery.fn.inputNumber"
}, },
"jquery.maskedinput": {
deps: ["jquery"],
exports: "jQuery.fn.mask"
},
"jquery.tinymce": { "jquery.tinymce": {
deps: ["jquery", "tinymce"], deps: ["jquery", "tinymce"],
exports: "jQuery.fn.tinymce" exports: "jQuery.fn.tinymce"
......
...@@ -391,8 +391,95 @@ define ["js/models/metadata", "js/collections/metadata", "js/views/metadata", "c ...@@ -391,8 +391,95 @@ define ["js/models/metadata", "js/collections/metadata", "js/views/metadata", "c
it "returns the intial value upon initialization", -> it "returns the intial value upon initialization", ->
assertValueInView(@view, '12:12:12') assertValueInView(@view, '12:12:12')
it "value is converted correctly", ->
view = @view
cases = [
{
input: '23:100:0'
output: '23:59:59'
},
{
input: '100000000000000000'
output: '23:59:59'
},
{
input: '80000'
output: '22:13:20'
},
{
input: '-100'
output: '00:00:00'
},
{
input: '-100:-10'
output: '00:00:00'
},
{
input: '99:99'
output: '01:40:39'
},
{
input: '2'
output: '00:00:02'
},
{
input: '1:2'
output: '00:01:02'
},
{
input: '1:25'
output: '00:01:25'
},
{
input: '3:1:25'
output: '03:01:25'
},
{
input: ' 2 3 : 5 9 : 5 9 '
output: '23:59:59'
},
{
input: '9:1:25'
output: '09:01:25'
},
{
input: '77:72:77'
output: '23:59:59'
},
{
input: '22:100:100'
output: '23:41:40'
},
# negative value
{
input: '-22:22:22'
output: '00:22:22'
},
# simple string
{
input: 'simple text'
output: '00:00:00'
},
{
input: 'a10a:a10a:a10a'
output: '00:00:00'
},
# empty string
{
input: ''
output: '00:00:00'
}
]
$.each cases, (index, data) ->
expect(view.parseRelativeTime(data.input)).toBe(data.output)
it "can update its value in the view", -> it "can update its value in the view", ->
assertCanUpdateView(@view, "23:59:59") assertCanUpdateView(@view, "23:59:59")
@view.setValueInEditor("33:59:59")
@view.updateModel()
assertValueInView(@view, "23:59:59")
it "has a clear method to revert to the model default", -> it "has a clear method to revert to the model default", ->
assertClear(@view, '00:00:00') assertClear(@view, '00:00:00')
......
define( define(
[ [
"backbone", "underscore", "js/models/metadata", "js/views/abstract_editor", "backbone", "underscore", "js/models/metadata", "js/views/abstract_editor",
"js/views/transcripts/metadata_videolist", "jquery.maskedinput" "js/views/transcripts/metadata_videolist"
], ],
function(Backbone, _, MetadataModel, AbstractEditor, VideoList) { function(Backbone, _, MetadataModel, AbstractEditor, VideoList) {
var Metadata = {}; var Metadata = {};
...@@ -301,6 +301,11 @@ function(Backbone, _, MetadataModel, AbstractEditor, VideoList) { ...@@ -301,6 +301,11 @@ function(Backbone, _, MetadataModel, AbstractEditor, VideoList) {
Metadata.RelativeTime = AbstractEditor.extend({ Metadata.RelativeTime = AbstractEditor.extend({
defaultValue : '00:00:00',
// By default max value of RelativeTime field on Backend is 23:59:59,
// that is 86399 seconds.
maxTimeInSeconds : 86399,
events : { events : {
"change input" : "updateModel", "change input" : "updateModel",
"keypress .setting-input" : "showClearButton" , "keypress .setting-input" : "showClearButton" ,
...@@ -309,46 +314,60 @@ function(Backbone, _, MetadataModel, AbstractEditor, VideoList) { ...@@ -309,46 +314,60 @@ function(Backbone, _, MetadataModel, AbstractEditor, VideoList) {
templateName: "metadata-string-entry", templateName: "metadata-string-entry",
initialize: function () { getValueFromEditor : function () {
AbstractEditor.prototype.initialize.apply(this); var $input = this.$el.find('#' + this.uniqueId);
// This list of definitions is used for creating appropriate
// time format mask;
//
// For example, mask 'hH:mM:sS':
// min value: 00:00:00
// max value: 23:59:59
//
// With this mask user cannot set following values:
// 93:23:23, 23:60:60, 77:77:77, etc.
var definitions = {
h: '[0-2]',
H: '[0-3]',
m: '[0-5]',
s: '[0-5]',
M: '[0-9]',
S: '[0-9]'
};
$.each(definitions, function(key, value) {
$.mask.definitions[key] = value;
});
this.$el return $input.val();
.find('#' + this.uniqueId)
.mask('hH:mM:sS', { placeholder: '0' });
}, },
getValueFromEditor : function () { updateModel: function () {
var $input = this.$el.find('#' + this.uniqueId), var value = this.getValueFromEditor(),
value = $input.val(); time = this.parseRelativeTime(value);
this.model.setValue(time);
// Sometimes, `parseRelativeTime` method returns the same value for
// the different inputs. In this case, model will not be
// updated (it already has the same value) and we should
// call `render` method manually.
// Examples:
// value => 23:59:59; parseRelativeTime => 23:59:59
// value => 44:59:59; parseRelativeTime => 23:59:59
if (value !== time && !this.model.hasChanged('value')) {
this.render();
}
},
return value; parseRelativeTime: function (value) {
// This function ensure you have two-digits
var pad = function (number) {
return (number < 10) ? "0" + number : number;
},
// Removes all white-spaces and splits by `:`.
list = value.replace(/\s+/g, '').split(':'),
seconds, date;
list = _.map(list, function(num) {
return Math.max(0, parseInt(num, 10) || 0);
}).reverse();
seconds = _.reduce(list, function(memo, num, index) {
return memo + num * Math.pow(60, index);
}, 0);
// multiply by 1000 because Date() requires milliseconds
date = new Date(Math.min(seconds, this.maxTimeInSeconds) * 1000);
return [
pad(date.getUTCHours()),
pad(date.getUTCMinutes()),
pad(date.getUTCSeconds())
].join(':');
}, },
setValueInEditor : function (value) { setValueInEditor : function (value) {
if (!value) { if (!value) {
value = '00:00:00'; value = this.defaultValue;
} }
this.$el.find('input').val(value); this.$el.find('input').val(value);
......
...@@ -49,7 +49,6 @@ lib_paths: ...@@ -49,7 +49,6 @@ lib_paths:
- xmodule_js/common_static/js/vendor/jasmine-stealth.js - xmodule_js/common_static/js/vendor/jasmine-stealth.js
- xmodule_js/common_static/js/vendor/jasmine-imagediff.js - xmodule_js/common_static/js/vendor/jasmine-imagediff.js
- xmodule_js/common_static/js/vendor/jasmine.async.js - xmodule_js/common_static/js/vendor/jasmine.async.js
- xmodule_js/common_static/js/vendor/jquery.maskedinput.min.js
- xmodule_js/common_static/js/vendor/CodeMirror/codemirror.js - xmodule_js/common_static/js/vendor/CodeMirror/codemirror.js
- xmodule_js/src/xmodule.js - xmodule_js/src/xmodule.js
- xmodule_js/common_static/js/test/i18n.js - xmodule_js/common_static/js/test/i18n.js
......
...@@ -58,7 +58,6 @@ ...@@ -58,7 +58,6 @@
"jquery.qtip": "js/vendor/jquery.qtip.min", "jquery.qtip": "js/vendor/jquery.qtip.min",
"jquery.scrollTo": "js/vendor/jquery.scrollTo-1.4.2-min", "jquery.scrollTo": "js/vendor/jquery.scrollTo-1.4.2-min",
"jquery.flot": "js/vendor/flot/jquery.flot.min", "jquery.flot": "js/vendor/flot/jquery.flot.min",
"jquery.maskedinput": "js/vendor/jquery.maskedinput.min",
"jquery.fileupload": "js/vendor/jQuery-File-Upload/js/jquery.fileupload", "jquery.fileupload": "js/vendor/jQuery-File-Upload/js/jquery.fileupload",
"jquery.iframe-transport": "js/vendor/jQuery-File-Upload/js/jquery.iframe-transport", "jquery.iframe-transport": "js/vendor/jQuery-File-Upload/js/jquery.iframe-transport",
"jquery.inputnumber": "js/vendor/html5-input-polyfills/number-polyfill", "jquery.inputnumber": "js/vendor/html5-input-polyfills/number-polyfill",
...@@ -138,10 +137,6 @@ ...@@ -138,10 +137,6 @@
deps: ["jquery"], deps: ["jquery"],
exports: "jQuery.fn.plot" exports: "jQuery.fn.plot"
}, },
"jquery.maskedinput": {
deps: ["jquery"],
exports: "jQuery.fn.mask"
},
"jquery.fileupload": { "jquery.fileupload": {
deps: ["jquery.iframe-transport"], deps: ["jquery.iframe-transport"],
exports: "jQuery.fn.fileupload" exports: "jQuery.fn.fileupload"
......
...@@ -81,13 +81,13 @@ class VideoFields(object): ...@@ -81,13 +81,13 @@ class VideoFields(object):
default="" default=""
) )
start_time = RelativeTime( # datetime.timedelta object start_time = RelativeTime( # datetime.timedelta object
help="Start time for the video (HH:MM:SS).", help="Start time for the video (HH:MM:SS). Max value is 23:59:59.",
display_name="Start Time", display_name="Start Time",
scope=Scope.settings, scope=Scope.settings,
default=datetime.timedelta(seconds=0) default=datetime.timedelta(seconds=0)
) )
end_time = RelativeTime( # datetime.timedelta object end_time = RelativeTime( # datetime.timedelta object
help="End time for the video (HH:MM:SS).", help="End time for the video (HH:MM:SS). Max value is 23:59:59.",
display_name="End Time", display_name="End Time",
scope=Scope.settings, scope=Scope.settings,
default=datetime.timedelta(seconds=0) default=datetime.timedelta(seconds=0)
......
/*
Masked Input plugin for jQuery
Copyright (c) 2007-2013 Josh Bush (digitalbush.com)
Licensed under the MIT license (http://digitalbush.com/projects/masked-input-plugin/#license)
Version: 1.3.1
*/
(function(e){function t(){var e=document.createElement("input"),t="onpaste";return e.setAttribute(t,""),"function"==typeof e[t]?"paste":"input"}var n,a=t()+".mask",r=navigator.userAgent,i=/iphone/i.test(r),o=/android/i.test(r);e.mask={definitions:{9:"[0-9]",a:"[A-Za-z]","*":"[A-Za-z0-9]"},dataName:"rawMaskFn",placeholder:"_"},e.fn.extend({caret:function(e,t){var n;if(0!==this.length&&!this.is(":hidden"))return"number"==typeof e?(t="number"==typeof t?t:e,this.each(function(){this.setSelectionRange?this.setSelectionRange(e,t):this.createTextRange&&(n=this.createTextRange(),n.collapse(!0),n.moveEnd("character",t),n.moveStart("character",e),n.select())})):(this[0].setSelectionRange?(e=this[0].selectionStart,t=this[0].selectionEnd):document.selection&&document.selection.createRange&&(n=document.selection.createRange(),e=0-n.duplicate().moveStart("character",-1e5),t=e+n.text.length),{begin:e,end:t})},unmask:function(){return this.trigger("unmask")},mask:function(t,r){var c,l,s,u,f,h;return!t&&this.length>0?(c=e(this[0]),c.data(e.mask.dataName)()):(r=e.extend({placeholder:e.mask.placeholder,completed:null},r),l=e.mask.definitions,s=[],u=h=t.length,f=null,e.each(t.split(""),function(e,t){"?"==t?(h--,u=e):l[t]?(s.push(RegExp(l[t])),null===f&&(f=s.length-1)):s.push(null)}),this.trigger("unmask").each(function(){function c(e){for(;h>++e&&!s[e];);return e}function d(e){for(;--e>=0&&!s[e];);return e}function m(e,t){var n,a;if(!(0>e)){for(n=e,a=c(t);h>n;n++)if(s[n]){if(!(h>a&&s[n].test(R[a])))break;R[n]=R[a],R[a]=r.placeholder,a=c(a)}b(),x.caret(Math.max(f,e))}}function p(e){var t,n,a,i;for(t=e,n=r.placeholder;h>t;t++)if(s[t]){if(a=c(t),i=R[t],R[t]=n,!(h>a&&s[a].test(i)))break;n=i}}function g(e){var t,n,a,r=e.which;8===r||46===r||i&&127===r?(t=x.caret(),n=t.begin,a=t.end,0===a-n&&(n=46!==r?d(n):a=c(n-1),a=46===r?c(a):a),k(n,a),m(n,a-1),e.preventDefault()):27==r&&(x.val(S),x.caret(0,y()),e.preventDefault())}function v(t){var n,a,i,l=t.which,u=x.caret();t.ctrlKey||t.altKey||t.metaKey||32>l||l&&(0!==u.end-u.begin&&(k(u.begin,u.end),m(u.begin,u.end-1)),n=c(u.begin-1),h>n&&(a=String.fromCharCode(l),s[n].test(a)&&(p(n),R[n]=a,b(),i=c(n),o?setTimeout(e.proxy(e.fn.caret,x,i),0):x.caret(i),r.completed&&i>=h&&r.completed.call(x))),t.preventDefault())}function k(e,t){var n;for(n=e;t>n&&h>n;n++)s[n]&&(R[n]=r.placeholder)}function b(){x.val(R.join(""))}function y(e){var t,n,a=x.val(),i=-1;for(t=0,pos=0;h>t;t++)if(s[t]){for(R[t]=r.placeholder;pos++<a.length;)if(n=a.charAt(pos-1),s[t].test(n)){R[t]=n,i=t;break}if(pos>a.length)break}else R[t]===a.charAt(pos)&&t!==u&&(pos++,i=t);return e?b():u>i+1?(x.val(""),k(0,h)):(b(),x.val(x.val().substring(0,i+1))),u?t:f}var x=e(this),R=e.map(t.split(""),function(e){return"?"!=e?l[e]?r.placeholder:e:void 0}),S=x.val();x.data(e.mask.dataName,function(){return e.map(R,function(e,t){return s[t]&&e!=r.placeholder?e:null}).join("")}),x.attr("readonly")||x.one("unmask",function(){x.unbind(".mask").removeData(e.mask.dataName)}).bind("focus.mask",function(){clearTimeout(n);var e;S=x.val(),e=y(),n=setTimeout(function(){b(),e==t.length?x.caret(0,e):x.caret(e)},10)}).bind("blur.mask",function(){y(),x.val()!=S&&x.change()}).bind("keydown.mask",g).bind("keypress.mask",v).bind(a,function(){setTimeout(function(){var e=y(!0);x.caret(e),r.completed&&e==x.val().length&&r.completed.call(x)},0)}),y()}))}})})(jQuery);
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