Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Forum Edit Categories (Reordering) #2126

Merged
merged 10 commits into from
Jun 12, 2018
2 changes: 1 addition & 1 deletion .setup/bin/setup_sample_courses.py
Original file line number Diff line number Diff line change
Expand Up @@ -951,7 +951,7 @@ def create(self):
forum_thread_cat = Table("thread_categories", metadata, autoload=True)

for catData in f_data[2]:
conn.execute(forum_cat_list.insert(), category_desc=catData[0])
conn.execute(forum_cat_list.insert(), category_desc=catData[0], rank=catData[1])

for thread_id, threadData in enumerate(f_data[1], start = 1):
conn.execute(forum_threads.insert(),
Expand Down
6 changes: 3 additions & 3 deletions .setup/data/forum/categories.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
Comment
Question
Tutorials
Comment | 1
Question | 0
Tutorials | 2
3 changes: 3 additions & 0 deletions .setup/update_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@
os.system("PGPASSWORD='{}' psql --host={} --username={} --dbname={} -c 'ALTER TABLE ONLY threads ADD COLUMN merged_thread_id int DEFAULT '-1''".format(*variables))
os.system("PGPASSWORD='{}' psql --host={} --username={} --dbname={} -c 'ALTER TABLE ONLY threads ADD COLUMN merged_post_id int DEFAULT '-1''".format(*variables))

# categories ordering for discussion forum
os.system("PGPASSWORD='{}' psql --host={} --username={} --dbname={} -c 'ALTER TABLE ONLY categories_list ADD COLUMN rank int'".format(*variables))

# To allow delete gradeable
os.system("""PGPASSWORD='{}' psql --host={} --username={} --dbname={} -c 'ALTER TABLE ONLY peer_assign DROP CONSTRAINT peer_assign_g_id_fkey'""".format(*variables))
os.system("""PGPASSWORD='{}' psql --host={} --username={} --dbname={} -c 'ALTER TABLE ONLY peer_assign ADD CONSTRAINT peer_assign_g_id_fkey FOREIGN KEY (g_id) REFERENCES gradeable(g_id) ON UPDATE CASCADE ON DELETE CASCADE'""".format(*variables))
Expand Down
1 change: 1 addition & 0 deletions migration/data/course_tables.sql
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,7 @@ CREATE TABLE "thread_categories" (
CREATE TABLE "categories_list" (
"category_id" serial NOT NULL,
"category_desc" varchar NOT NULL,
"rank" int,
CONSTRAINT categories_list_pk PRIMARY KEY ("category_id")
);

Expand Down
30 changes: 30 additions & 0 deletions site/app/controllers/forum/ForumController.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ public function run() {
case 'add_category':
$this->addNewCategory();
break;
case 'reorder_categories':
$this->reorderCategories();
break;
case 'show_stats':
$this->showStats();
break;
Expand Down Expand Up @@ -169,6 +172,33 @@ public function addNewCategory(){
return $result;
}

public function reorderCategories(){
$result = array();
if($this->core->getUser()->getGroup() <= 2){
$rows = $this->core->getQueries()->getCategories();

$current_order = array();
foreach ($rows as $row) {
$current_order[] = (int)$row['category_id'];
}
$new_order = array();
foreach ($_POST['categorylistitem'] as $item) {
$new_order[] = (int)$item;
}

if(count(array_diff(array_merge($current_order, $new_order), array_intersect($current_order, $new_order))) === 0) {
$this->core->getQueries()->reorderCategories($new_order);
$results["success"] = "ok";
} else {
$result["error"] = "Different Categories IDs given";
}
} else {
$result["error"] = "You do not have permissions to do that.";
}
$this->core->getOutput()->renderJson($result);
return $result;
}

//CODE WILL BE CONSOLIDATED IN FUTURE

public function publishThread(){
Expand Down
12 changes: 10 additions & 2 deletions site/app/libraries/database/DatabaseQueries.php
Original file line number Diff line number Diff line change
Expand Up @@ -2094,9 +2094,17 @@ public function addNewCategory($category) {
return $this->course_db->rows()[0];
}

public function reorderCategories($categories_in_order) {
$this->course_db->beginTransaction();
foreach ($categories_in_order as $rank => $id) {
$this->course_db->query("UPDATE categories_list SET rank = ? WHERE category_id = ?", array($rank, $id));
}
$this->course_db->commit();
}

public function getCategories(){
$this->course_db->query("SELECT * from categories_list ORDER BY category_id DESC");
return $this->course_db->rows();
$this->course_db->query("SELECT * from categories_list ORDER BY rank ASC NULLS LAST");
return $this->course_db->rows();
}

public function getPostsForThread($current_user, $thread_id, $option = "tree"){
Expand Down
85 changes: 61 additions & 24 deletions site/app/views/forum/ForumThreadView.php
Original file line number Diff line number Diff line change
Expand Up @@ -811,6 +811,7 @@ public function createThread() {
$this->core->getOutput()->addBreadcrumb("Discussion Forum", $this->core->buildUrl(array('component' => 'forum', 'page' => 'view_thread')));
$this->core->getOutput()->addBreadcrumb("Create Thread", $this->core->buildUrl(array('component' => 'forum', 'page' => 'create_thread')));
$return = <<<HTML
<script type="text/javascript" language="javascript" src="https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.6.0/Sortable.min.js"></script>
<script type="text/javascript" language="javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery.AreYouSure/1.9.0/jquery.are-you-sure.min.js"></script>

<script>
Expand All @@ -821,7 +822,54 @@ public function createThread() {
</script>

<div style="margin-top:5px;background-color:transparent; margin: !important auto;padding:0px;box-shadow: none;" class="content">
HTML;
if($this->core->getUser()->getGroup() <= 2){
$categories = $this->core->getQueries()->getCategories();
$return .= <<<HTML
<div class="popup-form" id="category-list">
<h3>Categories</h3>
<pre>(Drag to re-order)</pre><br>
HTML;
if(count($categories) == 0) {
$return .= <<<HTML
<span id='category-list-no-element' style="margin-left: 1em;" >
No categories exists please create one.
</span>
HTML;
}
$return .= <<<HTML
<ul id='ui-category-list' style="padding-left: 1em;">
HTML;
// TODO: scrollbar
for($i = 0; $i < count($categories); $i++){
$return .= <<<HTML
<li id="categorylistitem-{$categories[$i]['category_id']}">
<i class="fa fa-bars handle" aria-hidden="true" title="Drag to reorder"></i>
{$categories[$i]['category_desc']}
</li>
HTML;
}
$return .= <<<HTML
</ul>
<div style="float: right; width: auto; margin-top: 10px;">
<a onclick="$('#category-list').css('display', 'none');" class="btn btn-danger">Cancel</a>
</div>
<script type="text/javascript">
$(function() {
$("#ui-category-list").sortable({
handle: ".handle",
update: function (event, ui) {
reorderCategories();
}
});
});
</script>

</div>

HTML;
}
$return .= <<<HTML
<div style="background-color: #E9EFEF; box-shadow:0 2px 15px -5px #888888;margin-top:10px;margin-left:20px;margin-right:20px;border-radius:3px; height:40px; margin-bottom:10px;" id="forum_bar">

<a class="btn btn-primary" style="position:relative;top:3px;left:5px;" title="Back to threads" href="{$this->core->buildUrl(array('component' => 'forum', 'page' => 'view_thread'))}"><i class="fa fa-arrow-left"></i> Back to Threads</a>
Expand Down Expand Up @@ -857,8 +905,10 @@ public function createThread() {
if($this->core->getUser()->getGroup() <= 2){
$return .= <<<HTML
<span style="float:right;display:inline-block;">

New Category: <input id="new_category_text" style="resize:none;" rows="1" type="text" size="30" name="new_category" id="new_category" /><button type="button" title="Add new category" onclick="addNewCategory();" style="margin-left:10px;" class="btn btn-primary btn-sm"> <i class="fa fa-plus-circle fa-1x"></i> Add category </button></span>
New Category: <input id="new_category_text" style="resize:none;" rows="1" type="text" size="30" name="new_category" id="new_category" /><button type="button" title="Add new category" onclick="addNewCategory();" style="margin-left:10px;" class="btn btn-primary btn-sm"> <i class="fa fa-plus-circle fa-1x"></i> Add category </button>
&nbsp;
<a class="btn btn-primary btn-sm" style="position:relative;float:right;display:inline-block;margin-right:10px;" title="Edit Categories" onclick="$('#category-list').css('display', 'block');">Edit Categories</a>
</span>
HTML;
}
$return .= <<<HTML
Expand All @@ -879,35 +929,22 @@ public function createThread() {
$categories = $this->core->getQueries()->getCategories();
$return .= <<<HTML
<label for="cat" id="cat_label">Categories</label> <br>
<div id='categories-pick-list'>
<noscript>
HTML;
for($i = 0; $i < count($categories); $i++){
$return .= <<<HTML
for($i = 0; $i < count($categories); $i++){
$return .= <<<HTML
<a class="btn cat-buttons cat-notselected btn-default">{$categories[$i]['category_desc']}
<input type="checkbox" name="cat[]" value="{$categories[$i]['category_id']}">
</a>
HTML;
}
$return .= <<<HTML
}
$return .= <<<HTML
</noscript>
</div>
<script type="text/javascript">
$(function() {
// If JS enabled hide checkbox
$("a.cat-buttons input").hide();

$(".cat-buttons").click(function() {
if($(this).hasClass("cat-selected")) {
$(this).removeClass("cat-selected");
$(this).addClass("cat-notselected");
$(this).addClass("btn-default");
$(this).removeClass("btn-primary");
$(this).find("input[type='checkbox']").prop("checked", false);
} else {
$(this).removeClass("cat-notselected");
$(this).addClass("cat-selected");
$(this).removeClass("btn-default");
$(this).addClass("btn-primary");
$(this).find("input[type='checkbox']").prop("checked", true);
}
});
refreshCategories();
$("#create_thread_form").submit(function() {
if($(this).find('.cat-selected').length == 0) {
alert("At least one category must be selected.");
Expand Down
29 changes: 27 additions & 2 deletions site/public/css/server.css
Original file line number Diff line number Diff line change
Expand Up @@ -861,7 +861,7 @@ table .instructor .two-options{
-webkit-transition:background 1s;
-moz-transition:background 1s;
-o-transition:background 1s;
transition:background 1s
transition:background 1s;
}

.cat-selected:hover {
Expand All @@ -873,14 +873,39 @@ table .instructor .two-options{
-webkit-transition:background 1s;
-moz-transition:background 1s;
-o-transition:background 1s;
transition:background 1s
transition:background 1s;
}

.cat-notselected:hover {
opacity: 0.8;
filter: alpha(opacity=80);
}

#ui-category-list {
list-style-type: none;
margin: 0;
padding: 0;
width: 60%;
}

#ui-category-list li {
border-style: solid;
border-color: LightGray;
border-width: thin;
border-radius: 0.3em;
margin: 0.3em 0.1em 0.3em 0.1em;
padding: 0.2em;
height: 18px;
}

#ui-category-list .handle {
color: LightGray;
}

#ui-category-list .handle:hover {
cursor: move;
}

.rainbow {
background: -moz-linear-gradient( left ,
rgba(255, 0, 0, 1) 0%,
Expand Down
112 changes: 112 additions & 0 deletions site/public/js/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -1337,6 +1337,118 @@ function addNewCategory(){
})
}

function refreshCategories() {
var data = $('#ui-category-list').sortable('serialize');
if(!data.trim()) {
return;
}
data = data.split("&");
var order = [];
for(var i = 0; i<data.length; i+=1) {
var category_id = parseInt(data[i].split('=')[1]);
order.push([category_id, $("#categorylistitem-"+category_id).text().trim()]);
}

// Obtain current selected category
var selected_button = new Set();
var category_pick_buttons = $('.cat-buttons');
for(var i = 0; i<category_pick_buttons.length; i+=1) {
var cat_button_checkbox = $(category_pick_buttons[i]).find("input");
var category_id = parseInt(cat_button_checkbox.val());
if(cat_button_checkbox.prop("checked")) {
selected_button.add(category_id);
}
}

// Refresh reorder categories list
$('#ui-category-list').empty();
order.forEach(function(category) {
var category_id = category[0];
var category_desc = category[1];
var li = ' <li id="categorylistitem-'+category_id+'">\
<i class="fa fa-bars handle" aria-hidden="true" title="Drag to reorder"></i>\
'+category_desc+' \
</li>';
$('#ui-category-list').append(li);
});


// Refresh selected categories
$('#categories-pick-list').empty();
order.forEach(function(category) {
var category_id = category[0];
var category_desc = category[1];
var selection_class;
if(selected_button.has(category_id)) {
selection_class = "cat-selected btn-primary";
} else {
selection_class = "cat-notselected btn-default";
}
var element = ' <a class="btn cat-buttons '+selection_class+' btn-default">'+category_desc+'\
<input type="checkbox" name="cat[]" value="'+category_id+'">\
</a>';
$('#categories-pick-list').append(element);
});

$(".cat-buttons input[type='checkbox']").each(function() {
if($(this).parent().hasClass("cat-selected")) {
$(this).prop("checked",true);
}
});

// Selectors for categories pick up
// If JS enabled hide checkbox
$("a.cat-buttons input").hide();

$(".cat-buttons").click(function() {
if($(this).hasClass("cat-selected")) {
$(this).removeClass("cat-selected");
$(this).addClass("cat-notselected");
$(this).addClass("btn-default");
$(this).removeClass("btn-primary");
$(this).find("input[type='checkbox']").prop("checked", false);
} else {
$(this).removeClass("cat-notselected");
$(this).addClass("cat-selected");
$(this).removeClass("btn-default");
$(this).addClass("btn-primary");
$(this).find("input[type='checkbox']").prop("checked", true);
}
});

}

function reorderCategories(){
var data = $('#ui-category-list').sortable('serialize');
var url = buildUrl({'component': 'forum', 'page': 'reorder_categories'});
$.ajax({
url: url,
type: "POST",
data: data,
success: function(data){
try {
var json = JSON.parse(data);
} catch (err){
var message ='<div class="inner-message alert alert-error" style="position: fixed;top: 40px;left: 50%;width: 40%;margin-left: -20%;" id="theid"><a class="fa fa-times message-close" onClick="removeMessagePopup(\'theid\');"></a><i class="fa fa-times-circle"></i>Error parsing data. Please try again.</div>';
$('#messages').append(message);
return;
}
if(json['error']){
var message ='<div class="inner-message alert alert-error" style="position: fixed;top: 40px;left: 50%;width: 40%;margin-left: -20%;" id="theid"><a class="fa fa-times message-close" onClick="removeMessagePopup(\'theid\');"></a><i class="fa fa-times-circle"></i>' + json['error'] + '</div>';
$('#messages').append(message);
return;
}
var message ='<div class="inner-message alert alert-success" style="position: fixed;top: 40px;left: 50%;width: 40%;margin-left: -20%;" id="theid"><a class="fa fa-times message-close" onClick="removeMessagePopup(\'theid\');"></a><i class="fa fa-times-circle"></i>Successfully reordered categories.';
$('#messages').append(message);
setTimeout(function() {removeMessagePopup('theid');}, 1000);
refreshCategories();
},
error: function(){
window.alert("Something went wrong while trying to reordering categories. Please try again.");
}
});
}

/*This function ensures that only one reply box is open at a time*/
function hideReplies(){
var hide_replies = document.getElementsByClassName("reply-box");
Expand Down