Commit e3d38601 authored by Robert Lyon's avatar Robert Lyon
Browse files

Allow collection's pages list to be added/edited by drag/drop (Bug #1320716)



You can now alter the sorting of the list by dragging the page name to
a new position.

You can also add in new pages to the collection by dragging them in
from the 'add pages to collection' list. If there are no existing
pages in collection you need to drop a page onto the 'No pages' grey
box.

Also added touch-punch js lib to allow for the drag/drop to work as
well on mobile devices.

Change-Id: Id1af83abc50b76a7a8d56da8767ed4ce0ef76f77
Signed-off-by: Robert Lyon's avatarRobert Lyon <robertl@catalyst.net.nz>
parent 6ba50ec5
<?php
/**
*
* @package mahara
* @subpackage core
* @author Catalyst IT Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
* @copyright For copyright information on Mahara, please see the README file distributed with this software.
*
*/
define('INTERNAL', 1);
define('JSON', 1);
require(dirname(dirname(__FILE__)) . '/init.php');
require_once('pieforms/pieform.php');
require_once('collection.php');
$id = param_integer('id');
$direction = param_variable('direction','');
$collection = new Collection($id);
if (!$USER->can_edit_collection($collection)) {
json_reply('local', get_string('accessdenied', 'error'));
}
$owner = $collection->get('owner');
$groupid = $collection->get('group');
$institutionname = $collection->get('institution');
$views = $collection->views();
if (!empty($direction)) {
parse_str($direction, $direction_array);
$viewids = array();
// get all the id's of the existing views attached to collection - if any
if (!empty($views['views'])) {
foreach ($views['views'] as $v) {
$viewids[] = $v->view;
}
}
// now check if there are any new views to add to the collection
// items dragged from the 'add to collection' list. (currently handles only one at a time)
$diff = array_diff($direction_array['row'], $viewids);
if (!empty($diff)) {
// turn it into an array understood by $collection->add_views()
$addviews = array();
foreach ($diff as $v) {
// We need to check that the id's are allowed to be added to the collection
// by checking if the user can edit the view.
require_once('view.php');
$view = new View($v);
$viewowner = $view->get('owner');
$viewgroup = $view->get('group');
$viewinstitution = $view->get('institution');
if ((!$USER->can_edit_view($view)) ||
(!empty($viewowner) && $viewowner != $collection->get('owner')) ||
(!empty($viewgroup) && $viewgroup != $collection->get('group')) ||
(!empty($viewinstitution) && $viewinstitution != $collection->get('institution'))
) {
continue;
}
$addviews['view_' . $v] = true;
}
if (!empty($addviews)) {
$collection->add_views($addviews);
}
}
$collection->set_viewdisplayorder(null, $direction_array['row']);
}
// We need to call the collection again to get the updated view list
$collection = new Collection($id);
$views = $collection->get('views');
if ($views) {
foreach ($views['views'] as &$v) {
$v->remove = pieform(array(
'name' => 'removeview_' . $v->view,
'successcallback' => 'removeview_submit',
'elements' => array(
'view' => array(
'type' => 'hidden',
'value' => $v->view,
),
'submit' => array(
'type' => 'submit',
'confirm' => get_string('viewconfirmremove', 'collection'),
'value' => get_string('remove'),
),
),
));
}
}
$smarty = smarty_core();
$smarty->assign_by_ref('views', $views);
$smarty->assign('displayurl', get_config('wwwroot') . 'collection/views.php?id=' . $id);
$html = $smarty->fetch('collection/views.json.tpl');
json_reply(false, array(
'message' => null,
'html' => $html,
));
......@@ -29,6 +29,7 @@ $collection = new Collection($id);
if (!$USER->can_edit_collection($collection)) {
throw new AccessDeniedException(get_string('canteditcollection', 'collection'));
}
$sesskey = $USER->get('sesskey');
$owner = $collection->get('owner');
$groupid = $collection->get('group');
$institutionname = $collection->get('institution');
......@@ -117,9 +118,112 @@ if ($available = Collection::available_views($owner, $groupid, $institutionname)
'elements' => $elements,
));
}
$noviewsavailable = get_string('noviewsavailable', 'collection');
$inlinejs = <<<EOF
\$j(function() {
var fixhelper = function(e, tr) {
var originals = tr.children();
var helper = tr.clone();
helper.children().each(function(index) {
\$j(this).width(originals.eq(index).width());
});
return helper;
};
var updaterows = function(viewid) {
var sortorder = \$j('#collectionviews tbody').sortable('serialize');
\$j.post(config['wwwroot'] + "collection/views.json.php", { sesskey: '$sesskey', id: $id, direction: sortorder })
.done(function(data) {
// update the page with the new table
if (data.returnCode == '0') {
\$j('#collectionviews').replaceWith(data.message.html);
if (viewid) {
\$j('#addviews_view_' + viewid + '_container').remove();
// check if we have just removed the last option leaving
// only the add pages button
if (\$j("#addviews tbody").children().length <= 1) {
\$j("#addviews").remove();
\$j("#pagestoadd").append('$noviewsavailable');
}
}
wiresortables();
wireaddrow();
}
});
};
var wiresortables = function() {
\$j('#collectionviews tbody').sortable({
items: 'tr',
cursor: 'move',
opacity: 0.6,
helper: fixhelper,
stop: function(e, ui) {
var labelfor = ui.item.attr('for');
if (typeof labelfor !== 'undefined' && labelfor !== false) {
var viewid = ui.item.attr('for').replace(/[^\d.]/g,''); // remove all but the digits
ui.item.replaceWith('<tr id="row_' + viewid + '"><td colspan="3">' + ui.item.text() + '</td></tr>');
updaterows(viewid);
}
else {
updaterows(false);
}
},
})
.disableSelection()
.hover(function() {
\$j(this).css('cursor', 'move');
});
};
$smarty = smarty();
var wireaddrow = function() {
\$j('#addviews label').draggable({
connectToSortable: '#collectionviews tbody',
cursor: 'move',
revert: 'invalid',
helper: 'clone',
}).hover(function() {
\$j(this).css('cursor', 'move');
});
};
var wireaddnewrow = function() {
\$j('#addviews label').draggable({
cursor: 'move',
revert: 'invalid',
helper: 'clone',
}).hover(function() {
\$j(this).css('cursor', 'move');
});
};
var wiredrop = function() {
\$j('#collectionpages .message').droppable({
accept: "label",
drop: function (e, ui) {
var labelfor = ui.draggable.attr('for');
if (typeof labelfor !== 'undefined' && labelfor !== false) {
var viewid = ui.draggable.attr('for').replace(/[^\d.]/g,''); // remove all but the digits
\$j('#collectionpages .message').replaceWith('<table id="collectionviews"><tbody><tr id="row_' + viewid + '"><td colspan="3">' + ui.draggable.text() + '</td></tr></tbody></table>');
wiresortables();
updaterows(viewid);
}
},
});
};
// init
if (\$j('#collectionviews tbody').length > 0) {
wireaddrow();
wiresortables();
}
else {
wireaddnewrow();
wiredrop();
}
});
EOF;
$smarty = smarty(array('jquery','js/jquery/jquery-ui/js/jquery-ui-1.10.2.min.js','js/jquery/jquery-ui/js/jquery-ui.touch-punch.min.js'));
if (!empty($groupid)) {
$smarty->assign('PAGESUBHEADING', SUBTITLE);
$smarty->assign('PAGEHELPNAME', '0');
......@@ -128,9 +232,10 @@ if (!empty($groupid)) {
else {
$smarty->assign('PAGEHEADING', SUBTITLE);
}
$smarty->assign('INLINEJAVASCRIPT', $inlinejs);
$smarty->assign('baseurl', $baseurl);
$smarty->assign('displayurl',get_config('wwwroot').'collection/views.php?id='.$id);
$smarty->assign('removeurl',get_config('wwwroot').'collection/deleteview.php?id='.$id);
$smarty->assign('displayurl', get_config('wwwroot') . 'collection/views.php?id=' . $id);
$smarty->assign('removeurl', get_config('wwwroot') . 'collection/deleteview.php?id=' . $id);
$smarty->assign_by_ref('views', $views);
$smarty->assign_by_ref('viewsform', $viewsform);
$smarty->display('collection/views.tpl');
......
......@@ -31,4 +31,22 @@ Dependency package: jquery-ui
Changes:
* None
\ No newline at end of file
* None
jQuery UI plugin touch-punch
============================
This javascript library is required for jQuery UI draggable / droppable / sortable to work on mobile devices
Website: http://touchpunch.furf.com/
Version: 0.2.3
Dependency package: jquery-ui
Changes:
* None
Notes:
* Touch Punch works by using simulated events to map touch events to their mouse event analogs.
/*!
* 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);
......@@ -58,6 +58,8 @@ $string['deletespecifiedcollection'] = 'Delete collection \'%s\'';
$string['deletingcollection'] = 'Deleting collection';
$string['deleteview'] = 'Remove page from collection';
$string['description'] = 'Collection description';
$string['collectiondragupdate'] = 'Drag page names or use the arrow buttons to reorder pages in the \'Pages already in collection\' box.<br>You can tick and click \'Add pages\', or drag page names to the \'Pages already in collection\' box to add them to your collection.';
$string['viewsincollection'] = 'Pages already in collection';
$string['editcollection'] = 'Edit collection';
$string['editingcollection'] = 'Editing collection';
$string['edittitleanddesc'] = 'Edit title and description';
......
......@@ -633,35 +633,40 @@ class Collection {
/**
* Sets the displayorder for a view
*
* @param integer view
* @param string direction
*
* @param integer $id view id
* @param mixed direction - either string consisting 'up' or 'down' to
* indicate which way to move $id item, or an array containing
* the ids in order you want them saved
*/
public function set_viewdisplayorder($id, $direction) {
$ids = get_column_sql('
SELECT view FROM {collection_view}
WHERE collection = ?
ORDER BY displayorder', array($this->get('id')));
foreach ($ids as $k => $v) {
if ($v == $id) {
$oldorder = $k;
break;
}
if (is_array($direction)) {
// we already have new sort order
$neworder = $direction;
}
else {
$ids = get_column_sql('
SELECT view FROM {collection_view}
WHERE collection = ?
ORDER BY displayorder', array($this->get('id')));
foreach ($ids as $k => $v) {
if ($v == $id) {
$oldorder = $k;
break;
}
}
if ($direction == 'up' && $oldorder > 0) {
$neworder = array_merge(array_slice($ids, 0, $oldorder - 1),
array($id, $ids[$oldorder-1]),
array_slice($ids, $oldorder+1));
}
else if ($direction == 'down' && ($oldorder + 1 < count($ids))) {
$neworder = array_merge(array_slice($ids, 0, $oldorder),
array($ids[$oldorder+1], $id),
array_slice($ids, $oldorder+2));
if ($direction == 'up' && $oldorder > 0) {
$neworder = array_merge(array_slice($ids, 0, $oldorder - 1),
array($id, $ids[$oldorder-1]),
array_slice($ids, $oldorder+1));
}
else if ($direction == 'down' && ($oldorder + 1 < count($ids))) {
$neworder = array_merge(array_slice($ids, 0, $oldorder),
array($ids[$oldorder+1], $id),
array_slice($ids, $oldorder+2));
}
}
if (isset($neworder)) {
foreach ($neworder as $k => $v) {
set_field('collection_view', 'displayorder', $k, 'view', $v, 'collection',$this->get('id'));
......
......@@ -2718,7 +2718,7 @@ ul.favorskins {
}
fieldset#pagestoadd {
width: 300px;
margin: -25px 0 0 -350px;
margin: 10px 0 0 -350px;
float: left;
padding: 10px;
}
......
<table id="collectionviews" class="fullwidth grid">
<tbody>
{foreach from=$views.views item=view}
<tr class="{cycle values='r0,r1'}" id="row_{$view->view}">
{if $views.count > 1}
<td class="displayordercontrols btns2">
{if $view->displayorder == $views.min}
<div id="viewdisplayorder_{$view->view}" class="justdown">
<a href="{$displayurl}&amp;view={$view->view}&amp;direction=down"><img src="{theme_url filename='images/btn_movedown.png'}" alt="{str tag=moveitemdown}" ></a>
</div>
{elseif $view->displayorder == $views.max}
<div id="viewdisplayorder_{$view->view}" class="justup">
<a href="{$displayurl}&amp;view={$view->view}&amp;direction=up"><img src="{theme_url filename='images/btn_moveup.png'}" alt="{str tag=moveitemup}" ></a>
</div>
{else}
<div id="viewdisplayorder_{$view->view}">
<a href="{$displayurl}&amp;view={$view->view}&amp;direction=up"><img src="{theme_url filename='images/btn_moveup.png'}" alt="{str tag=moveitemup}" ></a>
<a href="{$displayurl}&amp;view={$view->view}&amp;direction=down"><img src="{theme_url filename='images/btn_movedown.png'}" alt="{str tag=moveitemdown}" ></a>
</div>
{/if}
</td>
{else}
<td>&nbsp;</td>
{/if}
<td><label><a href="{$view->fullurl}">{$view->title}</a></label></td>
<td><div class="fr">{$view->remove|safe}</div></td>
</tr>
{/foreach}
</tbody>
</table>
......@@ -3,8 +3,8 @@
<h2>{$PAGESUBHEADING}{if $SUBPAGEHELPNAME}<span class="page-help-icon">{$PAGEHELPICON|safe}</span>{/if}</h2>
{/if}
<div id="collectionpageswrap">
<div id="draginstruction" class="center">{str tag=collectiondragupdate section=collection}</div>
<div id="collectionpages">
<h3>{str tag=Collection section=collection}</h3>
<fieldset id="pagestoadd">
<legend>{str tag=addviewstocollection section=collection}</legend>
{if $viewsform}
......@@ -13,27 +13,29 @@
{str tag=noviewsavailable section=collection}
{/if}
</fieldset>
<fieldset id="pagesadded">
<legend>{str tag=viewsincollection section=collection}</legend>
{if !$views}
<div class="message">{str tag=noviews section=collection}</div>
{else}
<table id="collectionviews" class="fullwidth">
<table id="collectionviews" class="fullwidth grid">
<tbody>
{foreach from=$views.views item=view}
<tr class="{cycle values='r0,r1'}">
<tr class="{cycle values='r0,r1'}" id="row_{$view->view}">
{if $views.count > 1}
<td class="displayordercontrols btns2">
{if $view->displayorder == $views.min}
<div id="viewdisplayorder_{$view->view}" class="justdown">
<a href="{$displayurl}&amp;view={$view->view}&amp;direction=down"><img src="{theme_url filename='images/btn_movedown.png'}" alt="Move Down" ></a>
<a href="{$displayurl}&amp;view={$view->view}&amp;direction=down"><img src="{theme_url filename='images/btn_movedown.png'}" alt="{str tag=moveitemdown}" ></a>
</div>
{elseif $view->displayorder == $views.max}
<div id="viewdisplayorder_{$view->view}" class="justup">
<a href="{$displayurl}&amp;view={$view->view}&amp;direction=up"><img src="{theme_url filename='images/btn_moveup.png'}" alt="Move Up" ></a>
<a href="{$displayurl}&amp;view={$view->view}&amp;direction=up"><img src="{theme_url filename='images/btn_moveup.png'}" alt="{str tag=moveitemup}" ></a>
</div>
{else}
<div id="viewdisplayorder_{$view->view}">
<a href="{$displayurl}&amp;view={$view->view}&amp;direction=up"><img src="{theme_url filename='images/btn_moveup.png'}" alt="Move Up" ></a>
<a href="{$displayurl}&amp;view={$view->view}&amp;direction=down"><img src="{theme_url filename='images/btn_movedown.png'}" alt="Move Down" ></a>
<a href="{$displayurl}&amp;view={$view->view}&amp;direction=up"><img src="{theme_url filename='images/btn_moveup.png'}" alt="{str tag=moveitemup}" ></a>
<a href="{$displayurl}&amp;view={$view->view}&amp;direction=down"><img src="{theme_url filename='images/btn_movedown.png'}" alt="{str tag=moveitemdown}" ></a>
</div>
{/if}
</td>
......@@ -47,6 +49,7 @@
</tbody>
</table>
{/if}
</fieldset>
<div class="cb"></div>
</div></div>
<div id="collectiondonewrap"><a class="btn" href="{$baseurl}">{str tag=done}</a></div>
......
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