diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8820c34783..cb86593a92 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -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. diff --git a/cms/djangoapps/contentstore/features/transcripts.py b/cms/djangoapps/contentstore/features/transcripts.py index c6060803ba..09fab97ae0 100644 --- a/cms/djangoapps/contentstore/features/transcripts.py +++ b/cms/djangoapps/contentstore/features/transcripts.py @@ -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() diff --git a/cms/static/coffee/spec/main.coffee b/cms/static/coffee/spec/main.coffee index 47afff2db4..6a50b56145 100644 --- a/cms/static/coffee/spec/main.coffee +++ b/cms/static/coffee/spec/main.coffee @@ -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" diff --git a/cms/static/coffee/spec/views/metadata_edit_spec.coffee b/cms/static/coffee/spec/views/metadata_edit_spec.coffee index cdc885ba35..40547323bb 100644 --- a/cms/static/coffee/spec/views/metadata_edit_spec.coffee +++ b/cms/static/coffee/spec/views/metadata_edit_spec.coffee @@ -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') diff --git a/cms/static/js/views/metadata.js b/cms/static/js/views/metadata.js index 803d7809e6..fc9bcdd922 100644 --- a/cms/static/js/views/metadata.js +++ b/cms/static/js/views/metadata.js @@ -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); diff --git a/cms/static/js_test.yml b/cms/static/js_test.yml index cdf8d9cec2..c0a5c90487 100644 --- a/cms/static/js_test.yml +++ b/cms/static/js_test.yml @@ -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 diff --git a/cms/templates/base.html b/cms/templates/base.html index 129a80890a..4f5c824c7b 100644 --- a/cms/templates/base.html +++ b/cms/templates/base.html @@ -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" diff --git a/common/lib/xmodule/xmodule/video_module.py b/common/lib/xmodule/xmodule/video_module.py index b7ca37b16e..c84498b2c8 100644 --- a/common/lib/xmodule/xmodule/video_module.py +++ b/common/lib/xmodule/xmodule/video_module.py @@ -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) diff --git a/common/static/js/vendor/jquery.maskedinput.min.js b/common/static/js/vendor/jquery.maskedinput.min.js deleted file mode 100644 index 4769d6fda4..0000000000 --- a/common/static/js/vendor/jquery.maskedinput.min.js +++ /dev/null @@ -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)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);