Commit bfd34fef by Filippo Valsorda

First working XBlock prototype with only student view

parent 0fe52737
# 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/
/*** xBlock styles ***/
.xblock--drag-and-drop {
width: 770px;
margin: 0;
padding: 0;
background: #fff;
}
.xblock--drag-and-drop h1,
.xblock--drag-and-drop h2,
.xblock--drag-and-drop h3,
.xblock--drag-and-drop h4,
.xblock--drag-and-drop h5,
.xblock--drag-and-drop h6,
.xblock--drag-and-drop p,
.xblock--drag-and-drop li,
.xblock--drag-and-drop a {
font-family: Arial;
}
.xblock--drag-and-drop h1 {
color: #adadad;
}
.xblock--drag-and-drop h2 {
color: #333;
margin: 0;
text-transform: uppercase;
}
.xblock--drag-and-drop header p,
.xblock--drag-and-drop footer p {
color: #adadad;
line-height: 1.5em;
}
.xblock--drag-and-drop .small {
font-size: 0.6em;
}
.xblock--drag-and-drop .drag-container {
width: 760px;
background: #ebf0f2;
position: relative;
}
/** Draggable Items **/
.xblock--drag-and-drop .items {
width: 210px;
margin: 10px;
padding: 0;
font-size: 14px;
position: relative;
display: inline;
float: left;
list-style-type: none;
}
.xblock--drag-and-drop .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 {
line-height: 1.5em;
font-weight: bold;
}
/** Builder **/
.xblock--drag-and-drop .hidden {
display: none!important;
}
.xblock--drag-and-drop .drag-builder .tab {
width: 98%;
background: #eee;
padding: 3px 0;
position: relative;
}
.xblock--drag-and-drop .drag-builder .tab:after,
.xblock--drag-and-drop .drag-builder .tab-footer:after,
.xblock--drag-and-drop .drag-builder .target:after {
content: "";
display: table;
clear: both;
}
.xblock--drag-and-drop .drag-builder .tab h3 {
margin: 10px 0;
}
.xblock--drag-and-drop .drag-builder .tab-header,
.xblock--drag-and-drop .drag-builder .tab-content,
.xblock--drag-and-drop .drag-builder .tab-footer {
width: 96%;
margin: 2%;
}
.xblock--drag-and-drop .drag-builder .tab-footer {
height: 25px;
position: relative;
display: block;
float: left;
}
.xblock--drag-and-drop .drag-builder .continue {
position: absolute;
right: 0;
top: -5px;
}
.xblock--drag-and-drop .drag-builder .items {
width: calc(100% - 515px);
margin: 10px 0 0 0;
}
.xblock--drag-and-drop .drag-builder .target {
margin-left: 0;
}
.xblock--drag-and-drop .drag-builder .target-image-form input {
width: 400px;
}
.xblock--drag-and-drop .zones-form .zone-row label {
display: inline-block;
width: 18%;
}
.xblock--drag-and-drop .zones-form .zone-row .title {
width: 60%;
margin: 0 0 5px;
}
.xblock--drag-and-drop .zones-form .zone-row .layout {
margin-bottom: 15px;
}
.xblock--drag-and-drop .zones-form .zone-row .layout .size,
.xblock--drag-and-drop .zones-form .zone-row .layout .coord {
width: 15%;
margin: 0 19px 5px 0;
}
.xblock--drag-and-drop .drag-builder .target {
margin-bottom: 40px;
}
.xblock--drag-and-drop .drag-builder .zone {
width: 200px;
height: 100px;
border: 1px dotted #666;
}
.xblock--drag-and-drop .feedback-form textarea {
width: 99%;
height: 128px;
margin-bottom: 30px;
}
.xblock--drag-and-drop .items-form {
margin-bottom: 30px;
}
.xblock--drag-and-drop .items-form .item {
background: #73bde7;
padding: 10px 0 1px;
margin: 15px 0;
}
.xblock--drag-and-drop .items-form label {
margin: 0 1%;
}
.xblock--drag-and-drop .items-form input,
.xblock--drag-and-drop .items-form select {
width: 35%;
}
.xblock--drag-and-drop .items-form textarea {
width: 97%;
margin: 0 1%;
}
.xblock--drag-and-drop .items-form .row {
margin-bottom: 20px;
}
.xblock--drag-and-drop .items-form .item-width,
.xblock--drag-and-drop .items-form .item-height {
width: 30px;
margin-right: 50px;
}
/** Buttons **/
.xblock--drag-and-drop .btn {
background: #2e83cd;
color: #fff;
border: 1px solid #156ab4;
border-radius: 6px;
padding: 5px 10px;
}
.xblock--drag-and-drop .btn:hover {
opacity: 0.8;
cursor: pointer;
}
.xblock--drag-and-drop .btn:focus {
outline: none;
opacity: 0.5;
}
.xblock--drag-and-drop .add-element {
text-decoration: none;
color: #2e83cd;
}
.xblock--drag-and-drop .remove-zone {
float: right;
margin-top: 2px;
margin-right: 16px;
}
.xblock--drag-and-drop .remove-item {
display: inline-block;
margin-left: 95px;
}
.xblock--drag-and-drop .icon {
width: 14px;
height: 14px;
border-radius: 7px;
background: #2e83cd;
position: relative;
float: left;
margin: 0 5px 0 0;
}
.xblock--drag-and-drop .add-zone:hover,
.xblock--drag-and-drop .add-zone:hover .icon,
.xblock--drag-and-drop .remove-zone:hover,
.xblock--drag-and-drop .remove-zone:hover .icon {
opacity: 0.7;
}
.xblock--drag-and-drop .icon.add:before {
content: '';
height: 10px;
width: 2px;
background: #fff;
position: relative;
display: inline;
float: left;
top: 2px;
left: 6px;
}
.xblock--drag-and-drop .icon.add:after {
content: '';
height: 2px;
width: 10px;
background: #fff;
position: relative;
display: inline;
float: left;
top: 6px;
left: 0;
}
.xblock--drag-and-drop .icon.remove:before {
content: '';
height: 10px;
width: 2px;
background: #fff;
position: relative;
display: inline;
float: left;
top: 2px;
left: 6px;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
.xblock--drag-and-drop .icon.remove:after {
content: '';
height: 2px;
width: 10px;
background: #fff;
position: relative;
display: inline;
float: left;
top: 6px;
left: 0;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
.xblock--drag-and-drop .remove-item .icon.remove {
background: #fff;
}
.xblock--drag-and-drop .remove-item .icon.remove:before,
.xblock--drag-and-drop .remove-item .icon.remove:after {
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; }
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":"http://i0.kym-cdn.com/photos/images/newsfeed/000/030/404/1260585284155.png?1318992465"}'
# 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
"""
fragment = Fragment()
fragment.add_content(render_template('/templates/html/drag_and_drop_edit.html', {
'self': self,
}))
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;
}
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">',
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="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() {
// 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');
},
build: {
$el: {
feedback: {
form: $('.xblock--drag-and-drop .drag-builder .feedback-form'),
tab: $('.xblock--drag-and-drop .drag-builder .feedback-tab')
},
zones: {
form: $('.xblock--drag-and-drop .drag-builder .zones-form'),
tab: $('.xblock--drag-and-drop .drag-builder .zones-tab')
},
items: {
form: $('.xblock--drag-and-drop .drag-builder .items-form'),
tab: $('.xblock--drag-and-drop .drag-builder .items-tab')
},
target: $('.xblock--drag-and-drop .drag-builder .target-img')
},
init: function() {
_fn.build.clickHandlers();
_fn.build.form.zone.add();
_fn.$target.css('background', 'url(' + _fn.data.targetImg + ') no-repeat');
},
clickHandlers: function() {
var $fbkTab = _fn.build.$el.feedback.tab,
$zoneTab = _fn.build.$el.zones.tab,
$itemTab = _fn.build.$el.items.tab;
$fbkTab.on( 'click', '.goto-zones', function(e) {
e.preventDefault();
_fn.build.form.feedback( _fn.build.$el.feedback.form );
$fbkTab.addClass('hidden');
$zoneTab.removeClass('hidden');
// Placeholder shim for IE9
$.placeholder.shim();
});
$zoneTab
.on( 'click', '.add-zone', _fn.build.form.zone.add )
.on( 'click', '.remove-zone', _fn.build.form.zone.remove )
.on( 'click', '.goto-items', function(e) {
e.preventDefault();
_fn.build.form.zone.setAll();
_fn.build.form.item.add();
$zoneTab.addClass('hidden');
$itemTab.removeClass('hidden');
// Placeholder shim for IE9
$.placeholder.shim();
})
.on( 'click', '.target-image-form button', function(e) {
e.preventDefault();
_fn.data.targetImg = $('.target-image-form input').val();
_fn.$target.css('background', 'url(' + _fn.data.targetImg + ') no-repeat');
// Placeholder shim for IE9
$.placeholder.shim();
});
$itemTab
.on( 'click', '.add-item', _fn.build.form.item.add )
.on( 'click', '.remove-item', _fn.build.form.item.remove )
.on( 'click', '.goto-exercise', function(e) {
e.preventDefault();
_fn.build.form.submit();
});
},
form: {
zone: {
count: 0,
formCount: 0,
dropdown: '',
list: [],
obj: [],
add: function(e) {
var inputTemplate = _fn.tpl.zoneInput(),
zoneTemplate = _fn.tpl.zoneElement(),
name = 'zone-',
$elements = _fn.build.$el,
num,
obj;
if (e) {
e.preventDefault();
}
_fn.build.form.zone.count++;
_fn.build.form.zone.formCount++;
num = _fn.build.form.zone.count;
name += num;
// Update zone obj
zoneObj = {
title: 'Zone ' + num,
id: name,
active: false,
index: num,
width: 200,
height: 100,
x: 0,
y: 0
};
_fn.build.form.zone.obj.push( zoneObj );
// Add fields to zone position form
$elements.zones.form.append( _.template( inputTemplate, {
title: 'Zone ' + num,
name: name
}));
_fn.build.form.zone.enableDelete();
// Add zone div to target
$elements.target.append( _.template( zoneTemplate, zoneObj ) );
// Listen to changes in form to update zone div
_fn.build.form.zone.clickHandler( num );
// Placeholder shim for IE9
$.placeholder.shim();
},
remove: function(e) {
var $el = $(e.currentTarget).closest('.zone-row'),
classes = $el.attr('class'),
id = classes.slice(classes.indexOf('zone-row') + 9);
e.preventDefault();
$el.detach();
$('#' + id).detach();
_fn.build.form.zone.formCount--;
_fn.build.form.zone.disableDelete();
// Placeholder shim for IE9
$.placeholder.shim();
},
enableDelete: function() {
if ( _fn.build.form.zone.formCount > 1 ) {
_fn.build.$el.zones.form.find('.remove-zone').removeClass('hidden');
}
},
disableDelete: function() {
if ( _fn.build.form.zone.formCount === 1 ) {
_fn.build.$el.zones.form.find('.remove-zone').addClass('hidden');
}
},
setAll: function() {
var zones = [],
$form = _fn.build.$el.zones.form.find('.title');
$form.each(function(i, el) {
var val = $(el).val();
if ( val.length > 0 ) {
zones.push( val );
}
});
_fn.build.form.zone.list = zones;
_fn.build.form.createDropdown(zones);
},
clickHandler: function( num ) {
var $div = $('#zone-' + num),
$form = _fn.build.$el.zones.form.find('.zone-row.zone-' + num);
// Listen to form changes and update zone div position
$form.on('keyup', '.title', function(e) {
var text = $(e.currentTarget).val(),
record = _.findWhere( _fn.build.form.zone.obj, {
index: num
});
$div.find('p').html(text);
record.title = text;
if ( !record.active ) {
record.active = true;
}
}).on('keyup', '.width', function(e) {
var width = $(e.currentTarget).val(),
record = _.findWhere( _fn.build.form.zone.obj, {
index: num
});
$div.css('width', width + 'px');
record.width = width;
}).on('keyup', '.height', function(e) {
var height = $(e.currentTarget).val(),
record = _.findWhere( _fn.build.form.zone.obj, {
index: num
});
$div.css('height', height + 'px');
record.height = height;
}).on('keyup', '.x', function(e) {
var x = $(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) {
var y = $(e.currentTarget).val(),
record = _.findWhere( _fn.build.form.zone.obj, {
index: num
});
$div.css('top', y + 'px');
record.y = y;
});
},
cleanObject: function( arr ) {
var clean = [],
i,
len = arr.length;
for ( i=0; i<len; i++ ) {
if (arr[i].active) {
clean.push( arr[i] );
}
}
return clean;
}
},
createDropdown: function( arr ) {
var tpl = _fn.tpl.zoneDropdown,
i,
len = arr.length,
dropdown = [],
html;
for ( i=0; i<len; i++ ) {
dropdown.push( _.template( tpl, { value: arr[i] } ) );
}
// Add option to include dummy answers
dropdown.push( _.template( tpl, { value: 'none' } ) );
html = dropdown.join('');
_fn.build.form.zone.dropdown = html;
_fn.build.$el.items.form.find('.zone-select').html( html );
},
feedback: function( $form ) {
_fn.data.feedback = {
start: $form.find('.intro-feedback').val(),
finish: $form.find('.final-feedback').val()
};
},
item: {
count: 0,
add: function(e) {
var $form = _fn.build.$el.items.form,
tpl = _fn.tpl.itemInput();
if ( e ) {
e.preventDefault();
}
_fn.build.form.item.count++;
$form.append( _.template( tpl, { dropdown: _fn.build.form.zone.dropdown } ) );
_fn.build.form.item.enableDelete();
// Placeholder shim for IE9
$.placeholder.shim();
},
remove: function(e) {
var $el = $(e.currentTarget).closest('.item');
e.preventDefault();
$el.detach();
_fn.build.form.item.count--;
_fn.build.form.item.disableDelete();
// Placeholder shim for IE9
$.placeholder.shim();
},
enableDelete: function() {
if ( _fn.build.form.item.count > 1 ) {
_fn.build.$el.items.form.find('.remove-item').removeClass('hidden');
}
},
disableDelete: function() {
if ( _fn.build.form.item.count === 1 ) {
_fn.build.$el.items.form.find('.remove-item').addClass('hidden');
}
}
},
submit: function() {
var items = [],
$form = _fn.build.$el.items.form.find('.item');
$form.each( function(i, el) {
var $el = $(el),
name = $el.find('.item-text').val(),
backgroundImage = $el.find('.background-image').val();
if (name.length > 0 || backgroundImage.length > 0) {
var width = $el.find('.item-width').val(),
height = $el.find('.item-height').val();
if (height === '0') height = 'auto';
else height = height + 'px';
if (width === '0') width = 'auto';
else width = width + 'px';
items.push({
displayName: name,
zone: $el.find('.zone-select').val(),
id: i,
feedback: {
correct: $el.find('.success-feedback').val(),
incorrect: $el.find('.error-feedback').val()
},
size: {
width: width,
height: height
},
backgroundImage: backgroundImage
});
}
});
_fn.data.items = items;
_fn.data.zones = _fn.build.form.zone.cleanObject( _fn.build.form.zone.obj );
_fn.init();
_fn.build.$el.items.tab.addClass('hidden');
_fn.$app.removeClass('hidden');
_fn.$block.children('header, footer').removeClass('hidden');
_fn.$block.children('.feedback').removeClass('hidden');
}
}
},
// DEV-ONLY: For easier testing
reset: function() {
_fn.clickHandlers.drag.reset( _fn.$items );
_fn.feedback.set( _fn.data.feedback.start );
_fn.$items.draggable('enable');
_fn.test.completed = 0;
},
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 );
_fn.$block.find('.reset').on('click', _fn.reset )
},
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' : 'Error').text(str).dialog();
}
},
data: {
feedback: {},
items: [],
zones: [],
targetImg: 'img/triangle.png'
}
};
return {
init: _fn.init,
builder: _fn.build.init
};
})();
dragAndDrop.builder();
'<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);
});
}
function DragAndDropEditBlock(runtime, element) {
$(element).find('.save-button').bind('click', function() {
var data = {
'display_name': $(element).find('.edit-display-name').val(),
'question_text': $(element).find('.edit-question-text').val(),
'data': $(element).find('.edit-data').val()
};
$('.xblock-editor-error-message', element).html();
$('.xblock-editor-error-message', element).css('display', 'none');
var handlerUrl = runtime.handlerUrl(element, 'studio_submit');
$.post(handlerUrl, JSON.stringify(data)).done(function(response) {
if (response.result === 'success') {
window.location.reload(false);
} else {
$('.xblock-editor-error-message', element).html('Error: '+response.message);
$('.xblock-editor-error-message', element).css('display', 'block');
}
});
});
$(element).find('.cancel-button').bind('click', function() {
runtime.notify('cancel', {});
});
}
......@@ -283,7 +283,6 @@
};
// Shuffle an array, using the modern version of the
// [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
_.shuffle = function(obj) {
var rand;
var index = 0;
......@@ -1340,4 +1339,4 @@
return _;
});
}
}).call(this);
\ No newline at end of file
}).call(this);
<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>
<script type="text/javascript" src="https://code.jquery.com/jquery-1.10.2.js"></script>
<script type="text/javascript" src="https://code.jquery.com/ui/1.10.4/jquery-ui.js"></script>
</section>
{% load i18n %}
<!-- TODO: Replace by default edit view once available in Studio -->
<div class="wrapper-comp-settings is-active editor-with-buttons " id="settings-tab">
<ul class="list-input settings-list">
<li class="field comp-setting-entry is-set">
<div class="wrapper-comp-setting">
<label class="label setting-label" for="edit_display_name">Title</label>
<input class="input setting-input edit-display-name" id="edit_display_name" value="{{ self.display_name }}" type="text">
</div>
<span class="tip setting-help">The title of the Drag and Drop that is displayed to the user</span>
</li>
<li class="field comp-setting-entry is-set">
<div class="wrapper-comp-setting">
<label class="label setting-label" for="edit_question_text">Question text</label>
<input class="input setting-input edit-question-text" id="edit_question_text" value="{{ self.question_text }}" type="text">
</div>
<span class="tip setting-help">The question text that is displayed to the user</span>
</li>
<li class="field comp-setting-entry is-set">
<div class="wrapper-comp-setting">
<label class="label setting-label" for="edit_data">Title</label>
<input class="input setting-input edit-data" id="edit_data" value="{{ self.data }}" type="text">
</div>
<span class="tip setting-help">JSON spec as generated by the builder</span>
</li>
</ul>
<div class="xblock-actions">
<span class="xblock-editor-error-message"></span>
<ul>
<li class="action-item">
<a href="#" class="button action-primary save-button">{% trans "Save" %}</a>
</li>
<li class="action-item">
<a href="#" class="button cancel-button">{% trans "Cancel" %}</a>
</li>
</ul>
</div>
</div>
# -*- 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))
<!doctype html>
<!--[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>
<body>
<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>
</header>
<section class="feedback hidden">
<p class="message"></p>
</section>
<section class="drag-builder">
<div class="tab feedback-tab">
<section class="tab-content">
<form class="feedback-form">
<h3>Introduction Feedback</h3>
<textarea class="intro-feedback"></textarea>
<h3>Final Feedback</h3>
<textarea class="final-feedback"></textarea>
</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">
<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>
<section class="drag-container hidden">
<ul class="items"></ul>
<div class="target">
<div class="target-img"></div>
</div>
<!-- Only added for testing purposes -->
<button class="btn reset">Reset</button>
</section>
</section>
</div>
<script type="text/javascript" src="https://code.jquery.com/jquery-1.10.2.js"></script>
<script type="text/javascript" src="https://code.jquery.com/ui/1.10.4/jquery-ui.js"></script>
<script type="text/javascript" src="js/vendor/jquery.html5-placeholder-shim.js"></script>
<script type="text/javascript" src="js/vendor/underscore1.6.0.js"></script>
<script type="text/javascript" src="js/draggable_builder.js"></script>
</body>
</html>
# -*- 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