Commit 4cdf8045 by Matjaz Gregoric Committed by GitHub

Merge pull request #133 from edx-solutions/mtyaka/MCKIN-5772-mobile-drag-delay

[MCKIN-5774] Wait 500 milliseconds before starting drag on touch.
parents 93fbea44 b4bc19f8
......@@ -262,12 +262,9 @@ class DragAndDropBlock(
fragment = Fragment()
fragment.add_content(loader.render_template('/templates/html/drag_and_drop.html'))
css_urls = (
'public/css/vendor/jquery-ui-1.10.4.custom.min.css',
'public/css/drag_and_drop.css'
'public/css/drag_and_drop.css',
)
js_urls = (
'public/js/vendor/jquery-ui-1.10.4.custom.min.js',
'public/js/vendor/jquery-ui-touch-punch-0.2.3.min.js', # Makes it work on touch devices
'public/js/vendor/virtual-dom-1.3.0.min.js',
'public/js/drag_and_drop.js',
)
......@@ -357,11 +354,9 @@ class DragAndDropBlock(
fragment.add_content(loader.render_template('/templates/html/drag_and_drop_edit.html', context))
css_urls = (
'public/css/vendor/jquery-ui-1.10.4.custom.min.css',
'public/css/drag_and_drop_edit.css'
'public/css/drag_and_drop_edit.css',
)
js_urls = (
'public/js/vendor/jquery-ui-1.10.4.custom.min.js',
'public/js/vendor/handlebars-v1.1.2.js',
'public/js/drag_and_drop_edit.js',
)
......
......@@ -67,6 +67,7 @@
width: auto;
padding: 1%;
background-color: #ebf0f2;
position: relative;
}
/*** DRAGGABLE ITEMS ***/
......@@ -725,3 +726,12 @@
background-color: #ffffff;
color: #000000;
}
/* Prevent mobile browsers from emulating hover effects on item tap, which can be confusing. */
@media (hover: none) {
.xblock--drag-and-drop .drag-container .option[draggable='true']:hover {
outline: none;
box-shadow: none;
}
}
/*! jQuery UI - v1.10.4 - 2014-07-07
* http://jqueryui.com
* Includes: jquery.ui.core.css, jquery.ui.resizable.css, jquery.ui.selectable.css, jquery.ui.button.css, jquery.ui.dialog.css, jquery.ui.theme.css
* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana%2CArial%2Csans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=highlight_soft&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=flat&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=glass&bgImgOpacityDefault=75&borderColorDefault=d3d3d3&fcDefault=555555&iconColorDefault=888888&bgColorHover=dadada&bgTextureHover=glass&bgImgOpacityHover=75&borderColorHover=999999&fcHover=212121&iconColorHover=454545&bgColorActive=ffffff&bgTextureActive=glass&bgImgOpacityActive=65&borderColorActive=aaaaaa&fcActive=212121&iconColorActive=454545&bgColorHighlight=fbf9ee&bgTextureHighlight=glass&bgImgOpacityHighlight=55&borderColorHighlight=fcefa1&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=glass&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=flat&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=flat&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px
* Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */
.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{min-height:0}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}.ui-button{display:inline-block;position:relative;padding:0;line-height:normal;margin-right:.1em;cursor:pointer;vertical-align:middle;text-align:center;overflow:visible}.ui-button,.ui-button:link,.ui-button:visited,.ui-button:hover,.ui-button:active{text-decoration:none}.ui-button-icon-only{width:2.2em}button.ui-button-icon-only{width:2.4em}.ui-button-icons-only{width:3.4em}button.ui-button-icons-only{width:3.7em}.ui-button .ui-button-text{display:block;line-height:normal}.ui-button-text-only .ui-button-text{padding:.4em 1em}.ui-button-icon-only .ui-button-text,.ui-button-icons-only .ui-button-text{padding:.4em;text-indent:-9999999px}.ui-button-text-icon-primary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 1em .4em 2.1em}.ui-button-text-icon-secondary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 2.1em .4em 1em}.ui-button-text-icons .ui-button-text{padding-left:2.1em;padding-right:2.1em}input.ui-button{padding:.4em 1em}.ui-button-icon-only .ui-icon,.ui-button-text-icon-primary .ui-icon,.ui-button-text-icon-secondary .ui-icon,.ui-button-text-icons .ui-icon,.ui-button-icons-only .ui-icon{position:absolute;top:50%;margin-top:-8px}.ui-button-icon-only .ui-icon{left:50%;margin-left:-8px}.ui-button-text-icon-primary .ui-button-icon-primary,.ui-button-text-icons .ui-button-icon-primary,.ui-button-icons-only .ui-button-icon-primary{left:.5em}.ui-button-text-icon-secondary .ui-button-icon-secondary,.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-buttonset{margin-right:7px}.ui-buttonset .ui-button{margin-left:0;margin-right:-.3em}input.ui-button::-moz-focus-inner,button.ui-button::-moz-focus-inner{border:0;padding:0}.ui-dialog{overflow:hidden;position:absolute;top:0;left:0;padding:.2em;outline:0}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative}.ui-dialog .ui-dialog-title{float:left;margin:.1em 0;white-space:nowrap;width:90%;overflow:hidden;text-overflow:ellipsis}.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:20px;margin:-10px 0 0 0;padding:1px;height:20px}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:none;overflow:auto}.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0 0;background-image:none;margin-top:.5em;padding:.3em 1em .5em .4em}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right}.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer}.ui-dialog .ui-resizable-se{width:12px;height:12px;right:-5px;bottom:-5px;background-position:16px 16px}.ui-draggable .ui-dialog-titlebar{cursor:move}.ui-widget{font-family:Verdana,Arial,sans-serif;font-size:1.1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Verdana,Arial,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #aaa;background:#fff url("images/ui-bg_flat_75_ffffff_40x100.png") 50% 50% repeat-x;color:#222}.ui-widget-content a{color:#222}.ui-widget-header{border:1px solid #aaa;background:#ccc url("images/ui-bg_highlight-soft_75_cccccc_1x100.png") 50% 50% repeat-x;color:#222;font-weight:bold}.ui-widget-header a{color:#222}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #d3d3d3;background:#e6e6e6 url("images/ui-bg_glass_75_e6e6e6_1x400.png") 50% 50% repeat-x;font-weight:normal;color:#555}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#555;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #999;background:#dadada url("images/ui-bg_glass_75_dadada_1x400.png") 50% 50% repeat-x;font-weight:normal;color:#212121}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited{color:#212121;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #aaa;background:#fff url("images/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x;font-weight:normal;color:#212121}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#212121;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #fcefa1;background:#fbf9ee url("images/ui-bg_glass_55_fbf9ee_1x400.png") 50% 50% repeat-x;color:#363636}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#363636}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#fef1ec url("images/ui-bg_glass_95_fef1ec_1x400.png") 50% 50% repeat-x;color:#cd0a0a}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#cd0a0a}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#cd0a0a}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url("images/ui-icons_222222_256x240.png")}.ui-widget-header .ui-icon{background-image:url("images/ui-icons_222222_256x240.png")}.ui-state-default .ui-icon{background-image:url("images/ui-icons_888888_256x240.png")}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url("images/ui-icons_454545_256x240.png")}.ui-state-active .ui-icon{background-image:url("images/ui-icons_454545_256x240.png")}.ui-state-highlight .ui-icon{background-image:url("images/ui-icons_2e83ff_256x240.png")}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url("images/ui-icons_cd0a0a_256x240.png")}.ui-icon-blank{background-position:16px 16px}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-64px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-64px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:0 -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:4px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:4px}.ui-widget-overlay{background:#aaa url("images/ui-bg_flat_0_aaaaaa_40x100.png") 50% 50% repeat-x;opacity:.3;filter:Alpha(Opacity=30)}.ui-widget-shadow{margin:-8px 0 0 -8px;padding:8px;background:#aaa url("images/ui-bg_flat_0_aaaaaa_40x100.png") 50% 50% repeat-x;opacity:.3;filter:Alpha(Opacity=30);border-radius:8px}
\ No newline at end of file
......@@ -83,6 +83,11 @@ function DragAndDropTemplates(configuration) {
// matches contrast between text color and background color:
style['outline-color'] = item.color;
}
if (item.is_dragged) {
style.position = 'absolute';
style.left = item.drag_position.left + 'px';
style.top = item.drag_position.top + 'px';
}
if (item.is_placed) {
var maxWidth = (item.widthPercent || 30) / 100;
var widthPercent = zone.width_percent / 100;
......@@ -133,13 +138,24 @@ function DragAndDropTemplates(configuration) {
itemSpinnerTemplate(item), item_content, itemSRNote, item_description
];
// Unique key for virtual dom change tracking. Key must be different for
// Placed vs Dragged vs Unplaced, or weird bugs can occur.
var key = item.value;
if (item.is_placed && item.is_dragged) {
key += '-pd';
} else if (item.is_placed) {
key += '-p';
} else if (item.is_dragged) {
key += '-d';
} else {
key += '-u';
}
return (
h(
'div.option',
{
// Unique key for virtual dom change tracking. Key must be different for
// Placed vs Unplaced, or weird bugs can occur.
key: item.value + (item.is_placed ? "-p" : "-u"),
key: key,
className: className,
attributes: attributes,
style: style
......@@ -183,7 +199,9 @@ function DragAndDropTemplates(configuration) {
var selector = ctx.display_zone_borders ? 'div.zone.zone-with-borders' : 'div.zone';
// Mark item alignment and render its placed items as children
var item_wrapper = 'div.item-wrapper.item-align.item-align-' + zone.align;
var is_item_in_zone = function(i) { return i.is_placed && (i.zone === zone.uid); };
// In assessment mode already placed items can be dragged out of their current zone.
// Only render placed items that are not currently being dragged out of the zone.
var is_item_in_zone = function(i) { return i.is_placed && !i.is_dragged && (i.zone === zone.uid); };
var items_in_zone = $.grep(ctx.items, is_item_in_zone);
var zone_description_id = zone.prefixed_uid + '-description';
if (items_in_zone.length == 0) {
......@@ -528,9 +546,25 @@ function DragAndDropTemplates(configuration) {
var problemHeader = ctx.show_problem_header ? h('h4.title1', gettext('Problem')) : null;
// Render only items in the bank here, including placeholders. Placed
// items will be rendered by zoneTemplate.
var is_item_placed = function(i) { return i.is_placed; };
var items_placed = $.grep(ctx.items, is_item_placed);
var items_in_bank = $.grep(ctx.items, is_item_placed, true);
var items_in_bank = [];
var items_dragged = [];
var items_placed = [];
ctx.items.forEach(function(item) {
if (item.is_dragged) {
items_dragged.push(item);
// Dragged items require a placeholder in the bank.
// In assessment mode, already placed items can be dragged.
if (item.is_placed) {
items_placed.push(item)
} else {
items_in_bank.push(item);
}
} else if (item.is_placed) {
items_placed.push(item);
} else {
items_in_bank.push(item);
}
});
var item_bank_properties = {
attributes: {
'role': 'group',
......@@ -543,6 +577,19 @@ function DragAndDropTemplates(configuration) {
item_bank_properties.attributes['aria-dropeffect'] = 'move';
item_bank_properties.attributes['role'] = 'button';
}
// Render items in the bank. If the item is currently being dragged, it should be
// rendered as a placeholder. All already placed items should also be rendered as
// placedholders (as the last content in the bank) to maintain original bank dimensions.
var bank_children = [];
items_in_bank.forEach(function(item) {
if (item.is_dragged) {
bank_children.push(itemPlaceholderTemplate(item, ctx));
} else {
bank_children.push(itemTemplate(item, ctx));
}
});
bank_children = bank_children.concat(renderCollection(itemPlaceholderTemplate, items_placed, ctx));
return (
h('div.themed-xblock.xblock--drag-and-drop', main_element_properties, [
problemTitle,
......@@ -553,10 +600,7 @@ function DragAndDropTemplates(configuration) {
h('p', {innerHTML: ctx.problem_html}),
]),
h('div.drag-container', {}, [
h('div.item-bank', item_bank_properties, [
renderCollection(itemTemplate, items_in_bank, ctx),
renderCollection(itemPlaceholderTemplate, items_placed, ctx)
]),
h('div.item-bank', item_bank_properties, bank_children),
h('div.target', {attributes: {'role': 'group', 'arial-label': gettext('Drop Targets')}}, [
itemFeedbackPopupTemplate(ctx),
h('div.target-img-wrapper', [
......@@ -564,6 +608,7 @@ function DragAndDropTemplates(configuration) {
]),
renderCollection(zoneTemplate, ctx.zones, ctx)
]),
h('div.dragged-items', renderCollection(itemTemplate, items_dragged, ctx)),
]),
h("div.actions-toolbar", {attributes: {'role': 'group', 'aria-label': gettext('Actions')}}, [
(ctx.show_submit_answer ? submitAnswerTemplate(ctx) : null),
......@@ -617,6 +662,11 @@ function DragAndDropBlock(runtime, element, configuration) {
var DEFAULT_ZONE_ALIGN = 'center';
// Number of miliseconds the user has to keep their finger on the item
// without moving for drag to begin.
// This allows user to scroll the container without accidentally dragging the items.
var TOUCH_DRAG_DELAY = 500;
// Keyboard accessibility
var ESC = 27;
var RET = 13;
......@@ -689,6 +739,7 @@ function DragAndDropBlock(runtime, element, configuration) {
$root.empty();
applyState();
initDraggable();
initDroppable();
// Indicate that problem is done loading
......@@ -795,7 +846,7 @@ function DragAndDropBlock(runtime, element, configuration) {
$keyboardHelpDialog.find('.modal-window').show().focus();
// Set up event handlers
$(document).on('keydown', function(evt) {
$(document).on('keydown.keyboard-help', function(evt) {
keyboardEventDispatcher(evt, focusId);
});
......@@ -813,7 +864,7 @@ function DragAndDropBlock(runtime, element, configuration) {
$keyboardHelpDialog.find('.modal-window').hide();
// Remove event handlers
$(document).off('keydown');
$(document).off('keydown.keyboard-help');
$keyboardHelpDialog.find('.modal-dismiss-button').off();
// Handle focus
......@@ -892,15 +943,9 @@ function DragAndDropBlock(runtime, element, configuration) {
/**
* Update the DOM to reflect 'state'.
*/
var applyState = function(keepDraggableInit) {
var applyState = function() {
sendFeedbackPopupEvents();
updateDOM();
if (!keepDraggableInit) {
destroyDraggable();
if (!state.finished) {
initDraggable();
}
}
};
var sendFeedbackPopupEvents = function() {
......@@ -1053,6 +1098,18 @@ function DragAndDropBlock(runtime, element, configuration) {
return false;
};
var returnItemToBank = function(item_id) {
if (!state.items[item_id]) {
// Nothing to do here, item is already in the bank.
return;
}
delete state.items[item_id];
applyState();
var url = runtime.handlerUrl(element, 'drop_item');
var data = {val: item_id, zone: null};
$.post(url, JSON.stringify(data), 'json');
};
var placeGrabbedItem = function($zone) {
var zone = String($zone.data('uid'));
var zone_align = $zone.data('zone_align');
......@@ -1066,6 +1123,11 @@ function DragAndDropBlock(runtime, element, configuration) {
}
}
if (state.items[item_id] && state.items[item_id].zone === zone) {
// Nothing to do here, item already in zone.
return;
}
var items_in_zone_count = countItemsInZone(zone, [item_id.toString()]);
if (configuration.max_items_per_zone && configuration.max_items_per_zone <= items_in_zone_count) {
state.last_action_correct = false;
......@@ -1080,11 +1142,8 @@ function DragAndDropBlock(runtime, element, configuration) {
submitting_location: true,
};
// Wrap in setTimeout to let the droppable event finish.
setTimeout(function() {
applyState();
submitLocation(item_id, zone);
}, 0);
applyState();
submitLocation(item_id, zone);
};
var countItemsInZone = function(zone, exclude_ids) {
......@@ -1101,132 +1160,285 @@ function DragAndDropBlock(runtime, element, configuration) {
var initDroppable = function() {
// Set up zones for keyboard interaction
$root.find('.zone, .item-bank').each(function() {
$root.on('keydown', '.zone, .item-bank', function(evt) {
var $zone = $(this);
$zone.on('keydown', function(evt) {
if (state.keyboard_placement_mode) {
if (isCycleKey(evt)) {
focusNextZone(evt, $zone);
} else if (isCancelKey(evt)) {
evt.preventDefault();
state.keyboard_placement_mode = false;
releaseGrabbedItems();
} else if (isActionKey(evt)) {
evt.preventDefault();
evt.stopPropagation();
state.keyboard_placement_mode = false;
if ($zone.is('.item-bank')) {
delete state.items[$selectedItem.data('value')];
} else {
placeGrabbedItem($zone);
}
releaseGrabbedItems();
}
} else if (isTabKey(evt) && !evt.shiftKey) {
// If the user just dropped an item to this zone, next TAB keypress
// should move focus to "Go to Beginning" button.
if (state.tab_to_go_to_beginning_button && canGoToBeginning()) {
evt.preventDefault();
focusGoToBeginningButton();
if (state.keyboard_placement_mode) {
if (isCycleKey(evt)) {
focusNextZone(evt, $zone);
} else if (isCancelKey(evt)) {
evt.preventDefault();
state.keyboard_placement_mode = false;
releaseGrabbedItems();
} else if (isActionKey(evt)) {
evt.preventDefault();
evt.stopPropagation();
state.keyboard_placement_mode = false;
if ($zone.is('.item-bank')) {
delete state.items[$selectedItem.data('value')];
} else {
placeGrabbedItem($zone);
}
} else if (isSpaceKey(evt)) {
// Pressing the space bar moves the page down by default in most browsers.
// That can be distracting while moving items with the keyboard, so prevent
// the default scroll from happening while a zone is focused.
releaseGrabbedItems();
}
} else if (isTabKey(evt) && !evt.shiftKey) {
// If the user just dropped an item to this zone, next TAB keypress
// should move focus to "Go to Beginning" button.
if (state.tab_to_go_to_beginning_button && canGoToBeginning()) {
evt.preventDefault();
focusGoToBeginningButton();
}
});
$zone.on('blur', function() {
delete state.tab_to_go_to_beginning_button;
});
});
// Make zones accept items that are dropped using the mouse
$root.find('.zone').droppable({
accept: '.drag-container .option',
tolerance: 'pointer',
drop: function(evt, ui) {
var $zone = $(this);
placeGrabbedItem($zone);
} else if (isSpaceKey(evt)) {
// Pressing the space bar moves the page down by default in most browsers.
// That can be distracting while moving items with the keyboard, so prevent
// the default scroll from happening while a zone is focused.
evt.preventDefault();
}
});
if (configuration.mode === DragAndDropBlock.ASSESSMENT_MODE) {
// Make item bank accept items that are returned to the bank using the mouse
$root.find('.item-bank').droppable({
accept: '.target .option',
tolerance: 'pointer',
drop: function(evt, ui) {
var $item = ui.helper;
var item_id = $item.data('value');
releaseGrabbedItems();
delete state.items[item_id];
applyState();
var url = runtime.handlerUrl(element, 'drop_item');
var data = {
val: item_id,
zone: null
};
$root.on('blur', '.zone, .item-bank', function(evt) {
delete state.tab_to_go_to_beginning_button;
});
};
$.post(url, JSON.stringify(data), 'json')
}
});
var getItemById = function(item_id) {
for (var i = 0; i < configuration.items.length; i++) {
if (configuration.items[i].id === item_id) {
return configuration.items[i];
}
}
};
var initDraggable = function() {
$root.find('.drag-container .option[draggable=true]').each(function() {
var $item = $(this);
var $container = $root.find('.drag-container');
// Allow items to be "picked up" using the keyboard
$container.on('keydown', '.option[draggable=true]', function(evt) {
if (isActionKey(evt)) {
var $item = $(this);
evt.preventDefault();
evt.stopPropagation();
state.keyboard_placement_mode = true;
grabItem($item, 'keyboard');
$selectedItem = $item;
$root.find('.target .zone').first().focus();
}
});
// Allow item to be "picked up" using the keyboard
$item.on('keydown', function(evt) {
if (isActionKey(evt)) {
evt.preventDefault();
evt.stopPropagation();
state.keyboard_placement_mode = true;
grabItem($item, 'keyboard');
$selectedItem = $item;
$root.find('.target .zone').first().focus();
// Helper functions that return pageX/pageY from either touch or mouse events.
var pageX = function(evt) {
if (evt.type === 'touchstart' || evt.type === 'touchmove') {
return evt.originalEvent.targetTouches[0].pageX;
} else {
return evt.pageX;
}
};
var pageY = function(evt) {
if (evt.type === 'touchstart' || evt.type === 'touchmove') {
return evt.originalEvent.targetTouches[0].pageY;
} else {
return evt.pageY;
}
};
var isValidDropTarget = function(element) {
var valid = element.classList.contains('zone');
if (!valid && configuration.mode === DragAndDropBlock.ASSESSMENT_MODE) {
// In assessment mode, items can be dropped back into the item bank.
valid = element.classList.contains('item-bank');
}
return valid;
};
// If the drag ended on a valid target zone, returns the zone, otherwise returns null;
var getTargetZone = function(evt) {
var $zone = null;
var x, y;
if (evt.type === 'mouseup') {
x = evt.clientX;
y = evt.clientY;
} else {
x = evt.originalEvent.changedTouches[0].clientX;
y = evt.originalEvent.changedTouches[0].clientY;
}
// We have to temporarily hide the dragged item so that we can get the
// to the element below it.
var $dragged_items_container = $container.find('.dragged-items');
$dragged_items_container.hide();
var element = document.elementFromPoint(x, y);
while (element) {
if (isValidDropTarget(element)) {
$zone = $(element);
break;
} else {
element = element.parentElement;
}
}
$dragged_items_container.show();
return $zone;
};
var onDragStart = function(interaction_type, $item, drag_origin) {
var item_id = $item.data('value');
var item = getItemById(item_id);
var $document = $(document);
grabItem($item, 'mouse');
publishEvent({
event_type: 'edx.drag_and_drop_v2.item.picked_up',
item_id: item_id
});
// Make item draggable using the mouse
try {
$item.draggable({
addClasses: false, // don't add ui-draggable-* classes as they don't play well with virtual DOM.
containment: $root.find('.drag-container'),
cursor: 'move',
stack: $root.find('.drag-container .option'),
revert: 'invalid',
revertDuration: 150,
start: function(evt, ui) {
var $item = $(this);
// Store initial position of dragged item to be able to revert back to it on cancelled drag
// (when user drops the item onto an area that is not a droppable zone).
// The jQuery UI draggable library usually knows how to revert correctly, but our dropped items
// have a translation transform that confuses jQuery UI draggable, so we "help" it do the right
// thing by manually storing the initial position and resetting it in the 'stop' handler below.
$item.data('initial-position', {
left: $item.css('left'),
top: $item.css('top')
});
grabItem($item, 'mouse');
publishEvent({
event_type: 'edx.drag_and_drop_v2.item.picked_up',
item_id: $item.data('value'),
});
},
stop: function(evt, ui) {
// Revert to original position.
$item.css($item.data('initial-position'));
var max_left = $container.innerWidth() - $item.outerWidth();
var max_top = $container.innerHeight() - $item.outerHeight();
var item_size = {width: $item.width(), height: $item.height()};
// We need to get the item position relative to the $container.
var item_offset = $item.offset();
var container_offset = $container.offset();
var original_position = {
left: item_offset.left - container_offset.left,
top: item_offset.top - container_offset.top
};
item.drag_position = original_position;
applyState();
// Animate the item back to its original position in the bank.
var revertDrag = function() {
var revert_duration = 150;
var start_position = item.drag_position;
var start_ts = null;
var step = function(ts) {
if (!start_ts) {
start_ts = ts;
}
var progress = Math.min(1, (ts - start_ts) / revert_duration);
item.drag_position = {
left: start_position.left + (progress * (original_position.left - start_position.left)),
top: start_position.top + (progress * (original_position.top - start_position.top)),
};
if (progress === 1) {
delete item.drag_position;
releaseGrabbedItems();
} else {
applyState();
requestAnimationFrame(step);
}
}
requestAnimationFrame(step);
};
var raf_id = null;
var onDragMove = function(evt) {
evt.preventDefault();
if (raf_id) {
cancelAnimationFrame(raf_id);
}
raf_id = requestAnimationFrame(function() {
var dx = pageX(evt) - drag_origin.x;
var dy = pageY(evt) - drag_origin.y;
var left = original_position.left + dx;
var top = original_position.top + dy;
left = Math.max(0, Math.min(max_left, left));
top = Math.max(0, Math.min(max_top, top));
item.drag_position = {left: left, top: top};
applyState();
raf_id = null;
});
} catch (e) {
// Initializing the draggable will fail if draggable was already
// initialized. That's expected, ignore the exception.
};
var onDragEnd = function(evt) {
if (raf_id) {
cancelAnimationFrame(raf_id);
}
if (evt.type === 'mouseup') {
$document.off('mousemove', onDragMove);
$document.off('mouseup', onDragEnd);
} else {
$item.off('touchmove', onDragMove);
$item.off('touchend touchcancel', onDragEnd);
}
if (evt.type === 'touchcancel') {
revertDrag();
return;
}
var $zone = getTargetZone(evt);
if ($zone) {
delete item.drag_position;
if ($zone.is('.item-bank')) {
returnItemToBank(item_id);
} else {
placeGrabbedItem($zone);
}
releaseGrabbedItems();
} else {
revertDrag();
}
};
if (interaction_type === 'mouse') {
// Mouse events have to be bound to the document or they won't fire after
// the item is removed from the bank.
$document.on('mousemove', onDragMove);
$document.on('mouseup', onDragEnd);
} else {
// Touch events behave in the opposite way - they have to be bound to the item
// where the touchstart event was fired, or touchmove will not fire if the item
// gets removed from the DOM; see: https://stackoverflow.com/a/45760014/51397
$item.on('touchmove', onDragMove);
$item.on('touchend touchcancel', onDragEnd);
}
};
// Touch devices emulate mousedown events after touchstart, which would cause our drag
// start event to be handled twice.
// One way to prevent that would be to call evt.preventDefault() in the touchstart handler,
// but that would also prevent scrolling, which we do not want.
// Instead of preventing the default, we use the handled_by_touch flag, which we set to true
// in the touchstart handler (and set it back to false after the event is processed).
// If the mousedown handler sees the flag set to true, it will simply ignore the event.
var handled_by_touch = false;
// Mousedown events should start the drag immediately.
$container.on('mousedown', '.option[draggable=true]', function(evt) {
if (!handled_by_touch) {
evt.preventDefault();
var $item = $(this);
var drag_origin = {x: pageX(evt), y: pageY(evt)};
onDragStart('mouse', $(this), drag_origin);
}
});
// Touchstart events should start the event after a timeout.
// If the user starts moving the finger during the timeout, the drag should be cancelled.
// This allows users to scroll the item bank with their finger without accidentally starting
// to drag items.
$container.on('touchstart', '.option[draggable=true]', function(evt) {
handled_by_touch = true;
var $item = $(this);
var drag_origin = {x: pageX(evt), y: pageY(evt)};
var timeout_id = null;
var cancelDrag = function() {
clearTimeout(timeout_id);
// We need to reset the handled_by_touch in a timeout,
// so that it happens after the potentially emulated mousedown event.
setTimeout(function() {
handled_by_touch = false;
}, 0);
};
$item.one('touchmove touchend touchcancel', cancelDrag);
timeout_id = setTimeout(function() {
handled_by_touch = false;
$item.off('touchmove touchend touchcancel', cancelDrag);
onDragStart('touch', $item, drag_origin);
}, TOUCH_DRAG_DELAY);
});
// Prevent touchmove events fired on the dragged item causing scroll.
$container.on('touchmove', '.dragged-items .options[draggable=true]', function(evt) {
evt.preventDefault();
});
};
var grabItem = function($item, interaction_type) {
......@@ -1241,8 +1453,7 @@ function DragAndDropBlock(runtime, element, configuration) {
}
});
closePopup(false);
// applyState(true) skips destroying and initializing draggable
applyState(true);
applyState();
};
var releaseGrabbedItems = function() {
......@@ -1250,23 +1461,7 @@ function DragAndDropBlock(runtime, element, configuration) {
item.grabbed = false;
delete item.grabbed_with;
});
// applyState(true) skips destroying and initializing draggable
applyState(true);
};
var destroyDraggable = function() {
$root.find('.drag-container .option[draggable=false]').each(function() {
var $item = $(this);
$item.off();
try {
$item.draggable('destroy');
} catch (e) {
// Destroying the draggable will fail if draggable was
// not initialized in the first place. Ignore the exception.
}
});
applyState();
};
var submitLocation = function(item_id, zone) {
......@@ -1473,6 +1668,8 @@ function DragAndDropBlock(runtime, element, configuration) {
has_image: !!item.expandedImageURL,
grabbed: grabbed,
grabbed_with: item.grabbed_with,
is_dragged: Boolean(item.drag_position),
drag_position: item.drag_position,
is_placed: Boolean(item_user_state),
widthPercent: item.widthPercent, // widthPercent may be undefined (auto width)
imgNaturalWidth: item.imgNaturalWidth,
......
This source diff could not be displayed because it is too large. You can view the blob instead.
/*!
* jQuery UI Touch Punch 0.2.3
*
* Copyright 2011–2014, Dave Furfero
* Dual licensed under the MIT or GPL Version 2 licenses.
*
* Depends:
* jquery.ui.widget.js
* jquery.ui.mouse.js
*/
!function(a){function f(a,b){if(!(a.originalEvent.touches.length>1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery);
\ No newline at end of file
git+https://github.com/edx/xblock-utils.git@v1.0.4#egg=xblock-utils==1.0.4
git+https://github.com/edx/XBlock.git@xblock-0.5.0#egg=XBlock==0.5.0
-e .
......@@ -188,7 +188,6 @@ class TestDragAndDropRender(BaseIntegrationTest):
self.assertEqual(zone.get_attribute('aria-dropeffect'), 'move')
self.assertEqual(zone.get_attribute('data-uid'), 'Zone {}'.format(zone_number))
self.assertEqual(zone.get_attribute('data-zone_align'), 'center')
self.assertIn('ui-droppable', self.get_element_classes(zone))
zone_box_percentages = box_percentages[index]
self._assert_box_percentages( # pylint: disable=star-args
'#-Zone_{}'.format(zone_number), **zone_box_percentages
......
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