Merged from Master
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
|
||||
@@ -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)),
|
||||
))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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"],
|
||||
)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
metadata:
|
||||
display_name: Blank HTML Page
|
||||
empty: True
|
||||
|
||||
data: |
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
metadata:
|
||||
display_name: Custom Python-Evaluated Input
|
||||
rerandomize: never
|
||||
showanswer: always
|
||||
weight: ""
|
||||
attempts: ""
|
||||
showanswer: finished
|
||||
data: |
|
||||
<problem>
|
||||
<p>
|
||||
|
||||
@@ -2,11 +2,8 @@
|
||||
metadata:
|
||||
display_name: Blank Common Problem
|
||||
rerandomize: never
|
||||
showanswer: always
|
||||
showanswer: finished
|
||||
markdown: ""
|
||||
weight: ""
|
||||
empty: True
|
||||
attempts: ""
|
||||
data: |
|
||||
<problem>
|
||||
</problem>
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
metadata:
|
||||
display_name: Blank Advanced Problem
|
||||
rerandomize: never
|
||||
showanswer: always
|
||||
weight: ""
|
||||
attempts: ""
|
||||
empty: True
|
||||
showanswer: finished
|
||||
data: |
|
||||
<problem>
|
||||
</problem>
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
metadata:
|
||||
display_name: Math Expression Input
|
||||
rerandomize: never
|
||||
showanswer: always
|
||||
weight: ""
|
||||
attempts: ""
|
||||
showanswer: finished
|
||||
data: |
|
||||
<problem>
|
||||
<p>
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
metadata:
|
||||
display_name: Image Mapped Input
|
||||
rerandomize: never
|
||||
showanswer: always
|
||||
weight: ""
|
||||
attempts: ""
|
||||
showanswer: finished
|
||||
data: |
|
||||
<problem>
|
||||
<p>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
---
|
||||
metadata:
|
||||
display_name: Word cloud
|
||||
version: 1
|
||||
num_inputs: 5
|
||||
num_top_words: 250
|
||||
display_student_percents: True
|
||||
data: {}
|
||||
children: []
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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'])
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
87
common/static/css/vendor/html5-input-polyfills/number-polyfill.css
vendored
Normal file
87
common/static/css/vendor/html5-input-polyfills/number-polyfill.css
vendored
Normal 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%; }
|
||||
298
common/static/js/vendor/html5-input-polyfills/number-polyfill.js
vendored
Normal file
298
common/static/js/vendor/html5-input-polyfills/number-polyfill.js
vendored
Normal 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);
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user