Merged from Master

This commit is contained in:
JonahStanley
2013-06-03 09:46:19 -04:00
123 changed files with 3229 additions and 918 deletions

View File

@@ -13,6 +13,7 @@ class UserFactory(sf.UserFactory):
"""
User account for lms / cms
"""
FACTORY_DJANGO_GET_OR_CREATE = ('username',)
pass
@@ -21,6 +22,7 @@ class UserProfileFactory(sf.UserProfileFactory):
"""
Demographics etc for the User
"""
FACTORY_DJANGO_GET_OR_CREATE = ('user',)
pass
@@ -29,6 +31,7 @@ class RegistrationFactory(sf.RegistrationFactory):
"""
Activation key for registering the user account
"""
FACTORY_DJANGO_GET_OR_CREATE = ('user',)
pass

View File

@@ -4,7 +4,7 @@
from lettuce import world
import time
from urllib import quote_plus
from selenium.common.exceptions import WebDriverException
from selenium.common.exceptions import WebDriverException, StaleElementReferenceException
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
@@ -69,7 +69,7 @@ def css_click(css_selector):
# Occassionally, MathJax or other JavaScript can cover up
# an element temporarily.
# If this happens, wait a second, then try again
time.sleep(1)
world.wait(1)
world.browser.find_by_css(css_selector).click()
@@ -85,6 +85,14 @@ def css_click_at(css, x=10, y=10):
e.action_chains.perform()
@world.absorb
def id_click(elem_id):
"""
Perform a click on an element as specified by its id
"""
world.css_click('#%s' % elem_id)
@world.absorb
def css_fill(css_selector, text):
assert is_css_present(css_selector)
@@ -101,7 +109,12 @@ def css_text(css_selector):
# Wait for the css selector to appear
if world.is_css_present(css_selector):
return world.browser.find_by_css(css_selector).first.text
try:
return world.browser.find_by_css(css_selector).first.text
except StaleElementReferenceException:
# The DOM was still redrawing. Wait a second and try again.
world.wait(1)
return world.browser.find_by_css(css_selector).first.text
else:
return ""

View File

@@ -209,30 +209,3 @@ def accepts(request, media_type):
accept = parse_accept_header(request.META.get("HTTP_ACCEPT", ""))
return media_type in [t for (t, p, q) in accept]
def debug_request(request):
"""Return a pretty printed version of the request"""
return HttpResponse("""<html>
<h1>request:</h1>
<pre>{0}</pre>
<h1>request.GET</h1>:
<pre>{1}</pre>
<h1>request.POST</h1>:
<pre>{2}</pre>
<h1>request.REQUEST</h1>:
<pre>{3}</pre>
</html>
""".format(
pprint.pformat(request),
pprint.pformat(dict(request.GET)),
pprint.pformat(dict(request.POST)),
pprint.pformat(dict(request.REQUEST)),
))

View File

@@ -182,8 +182,8 @@ def evaluator(variables, functions, string, cs=False):
number_part = Word(nums)
# 0.33 or 7 or .34
inner_number = (number_part + Optional("." + number_part)) | ("." + number_part)
# 0.33 or 7 or .34 or 16.
inner_number = (number_part + Optional("." + Optional(number_part))) | ("." + number_part)
# 0.33k or -17
number = (Optional(minus | plus) + inner_number

View File

@@ -5,6 +5,7 @@ Unit tests for calc.py
import unittest
import numpy
import calc
from pyparsing import ParseException
class EvaluatorTest(unittest.TestCase):
@@ -20,6 +21,11 @@ class EvaluatorTest(unittest.TestCase):
def test_number_input(self):
"""
Test different kinds of float inputs
See also
test_trailing_period (slightly different)
test_exponential_answer
test_si_suffix
"""
easy_eval = lambda x: calc.evaluator({}, {}, x)
@@ -30,7 +36,22 @@ class EvaluatorTest(unittest.TestCase):
self.assertEqual(easy_eval("-13"), -13)
self.assertEqual(easy_eval("-3.14"), -3.14)
self.assertEqual(easy_eval("-.618033989"), -0.618033989)
# See also test_exponential_answer and test_si_suffix
def test_period(self):
"""
The string '.' should not evaluate to anything.
"""
self.assertRaises(ParseException, calc.evaluator, {}, {}, '.')
self.assertRaises(ParseException, calc.evaluator, {}, {}, '1+.')
def test_trailing_period(self):
"""
Test that things like '4.' will be 4 and not throw an error
"""
try:
self.assertEqual(4.0, calc.evaluator({}, {}, '4.'))
except ParseException:
self.fail("'4.' is a valid input, but threw an exception")
def test_exponential_answer(self):
"""

View File

@@ -4,5 +4,5 @@ setup(
name="capa",
version="0.1",
packages=find_packages(exclude=["tests"]),
install_requires=["distribute==0.6.28"],
install_requires=["distribute>=0.6.28"],
)

View File

@@ -66,22 +66,51 @@ class ComplexEncoder(json.JSONEncoder):
class CapaFields(object):
attempts = StringyInteger(help="Number of attempts taken by the student on this problem", default=0, scope=Scope.user_state)
max_attempts = StringyInteger(help="Maximum number of attempts that a student is allowed", scope=Scope.settings)
max_attempts = StringyInteger(
display_name="Maximum Attempts",
help="Defines the number of times a student can try to answer this problem. If the value is not set, infinite attempts are allowed.",
values={"min": 1}, scope=Scope.settings
)
due = Date(help="Date that this problem is due by", scope=Scope.settings)
graceperiod = Timedelta(help="Amount of time after the due date that submissions will be accepted", scope=Scope.settings)
showanswer = String(help="When to show the problem answer to the student", scope=Scope.settings, default="closed",
values=["answered", "always", "attempted", "closed", "never"])
showanswer = String(
display_name="Show Answer",
help="Defines when to show the answer to the problem. A default value can be set in Advanced Settings.",
scope=Scope.settings, default="closed",
values=[
{"display_name": "Always", "value": "always"},
{"display_name": "Answered", "value": "answered"},
{"display_name": "Attempted", "value": "attempted"},
{"display_name": "Closed", "value": "closed"},
{"display_name": "Finished", "value": "finished"},
{"display_name": "Past Due", "value": "past_due"},
{"display_name": "Never", "value": "never"}]
)
force_save_button = Boolean(help="Whether to force the save button to appear on the page", scope=Scope.settings, default=False)
rerandomize = Randomization(help="When to rerandomize the problem", default="always", scope=Scope.settings)
rerandomize = Randomization(
display_name="Randomization", help="Defines how often inputs are randomized when a student loads the problem. This setting only applies to problems that can have randomly generated numeric values. A default value can be set in Advanced Settings.",
default="always", scope=Scope.settings, values=[{"display_name": "Always", "value": "always"},
{"display_name": "On Reset", "value": "onreset"},
{"display_name": "Never", "value": "never"},
{"display_name": "Per Student", "value": "per_student"}]
)
data = String(help="XML data for the problem", scope=Scope.content)
correct_map = Object(help="Dictionary with the correctness of current student answers", scope=Scope.user_state, default={})
input_state = Object(help="Dictionary for maintaining the state of inputtypes", scope=Scope.user_state)
student_answers = Object(help="Dictionary with the current student responses", scope=Scope.user_state)
done = Boolean(help="Whether the student has answered the problem", scope=Scope.user_state)
seed = StringyInteger(help="Random seed for this student", scope=Scope.user_state)
weight = StringyFloat(help="How much to weight this problem by", scope=Scope.settings)
weight = StringyFloat(
display_name="Problem Weight",
help="Defines the number of points each problem is worth. If the value is not set, each response field in the problem is worth one point.",
values={"min": 0, "step": .1},
scope=Scope.settings
)
markdown = String(help="Markdown source of this module", scope=Scope.settings)
source_code = String(help="Source code for LaTeX and Word problems. This feature is not well-supported.", scope=Scope.settings)
source_code = String(
help="Source code for LaTeX and Word problems. This feature is not well-supported.",
scope=Scope.settings
)
class CapaModule(CapaFields, XModule):

View File

@@ -5,7 +5,7 @@ from pkg_resources import resource_string
from xmodule.raw_module import RawDescriptor
from .x_module import XModule
from xblock.core import Integer, Scope, String, Boolean, List
from xblock.core import Integer, Scope, String, List
from xmodule.open_ended_grading_classes.combined_open_ended_modulev1 import CombinedOpenEndedV1Module, CombinedOpenEndedV1Descriptor
from collections import namedtuple
from .fields import Date, StringyFloat, StringyInteger, StringyBoolean
@@ -48,27 +48,49 @@ class VersionInteger(Integer):
class CombinedOpenEndedFields(object):
display_name = String(help="Display name for this module", default="Open Ended Grading", scope=Scope.settings)
display_name = String(
display_name="Display Name",
help="This name appears in the horizontal navigation at the top of the page.",
default="Open Ended Grading", scope=Scope.settings
)
current_task_number = StringyInteger(help="Current task that the student is on.", default=0, scope=Scope.user_state)
task_states = List(help="List of state dictionaries of each task within this module.", scope=Scope.user_state)
state = String(help="Which step within the current task that the student is on.", default="initial",
scope=Scope.user_state)
student_attempts = StringyInteger(help="Number of attempts taken by the student on this problem", default=0,
scope=Scope.user_state)
ready_to_reset = StringyBoolean(help="If the problem is ready to be reset or not.", default=False,
scope=Scope.user_state)
attempts = StringyInteger(help="Maximum number of attempts that a student is allowed.", default=1, scope=Scope.settings)
is_graded = StringyBoolean(help="Whether or not the problem is graded.", default=False, scope=Scope.settings)
accept_file_upload = StringyBoolean(help="Whether or not the problem accepts file uploads.", default=False,
scope=Scope.settings)
skip_spelling_checks = StringyBoolean(help="Whether or not to skip initial spelling checks.", default=True,
scope=Scope.settings)
ready_to_reset = StringyBoolean(
help="If the problem is ready to be reset or not.", default=False,
scope=Scope.user_state
)
attempts = StringyInteger(
display_name="Maximum Attempts",
help="The number of times the student can try to answer this problem.", default=1,
scope=Scope.settings, values = {"min" : 1 }
)
is_graded = StringyBoolean(display_name="Graded", help="Whether or not the problem is graded.", default=False, scope=Scope.settings)
accept_file_upload = StringyBoolean(
display_name="Allow File Uploads",
help="Whether or not the student can submit files as a response.", default=False, scope=Scope.settings
)
skip_spelling_checks = StringyBoolean(
display_name="Disable Quality Filter",
help="If False, the Quality Filter is enabled and submissions with poor spelling, short length, or poor grammar will not be peer reviewed.",
default=False, scope=Scope.settings
)
due = Date(help="Date that this problem is due by", default=None, scope=Scope.settings)
graceperiod = String(help="Amount of time after the due date that submissions will be accepted", default=None,
scope=Scope.settings)
graceperiod = String(
help="Amount of time after the due date that submissions will be accepted",
default=None,
scope=Scope.settings
)
version = VersionInteger(help="Current version number", default=DEFAULT_VERSION, scope=Scope.settings)
data = String(help="XML data for the problem", scope=Scope.content)
weight = StringyFloat(help="How much to weight this problem by", scope=Scope.settings)
weight = StringyFloat(
display_name="Problem Weight",
help="Defines the number of points each problem is worth. If the value is not set, each problem is worth one point.",
scope=Scope.settings, values = {"min" : 0 , "step": ".1"}
)
markdown = String(help="Markdown source of this module", scope=Scope.settings)
@@ -244,6 +266,6 @@ class CombinedOpenEndedDescriptor(CombinedOpenEndedFields, RawDescriptor):
def non_editable_metadata_fields(self):
non_editable_fields = super(CombinedOpenEndedDescriptor, self).non_editable_metadata_fields
non_editable_fields.extend([CombinedOpenEndedDescriptor.due, CombinedOpenEndedDescriptor.graceperiod,
CombinedOpenEndedDescriptor.markdown])
CombinedOpenEndedDescriptor.markdown, CombinedOpenEndedDescriptor.version])
return non_editable_fields

View File

@@ -10,8 +10,6 @@
position: relative;
@include linear-gradient(top, #d4dee8, #c9d5e2);
padding: 5px;
border: 1px solid #3c3c3c;
border-radius: 3px 3px 0 0;
border-bottom-color: #a5aaaf;
@include clearfix;

View File

@@ -5,7 +5,7 @@
.advanced-toggle {
@include white-button;
height: auto;
margin-top: -1px;
margin-top: -4px;
padding: 3px 9px;
font-size: 12px;
@@ -16,7 +16,7 @@
color: $darkGrey !important;
pointer-events: none;
cursor: none;
&:hover {
box-shadow: 0 0 0 0 !important;
}
@@ -27,7 +27,7 @@
width: 21px;
height: 21px;
padding: 0;
margin: 0 5px 0 15px;
margin: -1px 5px 0 15px;
border-radius: 22px;
border: 1px solid #a5aaaf;
background: #e5ecf3;
@@ -99,6 +99,13 @@
}
}
.problem-editor {
// adding padding to simple editor only - adjacent selector is needed since there are no toggles for CodeMirror
.markdown-box+.CodeMirror {
padding: 10px;
}
}
.problem-editor-icon {
display: inline-block;
width: 26px;

View File

@@ -170,7 +170,7 @@ nav.sequence-nav {
font-family: $sans-serif;
line-height: lh();
left: 0px;
opacity: 0;
opacity: 0.0;
padding: 6px;
position: absolute;
top: 48px;
@@ -204,7 +204,7 @@ nav.sequence-nav {
p {
display: block;
margin-top: 4px;
opacity: 1;
opacity: 1.0;
}
}
}
@@ -248,12 +248,12 @@ nav.sequence-nav {
}
&:hover {
opacity: .5;
opacity: 0.5;
}
&.disabled {
cursor: normal;
opacity: .4;
opacity: 0.4;
}
}
}
@@ -320,12 +320,12 @@ nav.sequence-bottom {
outline: 0;
&:hover {
opacity: .5;
opacity: 0.5;
background-position: center 15px;
}
&.disabled {
opacity: .4;
opacity: 0.4;
}
&:focus {

View File

@@ -41,7 +41,7 @@ div.video {
&:hover {
ul, div {
opacity: 1;
opacity: 1.0;
}
}
@@ -158,7 +158,7 @@ div.video {
ol.video_speeds {
display: block;
opacity: 1;
opacity: 1.0;
padding: 0;
margin: 0;
list-style: none;
@@ -208,7 +208,7 @@ div.video {
}
&:hover, &:active, &:focus {
opacity: 1;
opacity: 1.0;
background-color: #444;
}
}
@@ -221,7 +221,7 @@ div.video {
border: 1px solid #000;
bottom: 46px;
display: none;
opacity: 0;
opacity: 0.0;
position: absolute;
width: 133px;
z-index: 10;
@@ -264,7 +264,7 @@ div.video {
&.open {
.volume-slider-container {
display: block;
opacity: 1;
opacity: 1.0;
}
}
@@ -302,7 +302,7 @@ div.video {
border: 1px solid #000;
bottom: 46px;
display: none;
opacity: 0;
opacity: 0.0;
position: absolute;
width: 45px;
height: 125px;
@@ -395,7 +395,7 @@ div.video {
font-weight: 800;
line-height: 46px; //height of play pause buttons
margin-left: 0;
opacity: 1;
opacity: 1.0;
padding: 0 lh(.5);
position: relative;
text-indent: -9999px;
@@ -410,7 +410,7 @@ div.video {
}
&.off {
opacity: .7;
opacity: 0.7;
}
}
}
@@ -418,7 +418,7 @@ div.video {
&:hover section.video-controls {
ul, div {
opacity: 1;
opacity: 1.0;
}
div.slider {

View File

@@ -41,7 +41,7 @@ div.video {
&:hover {
ul, div {
opacity: 1;
opacity: 1.0;
}
}
@@ -158,7 +158,7 @@ div.video {
ol.video_speeds {
display: block;
opacity: 1;
opacity: 1.0;
padding: 0;
margin: 0;
list-style: none;
@@ -208,7 +208,7 @@ div.video {
}
&:hover, &:active, &:focus {
opacity: 1;
opacity: 1.0;
background-color: #444;
}
}
@@ -221,7 +221,7 @@ div.video {
border: 1px solid #000;
bottom: 46px;
display: none;
opacity: 0;
opacity: 0.0;
position: absolute;
width: 133px;
z-index: 10;
@@ -264,7 +264,7 @@ div.video {
&.open {
.volume-slider-container {
display: block;
opacity: 1;
opacity: 1.0;
}
}
@@ -302,7 +302,7 @@ div.video {
border: 1px solid #000;
bottom: 46px;
display: none;
opacity: 0;
opacity: 0.0;
position: absolute;
width: 45px;
height: 125px;
@@ -395,7 +395,7 @@ div.video {
font-weight: 800;
line-height: 46px; //height of play pause buttons
margin-left: 0;
opacity: 1;
opacity: 1.0;
padding: 0 lh(.5);
position: relative;
text-indent: -9999px;
@@ -410,7 +410,7 @@ div.video {
}
&.off {
opacity: .7;
opacity: 0.7;
}
}
}
@@ -418,7 +418,7 @@ div.video {
&:hover section.video-controls {
ul, div {
opacity: 1;
opacity: 1.0;
}
div.slider {

View File

@@ -8,8 +8,16 @@ from xblock.core import String, Scope
class DiscussionFields(object):
discussion_id = String(scope=Scope.settings)
discussion_category = String(scope=Scope.settings)
discussion_target = String(scope=Scope.settings)
discussion_category = String(
display_name="Category",
help="A category name for the discussion. This name appears in the left pane of the discussion forum for the course.",
scope=Scope.settings
)
discussion_target = String(
display_name="Subcategory",
help="A subcategory name for the discussion. This name appears in the left pane of the discussion forum for the course.",
scope=Scope.settings
)
sort_key = String(scope=Scope.settings)

View File

@@ -289,6 +289,9 @@ class @CombinedOpenEnded
if @child_type == "openended"
@submit_button.hide()
@queueing()
if @task_number==1 and @task_count==1
@grader_status = $('.grader-status')
@grader_status.html("<p>Response submitted for scoring.</p>")
else if @child_state == 'post_assessment'
if @child_type=="openended"
@skip_button.show()
@@ -311,6 +314,8 @@ class @CombinedOpenEnded
if @task_number<@task_count
@next_problem()
else
if @task_number==1 and @task_count==1
@show_combined_rubric_current()
@show_results_current()
@reset_button.show()

View File

@@ -66,7 +66,7 @@ class @VideoPlayer extends Subview
at: 'top center'
onReady: (event) =>
unless onTouchBasedDevice()
unless onTouchBasedDevice() or $('.video:first').data('autoplay') == 'False'
$('.video-load-complete:first').data('video').player.play()
onStateChange: (event) =>

View File

@@ -85,7 +85,7 @@ class MockControllerQueryService(object):
def __init__(self, config, system):
pass
def check_if_name_is_unique(self, **params):
def check_if_name_is_unique(self, *args, **kwargs):
"""
Mock later if needed. Stub function for now.
@param params:
@@ -93,7 +93,7 @@ class MockControllerQueryService(object):
"""
pass
def check_for_eta(self, **params):
def check_for_eta(self, *args, **kwargs):
"""
Mock later if needed. Stub function for now.
@param params:
@@ -101,19 +101,19 @@ class MockControllerQueryService(object):
"""
pass
def check_combined_notifications(self, **params):
def check_combined_notifications(self, *args, **kwargs):
combined_notifications = '{"flagged_submissions_exist": false, "version": 1, "new_student_grading_to_view": false, "success": true, "staff_needs_to_grade": false, "student_needs_to_peer_grade": true, "overall_need_to_check": true}'
return combined_notifications
def get_grading_status_list(self, **params):
def get_grading_status_list(self, *args, **kwargs):
grading_status_list = '{"version": 1, "problem_list": [{"problem_name": "Science Question -- Machine Assessed", "grader_type": "NA", "eta_available": true, "state": "Waiting to be Graded", "eta": 259200, "location": "i4x://MITx/oe101x/combinedopenended/Science_SA_ML"}, {"problem_name": "Humanities Question -- Peer Assessed", "grader_type": "NA", "eta_available": true, "state": "Waiting to be Graded", "eta": 259200, "location": "i4x://MITx/oe101x/combinedopenended/Humanities_SA_Peer"}], "success": true}'
return grading_status_list
def get_flagged_problem_list(self, **params):
def get_flagged_problem_list(self, *args, **kwargs):
flagged_problem_list = '{"version": 1, "success": false, "error": "No flagged submissions exist for course: MITx/oe101x/2012_Fall"}'
return flagged_problem_list
def take_action_on_flags(self, **params):
def take_action_on_flags(self, *args, **kwargs):
"""
Mock later if needed. Stub function for now.
@param params:

View File

@@ -10,7 +10,7 @@ from .x_module import XModule
from xmodule.raw_module import RawDescriptor
from xmodule.modulestore.django import modulestore
from .timeinfo import TimeInfo
from xblock.core import Object, Integer, Boolean, String, Scope
from xblock.core import Object, String, Scope
from xmodule.fields import Date, StringyFloat, StringyInteger, StringyBoolean
from xmodule.open_ended_grading_classes.peer_grading_service import PeerGradingService, GradingServiceError, MockPeerGradingService
@@ -22,24 +22,43 @@ USE_FOR_SINGLE_LOCATION = False
LINK_TO_LOCATION = ""
TRUE_DICT = [True, "True", "true", "TRUE"]
MAX_SCORE = 1
IS_GRADED = True
IS_GRADED = False
EXTERNAL_GRADER_NO_CONTACT_ERROR = "Failed to contact external graders. Please notify course staff."
class PeerGradingFields(object):
use_for_single_location = StringyBoolean(help="Whether to use this for a single location or as a panel.",
default=USE_FOR_SINGLE_LOCATION, scope=Scope.settings)
link_to_location = String(help="The location this problem is linked to.", default=LINK_TO_LOCATION,
scope=Scope.settings)
is_graded = StringyBoolean(help="Whether or not this module is scored.", default=IS_GRADED, scope=Scope.settings)
use_for_single_location = StringyBoolean(
display_name="Show Single Problem",
help='When True, only the single problem specified by "Link to Problem Location" is shown. '
'When False, a panel is displayed with all problems available for peer grading.',
default=USE_FOR_SINGLE_LOCATION, scope=Scope.settings
)
link_to_location = String(
display_name="Link to Problem Location",
help='The location of the problem being graded. Only used when "Show Single Problem" is True.',
default=LINK_TO_LOCATION, scope=Scope.settings
)
is_graded = StringyBoolean(
display_name="Graded",
help='Defines whether the student gets credit for grading this problem. Only used when "Show Single Problem" is True.',
default=IS_GRADED, scope=Scope.settings
)
due_date = Date(help="Due date that should be displayed.", default=None, scope=Scope.settings)
grace_period_string = String(help="Amount of grace to give on the due date.", default=None, scope=Scope.settings)
max_grade = StringyInteger(help="The maximum grade that a student can receieve for this problem.", default=MAX_SCORE,
scope=Scope.settings)
student_data_for_location = Object(help="Student data for a given peer grading problem.",
scope=Scope.user_state)
weight = StringyFloat(help="How much to weight this problem by", scope=Scope.settings)
max_grade = StringyInteger(
help="The maximum grade that a student can receive for this problem.", default=MAX_SCORE,
scope=Scope.settings, values={"min": 0}
)
student_data_for_location = Object(
help="Student data for a given peer grading problem.",
scope=Scope.user_state
)
weight = StringyFloat(
display_name="Problem Weight",
help="Defines the number of points each problem is worth. If the value is not set, each problem is worth one point.",
scope=Scope.settings, values={"min": 0, "step": ".1"}
)
class PeerGradingModule(PeerGradingFields, XModule):
@@ -590,3 +609,11 @@ class PeerGradingDescriptor(PeerGradingFields, RawDescriptor):
#Specify whether or not to pass in open ended interface
needs_open_ended_interface = True
@property
def non_editable_metadata_fields(self):
non_editable_fields = super(PeerGradingDescriptor, self).non_editable_metadata_fields
non_editable_fields.extend([PeerGradingFields.due_date, PeerGradingFields.grace_period_string,
PeerGradingFields.max_grade])
return non_editable_fields

View File

@@ -1,12 +1,6 @@
---
metadata:
display_name: Open Ended Response
attempts: 1
is_graded: False
version: 1
skip_spelling_checks: False
accept_file_upload: False
weight: ""
markdown: ""
data: |
<combinedopenended>

View File

@@ -1,7 +1,6 @@
---
metadata:
display_name: Blank HTML Page
empty: True
data: |

View File

@@ -1,11 +1,7 @@
---
metadata:
display_name: Peer Grading Interface
use_for_single_location: False
link_to_location: None
is_graded: False
max_grade: 1
weight: ""
data: |
<peergrading>
</peergrading>

View File

@@ -3,9 +3,7 @@
metadata:
display_name: Circuit Schematic Builder
rerandomize: never
showanswer: always
weight: ""
attempts: ""
showanswer: finished
data: |
<problem >
Please make a voltage divider that splits the provided voltage evenly.

View File

@@ -2,9 +2,7 @@
metadata:
display_name: Custom Python-Evaluated Input
rerandomize: never
showanswer: always
weight: ""
attempts: ""
showanswer: finished
data: |
<problem>
<p>

View File

@@ -2,11 +2,8 @@
metadata:
display_name: Blank Common Problem
rerandomize: never
showanswer: always
showanswer: finished
markdown: ""
weight: ""
empty: True
attempts: ""
data: |
<problem>
</problem>

View File

@@ -2,10 +2,7 @@
metadata:
display_name: Blank Advanced Problem
rerandomize: never
showanswer: always
weight: ""
attempts: ""
empty: True
showanswer: finished
data: |
<problem>
</problem>

View File

@@ -2,9 +2,7 @@
metadata:
display_name: Math Expression Input
rerandomize: never
showanswer: always
weight: ""
attempts: ""
showanswer: finished
data: |
<problem>
<p>

View File

@@ -2,9 +2,7 @@
metadata:
display_name: Image Mapped Input
rerandomize: never
showanswer: always
weight: ""
attempts: ""
showanswer: finished
data: |
<problem>
<p>

View File

@@ -2,9 +2,7 @@
metadata:
display_name: Multiple Choice
rerandomize: never
showanswer: always
weight: ""
attempts: ""
showanswer: finished
markdown:
"A multiple choice problem presents radio buttons for student input. Students can only select a single
option presented. Multiple Choice questions have been the subject of many areas of research due to the early

View File

@@ -2,9 +2,7 @@
metadata:
display_name: Numerical Input
rerandomize: never
showanswer: always
weight: ""
attempts: ""
showanswer: finished
markdown:
"A numerical input problem accepts a line of text input from the
student, and evaluates the input for correctness based on its

View File

@@ -2,9 +2,7 @@
metadata:
display_name: Dropdown
rerandomize: never
showanswer: always
weight: ""
attempts: ""
showanswer: finished
markdown:
"Dropdown problems give a limited set of options for students to respond with, and present those options
in a format that encourages them to search for a specific answer rather than being immediately presented

View File

@@ -2,9 +2,7 @@
metadata:
display_name: Text Input
rerandomize: never
showanswer: always
weight: ""
attempts: ""
showanswer: finished
# Note, the extra newlines are needed to make the yaml parser add blank lines instead of folding
markdown:
"A text input problem accepts a line of text from the

View File

@@ -1,9 +1,5 @@
---
metadata:
display_name: Word cloud
version: 1
num_inputs: 5
num_top_words: 250
display_student_percents: True
data: {}
children: []

View File

@@ -460,8 +460,8 @@ class ImportTestCase(BaseCourseTestCase):
)
module = modulestore.get_instance(course.id, location)
self.assertEqual(len(module.get_children()), 0)
self.assertEqual(module.num_inputs, '5')
self.assertEqual(module.num_top_words, '250')
self.assertEqual(module.num_inputs, 5)
self.assertEqual(module.num_top_words, 250)
def test_cohort_config(self):
"""

View File

@@ -1,69 +1,141 @@
# disable missing docstring
#pylint: disable=C0111
from xmodule.x_module import XModuleFields
from xblock.core import Scope, String, Object
from xmodule.fields import Date, StringyInteger
from xblock.core import Scope, String, Object, Boolean
from xmodule.fields import Date, StringyInteger, StringyFloat
from xmodule.xml_module import XmlDescriptor
import unittest
from . import test_system
from .import test_system
from mock import Mock
class CrazyJsonString(String):
def to_json(self, value):
return value + " JSON"
class TestFields(object):
# Will be returned by editable_metadata_fields.
max_attempts = StringyInteger(scope=Scope.settings, default=1000)
max_attempts = StringyInteger(scope=Scope.settings, default=1000, values={'min': 1, 'max': 10})
# Will not be returned by editable_metadata_fields because filtered out by non_editable_metadata_fields.
due = Date(scope=Scope.settings)
# Will not be returned by editable_metadata_fields because is not Scope.settings.
student_answers = Object(scope=Scope.user_state)
# Will be returned, and can override the inherited value from XModule.
display_name = String(scope=Scope.settings, default='local default')
display_name = String(scope=Scope.settings, default='local default', display_name='Local Display Name',
help='local help')
# Used for testing select type, effect of to_json method
string_select = CrazyJsonString(
scope=Scope.settings,
default='default value',
values=[{'display_name': 'first', 'value': 'value a'},
{'display_name': 'second', 'value': 'value b'}]
)
# Used for testing select type
float_select = StringyFloat(scope=Scope.settings, default=.999, values=[1.23, 0.98])
# Used for testing float type
float_non_select = StringyFloat(scope=Scope.settings, default=.999, values={'min': 0, 'step': .3})
# Used for testing that Booleans get mapped to select type
boolean_select = Boolean(scope=Scope.settings)
class EditableMetadataFieldsTest(unittest.TestCase):
def test_display_name_field(self):
editable_fields = self.get_xml_editable_fields({})
# Tests that the xblock fields (currently tags and name) get filtered out.
# Also tests that xml_attributes is filtered out of XmlDescriptor.
self.assertEqual(1, len(editable_fields), "Expected only 1 editable field for xml descriptor.")
self.assert_field_values(editable_fields, 'display_name', XModuleFields.display_name,
explicitly_set=False, inheritable=False, value=None, default_value=None)
self.assert_field_values(
editable_fields, 'display_name', XModuleFields.display_name,
explicitly_set=False, inheritable=False, value=None, default_value=None
)
def test_override_default(self):
# Tests that explicitly_set is correct when a value overrides the default (not inheritable).
editable_fields = self.get_xml_editable_fields({'display_name': 'foo'})
self.assert_field_values(editable_fields, 'display_name', XModuleFields.display_name,
explicitly_set=True, inheritable=False, value='foo', default_value=None)
self.assert_field_values(
editable_fields, 'display_name', XModuleFields.display_name,
explicitly_set=True, inheritable=False, value='foo', default_value=None
)
def test_additional_field(self):
descriptor = self.get_descriptor({'max_attempts' : '7'})
def test_integer_field(self):
descriptor = self.get_descriptor({'max_attempts': '7'})
editable_fields = descriptor.editable_metadata_fields
self.assertEqual(2, len(editable_fields))
self.assert_field_values(editable_fields, 'max_attempts', TestFields.max_attempts,
explicitly_set=True, inheritable=False, value=7, default_value=1000)
self.assert_field_values(editable_fields, 'display_name', XModuleFields.display_name,
explicitly_set=False, inheritable=False, value='local default', default_value='local default')
self.assertEqual(6, len(editable_fields))
self.assert_field_values(
editable_fields, 'max_attempts', TestFields.max_attempts,
explicitly_set=True, inheritable=False, value=7, default_value=1000, type='Integer',
options=TestFields.max_attempts.values
)
self.assert_field_values(
editable_fields, 'display_name', TestFields.display_name,
explicitly_set=False, inheritable=False, value='local default', default_value='local default'
)
editable_fields = self.get_descriptor({}).editable_metadata_fields
self.assert_field_values(editable_fields, 'max_attempts', TestFields.max_attempts,
explicitly_set=False, inheritable=False, value=1000, default_value=1000)
self.assert_field_values(
editable_fields, 'max_attempts', TestFields.max_attempts,
explicitly_set=False, inheritable=False, value=1000, default_value=1000, type='Integer',
options=TestFields.max_attempts.values
)
def test_inherited_field(self):
model_val = {'display_name' : 'inherited'}
model_val = {'display_name': 'inherited'}
descriptor = self.get_descriptor(model_val)
# Mimic an inherited value for display_name (inherited and inheritable are the same in this case).
descriptor._inherited_metadata = model_val
descriptor._inheritable_metadata = model_val
editable_fields = descriptor.editable_metadata_fields
self.assert_field_values(editable_fields, 'display_name', XModuleFields.display_name,
explicitly_set=False, inheritable=True, value='inherited', default_value='inherited')
self.assert_field_values(
editable_fields, 'display_name', TestFields.display_name,
explicitly_set=False, inheritable=True, value='inherited', default_value='inherited'
)
descriptor = self.get_descriptor({'display_name' : 'explicit'})
descriptor = self.get_descriptor({'display_name': 'explicit'})
# Mimic the case where display_name WOULD have been inherited, except we explicitly set it.
descriptor._inheritable_metadata = {'display_name' : 'inheritable value'}
descriptor._inheritable_metadata = {'display_name': 'inheritable value'}
descriptor._inherited_metadata = {}
editable_fields = descriptor.editable_metadata_fields
self.assert_field_values(editable_fields, 'display_name', XModuleFields.display_name,
explicitly_set=True, inheritable=True, value='explicit', default_value='inheritable value')
self.assert_field_values(
editable_fields, 'display_name', TestFields.display_name,
explicitly_set=True, inheritable=True, value='explicit', default_value='inheritable value'
)
def test_type_and_options(self):
# test_display_name_field verifies that a String field is of type "Generic".
# test_integer_field verifies that a StringyInteger field is of type "Integer".
descriptor = self.get_descriptor({})
editable_fields = descriptor.editable_metadata_fields
# Tests for select
self.assert_field_values(
editable_fields, 'string_select', TestFields.string_select,
explicitly_set=False, inheritable=False, value='default value', default_value='default value',
type='Select', options=[{'display_name': 'first', 'value': 'value a JSON'},
{'display_name': 'second', 'value': 'value b JSON'}]
)
self.assert_field_values(
editable_fields, 'float_select', TestFields.float_select,
explicitly_set=False, inheritable=False, value=.999, default_value=.999,
type='Select', options=[1.23, 0.98]
)
self.assert_field_values(
editable_fields, 'boolean_select', TestFields.boolean_select,
explicitly_set=False, inheritable=False, value=None, default_value=None,
type='Select', options=[{'display_name': "True", "value": True}, {'display_name': "False", "value": False}]
)
# Test for float
self.assert_field_values(
editable_fields, 'float_non_select', TestFields.float_non_select,
explicitly_set=False, inheritable=False, value=.999, default_value=.999,
type='Float', options={'min': 0, 'step': .3}
)
# Start of helper methods
def get_xml_editable_fields(self, model_data):
@@ -73,7 +145,6 @@ class EditableMetadataFieldsTest(unittest.TestCase):
def get_descriptor(self, model_data):
class TestModuleDescriptor(TestFields, XmlDescriptor):
@property
def non_editable_metadata_fields(self):
non_editable_fields = super(TestModuleDescriptor, self).non_editable_metadata_fields
@@ -84,10 +155,19 @@ class EditableMetadataFieldsTest(unittest.TestCase):
system.render_template = Mock(return_value="<div>Test Template HTML</div>")
return TestModuleDescriptor(system=system, location=None, model_data=model_data)
def assert_field_values(self, editable_fields, name, field, explicitly_set, inheritable, value, default_value):
def assert_field_values(self, editable_fields, name, field, explicitly_set, inheritable, value, default_value,
type='Generic', options=[]):
test_field = editable_fields[name]
self.assertEqual(field, test_field['field'])
self.assertEqual(field.name, test_field['field_name'])
self.assertEqual(field.display_name, test_field['display_name'])
self.assertEqual(field.help, test_field['help'])
self.assertEqual(field.to_json(value), test_field['value'])
self.assertEqual(field.to_json(default_value), test_field['default_value'])
self.assertEqual(options, test_field['options'])
self.assertEqual(type, test_field['type'])
self.assertEqual(explicitly_set, test_field['explicitly_set'])
self.assertEqual(inheritable, test_field['inheritable'])
self.assertEqual(value, test_field['value'])
self.assertEqual(default_value, test_field['default_value'])

View File

@@ -2,7 +2,7 @@
generate and view word cloud.
On the client side we show:
If student does not yet anwered - `num_inputs` numbers of text inputs.
If student does not yet answered - `num_inputs` numbers of text inputs.
If student have answered - words he entered and cloud.
"""
@@ -14,7 +14,8 @@ from xmodule.raw_module import RawDescriptor
from xmodule.editing_module import MetadataOnlyEditingDescriptor
from xmodule.x_module import XModule
from xblock.core import Scope, String, Object, Boolean, List, Integer
from xblock.core import Scope, Object, Boolean, List
from fields import StringyBoolean, StringyInteger
log = logging.getLogger(__name__)
@@ -31,22 +32,23 @@ def pretty_bool(value):
class WordCloudFields(object):
"""XFields for word cloud."""
display_name = String(
help="Display name for this module",
scope=Scope.settings
)
num_inputs = Integer(
help="Number of inputs.",
num_inputs = StringyInteger(
display_name="Inputs",
help="Number of text boxes available for students to input words/sentences.",
scope=Scope.settings,
default=5
default=5,
values={"min": 1}
)
num_top_words = Integer(
help="Number of max words, which will be displayed.",
num_top_words = StringyInteger(
display_name="Maximum Words",
help="Maximum number of words to be displayed in generated word cloud.",
scope=Scope.settings,
default=250
default=250,
values={"min": 1}
)
display_student_percents = Boolean(
help="Display usage percents for each word?",
display_student_percents = StringyBoolean(
display_name="Show Percents",
help="Statistics are shown for entered words near that word.",
scope=Scope.settings,
default=True
)
@@ -205,7 +207,7 @@ class WordCloudModule(WordCloudFields, XModule):
# Update top_words.
self.top_words = self.top_dict(
temp_all_words,
int(self.num_top_words)
self.num_top_words
)
# Save all_words in database.
@@ -226,7 +228,7 @@ class WordCloudModule(WordCloudFields, XModule):
'element_id': self.location.html_id(),
'element_class': self.location.category,
'ajax_url': self.system.ajax_url,
'num_inputs': int(self.num_inputs),
'num_inputs': self.num_inputs,
'submitted': self.submitted
}
self.content = self.system.render_template('word_cloud.html', context)

View File

@@ -1,4 +1,5 @@
import logging
import copy
import yaml
import os
@@ -9,7 +10,7 @@ from pkg_resources import resource_listdir, resource_string, resource_isdir
from xmodule.modulestore import Location
from xmodule.modulestore.exceptions import ItemNotFoundError
from xblock.core import XBlock, Scope, String
from xblock.core import XBlock, Scope, String, Integer, Float
log = logging.getLogger(__name__)
@@ -75,12 +76,13 @@ class HTMLSnippet(object):
"""
raise NotImplementedError(
"get_html() must be provided by specific modules - not present in {0}"
.format(self.__class__))
.format(self.__class__))
class XModuleFields(object):
display_name = String(
help="Display name for this module",
display_name="Display Name",
help="This name appears in the horizontal navigation at the top of the page.",
scope=Scope.settings,
default=None
)
@@ -356,7 +358,7 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
metadata_translations = {
'slug': 'url_name',
'name': 'display_name',
}
}
# ============================= STRUCTURAL MANIPULATION ===================
def __init__(self,
@@ -458,7 +460,6 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
"""
return False
# ================================= JSON PARSING ===========================
@staticmethod
def load_from_json(json_data, system, default_class=None):
@@ -523,10 +524,10 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
# ================================= XML PARSING ============================
@staticmethod
def load_from_xml(xml_data,
system,
org=None,
course=None,
default_class=None):
system,
org=None,
course=None,
default_class=None):
"""
This method instantiates the correct subclass of XModuleDescriptor based
on the contents of xml_data.
@@ -541,7 +542,7 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
class_ = XModuleDescriptor.load_class(
etree.fromstring(xml_data).tag,
default_class
)
)
# leave next line, commented out - useful for low-level debugging
# log.debug('[XModuleDescriptor.load_from_xml] tag=%s, class_=%s' % (
# etree.fromstring(xml_data).tag,class_))
@@ -625,7 +626,7 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
"""
inherited_metadata = getattr(self, '_inherited_metadata', {})
inheritable_metadata = getattr(self, '_inheritable_metadata', {})
metadata = {}
metadata_fields = {}
for field in self.fields:
if field.scope != Scope.settings or field in self.non_editable_metadata_fields:
@@ -641,13 +642,39 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
if field.name in inherited_metadata:
explicitly_set = False
metadata[field.name] = {'field': field,
'value': value,
'default_value': default_value,
'inheritable': inheritable,
'explicitly_set': explicitly_set }
# We support the following editors:
# 1. A select editor for fields with a list of possible values (includes Booleans).
# 2. Number editors for integers and floats.
# 3. A generic string editor for anything else (editing JSON representation of the value).
type = "Generic"
values = [] if field.values is None else copy.deepcopy(field.values)
if isinstance(values, tuple):
values = list(values)
if isinstance(values, list):
if len(values) > 0:
type = "Select"
for index, choice in enumerate(values):
json_choice = copy.deepcopy(choice)
if isinstance(json_choice, dict) and 'value' in json_choice:
json_choice['value'] = field.to_json(json_choice['value'])
else:
json_choice = field.to_json(json_choice)
values[index] = json_choice
elif isinstance(field, Integer):
type = "Integer"
elif isinstance(field, Float):
type = "Float"
metadata_fields[field.name] = {'field_name': field.name,
'type': type,
'display_name': field.display_name,
'value': field.to_json(value),
'options': values,
'default_value': field.to_json(default_value),
'inheritable': inheritable,
'explicitly_set': explicitly_set,
'help': field.help}
return metadata
return metadata_fields
class DescriptorSystem(object):
@@ -740,7 +767,7 @@ class ModuleSystem(object):
s3_interface=None,
cache=None,
can_execute_unsafe_code=None,
):
):
'''
Create a closure around the system environment.

View File

@@ -0,0 +1,87 @@
/* HTML5 Number polyfill | Jonathan Stipe | https://github.com/jonstipe/number-polyfill*/
div.number-spin-btn-container {
display: inline-block;
position: absolute;
vertical-align: middle;
margin: 0 0 0 3px;
padding: 0;
left: 74%;
top: 6px;
}
div.number-spin-btn {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
border-width: 2px;
border-color: #ededed #777777 #777777 #ededed;
border-style: solid;
background-color: #eeeeee;
width: 1em;
font-size: 14px; }
div.number-spin-btn:hover {
/* added blue hover color */
background-color: rgb(85, 151, 221);
cursor: pointer; }
div.number-spin-btn:active {
border-width: 2px;
border-color: #5e5e5e #d8d8d8 #d8d8d8 #5e5e5e;
border-style: solid;
background-color: #999999; }
div.number-spin-btn-up {
border-bottom-width: 1px;
-moz-border-radius: 0px;
-webkit-border-radius: 0px;
border-radius: 0px;
font-size: 14px; }
div.number-spin-btn-up:before {
border-width: 0 0.3em 0.3em 0.3em;
border-color: transparent transparent black transparent;
top: 25%; }
div.number-spin-btn-up:active {
border-bottom-width: 1px; }
div.number-spin-btn-up:active:before {
border-bottom-color: white;
top: 26%;
left: 51%; }
div.number-spin-btn-down {
border-top-width: 1px;
-moz-border-radius: 0px 0px 3px 3px;
-webkit-border-radius: 0px 0px 3px 3px;
border-radius: 0px 0px 3px 3px; }
div.number-spin-btn-down:before {
border-width: 0.3em 0.3em 0 0.3em;
border-color: black transparent transparent transparent;
top: 75%; }
div.number-spin-btn-down:active {
border-top-width: 1px; }
div.number-spin-btn-down:active:before {
border-top-color: white;
top: 76%;
left: 51%; }
div.number-spin-btn-up:before,
div.number-spin-btn-down:before {
content: "";
width: 0;
height: 0;
border-style: solid;
position: absolute;
left: 50%;
margin: -0.15em 0 0 -0.3em;
padding: 0; }
input:disabled + div.number-spin-btn-container > div.number-spin-btn-up:active, input:disabled + div.number-spin-btn-container > div.number-spin-btn-down:active {
border-color: #ededed #777777 #777777 #ededed;
border-style: solid;
background-color: #cccccc; }
input:disabled + div.number-spin-btn-container > div.number-spin-btn-up:before, input:disabled + div.number-spin-btn-container > div.number-spin-btn-up:active:before {
border-bottom-color: #999999;
top: 25%;
left: 50%; }
input:disabled + div.number-spin-btn-container > div.number-spin-btn-down:before, input:disabled + div.number-spin-btn-container > div.number-spin-btn-down:active:before {
border-top-color: #999999;
top: 75%;
left: 50%; }

View File

@@ -0,0 +1,298 @@
// Generated by CoffeeScript 1.4.0
/*
HTML5 Number polyfill | Jonathan Stipe | https://github.com/jonstipe/number-polyfill
*/
(function() {
(function($) {
var i;
i = document.createElement("input");
i.setAttribute("type", "number");
if (i.type === "text") {
$.fn.inputNumber = function() {
var clipValues, decrement, domMouseScrollHandler, extractNumDecimalDigits, getParams, increment, matchStep, mouseWheelHandler;
getParams = function(elem) {
var $elem, max, min, step, val;
$elem = $(elem);
step = $elem.attr('step');
min = $elem.attr('min');
max = $elem.attr('max');
val = parseFloat($elem.val());
step = /^-?\d+(?:\.\d+)?$/.test(step) ? parseFloat(step) : null;
min = /^-?\d+(?:\.\d+)?$/.test(min) ? parseFloat(min) : null;
max = /^-?\d+(?:\.\d+)?$/.test(max) ? parseFloat(max) : null;
if (isNaN(val)) {
val = min || 0;
}
return {
min: min,
max: max,
step: step,
val: val
};
};
clipValues = function(value, min, max) {
if ((max != null) && value > max) {
return max;
} else if ((min != null) && value < min) {
return min;
} else {
return value;
}
};
extractNumDecimalDigits = function(input) {
var num, raisedNum;
if (input != null) {
num = 0;
raisedNum = input;
while (raisedNum !== Math.round(raisedNum)) {
num += 1;
raisedNum = input * Math.pow(10, num);
}
return num;
} else {
return 0;
}
};
matchStep = function(value, min, max, step) {
var mod, raiseTo, raisedMod, raisedStep, raisedStepDown, raisedStepUp, raisedValue, stepDecimalDigits, stepDown, stepUp;
stepDecimalDigits = extractNumDecimalDigits(step);
if (step == null) {
return value;
} else if (stepDecimalDigits === 0) {
mod = (value - (min || 0)) % step;
if (mod === 0) {
return value;
} else {
stepDown = value - mod;
stepUp = stepDown + step;
if ((stepUp > max) || ((value - stepDown) < (stepUp - value))) {
return stepDown;
} else {
return stepUp;
}
}
} else {
raiseTo = Math.pow(10, stepDecimalDigits);
raisedStep = step * raiseTo;
raisedMod = (value - (min || 0)) * raiseTo % raisedStep;
if (raisedMod === 0) {
return value;
} else {
raisedValue = value * raiseTo;
raisedStepDown = raisedValue - raisedMod;
raisedStepUp = raisedStepDown + raisedStep;
if (((raisedStepUp / raiseTo) > max) || ((raisedValue - raisedStepDown) < (raisedStepUp - raisedValue))) {
return raisedStepDown / raiseTo;
} else {
return raisedStepUp / raiseTo;
}
}
}
};
increment = function(elem) {
var newVal, params, raiseTo;
if (!$(elem).is(":disabled")) {
params = getParams(elem);
raiseTo = Math.pow(10, Math.max(extractNumDecimalDigits(params['val']), extractNumDecimalDigits(params['step'])));
newVal = (Math.round(params['val'] * raiseTo) + Math.round((params['step'] || 1) * raiseTo)) / raiseTo;
if ((params['max'] != null) && newVal > params['max']) {
newVal = params['max'];
}
newVal = matchStep(newVal, params['min'], params['max'], params['step']);
$(elem).val(newVal).change();
}
return null;
};
decrement = function(elem) {
var newVal, params, raiseTo;
if (!$(elem).is(":disabled")) {
params = getParams(elem);
raiseTo = Math.pow(10, Math.max(extractNumDecimalDigits(params['val']), extractNumDecimalDigits(params['step'])));
newVal = (Math.round(params['val'] * raiseTo) - Math.round((params['step'] || 1) * raiseTo)) / raiseTo;
if ((params['min'] != null) && newVal < params['min']) {
newVal = params['min'];
}
newVal = matchStep(newVal, params['min'], params['max'], params['step']);
$(elem).val(newVal).change();
}
return null;
};
domMouseScrollHandler = function(e) {
e.preventDefault();
if (e.originalEvent.detail < 0) {
increment(this);
} else {
decrement(this);
}
return null;
};
mouseWheelHandler = function(e) {
e.preventDefault();
if (e.originalEvent.wheelDelta > 0) {
increment(this);
} else {
decrement(this);
}
return null;
};
$(this).filter('input[type="number"]').each(function() {
var $downBtn, $elem, $upBtn, attrMutationCallback, attrObserver, btnContainer, downBtn, elem, halfHeight, upBtn;
elem = this;
$elem = $(elem);
halfHeight = ($elem.outerHeight() / 6) + 'px';
upBtn = document.createElement('div');
downBtn = document.createElement('div');
$upBtn = $(upBtn);
$downBtn = $(downBtn);
btnContainer = document.createElement('div');
$upBtn.addClass('number-spin-btn number-spin-btn-up').css('height', halfHeight);
$downBtn.addClass('number-spin-btn number-spin-btn-down').css('height', halfHeight);
btnContainer.appendChild(upBtn);
btnContainer.appendChild(downBtn);
$(btnContainer).addClass('number-spin-btn-container').insertAfter(elem);
$elem.on({
focus: function(e) {
$elem.on({
DOMMouseScroll: domMouseScrollHandler,
mousewheel: mouseWheelHandler
});
return null;
},
blur: function(e) {
$elem.off({
DOMMouseScroll: domMouseScrollHandler,
mousewheel: mouseWheelHandler
});
return null;
},
keypress: function(e) {
var _ref, _ref1;
if (e.keyCode === 38) {
increment(this);
} else if (e.keyCode === 40) {
decrement(this);
} else if (((_ref = e.keyCode) !== 8 && _ref !== 9 && _ref !== 35 && _ref !== 36 && _ref !== 37 && _ref !== 39) && ((_ref1 = e.which) !== 45 && _ref1 !== 46 && _ref1 !== 48 && _ref1 !== 49 && _ref1 !== 50 && _ref1 !== 51 && _ref1 !== 52 && _ref1 !== 53 && _ref1 !== 54 && _ref1 !== 55 && _ref1 !== 56 && _ref1 !== 57)) {
e.preventDefault();
}
return null;
},
change: function(e) {
var newVal, params;
if (e.originalEvent != null) {
params = getParams(this);
newVal = clipValues(params['val'], params['min'], params['max']);
newVal = matchStep(newVal, params['min'], params['max'], params['step'], params['stepDecimal']);
$(this).val(newVal);
}
return null;
}
});
$upBtn.on("mousedown", function(e) {
var releaseFunc, timeoutFunc;
increment(elem);
timeoutFunc = function(elem, incFunc) {
incFunc(elem);
$elem.data("timeoutID", window.setTimeout(timeoutFunc, 10, elem, incFunc));
return null;
};
releaseFunc = function(e) {
window.clearTimeout($elem.data("timeoutID"));
$(document).off('mouseup', releaseFunc);
$upBtn.off('mouseleave', releaseFunc);
return null;
};
$(document).on('mouseup', releaseFunc);
$upBtn.on('mouseleave', releaseFunc);
$elem.data("timeoutID", window.setTimeout(timeoutFunc, 700, elem, increment));
return null;
});
$downBtn.on("mousedown", function(e) {
var releaseFunc, timeoutFunc;
decrement(elem);
timeoutFunc = function(elem, decFunc) {
decFunc(elem);
$elem.data("timeoutID", window.setTimeout(timeoutFunc, 10, elem, decFunc));
return null;
};
releaseFunc = function(e) {
window.clearTimeout($elem.data("timeoutID"));
$(document).off('mouseup', releaseFunc);
$downBtn.off('mouseleave', releaseFunc);
return null;
};
$(document).on('mouseup', releaseFunc);
$downBtn.on('mouseleave', releaseFunc);
$elem.data("timeoutID", window.setTimeout(timeoutFunc, 700, elem, decrement));
return null;
});
$elem.css("textAlign", 'left');
if ($elem.css("opacity") !== "1") {
$(btnContainer).css("opacity", $elem.css("opacity"));
}
if ($elem.css("visibility") !== "visible") {
$(btnContainer).css("visibility", $elem.css("visibility"));
}
if (elem.style.display !== "") {
$(btnContainer).css("display", $elem.css("display"));
}
if ((typeof WebKitMutationObserver !== "undefined" && WebKitMutationObserver !== null) || (typeof MutationObserver !== "undefined" && MutationObserver !== null)) {
attrMutationCallback = function(mutations, observer) {
var mutation, _i, _len;
for (_i = 0, _len = mutations.length; _i < _len; _i++) {
mutation = mutations[_i];
if (mutation.type === "attributes") {
if (mutation.attributeName === "class") {
$(btnContainer).removeClass(mutation.oldValue).addClass(elem.className);
} else if (mutation.attributeName === "style") {
$(btnContainer).css({
"opacity": elem.style.opacity,
"visibility": elem.style.visibility,
"display": elem.style.display
});
}
}
}
return null;
};
attrObserver = (typeof WebKitMutationObserver !== "undefined" && WebKitMutationObserver !== null) ? new WebKitMutationObserver(attrMutationCallback) : ((typeof MutationObserver !== "undefined" && MutationObserver !== null) ? new MutationObserver(attrMutationCallback) : null);
attrObserver.observe(elem, {
attributes: true,
attributeOldValue: true,
attributeFilter: ["class", "style"]
});
} else if (typeof MutationEvent !== "undefined" && MutationEvent !== null) {
$elem.on("DOMAttrModified", function(evt) {
if (evt.originalEvent.attrName === "class") {
$(btnContainer).removeClass(evt.originalEvent.prevValue).addClass(evt.originalEvent.newValue);
} else if (evt.originalEvent.attrName === "style") {
$(btnContainer).css({
"display": elem.style.display,
"visibility": elem.style.visibility,
"opacity": elem.style.opacity
});
}
return null;
});
}
return null;
});
return $(this);
};
$(function() {
$('input[type="number"]').inputNumber();
return null;
});
null;
} else {
$.fn.inputNumber = function() {
return $(this);
};
null;
}
return null;
})(jQuery);
}).call(this);

View File

@@ -25,8 +25,8 @@
/* Layout */
.studioSkin table.mceLayout {border:0;}
.studioSkin table.mceLayout tr.mceFirst td {border-top:1px solid #3c3c3c;}
.studioSkin table.mceLayout tr.mceLast td {border-bottom:1px solid #3c3c3c;}
.studioSkin table.mceLayout tr.mceFirst td {border-top: 1px solid #D1DCE6; border-left: none; border-right:none;}
.studioSkin table.mceLayout tr.mceLast td {border-bottom:none;}
.studioSkin table.mceToolbar, .studioSkin tr.mceFirst .mceToolbar tr td, .studioSkin tr.mceLast .mceToolbar tr td {border:0; margin:0; padding:0;}
.studioSkin td.mceToolbar {
background: -webkit-linear-gradient(top, #d4dee8, #c9d5e2);
@@ -36,11 +36,11 @@
background: linear-gradient(top, #d4dee8, #c9d5e2);
border: 1px solid #3c3c3c;
border-bottom-color: #a5aaaf;
border-radius: 3px 3px 0 0;
border-radius: 0;
padding: 10px 10px 9px;
vertical-align: top;
}
.studioSkin .mceIframeContainer {border: 1px solid #3c3c3c; border-top: none;}
.studioSkin .mceIframeContainer {border: 1px solid white; border-top: none;}
.studioSkin .mceStatusbar {background:#F0F0EE; font-size:9pt; line-height:16px; overflow:visible; color:#000; display:block; height:20px}
.studioSkin .mceStatusbar div {float:left; margin:2px}
.studioSkin .mceStatusbar a.mceResize {display:block; float:right; background:url(../../img/studio-icons.png) -800px 0; width:20px; height:20px; cursor:se-resize; outline:0}