Commit 0290f4ff by Xavier Antoviaque

Merge pull request #1 from FiloSottile/xblock

XBlock
parents 0fe52737 7dca95f7
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml
# Translations
*.mo
*.pot
# Django stuff:
*.log
# Sphinx documentation
docs/_build/
# PyBuilder
target/
from .drag_and_drop_v2 import DragAndDropBlock
# -*- coding: utf-8 -*-
#
# Imports ###########################################################
import logging
import textwrap
import json
import webob
from xblock.core import XBlock
from xblock.fields import Scope, String
from xblock.fragment import Fragment
from .utils import render_template, load_resource
# Globals ###########################################################
log = logging.getLogger(__name__)
# Classes ###########################################################
class DragAndDropBlock(XBlock):
"""
XBlock providing a Drag and Drop question
"""
display_name = String(
display_name="Title",
help="The title of the Drag and Drop that is displayed to the user",
scope=Scope.settings,
default="Drag and Drop"
)
question_text = String(
display_name="Question text",
help="The question text that is displayed to the user",
scope=Scope.settings
)
data = String(
display_name="Drag and Drop",
help="JSON spec as generated by the builder",
scope=Scope.content,
default='{"feedback":{"start":"Intro","finish":"Final"},"items":[{"displayName":"A","zone":"Uno","id":0,"feedback":{"correct":"Si","incorrect":"No"},"size":{"width":"190px","height":"auto"},"backgroundImage":""},{"displayName":"B","zone":"none","id":1,"feedback":{"correct":"","incorrect":""},"size":{"width":"190px","height":"auto"},"backgroundImage":""}],"zones":[{"title":"Uno","id":"zone-1","active":true,"index":1,"width":200,"height":100,"x":0,"y":0},{"title":"Due","id":"zone-2","active":true,"index":2,"width":200,"height":100,"x":"300","y":"210"}],"targetImg":"https://i.imgur.com/PoI27ox.png"}'
# default=textwrap.dedent("""
# {
# feedback: {},
# items: [],
# zones: [],
# targetImg: 'img/triangle.png'
# }
# """)
)
def student_view(self, context):
"""
Player view, displayed to the student
"""
context = {
'title': self.display_name,
'question_text': self.question_text
}
fragment = Fragment()
fragment.add_content(render_template('/templates/html/drag_and_drop.html', context))
fragment.add_css(load_resource('public/css/drag_and_drop.css'))
fragment.add_javascript(load_resource('public/js/vendor/jquery.html5-placeholder-shim.js'))
fragment.add_javascript(load_resource('public/js/vendor/underscore1.6.0.js'))
fragment.add_javascript(load_resource('public/js/drag_and_drop.js'))
fragment.initialize_js('DragAndDropBlock')
return fragment
def studio_view(self, context):
"""
Editing view in Studio
"""
context = {}
fragment = Fragment()
fragment.add_content(render_template('/templates/html/drag_and_drop_edit.html', context))
fragment.add_css(load_resource('public/css/drag_and_drop_edit.css'))
fragment.add_javascript(load_resource('public/js/vendor/jquery.html5-placeholder-shim.js'))
fragment.add_javascript(load_resource('public/js/vendor/underscore1.6.0.js'))
fragment.add_javascript(load_resource('public/js/drag_and_drop_edit.js'))
fragment.initialize_js('DragAndDropEditBlock')
return fragment
@XBlock.json_handler
def studio_submit(self, submissions, suffix=''):
self.display_name = submissions['display_name']
self.question_text = submissions['question_text']
data = submissions['data']
try:
json.loads(data)
self.data = data
except ValueError as e:
return {
'result': 'error',
'message': e.message
}
return {
'result': 'success',
}
@XBlock.handler
def get_data(self, request, suffix=''):
return webob.response.Response(body=self.data)
.xblock--drag-and-drop {
width: 770px;
margin: 0;
padding: 0;
background: #fff;
}
.xblock--drag-and-drop .problem-header {
display: inline-block;
margin: 0 0 15px 0;
}
.xblock--drag-and-drop .problem-progress {
display: inline-block;
padding-left: 5px;
color: #666;
font-weight: 100;
font-size: 1em;
}
.xblock--drag-and-drop .problem p {
margin-bottom: 1.41575em;
}
.xblock--drag-and-drop .drag-container {
width: 760px;
background: #ebf0f2;
position: relative;
}
.xblock--drag-and-drop .clear {
clear: both;
}
/** Draggable Items **/
.xblock--drag-and-drop .drag-container .items {
width: 210px;
margin: 10px;
padding: 0 !important; /* LMS tries to override this */
font-size: 14px;
position: relative;
display: inline;
float: left;
list-style-type: none;
}
.xblock--drag-and-drop .drag-container .items .option {
width: 190px;
background: #2e83cd;
color: #fff;
position: relative;
float: left;
display: inline;
z-index: 100;
margin-bottom: 5px;
padding: 10px;
}
.xblock--drag-and-drop .option.hover { background: #ccc; }
.xblock--drag-and-drop .option.fade { opacity: 0.6; }
/*** Drop Target ***/
.xblock--drag-and-drop .target {
width: 515px;
height: 510px;
position: relative;
display: inline;
float: left;
margin: 10px 0 15px 5px;
background: #fff;
z-index: 1;
}
.xblock--drag-and-drop .target-img {
width: 100%;
height: 100%;
}
.xblock--drag-and-drop .zone {
/*border: 1px solid #000;*/
position: absolute;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
/* Internet Explorer 10 */
-ms-flex-pack:center;
-ms-flex-align:center;
/* Firefox */
-moz-box-pack:center;
-moz-box-align:center;
/* Safari, Opera, and Chrome */
-webkit-box-pack:center;
-webkit-box-align:center;
/* W3C */
box-pack:center;
box-align:center;
}
.xblock--drag-and-drop .zone p {
width: 100%;
font-family: Arial;
font-size: 16px;
font-weight: bold;
text-align: center;
text-transform: uppercase;
margin-top: auto;
margin-bottom: auto;
}
.xblock--drag-and-drop .zone.one {
height: 75px;
width: 115px;
top: 130px;
left: 200px;
}
.xblock--drag-and-drop .zone.two {
height: 120px;
width: 200px;
top: 220px;
left: 157px;
}
.xblock--drag-and-drop .zone.three {
height: 120px;
width: 200px;
bottom: 30px;
left: 157px;
}
/*** IE9 alignment fix ***/
.lt-ie10 .xblock--drag-and-drop .zone {
display: table;
}
.lt-ie10 .xblock--drag-and-drop .zone p {
display: table-cell;
vertical-align: middle;
text-align: center;
}
/*** FEEDBACK ***/
.xblock--drag-and-drop .feedback p {
line-height: 1.5em;
font-weight: bold;
margin-bottom: 1.41575em;
}
.no-close .ui-dialog-titlebar-close {
display: none;
}
/*** xBlock styles ***/ /*** xBlock styles ***/
.xblock--drag-and-drop { .xblock--drag-and-drop {
width: 770px; width: 100%;
margin: 0; margin: 0;
padding: 0; padding: 0;
background: #fff; background: #fff;
} }
.xblock--drag-and-drop h1, .xblock--drag-and-drop h1,
...@@ -15,45 +15,45 @@ ...@@ -15,45 +15,45 @@
.xblock--drag-and-drop p, .xblock--drag-and-drop p,
.xblock--drag-and-drop li, .xblock--drag-and-drop li,
.xblock--drag-and-drop a { .xblock--drag-and-drop a {
font-family: Arial; font-family: Arial;
} }
.xblock--drag-and-drop h1 { .xblock--drag-and-drop h1 {
color: #adadad; color: #adadad;
} }
.xblock--drag-and-drop h2 { .xblock--drag-and-drop h2 {
color: #333; color: #333;
margin: 0; margin: 0;
text-transform: uppercase; text-transform: uppercase;
} }
.xblock--drag-and-drop header p, .xblock--drag-and-drop header p,
.xblock--drag-and-drop footer p { .xblock--drag-and-drop footer p {
color: #adadad; color: #adadad;
line-height: 1.5em; line-height: 1.5em;
} }
.xblock--drag-and-drop .small { .xblock--drag-and-drop .small {
font-size: 0.6em; font-size: 0.6em;
} }
.xblock--drag-and-drop .drag-container { .xblock--drag-and-drop .drag-container {
width: 760px; width: 760px;
background: #ebf0f2; background: #ebf0f2;
position: relative; position: relative;
} }
/** Draggable Items **/ /** Draggable Items **/
.xblock--drag-and-drop .items { .xblock--drag-and-drop .items {
width: 210px; width: 210px;
margin: 10px; margin: 10px;
padding: 0; padding: 0;
font-size: 14px; font-size: 14px;
position: relative; position: relative;
display: inline; display: inline;
float: left; float: left;
list-style-type: none; list-style-type: none;
} }
...@@ -75,106 +75,119 @@ ...@@ -75,106 +75,119 @@
/*** Drop Target ***/ /*** Drop Target ***/
.xblock--drag-and-drop .target { .xblock--drag-and-drop .target {
width: 515px; width: 515px;
height: 510px; height: 510px;
position: relative; position: relative;
display: inline; display: inline;
float: left; float: left;
margin: 10px 0 15px 5px; margin: 10px 0 15px 5px;
background: #fff; background: #fff;
z-index: 1; z-index: 1;
} }
.xblock--drag-and-drop .target-img { .xblock--drag-and-drop .target-img {
width: 100%; background: url(../img/triangle.png) no-repeat;
height: 100%; width: 100%;
height: 100%;
} }
.xblock--drag-and-drop .zone { .xblock--drag-and-drop .zone {
/*border: 1px solid #000;*/ /*border: 1px solid #000;*/
position: absolute; position: absolute;
display: -webkit-box; display: -webkit-box;
display: -moz-box; display: -moz-box;
display: -ms-flexbox; display: -ms-flexbox;
display: -webkit-flex; display: -webkit-flex;
display: flex; display: flex;
/* Internet Explorer 10 */ /* Internet Explorer 10 */
-ms-flex-pack:center; -ms-flex-pack:center;
-ms-flex-align:center; -ms-flex-align:center;
/* Firefox */ /* Firefox */
-moz-box-pack:center; -moz-box-pack:center;
-moz-box-align:center; -moz-box-align:center;
/* Safari, Opera, and Chrome */ /* Safari, Opera, and Chrome */
-webkit-box-pack:center; -webkit-box-pack:center;
-webkit-box-align:center; -webkit-box-align:center;
/* W3C */ /* W3C */
box-pack:center; box-pack:center;
box-align:center; box-align:center;
} }
.xblock--drag-and-drop .zone p { .xblock--drag-and-drop .zone p {
width: 100%; width: 100%;
font-family: Arial; font-family: Arial;
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;
text-transform: uppercase; text-transform: uppercase;
margin-top: auto; margin-top: auto;
margin-bottom: auto; margin-bottom: auto;
} }
.xblock--drag-and-drop .zone.one { .xblock--drag-and-drop .zone.one {
height: 75px; height: 75px;
width: 115px; width: 115px;
top: 130px; top: 130px;
left: 200px; left: 200px;
} }
.xblock--drag-and-drop .zone.two { .xblock--drag-and-drop .zone.two {
height: 120px; height: 120px;
width: 200px; width: 200px;
top: 220px; top: 220px;
left: 157px; left: 157px;
} }
.xblock--drag-and-drop .zone.three { .xblock--drag-and-drop .zone.three {
height: 120px; height: 120px;
width: 200px; width: 200px;
bottom: 30px; bottom: 30px;
left: 157px; left: 157px;
} }
/*** IE9 alignment fix ***/ /*** IE9 alignment fix ***/
.lt-ie10 .xblock--drag-and-drop .zone { .lt-ie10 .xblock--drag-and-drop .zone {
display: table; display: table;
} }
.lt-ie10 .xblock--drag-and-drop .zone p { .lt-ie10 .xblock--drag-and-drop .zone p {
display: table-cell; display: table-cell;
vertical-align: middle; vertical-align: middle;
text-align: center; text-align: center;
} }
/*** FEEDBACK ***/ /*** FEEDBACK ***/
.xblock--drag-and-drop .feedback { .xblock--drag-and-drop .feedback {
line-height: 1.5em; width: 740px;
font-weight: bold; border-top: #ccc 1px solid;
margin: 20px 10px;
padding-top: 10px;
}
.xblock--drag-and-drop .feedback .message {
margin: 5px 0 0;
} }
/** Builder **/ /** Builder **/
.xblock--drag-and-drop .hidden { .xblock--drag-and-drop .hidden {
display: none!important; display: none !important;
}
.xblock--drag-and-drop .drag-builder {
/* TODO */
height: 375px;
overflow: scroll;
} }
.xblock--drag-and-drop .drag-builder .tab { .xblock--drag-and-drop .drag-builder .tab {
width: 98%; width: 100%;
background: #eee; background: #eee;
padding: 3px 0; padding: 3px 0;
position: relative; position: relative;
} }
.xblock--drag-and-drop .drag-builder .tab:after, .xblock--drag-and-drop .drag-builder .tab:after,
...@@ -186,239 +199,219 @@ ...@@ -186,239 +199,219 @@
} }
.xblock--drag-and-drop .drag-builder .tab h3 { .xblock--drag-and-drop .drag-builder .tab h3 {
margin: 10px 0; margin: 10px 0;
} }
.xblock--drag-and-drop .drag-builder .tab-header, .xblock--drag-and-drop .drag-builder .tab-header,
.xblock--drag-and-drop .drag-builder .tab-content, .xblock--drag-and-drop .drag-builder .tab-content,
.xblock--drag-and-drop .drag-builder .tab-footer { .xblock--drag-and-drop .drag-builder .tab-footer {
width: 96%; width: 96%;
margin: 2%; margin: 2%;
} }
.xblock--drag-and-drop .drag-builder .tab-footer { .xblock--drag-and-drop .drag-builder .tab-footer {
height: 25px; height: 25px;
position: relative; position: relative;
display: block; display: block;
float: left; float: left;
} }
.xblock--drag-and-drop .drag-builder .continue { .xblock--drag-and-drop .drag-builder .continue {
position: absolute; position: absolute;
right: 0; right: 0;
top: -5px; top: -5px;
} }
.xblock--drag-and-drop .drag-builder .items { .xblock--drag-and-drop .drag-builder .items {
width: calc(100% - 515px); width: calc(100% - 515px);
margin: 10px 0 0 0; margin: 10px 0 0 0;
} }
.xblock--drag-and-drop .drag-builder .target { .xblock--drag-and-drop .drag-builder .target {
margin-left: 0; margin-left: 0;
}
.xblock--drag-and-drop .drag-builder .target-image-form input {
width: 400px;
} }
.xblock--drag-and-drop .zones-form .zone-row label { .xblock--drag-and-drop .zones-form .zone-row label {
display: inline-block; display: inline-block;
width: 18%; width: 18%;
} }
.xblock--drag-and-drop .zones-form .zone-row .title { .xblock--drag-and-drop .zones-form .zone-row .title {
width: 60%; width: 60%;
margin: 0 0 5px; margin: 0 0 5px;
} }
.xblock--drag-and-drop .zones-form .zone-row .layout { .xblock--drag-and-drop .zones-form .zone-row .layout {
margin-bottom: 15px; margin-bottom: 15px;
} }
.xblock--drag-and-drop .zones-form .zone-row .layout .size, .xblock--drag-and-drop .zones-form .zone-row .layout .size,
.xblock--drag-and-drop .zones-form .zone-row .layout .coord { .xblock--drag-and-drop .zones-form .zone-row .layout .coord {
width: 15%; width: 15%;
margin: 0 19px 5px 0; margin: 0 19px 5px 0;
} }
.xblock--drag-and-drop .drag-builder .target { .xblock--drag-and-drop .drag-builder .target {
margin-bottom: 40px; margin-bottom: 40px;
} }
.xblock--drag-and-drop .drag-builder .zone { .xblock--drag-and-drop .drag-builder .zone {
width: 200px; width: 200px;
height: 100px; height: 100px;
border: 1px dotted #666; border: 1px dotted #666;
} }
.xblock--drag-and-drop .feedback-form textarea { .xblock--drag-and-drop .feedback-form textarea {
width: 99%; width: 99%;
height: 128px; height: 128px;
margin-bottom: 30px; margin-bottom: 30px;
} }
.xblock--drag-and-drop .items-form { .xblock--drag-and-drop .items-form {
margin-bottom: 30px; margin-bottom: 30px;
} }
.xblock--drag-and-drop .items-form .item { .xblock--drag-and-drop .items-form .item {
background: #73bde7; background: #73bde7;
padding: 10px 0 1px; padding: 10px 0 1px;
margin: 15px 0; margin: 15px 0;
} }
.xblock--drag-and-drop .items-form label { .xblock--drag-and-drop .items-form label {
margin: 0 1%; margin: 0 1%;
} }
.xblock--drag-and-drop .items-form input, .xblock--drag-and-drop .items-form input,
.xblock--drag-and-drop .items-form select { .xblock--drag-and-drop .items-form select {
width: 35%; width: 35%;
} }
.xblock--drag-and-drop .items-form textarea { .xblock--drag-and-drop .items-form .item-width,
width: 97%; .xblock--drag-and-drop .items-form .item-height {
margin: 0 1%; width: 40px;
} }
.xblock--drag-and-drop .items-form .row { .xblock--drag-and-drop .items-form textarea {
margin-bottom: 20px; width: 97%;
margin: 0 1%;
} }
.xblock--drag-and-drop .items-form .item-width, .xblock--drag-and-drop .items-form .row {
.xblock--drag-and-drop .items-form .item-height { margin-bottom: 20px;
width: 30px;
margin-right: 50px;
} }
/** Buttons **/ /** Buttons **/
.xblock--drag-and-drop .btn { .xblock--drag-and-drop .btn {
background: #2e83cd; background: #2e83cd;
color: #fff; color: #fff;
border: 1px solid #156ab4; border: 1px solid #156ab4;
border-radius: 6px; border-radius: 6px;
padding: 5px 10px; padding: 5px 10px;
} }
.xblock--drag-and-drop .btn:hover { .xblock--drag-and-drop .btn:hover {
opacity: 0.8; opacity: 0.8;
cursor: pointer; cursor: pointer;
} }
.xblock--drag-and-drop .btn:focus { .xblock--drag-and-drop .btn:focus {
outline: none; outline: none;
opacity: 0.5; opacity: 0.5;
} }
.xblock--drag-and-drop .add-element { .xblock--drag-and-drop .add-element {
text-decoration: none; text-decoration: none;
color: #2e83cd; color: #2e83cd;
} }
.xblock--drag-and-drop .remove-zone { .xblock--drag-and-drop .remove-zone {
float: right; float: right;
margin-top: 2px; margin-top: 2px;
margin-right: 16px; margin-right: 16px;
} }
.xblock--drag-and-drop .remove-item { .xblock--drag-and-drop .remove-item {
display: inline-block; display: inline-block;
margin-left: 95px; margin-left: 95px;
} }
.xblock--drag-and-drop .icon { .xblock--drag-and-drop .icon {
width: 14px; width: 14px;
height: 14px; height: 14px;
border-radius: 7px; border-radius: 7px;
background: #2e83cd; background: #2e83cd;
position: relative; position: relative;
float: left; float: left;
margin: 0 5px 0 0; margin: 0 5px 0 0;
} }
.xblock--drag-and-drop .add-zone:hover, .xblock--drag-and-drop .add-zone:hover,
.xblock--drag-and-drop .add-zone:hover .icon, .xblock--drag-and-drop .add-zone:hover .icon,
.xblock--drag-and-drop .remove-zone:hover, .xblock--drag-and-drop .remove-zone:hover,
.xblock--drag-and-drop .remove-zone:hover .icon { .xblock--drag-and-drop .remove-zone:hover .icon {
opacity: 0.7; opacity: 0.7;
} }
.xblock--drag-and-drop .icon.add:before { .xblock--drag-and-drop .icon.add:before {
content: ''; content: '';
height: 10px; height: 10px;
width: 2px; width: 2px;
background: #fff; background: #fff;
position: relative; position: relative;
display: inline; display: inline;
float: left; float: left;
top: 2px; top: 2px;
left: 6px; left: 6px;
} }
.xblock--drag-and-drop .icon.add:after { .xblock--drag-and-drop .icon.add:after {
content: ''; content: '';
height: 2px; height: 2px;
width: 10px; width: 10px;
background: #fff; background: #fff;
position: relative; position: relative;
display: inline; display: inline;
float: left; float: left;
top: 6px; top: 6px;
left: 0; left: 0;
} }
.xblock--drag-and-drop .icon.remove:before { .xblock--drag-and-drop .icon.remove:before {
content: ''; content: '';
height: 10px; height: 10px;
width: 2px; width: 2px;
background: #fff; background: #fff;
position: relative; position: relative;
display: inline; display: inline;
float: left; float: left;
top: 2px; top: 2px;
left: 6px; left: 6px;
-webkit-transform: rotate(45deg); -webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg); -ms-transform: rotate(45deg);
transform: rotate(45deg); transform: rotate(45deg);
} }
.xblock--drag-and-drop .icon.remove:after { .xblock--drag-and-drop .icon.remove:after {
content: ''; content: '';
height: 2px; height: 2px;
width: 10px; width: 10px;
background: #fff; background: #fff;
position: relative; position: relative;
display: inline; display: inline;
float: left; float: left;
top: 6px; top: 6px;
left: 0; left: 0;
-webkit-transform: rotate(45deg); -webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg); -ms-transform: rotate(45deg);
transform: rotate(45deg); transform: rotate(45deg);
} }
.xblock--drag-and-drop .remove-item .icon.remove { .xblock--drag-and-drop .remove-item .icon.remove {
background: #fff; background: #fff;
} }
.xblock--drag-and-drop .remove-item .icon.remove:before, .xblock--drag-and-drop .remove-item .icon.remove:before,
.xblock--drag-and-drop .remove-item .icon.remove:after { .xblock--drag-and-drop .remove-item .icon.remove:after {
background: #2e83cd; background: #2e83cd;
}
/*** Temp. styles for surrounding area & dev ***/
body,
.container {
margin: 0;
padding: 0;
background: #e5ebee;
}
.xblock--drag-and-drop {
margin: 20px auto 0;
padding: 10px 0 10px 10px;
} }
.reset { margin: 0 0 15px 700px; }
function DragAndDropBlock(runtime, element) {
var dragAndDrop = (function($) {
var _fn = {
// DOM Elements
$block: $('.xblock--drag-and-drop'),
$app: $('.xblock--drag-and-drop .drag-container'),
$ul: $('.xblock--drag-and-drop .items'),
$target: $('.xblock--drag-and-drop .target-img'),
$feedback: $('.xblock--drag-and-drop .feedback .message'),
// Cannot set until items added to DOM
$items: {}, // $('.xblock--drag-and-drop .items .option'),
$zones: {}, // $('.xblock--drag-and-drop .target .zone'),
// jQuery UI Draggable options
options: {
drag: {
containment: '.xblock--drag-and-drop .drag-container',
cursor: 'move',
stack: '.xblock--drag-and-drop .items .option'
},
drop: {
accept: '.xblock--drag-and-drop .items .option',
tolerance: 'pointer'
}
},
// item template
tpl: {
item: function() {
return [
'<li class="option" data-value="<%= id %>"',
'style="width: <%= size.width %>; height: <%= size.height %>">',
'<%= displayName %>',
'</li>'
].join('');
},
image_item: function() {
return [
'<li class="option" data-value="<%= id %>"',
'style="width: <%= size.width %>; height: <%= size.height %>">',
'<img src="<%= backgroundImage %>" />',
'</li>'
].join('');
},
zoneInput: function() {
return [
'<div class="zone-row <%= name %>">',
'<label>Text</label>',
'<input type="text" class="title" placeholder="<%= title %>" />',
'<a href="#" class="remove-zone hidden">',
'<div class="icon remove"></div>',
'</a>',
'<div class="layout">',
'<label>width</label>',
'<input type="text" class="size width" value="200" />',
'<label>height</label>',
'<input type="text" class="size height" value="100" />',
'<label>x</label>',
'<input type="text" class="coord x" value="0" />',
'<label>y</label>',
'<input type="text" class="coord y" value="0" />',
'</div>',
'</div>'
].join('');
},
zoneElement: function() {
return [
'<div id="<%= id %>" class="zone" data-zone="<%= title %>" style="',
'top:<%= y %>px;',
'left:<%= x %>px;',
'width:<%= width %>px;',
'height:<%= height %>px;">',
'<p><%= title %></p>',
'</div>'
].join('');
},
zoneDropdown: '<option value="<%= value %>"><%= value %></option>',
itemInput: function() {
return [
'<div class="item">',
'<div class="row">',
'<label>Text</label>',
'<input type="text" class="item-text"></input>',
'<label>Zone</label>',
'<select class="zone-select"><%= dropdown %></select>',
'<a href="#" class="remove-item hidden">',
'<div class="icon remove"></div>',
'</a>',
'</div>',
'<div class="row">',
'<label>Background image URL (alternative to the text)</label>',
'<textarea class="background-image"></textarea>',
'</div>',
'<div class="row">',
'<label>Success Feedback</label>',
'<textarea class="success-feedback"></textarea>',
'</div>',
'<div class="row">',
'<label>Error Feedback</label>',
'<textarea class="error-feedback"></textarea>',
'</div>',
'<div class="row">',
'<label>Width (px - 0 for auto)</label>',
'<input type="text" class="item-width" value="190"></input>',
'<label>Height (px - 0 for auto)</label>',
'<input type="text" class="item-height" value="0"></input>',
'</div>',
'</div>'
].join('');
}
},
init: function(data) {
_fn.data = data;
// Add the items to the page
_fn.items.draw();
_fn.zones.draw();
// Load welcome feedback
_fn.feedback.set( _fn.data.feedback.start );
// Init drag and drop plugin
_fn.$items.draggable( _fn.options.drag );
_fn.$zones.droppable( _fn.options.drop );
// Init click handlers
_fn.clickHandlers.init( _fn.$items, _fn.$zones );
// Get count of all active items
_fn.items.init();
// Set the target image
_fn.$target.css('background', 'url(' + _fn.data.targetImg + ') no-repeat');
},
finish: function() {
// Disable any decoy items
_fn.$items.draggable('disable');
// Show final feedback
_fn.feedback.set( _fn.data.feedback.finish );
},
clickHandlers: {
init: function( $drag, $dropzone ) {
var clk = _fn.clickHandlers;
$drag.on( 'dragstart', clk.drag.start );
$drag.on( 'dragstop', clk.drag.stop );
$dropzone.on( 'drop', clk.drop.success );
$dropzone.on( 'dropover', clk.drop.hover.in );
$dropzone.on( 'dropout', clk.drop.hover.out );
},
drag: {
start: function( event, ui ) {
$(event.currentTarget).removeClass('within-dropzone fade');
},
stop: function( event, ui ) {
var $el = $(event.currentTarget),
val = $el.data('value'),
zone = $el.data('zone') || null;
if ( $el.hasClass('within-dropzone') && _fn.test.match( val, zone ) ) {
$el.removeClass('hover')
.draggable('disable');
_fn.test.completed++;
_fn.feedback.popup( _fn.feedback.get(val, true), true );
if ( _fn.items.allSubmitted() ) {
_fn.finish();
}
} else {
// Return to original position
_fn.clickHandlers.drag.reset( $el );
_fn.feedback.popup( _fn.feedback.get(val, false), false );
}
},
reset: function( $el ) {
$el.removeClass('within-dropzone fade hover')
.css({
top: '',
left: ''
});
}
},
drop: {
hover: {
in: function( event, ui ) {
var zone = $(event.currentTarget).data('zone');
ui.draggable.addClass('hover').data('zone', zone);
},
out: function( event, ui ) {
ui.draggable.removeClass('hover');
}
},
success: function( event, ui ) {
ui.draggable.addClass('within-dropzone');
}
}
},
items: {
count: 0,
init: function() {
var items = _fn.data.items,
i,
len = items.length,
total = 0;
for ( i=0; i<len; i++ ) {
if ( items[i].zone !== 'none' ) {
total++;
}
}
_fn.items.count = total;
},
allSubmitted: function() {
return _fn.test.completed === _fn.items.count;
},
draw: function() {
var list = [],
items = _fn.data.items,
tpl = _fn.tpl.item(),
img_tpl = _fn.tpl.image_item();
_.each(items, function(item) {
if (item.backgroundImage.length > 0) {
list.push( _.template( img_tpl, item ) );
} else {
list.push( _.template( tpl, item ) );
}
});
// Update DOM
_fn.$ul.html( list.join('') );
// Set variable
_fn.$items = $('.xblock--drag-and-drop .items .option');
}
},
zones: {
draw: function() {
var html = [],
zones = _fn.data.zones,
tpl = _fn.tpl.zoneElement(),
i,
len = zones.length;
for ( i=0; i<len; i++ ) {
html.push( _.template( tpl, zones[i] ) );
}
// Update DOM
_fn.$target.html( html.join('') );
// Set variable
_fn.$zones = _fn.$target.find('.zone');
}
},
test: {
completed: 0,
match: function( id, zone ) {
var item = _.findWhere( _fn.data.items, { id: id } );
return item.zone === zone;
}
},
feedback: {
// Returns string based on user's answer
get: function( id, boo ) {
var item,
type = boo ? 'correct' : 'incorrect';
// Null loses its string-ness
if ( id === null ) {
id = 'null';
}
// Get object from data.items that matches val
item = _.findWhere( _fn.data.items, { id: id });
return item.feedback[type];
},
// Update DOM with feedback
set: function(str) {
return _fn.$feedback.html(str);
},
// Show a feedback popup
popup: function(str, boo) {
if (str === undefined || str === '') return;
return $("<div>").attr('title', boo ? 'Correct' : 'Incorrect')
.text(str)
.dialog({
dialogClass: "no-close",
modal: true,
buttons: {
Ok: function() {
$( this ).dialog( "close" );
}
}
});
}
},
data: {
feedback: {},
items: [],
zones: [],
targetImg: 'img/triangle.png'
}
};
return {
init: _fn.init,
data: _fn.data
};
})(jQuery);
$.ajax(runtime.handlerUrl(element, 'get_data')).done(function(data){
dragAndDrop.init(data);
});
}
var dragAndDrop = (function() { function DragAndDropEditBlock(runtime, element) {
var _fn = { var dragAndDrop = (function($) {
var _fn = {
// DOM Elements
$block: $('.xblock--drag-and-drop'), // DOM Elements
$app: $('.xblock--drag-and-drop .drag-container'), $block: $('.xblock--drag-and-drop', element),
$ul: $('.xblock--drag-and-drop .items'), $app: $('.xblock--drag-and-drop .drag-container', element),
$target: $('.xblock--drag-and-drop .target-img'), $ul: $('.xblock--drag-and-drop .items', element),
$feedback: $('.xblock--drag-and-drop .feedback .message'), $target: $('.xblock--drag-and-drop .target-img', element),
$feedback: $('.xblock--drag-and-drop .feedback .message', element),
// Cannot set until items added to DOM
$items: {}, // $('.xblock--drag-and-drop .items .option'), // Cannot set until items added to DOM
$zones: {}, // $('.xblock--drag-and-drop .target .zone'), $items: {}, // $('.xblock--drag-and-drop .items .option'),
$zones: {}, // $('.xblock--drag-and-drop .target .zone'),
// jQuery UI Draggable options
options: { // jQuery UI Draggable options
drag: { options: {
containment: '.xblock--drag-and-drop .drag-container', drag: {
cursor: 'move', containment: '.xblock--drag-and-drop .drag-container',
stack: '.xblock--drag-and-drop .items .option' cursor: 'move',
}, stack: '.xblock--drag-and-drop .items .option'
drop: { },
accept: '.xblock--drag-and-drop .items .option', drop: {
tolerance: 'pointer' accept: '.xblock--drag-and-drop .items .option',
} tolerance: 'pointer'
}, }
},
// item template
tpl: { // item template
item: function() { tpl: {
return [ item: function() {
'<li class="option" data-value="<%= id %>"', return [
'style="width: <%= size.width %>; height: <%= size.height %>">', '<li class="option" data-value="<%= id %>"',
'<%= displayName %>', 'style="width: <%= size.width %>; height: <%= size.height %>">',
'</li>' '<%= displayName %>',
].join(''); '</li>'
}, ].join('');
image_item: function() { },
return [ image_item: function() {
'<li class="option" data-value="<%= id %>"', return [
'style="width: <%= size.width %>; height: <%= size.height %>">', '<li class="option" data-value="<%= id %>"',
'<img src="<%= backgroundImage %>" />', 'style="width: <%= size.width %>; height: <%= size.height %>">',
'</li>' '<img src="<%= backgroundImage %>" />',
].join(''); '</li>'
}, ].join('');
zoneInput: function() { },
return [ zoneInput: function() {
'<div class="zone-row <%= name %>">', return [
'<label>Text</label>', '<div class="zone-row <%= name %>">',
'<input type="text" class="title" placeholder="<%= title %>" />',
'<a href="#" class="remove-zone hidden">',
'<div class="icon remove"></div>',
'</a>',
'<div class="layout">',
'<label>width</label>',
'<input type="text" class="size width" value="200" />',
'<label>height</label>',
'<input type="text" class="size height" value="100" />',
'<label>x</label>',
'<input type="text" class="coord x" value="0" />',
'<label>y</label>',
'<input type="text" class="coord y" value="0" />',
'</div>',
'</div>'
].join('');
},
zoneElement: function() {
return [
'<div id="<%= id %>" class="zone" data-zone="<%= title %>" style="',
'top:<%= y %>px;',
'left:<%= x %>px;',
'width:<%= width %>px;',
'height:<%= height %>px;">',
'<p><%= title %></p>',
'</div>'
].join('');
},
zoneDropdown: '<option value="<%= value %>"><%= value %></option>',
itemInput: function() {
return [
'<div class="item">',
'<div class="row">',
'<label>Text</label>', '<label>Text</label>',
'<input type="text" class="item-text"></input>', '<input type="text" class="title" placeholder="<%= title %>" />',
'<label>Zone</label>', '<a href="#" class="remove-zone hidden">',
'<select class="zone-select"><%= dropdown %></select>', '<div class="icon remove"></div>',
'<a href="#" class="remove-item hidden">', '</a>',
'<div class="icon remove"></div>', '<div class="layout">',
'</a>', '<label>width</label>',
'</div>', '<input type="text" class="size width" value="200" />',
'<div class="row">', '<label>height</label>',
'<label>Background image URL (alternative to the text)</label>', '<input type="text" class="size height" value="100" />',
'<textarea class="background-image"></textarea>', '<br />',
'</div>', '<label>x</label>',
'<div class="row">', '<input type="text" class="coord x" value="0" />',
'<label>Success Feedback</label>', '<label>y</label>',
'<textarea class="success-feedback"></textarea>', '<input type="text" class="coord y" value="0" />',
'</div>', '</div>',
'<div class="row">', '</div>'
'<label>Error Feedback</label>', ].join('');
'<textarea class="error-feedback"></textarea>', },
'</div>', zoneElement: function() {
'<div class="row">', return [
'<label>Width (px - 0 for auto)</label>', '<div id="<%= id %>" class="zone" data-zone="<%= title %>" style="',
'<input type="text" class="item-width" value="190"></input>', 'top:<%= y %>px;',
'<label>Height (px - 0 for auto)</label>', 'left:<%= x %>px;',
'<input type="text" class="item-height" value="0"></input>', 'width:<%= width %>px;',
'</div>', 'height:<%= height %>px;">',
'</div>' '<p><%= title %></p>',
].join(''); '</div>'
} ].join('');
}, },
zoneDropdown: '<option value="<%= value %>"><%= value %></option>',
init: function() { itemInput: function() {
// Add the items to the page return [
_fn.items.draw(); '<div class="item">',
_fn.zones.draw(); '<div class="row">',
'<label>Text</label>',
// Load welcome feedback '<input type="text" class="item-text"></input>',
_fn.feedback.set( _fn.data.feedback.start ); '<label>Zone</label>',
'<select class="zone-select"><%= dropdown %></select>',
// Init drag and drop plugin '<a href="#" class="remove-item hidden">',
_fn.$items.draggable( _fn.options.drag ); '<div class="icon remove"></div>',
_fn.$zones.droppable( _fn.options.drop ); '</a>',
'</div>',
// Init click handlers '<div class="row">',
_fn.clickHandlers.init( _fn.$items, _fn.$zones ); '<label>Background image URL (alternative to the text)</label>',
'<textarea class="background-image"></textarea>',
// Get count of all active items '</div>',
_fn.items.init(); '<div class="row">',
'<label>Success Feedback</label>',
// Set the target image '<textarea class="success-feedback"></textarea>',
_fn.$target.css('background', 'url(' + _fn.data.targetImg + ') no-repeat'); '</div>',
}, '<div class="row">',
'<label>Error Feedback</label>',
build: { '<textarea class="error-feedback"></textarea>',
$el: { '</div>',
feedback: { '<div class="row">',
form: $('.xblock--drag-and-drop .drag-builder .feedback-form'), '<label>Width (px - 0 for auto)</label>',
tab: $('.xblock--drag-and-drop .drag-builder .feedback-tab') '<input type="text" class="item-width" value="190"></input>',
}, '<label>Height (px - 0 for auto)</label>',
zones: { '<input type="text" class="item-height" value="0"></input>',
form: $('.xblock--drag-and-drop .drag-builder .zones-form'), '</div>',
tab: $('.xblock--drag-and-drop .drag-builder .zones-tab') '</div>'
}, ].join('');
items: { }
form: $('.xblock--drag-and-drop .drag-builder .items-form'), },
tab: $('.xblock--drag-and-drop .drag-builder .items-tab')
}, build: {
target: $('.xblock--drag-and-drop .drag-builder .target-img') $el: {
}, feedback: {
init: function() { form: $('.xblock--drag-and-drop .drag-builder .feedback-form', element),
_fn.build.clickHandlers(); tab: $('.xblock--drag-and-drop .drag-builder .feedback-tab', element)
_fn.build.form.zone.add(); },
_fn.$target.css('background', 'url(' + _fn.data.targetImg + ') no-repeat'); zones: {
}, form: $('.xblock--drag-and-drop .drag-builder .zones-form', element),
clickHandlers: function() { tab: $('.xblock--drag-and-drop .drag-builder .zones-tab', element)
var $fbkTab = _fn.build.$el.feedback.tab, },
$zoneTab = _fn.build.$el.zones.tab, items: {
$itemTab = _fn.build.$el.items.tab; form: $('.xblock--drag-and-drop .drag-builder .items-form', element),
tab: $('.xblock--drag-and-drop .drag-builder .items-tab', element)
$fbkTab.on( 'click', '.goto-zones', function(e) { },
e.preventDefault(); target: $('.xblock--drag-and-drop .drag-builder .target-img', element)
_fn.build.form.feedback( _fn.build.$el.feedback.form ); },
init: function() {
$fbkTab.addClass('hidden'); _fn.build.clickHandlers();
$zoneTab.removeClass('hidden'); _fn.build.form.zone.add();
_fn.$target.css('background', 'url(' + _fn.data.targetImg + ') no-repeat');
// Placeholder shim for IE9 },
$.placeholder.shim(); clickHandlers: function() {
}); var $fbkTab = _fn.build.$el.feedback.tab,
$zoneTab = _fn.build.$el.zones.tab,
$zoneTab $itemTab = _fn.build.$el.items.tab;
.on( 'click', '.add-zone', _fn.build.form.zone.add )
.on( 'click', '.remove-zone', _fn.build.form.zone.remove ) $(element).one( 'click', '.continue-button', function(e) {
.on( 'click', '.goto-items', function(e) { e.preventDefault();
e.preventDefault(); _fn.build.form.feedback( _fn.build.$el.feedback.form );
_fn.build.form.zone.setAll();
_fn.build.form.item.add(); $fbkTab.addClass('hidden');
$zoneTab.removeClass('hidden');
$zoneTab.addClass('hidden');
$itemTab.removeClass('hidden'); // Placeholder shim for IE9
$.placeholder.shim();
// Placeholder shim for IE9
$.placeholder.shim(); $(this).one( 'click', function(e) {
}) e.preventDefault();
.on( 'click', '.target-image-form button', function(e) { _fn.build.form.zone.setAll();
e.preventDefault(); _fn.build.form.item.add();
_fn.data.targetImg = $('.target-image-form input').val(); $zoneTab.addClass('hidden');
_fn.$target.css('background', 'url(' + _fn.data.targetImg + ') no-repeat'); $itemTab.removeClass('hidden');
// Placeholder shim for IE9 // Placeholder shim for IE9
$.placeholder.shim(); $.placeholder.shim();
});
$(this).addClass('hidden');
$itemTab $('.save-button', element).parent()
.on( 'click', '.add-item', _fn.build.form.item.add ) .removeClass('hidden')
.on( 'click', '.remove-item', _fn.build.form.item.remove ) .one( 'click', function(e) {
.on( 'click', '.goto-exercise', function(e) { e.preventDefault();
e.preventDefault(); _fn.build.form.submit();
_fn.build.form.submit(); });
}); });
}, });
form: {
zone: { $zoneTab
count: 0, .on( 'click', '.add-zone', _fn.build.form.zone.add )
formCount: 0, .on( 'click', '.remove-zone', _fn.build.form.zone.remove )
dropdown: '', .on( 'click', '.target-image-form button', function(e) {
list: [], e.preventDefault();
obj: [],
add: function(e) { _fn.data.targetImg = $('.target-image-form input', element).val();
var inputTemplate = _fn.tpl.zoneInput(), _fn.$target.css('background', 'url(' + _fn.data.targetImg + ') no-repeat');
zoneTemplate = _fn.tpl.zoneElement(),
name = 'zone-', // Placeholder shim for IE9
$elements = _fn.build.$el, $.placeholder.shim();
num, });
obj;
$itemTab
if (e) { .on( 'click', '.add-item', _fn.build.form.item.add )
e.preventDefault(); .on( 'click', '.remove-item', _fn.build.form.item.remove );
} },
form: {
_fn.build.form.zone.count++; zone: {
_fn.build.form.zone.formCount++; count: 0,
num = _fn.build.form.zone.count; formCount: 0,
name += num; dropdown: '',
list: [],
// Update zone obj obj: [],
zoneObj = { add: function(e) {
title: 'Zone ' + num, var inputTemplate = _fn.tpl.zoneInput(),
id: name, zoneTemplate = _fn.tpl.zoneElement(),
active: false, name = 'zone-',
index: num, $elements = _fn.build.$el,
width: 200, num,
height: 100, obj;
x: 0,
y: 0 if (e) {
}; e.preventDefault();
}
_fn.build.form.zone.obj.push( zoneObj );
_fn.build.form.zone.count++;
// Add fields to zone position form _fn.build.form.zone.formCount++;
$elements.zones.form.append( _.template( inputTemplate, { num = _fn.build.form.zone.count;
title: 'Zone ' + num, name += num;
name: name
})); // Update zone obj
_fn.build.form.zone.enableDelete(); zoneObj = {
title: 'Zone ' + num,
// Add zone div to target id: name,
$elements.target.append( _.template( zoneTemplate, zoneObj ) ); active: false,
index: num,
// Listen to changes in form to update zone div width: 200,
_fn.build.form.zone.clickHandler( num ); height: 100,
x: 0,
// Placeholder shim for IE9 y: 0
$.placeholder.shim(); };
},
remove: function(e) { _fn.build.form.zone.obj.push( zoneObj );
var $el = $(e.currentTarget).closest('.zone-row'),
classes = $el.attr('class'), // Add fields to zone position form
id = classes.slice(classes.indexOf('zone-row') + 9); $elements.zones.form.append( _.template( inputTemplate, {
title: 'Zone ' + num,
e.preventDefault(); name: name
$el.detach(); }));
$('#' + id).detach(); _fn.build.form.zone.enableDelete();
_fn.build.form.zone.formCount--; // Add zone div to target
_fn.build.form.zone.disableDelete(); $elements.target.append( _.template( zoneTemplate, zoneObj ) );
// Placeholder shim for IE9 // Listen to changes in form to update zone div
$.placeholder.shim(); _fn.build.form.zone.clickHandler( num );
},
enableDelete: function() { // Placeholder shim for IE9
if ( _fn.build.form.zone.formCount > 1 ) { $.placeholder.shim();
_fn.build.$el.zones.form.find('.remove-zone').removeClass('hidden'); },
} remove: function(e) {
}, var $el = $(e.currentTarget).closest('.zone-row'),
disableDelete: function() { classes = $el.attr('class'),
if ( _fn.build.form.zone.formCount === 1 ) { id = classes.slice(classes.indexOf('zone-row') + 9);
_fn.build.$el.zones.form.find('.remove-zone').addClass('hidden');
} e.preventDefault();
}, $el.detach();
setAll: function() { $('#' + id, element).detach();
var zones = [],
$form = _fn.build.$el.zones.form.find('.title'); _fn.build.form.zone.formCount--;
_fn.build.form.zone.disableDelete();
$form.each(function(i, el) {
var val = $(el).val(); // Placeholder shim for IE9
$.placeholder.shim();
if ( val.length > 0 ) { },
zones.push( val ); enableDelete: function() {
} if ( _fn.build.form.zone.formCount > 1 ) {
}); _fn.build.$el.zones.form.find('.remove-zone').removeClass('hidden');
}
_fn.build.form.zone.list = zones; },
_fn.build.form.createDropdown(zones); disableDelete: function() {
}, if ( _fn.build.form.zone.formCount === 1 ) {
clickHandler: function( num ) { _fn.build.$el.zones.form.find('.remove-zone').addClass('hidden');
var $div = $('#zone-' + num), }
$form = _fn.build.$el.zones.form.find('.zone-row.zone-' + num); },
setAll: function() {
// Listen to form changes and update zone div position var zones = [],
$form.on('keyup', '.title', function(e) { $form = _fn.build.$el.zones.form.find('.title');
var text = $(e.currentTarget).val(),
record = _.findWhere( _fn.build.form.zone.obj, { $form.each(function(i, el) {
index: num var val = $(el).val();
});
if ( val.length > 0 ) {
$div.find('p').html(text); zones.push( val );
record.title = text; }
});
if ( !record.active ) {
record.active = true; _fn.build.form.zone.list = zones;
} _fn.build.form.createDropdown(zones);
}).on('keyup', '.width', function(e) { },
var width = $(e.currentTarget).val(), clickHandler: function( num ) {
record = _.findWhere( _fn.build.form.zone.obj, { var $div = $('#zone-' + num, element),
index: num $form = _fn.build.$el.zones.form.find('.zone-row.zone-' + num);
});
// Listen to form changes and update zone div position
$div.css('width', width + 'px'); $form.on('keyup', '.title', function(e) {
record.width = width; var text = $(e.currentTarget).val(),
}).on('keyup', '.height', function(e) { record = _.findWhere( _fn.build.form.zone.obj, {
var height = $(e.currentTarget).val(), index: num
record = _.findWhere( _fn.build.form.zone.obj, { });
index: num
}); $div.find('p').html(text);
record.title = text;
$div.css('height', height + 'px');
record.height = height; if ( !record.active ) {
}).on('keyup', '.x', function(e) { record.active = true;
var x = $(e.currentTarget).val(), }
record = _.findWhere( _fn.build.form.zone.obj, { }).on('keyup', '.width', function(e) {
index: num var width = $(e.currentTarget).val(),
}); record = _.findWhere( _fn.build.form.zone.obj, {
index: num
$div.css('left', x + 'px'); });
record.x = x;
}).on('keyup', '.y', function(e) { $div.css('width', width + 'px');
var y = $(e.currentTarget).val(), record.width = width;
record = _.findWhere( _fn.build.form.zone.obj, { }).on('keyup', '.height', function(e) {
index: num var height = $(e.currentTarget).val(),
}); record = _.findWhere( _fn.build.form.zone.obj, {
index: num
$div.css('top', y + 'px'); });
record.y = y;
}); $div.css('height', height + 'px');
}, record.height = height;
cleanObject: function( arr ) { }).on('keyup', '.x', function(e) {
var clean = [], var x = $(e.currentTarget).val(),
i, record = _.findWhere( _fn.build.form.zone.obj, {
len = arr.length; index: num
});
for ( i=0; i<len; i++ ) {
if (arr[i].active) { $div.css('left', x + 'px');
clean.push( arr[i] ); record.x = x;
} }).on('keyup', '.y', function(e) {
} var y = $(e.currentTarget).val(),
record = _.findWhere( _fn.build.form.zone.obj, {
return clean; index: num
} });
},
createDropdown: function( arr ) { $div.css('top', y + 'px');
var tpl = _fn.tpl.zoneDropdown, record.y = y;
i, });
len = arr.length, },
dropdown = [], cleanObject: function( arr ) {
html; var clean = [],
i,
for ( i=0; i<len; i++ ) { len = arr.length;
dropdown.push( _.template( tpl, { value: arr[i] } ) );
} for ( i=0; i<len; i++ ) {
if (arr[i].active) {
// Add option to include dummy answers clean.push( arr[i] );
dropdown.push( _.template( tpl, { value: 'none' } ) ); }
}
html = dropdown.join('');
_fn.build.form.zone.dropdown = html; return clean;
_fn.build.$el.items.form.find('.zone-select').html( html ); }
}, },
feedback: function( $form ) { createDropdown: function( arr ) {
_fn.data.feedback = { var tpl = _fn.tpl.zoneDropdown,
start: $form.find('.intro-feedback').val(), i,
finish: $form.find('.final-feedback').val() len = arr.length,
}; dropdown = [],
}, html;
item: {
count: 0, for ( i=0; i<len; i++ ) {
add: function(e) { dropdown.push( _.template( tpl, { value: arr[i] } ) );
var $form = _fn.build.$el.items.form, }
tpl = _fn.tpl.itemInput();
// Add option to include dummy answers
if ( e ) { dropdown.push( _.template( tpl, { value: 'none' } ) );
e.preventDefault();
} html = dropdown.join('');
_fn.build.form.zone.dropdown = html;
_fn.build.form.item.count++; _fn.build.$el.items.form.find('.zone-select').html( html );
$form.append( _.template( tpl, { dropdown: _fn.build.form.zone.dropdown } ) ); },
_fn.build.form.item.enableDelete(); feedback: function( $form ) {
_fn.data.feedback = {
// Placeholder shim for IE9 start: $form.find('.intro-feedback').val(),
$.placeholder.shim(); finish: $form.find('.final-feedback').val()
}, };
remove: function(e) { },
var $el = $(e.currentTarget).closest('.item'); item: {
count: 0,
e.preventDefault(); add: function(e) {
$el.detach(); var $form = _fn.build.$el.items.form,
tpl = _fn.tpl.itemInput();
_fn.build.form.item.count--;
_fn.build.form.item.disableDelete(); if ( e ) {
e.preventDefault();
// Placeholder shim for IE9 }
$.placeholder.shim();
}, _fn.build.form.item.count++;
enableDelete: function() { $form.append( _.template( tpl, { dropdown: _fn.build.form.zone.dropdown } ) );
if ( _fn.build.form.item.count > 1 ) { _fn.build.form.item.enableDelete();
_fn.build.$el.items.form.find('.remove-item').removeClass('hidden');
} // Placeholder shim for IE9
}, $.placeholder.shim();
disableDelete: function() { },
if ( _fn.build.form.item.count === 1 ) { remove: function(e) {
_fn.build.$el.items.form.find('.remove-item').addClass('hidden'); var $el = $(e.currentTarget).closest('.item');
}
} e.preventDefault();
}, $el.detach();
submit: function() {
var items = [], _fn.build.form.item.count--;
$form = _fn.build.$el.items.form.find('.item'); _fn.build.form.item.disableDelete();
$form.each( function(i, el) { // Placeholder shim for IE9
var $el = $(el), $.placeholder.shim();
name = $el.find('.item-text').val(), },
backgroundImage = $el.find('.background-image').val(); enableDelete: function() {
if ( _fn.build.form.item.count > 1 ) {
if (name.length > 0 || backgroundImage.length > 0) { _fn.build.$el.items.form.find('.remove-item').removeClass('hidden');
var width = $el.find('.item-width').val(), }
height = $el.find('.item-height').val(); },
disableDelete: function() {
if (height === '0') height = 'auto'; if ( _fn.build.form.item.count === 1 ) {
else height = height + 'px'; _fn.build.$el.items.form.find('.remove-item').addClass('hidden');
}
if (width === '0') width = 'auto'; }
else width = width + 'px'; },
submit: function() {
items.push({ var items = [],
displayName: name, $form = _fn.build.$el.items.form.find('.item');
zone: $el.find('.zone-select').val(),
id: i, $form.each( function(i, el) {
feedback: { var $el = $(el),
correct: $el.find('.success-feedback').val(), name = $el.find('.item-text').val(),
incorrect: $el.find('.error-feedback').val() backgroundImage = $el.find('.background-image').val();
},
size: { if (name.length > 0 || backgroundImage.length > 0) {
width: width, var width = $el.find('.item-width').val(),
height: height height = $el.find('.item-height').val();
},
backgroundImage: backgroundImage if (height === '0') height = 'auto';
}); else height = height + 'px';
}
}); if (width === '0') width = 'auto';
else width = width + 'px';
_fn.data.items = items;
_fn.data.zones = _fn.build.form.zone.cleanObject( _fn.build.form.zone.obj ); items.push({
_fn.init(); displayName: name,
_fn.build.$el.items.tab.addClass('hidden'); zone: $el.find('.zone-select').val(),
_fn.$app.removeClass('hidden'); id: i,
_fn.$block.children('header, footer').removeClass('hidden'); feedback: {
_fn.$block.children('.feedback').removeClass('hidden'); correct: $el.find('.success-feedback').val(),
} incorrect: $el.find('.error-feedback').val()
} },
}, size: {
width: width,
// DEV-ONLY: For easier testing height: height
reset: function() { },
_fn.clickHandlers.drag.reset( _fn.$items ); backgroundImage: backgroundImage
_fn.feedback.set( _fn.data.feedback.start ); });
_fn.$items.draggable('enable'); }
_fn.test.completed = 0; });
},
_fn.data.items = items;
finish: function() { _fn.data.zones = _fn.build.form.zone.cleanObject( _fn.build.form.zone.obj );
// Disable any decoy items
_fn.$items.draggable('disable'); var data = {
'display_name': $(element).find('.display-name').val(),
// Show final feedback 'question_text': $(element).find('.question-text').val(),
_fn.feedback.set( _fn.data.feedback.finish ); 'data': JSON.stringify(_fn.data),
}, };
clickHandlers: { $('.xblock-editor-error-message', element).html();
init: function( $drag, $dropzone ) { $('.xblock-editor-error-message', element).css('display', 'none');
var clk = _fn.clickHandlers; var handlerUrl = runtime.handlerUrl(element, 'studio_submit');
$.post(handlerUrl, JSON.stringify(data)).done(function(response) {
$drag.on( 'dragstart', clk.drag.start ); if (response.result === 'success') {
$drag.on( 'dragstop', clk.drag.stop ); window.location.reload(false);
} else {
$dropzone.on( 'drop', clk.drop.success ); $('.xblock-editor-error-message', element).html('Error: '+response.message);
$dropzone.on( 'dropover', clk.drop.hover.in ); $('.xblock-editor-error-message', element).css('display', 'block');
$dropzone.on( 'dropout', clk.drop.hover.out ); }
});
_fn.$block.find('.reset').on('click', _fn.reset ) }
}, }
drag: { },
start: function( event, ui ) {
$(event.currentTarget).removeClass('within-dropzone fade'); finish: function() {
}, // Disable any decoy items
_fn.$items.draggable('disable');
stop: function( event, ui ) {
var $el = $(event.currentTarget), // Show final feedback
val = $el.data('value'), _fn.feedback.set( _fn.data.feedback.finish );
zone = $el.data('zone') || null; },
if ( $el.hasClass('within-dropzone') && _fn.test.match( val, zone ) ) { clickHandlers: {
$el.removeClass('hover') init: function( $drag, $dropzone ) {
.draggable('disable'); var clk = _fn.clickHandlers;
_fn.test.completed++; $drag.on( 'dragstart', clk.drag.start );
_fn.feedback.popup( _fn.feedback.get(val, true), true ); $drag.on( 'dragstop', clk.drag.stop );
if ( _fn.items.allSubmitted() ) { $dropzone.on( 'drop', clk.drop.success );
_fn.finish(); $dropzone.on( 'dropover', clk.drop.hover.in );
} $dropzone.on( 'dropout', clk.drop.hover.out );
} else { },
// Return to original position drag: {
_fn.clickHandlers.drag.reset( $el ); start: function( event, ui ) {
_fn.feedback.popup( _fn.feedback.get(val, false), false ); $(event.currentTarget).removeClass('within-dropzone fade');
} },
},
stop: function( event, ui ) {
reset: function( $el ) { var $el = $(event.currentTarget),
$el.removeClass('within-dropzone fade hover') val = $el.data('value'),
.css({ zone = $el.data('zone') || null;
top: '',
left: '' if ( $el.hasClass('within-dropzone') && _fn.test.match( val, zone ) ) {
}); $el.removeClass('hover')
} .draggable('disable');
},
drop: { _fn.test.completed++;
hover: { _fn.feedback.popup( _fn.feedback.get(val, true), true );
in: function( event, ui ) {
var zone = $(event.currentTarget).data('zone'); if ( _fn.items.allSubmitted() ) {
_fn.finish();
ui.draggable.addClass('hover').data('zone', zone); }
}, } else {
out: function( event, ui ) { // Return to original position
ui.draggable.removeClass('hover'); _fn.clickHandlers.drag.reset( $el );
} _fn.feedback.popup( _fn.feedback.get(val, false), false );
}, }
success: function( event, ui ) { },
ui.draggable.addClass('within-dropzone')
} reset: function( $el ) {
} $el.removeClass('within-dropzone fade hover')
}, .css({
top: '',
items: { left: ''
count: 0, });
init: function() { }
var items = _fn.data.items, },
i, drop: {
len = items.length, hover: {
total = 0; in: function( event, ui ) {
var zone = $(event.currentTarget).data('zone');
for ( i=0; i<len; i++ ) {
if ( items[i].zone !== 'none' ) { ui.draggable.addClass('hover').data('zone', zone);
total++; },
} out: function( event, ui ) {
} ui.draggable.removeClass('hover');
}
_fn.items.count = total; },
}, success: function( event, ui ) {
allSubmitted: function() { ui.draggable.addClass('within-dropzone')
return _fn.test.completed === _fn.items.count; }
}, }
draw: function() { },
var list = [],
items = _fn.data.items, items: {
tpl = _fn.tpl.item(), count: 0,
img_tpl = _fn.tpl.image_item(); init: function() {
var items = _fn.data.items,
_.each(items, function(item) { i,
if (item.backgroundImage.length > 0) { len = items.length,
list.push( _.template( img_tpl, item ) ); total = 0;
} else {
list.push( _.template( tpl, item ) ); for ( i=0; i<len; i++ ) {
} if ( items[i].zone !== 'none' ) {
}); total++;
}
// Update DOM }
_fn.$ul.html( list.join('') );
_fn.items.count = total;
// Set variable },
_fn.$items = $('.xblock--drag-and-drop .items .option'); allSubmitted: function() {
} return _fn.test.completed === _fn.items.count;
}, },
draw: function() {
zones: { var list = [],
draw: function() { items = _fn.data.items,
var html = [], tpl = _fn.tpl.item(),
zones = _fn.data.zones, img_tpl = _fn.tpl.image_item();
tpl = _fn.tpl.zoneElement(),
i, _.each(items, function(item) {
len = zones.length; if (item.backgroundImage.length > 0) {
list.push( _.template( img_tpl, item ) );
for ( i=0; i<len; i++ ) { } else {
html.push( _.template( tpl, zones[i] ) ); list.push( _.template( tpl, item ) );
} }
});
// Update DOM
_fn.$target.html( html.join('') ); // Update DOM
_fn.$ul.html( list.join('') );
// Set variable
_fn.$zones = _fn.$target.find('.zone'); // Set variable
} _fn.$items = $('.xblock--drag-and-drop .items .option', element);
}, }
},
test: {
completed: 0, zones: {
match: function( id, zone ) { draw: function() {
var item = _.findWhere( _fn.data.items, { id: id } ); var html = [],
zones = _fn.data.zones,
return item.zone === zone; tpl = _fn.tpl.zoneElement(),
} i,
}, len = zones.length;
feedback: { for ( i=0; i<len; i++ ) {
// Returns string based on user's answer html.push( _.template( tpl, zones[i] ) );
get: function( id, boo ) { }
var item,
type = boo ? 'correct' : 'incorrect'; // Update DOM
_fn.$target.html( html.join('') );
// Null loses its string-ness
if ( id === null ) { // Set variable
id = 'null'; _fn.$zones = _fn.$target.find('.zone');
} }
},
// Get object from data.items that matches val
item = _.findWhere( _fn.data.items, { id: id }); test: {
completed: 0,
return item.feedback[type]; match: function( id, zone ) {
}, var item = _.findWhere( _fn.data.items, { id: id } );
// Update DOM with feedback return item.zone === zone;
set: function(str) { }
return _fn.$feedback.html(str); },
},
feedback: {
// Show a feedback popup // Returns string based on user's answer
popup: function(str, boo) { get: function( id, boo ) {
if (str === undefined || str === '') return; var item,
return $("<div>").attr('title', boo ? 'Correct' : 'Error').text(str).dialog(); type = boo ? 'correct' : 'incorrect';
}
}, // Null loses its string-ness
if ( id === null ) {
data: { id = 'null';
feedback: {}, }
items: [],
zones: [], // Get object from data.items that matches val
targetImg: 'img/triangle.png' item = _.findWhere( _fn.data.items, { id: id });
}
}; return item.feedback[type];
},
return {
init: _fn.init, // Update DOM with feedback
builder: _fn.build.init set: function(str) {
}; return _fn.$feedback.html(str);
})(); },
dragAndDrop.builder(); // Show a feedback popup
popup: function(str, boo) {
if (str === undefined || str === '') return;
return $("<div>").attr('title', boo ? 'Correct' : 'Error').text(str).dialog();
}
},
data: {
feedback: {},
items: [],
zones: [],
targetImg: 'https://i.imgur.com/PoI27ox.png'
}
};
return {
builder: _fn.build.init
};
})(jQuery);
$(element).find('.cancel-button').bind('click', function() {
runtime.notify('cancel', {});
});
dragAndDrop.builder();
}
...@@ -283,7 +283,6 @@ ...@@ -283,7 +283,6 @@
}; };
// Shuffle an array, using the modern version of the // Shuffle an array, using the modern version of the
// [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
_.shuffle = function(obj) { _.shuffle = function(obj) {
var rand; var rand;
var index = 0; var index = 0;
...@@ -1340,4 +1339,4 @@ ...@@ -1340,4 +1339,4 @@
return _; return _;
}); });
} }
}).call(this); }).call(this);
\ No newline at end of file
<section class="xblock--drag-and-drop">
<link rel="stylesheet" href="https://code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css">
<h2 class="problem-header">
{{ title }}
</h2>
<div class="problem-progress">(1 point possible)</div>
<section class="problem" role="application">
<p>
{{ question_text }}
</p>
</section>
<section class="feedback">
<p class="message"></p>
</section>
<section class="drag-container">
<ul class="items"></ul>
<div class="target">
<div class="target-img"></div>
</div>
<div class="clear"></div>
</section>
</section>
<!doctype html> {% load i18n %}
<!--[if lt IE 7 ]> <html class="lt-ie10"> <![endif]-->
<!--[if IE 7 ]> <html class="lt-ie10"> <![endif]-->
<!--[if IE 8 ]> <html class="lt-ie10"> <![endif]-->
<!--[if IE 9 ]> <html class="lt-ie10"> <![endif]-->
<!--[if (gt IE 9)|!(IE)]><!--> <html> <!--<![endif]-->
<head>
<meta charset="utf-8">
<title>draggable demo</title>
<link rel="stylesheet" href="https://code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css">
<link rel="stylesheet" href="css/draggable_builder.css">
</head> <div class="xblock--drag-and-drop editor-with-buttons">
<link rel="stylesheet" href="https://code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css">
<body> <section class="drag-builder">
<div class="container">
<section class="xblock--drag-and-drop">
<header class="hidden">
<h1>Title <span class="small">(1 point possible)</span></h1>
<p>Lorem ipsum?</p> <div class="tab feedback-tab">
</header> <section class="tab-content">
<form class="feedback-form">
<h3>Question title</h3>
<input class="display-name" />
<section class="feedback hidden"> <h3>Question text</h3>
<p class="message"></p> <textarea class="question-text"></textarea>
</section>
<section class="drag-builder"> <h3>Introduction Feedback</h3>
<textarea class="intro-feedback"></textarea>
<div class="tab feedback-tab"> <h3>Final Feedback</h3>
<section class="tab-content"> <textarea class="final-feedback"></textarea>
<form class="feedback-form"> </form>
<h3>Introduction Feedback</h3> </section>
<textarea class="intro-feedback"></textarea> <!-- <footer class="tab-footer">
<button class="btn continue goto-zones">Continue</button>
<h3>Final Feedback</h3> </footer> -->
<textarea class="final-feedback"></textarea> </div>
</form>
</section>
<footer class="tab-footer">
<button class="btn continue goto-zones">Continue</button>
</footer>
</div>
<div class="tab zones-tab hidden">
<header class="tab-header">
<h3>Zone Positions</h3>
</header>
<section class="tab-content target-image-form">
<label>New background URL:</label>
<input type="text">
<button class="btn">Change background</button>
</section>
<section class="tab-content">
<div class="items">
<form class="zones-form"></form>
<a href="#" class="add-zone add-element"><div class="icon add"></div>Add a zone</a>
</div>
<div class="target">
<div class="target-img"></div>
</div>
</section>
<footer class="tab-footer">
<button class="btn continue goto-items">Continue</button>
</footer>
</div>
<div class="tab items-tab hidden"> <div class="tab zones-tab hidden">
<header class="tab-header"> <header class="tab-header">
<h3>Items</h3> <h3>Zone Positions</h3>
</header> </header>
<section class="tab-content"> <section class="tab-content">
<form class="items-form"></form> <div class="items">
</section> <form class="zones-form"></form>
<footer class="tab-footer"> <a href="#" class="add-zone add-element"><div class="icon add"></div>Add a zone</a>
<a href="#" class="add-item add-element"><div class="icon add"></div>Add an item</a> </div>
<button class="btn continue goto-exercise">Finish</button> <div class="target">
</footer> <div class="target-img"></div>
</div> </div>
</section>
<!-- <footer class="tab-footer">
<button class="btn continue goto-items">Continue</button>
</footer> -->
</div>
</section> <div class="tab items-tab hidden">
<header class="tab-header">
<h3>Items</h3>
</header>
<section class="tab-content">
<form class="items-form"></form>
</section>
<footer class="tab-footer">
<a href="#" class="add-item add-element"><div class="icon add"></div>Add an item</a>
<!-- <button class="btn continue goto-exercise">Finish</button> -->
</footer>
</div>
<section class="drag-container hidden"> </section>
<ul class="items"></ul>
<div class="target"> <div class="xblock-actions">
<div class="target-img"></div> <span class="xblock-editor-error-message"></span>
</div> <ul>
<li class="action-item">
<a href="#" class="button action-primary continue-button">{% trans "Continue" %}</a>
</li>
<!-- Only added for testing purposes --> <li class="action-item hidden">
<button class="btn reset">Reset</button> <a href="#" class="button action-primary save-button">{% trans "Save" %}</a>
</section> </li>
</section>
</div>
<script type="text/javascript" src="https://code.jquery.com/jquery-1.10.2.js"></script> <li class="action-item">
<script type="text/javascript" src="https://code.jquery.com/ui/1.10.4/jquery-ui.js"></script> <a href="#" class="button cancel-button">{% trans "Cancel" %}</a>
<script type="text/javascript" src="js/vendor/jquery.html5-placeholder-shim.js"></script> </li>
<script type="text/javascript" src="js/vendor/underscore1.6.0.js"></script> </ul>
<script type="text/javascript" src="js/draggable_builder.js"></script> </div>
</body> </div>
</html>
# -*- coding: utf-8 -*-
#
# Imports ###########################################################
import logging
import pkg_resources
from django.template import Context, Template
# Globals ###########################################################
log = logging.getLogger(__name__)
# Functions #########################################################
def load_resource(resource_path):
"""
Gets the content of a resource
"""
resource_content = pkg_resources.resource_string(__name__, resource_path)
return resource_content
def render_template(template_path, context={}):
"""
Evaluate a template by resource path, applying the provided context
"""
template_str = load_resource(template_path)
template = Template(template_str)
return template.render(Context(context))
# -*- coding: utf-8 -*-
# Imports ###########################################################
import os
from setuptools import setup
# Functions #########################################################
def package_data(pkg, root_list):
"""Generic function to find package_data for `pkg` under `root`."""
data = []
for root in root_list:
for dirname, _, files in os.walk(os.path.join(pkg, root)):
for fname in files:
data.append(os.path.relpath(os.path.join(dirname, fname), pkg))
return {pkg: data}
# Main ##############################################################
setup(
name='xblock-drag-and-drop-v2',
version='0.1',
description='XBlock - Drag-and-Drop v2',
packages=['drag_and_drop_v2'],
install_requires=[
'XBlock',
],
entry_points={
'xblock.v1': 'drag-and-drop-v2 = drag_and_drop_v2:DragAndDropBlock',
},
package_data=package_data("drag_and_drop_v2", ["static", "templates", "public"]),
)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment