BLD-506: Update behavior of start/end time fields.
This commit is contained in:
@@ -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
|
||||
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: Show answer for imageresponse. BLD-21.
|
||||
|
||||
@@ -230,8 +230,18 @@ def open_tab(_step, tab_name):
|
||||
|
||||
@step('I set value "([^"]*)" to the field "([^"]*)"$')
|
||||
def set_value_transcripts_field(_step, value, field_name):
|
||||
field_id = '#' + world.browser.find_by_xpath('//label[text()="%s"]' % field_name.strip())[0]['for']
|
||||
world.css_fill(field_id, value.strip())
|
||||
XPATH = '//label[text()="{name}"]'.format(name=field_name)
|
||||
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()
|
||||
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ requirejs.config({
|
||||
"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.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",
|
||||
"date": "xmodule_js/common_static/js/vendor/date",
|
||||
"underscore": "xmodule_js/common_static/js/vendor/underscore-min",
|
||||
@@ -98,10 +97,6 @@ requirejs.config({
|
||||
deps: ["jquery"],
|
||||
exports: "jQuery.fn.inputNumber"
|
||||
},
|
||||
"jquery.maskedinput": {
|
||||
deps: ["jquery"],
|
||||
exports: "jQuery.fn.mask"
|
||||
},
|
||||
"jquery.tinymce": {
|
||||
deps: ["jquery", "tinymce"],
|
||||
exports: "jQuery.fn.tinymce"
|
||||
|
||||
@@ -391,8 +391,95 @@ define ["js/models/metadata", "js/collections/metadata", "js/views/metadata", "c
|
||||
it "returns the intial value upon initialization", ->
|
||||
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", ->
|
||||
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", ->
|
||||
assertClear(@view, '00:00:00')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
define(
|
||||
[
|
||||
"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) {
|
||||
var Metadata = {};
|
||||
@@ -301,6 +301,11 @@ function(Backbone, _, MetadataModel, AbstractEditor, VideoList) {
|
||||
|
||||
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 : {
|
||||
"change input" : "updateModel",
|
||||
"keypress .setting-input" : "showClearButton" ,
|
||||
@@ -309,46 +314,60 @@ function(Backbone, _, MetadataModel, AbstractEditor, VideoList) {
|
||||
|
||||
templateName: "metadata-string-entry",
|
||||
|
||||
initialize: function () {
|
||||
AbstractEditor.prototype.initialize.apply(this);
|
||||
getValueFromEditor : function () {
|
||||
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
|
||||
.find('#' + this.uniqueId)
|
||||
.mask('hH:mM:sS', { placeholder: '0' });
|
||||
return $input.val();
|
||||
},
|
||||
|
||||
getValueFromEditor : function () {
|
||||
var $input = this.$el.find('#' + this.uniqueId),
|
||||
value = $input.val();
|
||||
updateModel: function () {
|
||||
var value = this.getValueFromEditor(),
|
||||
time = this.parseRelativeTime(value);
|
||||
|
||||
return 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();
|
||||
}
|
||||
},
|
||||
|
||||
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) {
|
||||
if (!value) {
|
||||
value = '00:00:00';
|
||||
value = this.defaultValue;
|
||||
}
|
||||
|
||||
this.$el.find('input').val(value);
|
||||
|
||||
@@ -49,7 +49,6 @@ lib_paths:
|
||||
- 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.async.js
|
||||
- xmodule_js/common_static/js/vendor/jquery.maskedinput.min.js
|
||||
- xmodule_js/common_static/js/vendor/CodeMirror/codemirror.js
|
||||
- xmodule_js/src/xmodule.js
|
||||
- xmodule_js/common_static/js/test/i18n.js
|
||||
|
||||
@@ -58,7 +58,6 @@
|
||||
"jquery.qtip": "js/vendor/jquery.qtip.min",
|
||||
"jquery.scrollTo": "js/vendor/jquery.scrollTo-1.4.2-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.iframe-transport": "js/vendor/jQuery-File-Upload/js/jquery.iframe-transport",
|
||||
"jquery.inputnumber": "js/vendor/html5-input-polyfills/number-polyfill",
|
||||
@@ -138,10 +137,6 @@
|
||||
deps: ["jquery"],
|
||||
exports: "jQuery.fn.plot"
|
||||
},
|
||||
"jquery.maskedinput": {
|
||||
deps: ["jquery"],
|
||||
exports: "jQuery.fn.mask"
|
||||
},
|
||||
"jquery.fileupload": {
|
||||
deps: ["jquery.iframe-transport"],
|
||||
exports: "jQuery.fn.fileupload"
|
||||
|
||||
@@ -81,13 +81,13 @@ class VideoFields(object):
|
||||
default=""
|
||||
)
|
||||
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",
|
||||
scope=Scope.settings,
|
||||
default=datetime.timedelta(seconds=0)
|
||||
)
|
||||
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",
|
||||
scope=Scope.settings,
|
||||
default=datetime.timedelta(seconds=0)
|
||||
|
||||
@@ -1,7 +0,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);
|
||||
Reference in New Issue
Block a user