diff --git a/app/views/plans/index.html.erb b/app/views/plans/index.html.erb
index 9567967..4093ba5 100644
--- a/app/views/plans/index.html.erb
+++ b/app/views/plans/index.html.erb
@@ -54,12 +54,16 @@
<%= display_role(plan.roles.find_by(user: current_user)) %>
<% if plan.administerable_by?(current_user.id) then %>
- <%= plan.administerable_by?(current_user.id) ? '' : 'disabled="true"' %> />
+ <%= form_for plan, url: set_test_plan_url(plan), html: {method: :post, id: 'update-test-plan'} do |f| %>
+ <%= check_box_tag(:is_test, "1", (plan.visibility === 'is_test')) %>
+ <% end %>
<% else %>
<%= plan.visibility === 'is_test' ? _('Yes') : _('No') %>
<% end %>
- <%= raw display_visibility(plan.visibility) %>
+
+ <%= plan.visibility === 'is_test' ? _('N/A') : raw(display_visibility(plan.visibility)) %>
+
-
<%= raw _('Public DMPs') %>
diff --git a/lib/assets/javascripts/application.js b/lib/assets/javascripts/application.js
index 7f96d51..7ad1efc 100644
--- a/lib/assets/javascripts/application.js
+++ b/lib/assets/javascripts/application.js
@@ -3,17 +3,27 @@
import './views/devise/invitations/edit';
import './views/devise/passwords/edit';
import './views/devise/passwords/new';
+import './views/devise/registrations/edit';
+import './views/notes/index';
import './views/phases/edit';
import './views/phases/show';
import './views/plans/download';
import './views/plans/edit_details';
+import './views/plans/index';
import './views/plans/new';
import './views/plans/share';
-import './views/devise/registrations/edit';
-import './views/notes/index';
-import './views/templates/show';
-import './views/templates/edit';
+import './views/questions/new';
import './views/sections/index';
import './views/sections/new';
import './views/questions/index';
-import './views/questions/new';
+import './views/templates/show';
+import './views/templates/edit';
+
+// Not sure if this belongs here. All tables share the same class/id selectors
+// so having it on one page makes it work everywhere
+import { collateTable, filteriseTable } from './utils/tableHelper';
+
+$().ready(() => {
+ collateTable({ selector: 'table.tablesorter' });
+ filteriseTable({ selector: '#filter' });
+});
diff --git a/lib/assets/javascripts/constants.js b/lib/assets/javascripts/constants.js
index cd8245f..3d717f1 100644
--- a/lib/assets/javascripts/constants.js
+++ b/lib/assets/javascripts/constants.js
@@ -13,3 +13,7 @@
export const SHOW_PASSWORD_MESSAGE = 'Show password';
export const SHOW_SELECT_ORG_MESSAGE = 'Select an organisation from the list.';
export const SHOW_OTHER_ORG_MESSAGE = 'My organisation isn\'t listed';
+
+export const PLAN_VISIBILITY_WHEN_TEST = 'N/A';
+export const PLAN_VISIBILITY_WHEN_NOT_TEST = 'Private';
+export const PLAN_VISIBILITY_WHEN_NOT_TEST_TOOLTIP = 'Private: restricted to me and people I invite.';
diff --git a/lib/assets/javascripts/utils/tableHelper.js b/lib/assets/javascripts/utils/tableHelper.js
new file mode 100644
index 0000000..c4e8dd8
--- /dev/null
+++ b/lib/assets/javascripts/utils/tableHelper.js
@@ -0,0 +1,51 @@
+import 'tablesorter/dist/js/jquery.tablesorter.min';
+import debounce from '../utils/debounce';
+import { isObject, isString } from './isType';
+
+export const collateTable = (options) => {
+ if (isObject(options) && isString(options.selector)) {
+ $(options.selector).tablesorter({
+ theme: 'bootstrap_3',
+ headerTemplate: '{content} {icon}',
+ cssIconAsc: 'fa fa-sort-asc',
+ cssIconDesc: 'fa fa-sort-desc',
+ cssIconNone: 'fa fa-sort',
+ });
+ }
+};
+
+export const filteriseTable = (options) => {
+ if (isObject(options) && isString(options.selector)) {
+ const filter = ((el) => {
+ const query = $(el).val();
+ const regex = new RegExp(query, 'i');
+
+ $.each($(el).closest('table').find('tbody tr'), (idx, tr) => {
+ if (regex.test($(tr).text())) {
+ $(tr).show();
+ } else {
+ $(tr).hide();
+ }
+ });
+ });
+
+ const clear = ((el) => {
+ $(el).val('');
+ $(el).closest('table').find('tbody tr').show();
+ });
+
+ /* initialize a debounced listener for the filter box */
+ const debounced = debounce(filter);
+
+ /* Bind the clear function to the clear icon's click event */
+ $(options.selector).keyup((e) => {
+ debounced(e.currentTarget);
+ });
+
+ $(options.selector).siblings('#clear_filter').click((e) => {
+ e.preventDefault();
+ clear(e.currentTarget);
+ debounced.cancel();
+ });
+ }
+};
diff --git a/lib/assets/javascripts/views/plans/index.js b/lib/assets/javascripts/views/plans/index.js
index 95f497b..d37e94f 100644
--- a/lib/assets/javascripts/views/plans/index.js
+++ b/lib/assets/javascripts/views/plans/index.js
@@ -1,23 +1,30 @@
-$(document).ready(function(){
- dmproadmap.utils.collateTable.init({ selector: 'table.tablesorter' });
- dmproadmap.utils.filteriseTable.init({ selector: '#filter' });
+import { PLAN_VISIBILITY_WHEN_TEST, PLAN_VISIBILITY_WHEN_NOT_TEST, PLAN_VISIBILITY_WHEN_NOT_TEST_TOOLTIP } from '../../constants';
- // Update the plan's test status via ajax when the checkbox is clicked
- $("input[type='checkbox']").on('click, change', function(e){
- var self = this;
- var id = $(this).attr("id").replace("is_test-", "");
- var params = {plan: {visibility: $(this).is(':checked') ? 'is_test' : 'privately_visible'}};
+$().ready(() => {
+ const checkboxHandler = (el) => {
+ const form = $(el).closest('form');
- asyncRequest(
- {url: "/plans/" + id + "/set_test",
- type: 'POST',
- data: JSON.stringify(params)},
- {success: function(data){
- if($(self).is(':checked')){
- $("#visibility-" + id + " span").html(__('N/A')).attr('title', '');
- }else{
- $("#visibility-" + id + " span").html(__('Private'))
- }
- }});
+ $.ajax({
+ method: $(form).attr('method'),
+ url: $(form).attr('action'),
+ data: $(form).serializeArray(),
+ }).done((data) => {
+ $('#notification-area').removeClass('hide').find('span').last()
+ .html(data.msg);
+
+ if ($(el).is(':checked')) {
+ $(form).parent().siblings('.plan-visibility').html(PLAN_VISIBILITY_WHEN_TEST)
+ .attr('title', '');
+ } else {
+ $(form).parent().siblings('.plan-visibility').html(PLAN_VISIBILITY_WHEN_NOT_TEST)
+ .attr('title', PLAN_VISIBILITY_WHEN_NOT_TEST_TOOLTIP);
+ }
+ }, () => {
+ // TODO adequate error handling for network error
+ });
+ };
+
+ $("input[type='checkbox']").on('click, change', (e) => {
+ checkboxHandler(e.currentTarget);
});
});