diff --git a/beat/web/reports/static/reports/app/directives/view/panelItems.js b/beat/web/reports/static/reports/app/directives/dragHandle.js
similarity index 70%
rename from beat/web/reports/static/reports/app/directives/view/panelItems.js
rename to beat/web/reports/static/reports/app/directives/dragHandle.js
index cb0dc0b90000827a3a31d83d5cf2e4b888f3310c..4eb0fce3646b9fc1caaccfb8a4814dfb6f2e8a74 100644
--- a/beat/web/reports/static/reports/app/directives/view/panelItems.js
+++ b/beat/web/reports/static/reports/app/directives/dragHandle.js
@@ -21,27 +21,26 @@
  */
 
 /*
- * groupPanelItems
+ * dragHandle
  * Desc:
- * 	displays the panel of report items of the group,
- * 	using the item container adaptor
+ * 	displays the drag handle button, and adds the specified element class
+ * 	to the parent el
  */
-angular.module('reportApp').directive("groupPanelItems", [function(){
+angular.module('reportApp')
+.directive("dragHandle", [function(){
 	return {
 		scope: {
-			group: '='
+			handleHelperClass: '@'
 		},
-		link: function(scope){
+		link: function(scope, el){
+			el.addClass(`${scope.handleHelperClass} btn-group`);
 		},
 		template: `
-<div>
-	<div group-item-container
-		style='margin-top: 5px;'
-		ng-repeat='item in group.reportItems'
-		group='group'
-		report-item='item'>
-	</div>
-</div>
+<span
+	class='btn btn-default drag-handle'
+	data-toggle='tooltip' data-placement='top' title='Drag to re-order'>
+	<i class='fa fa-arrows fa-lg'></i>
+</span>
 `
 	};
 }]);
diff --git a/beat/web/reports/static/reports/app/directives/edit/itemContainer.js b/beat/web/reports/static/reports/app/directives/edit/itemContainer.js
deleted file mode 100644
index ad80156fc0bec09e50026c2cfb6a7b73fee49cd6..0000000000000000000000000000000000000000
--- a/beat/web/reports/static/reports/app/directives/edit/itemContainer.js
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/
- * Contact: beat.support@idiap.ch
- *
- * This file is part of the beat.web module of the BEAT platform.
- *
- * Commercial License Usage
- * Licensees holding valid commercial BEAT licenses may use this file in
- * accordance with the terms contained in a written agreement between you
- * and Idiap. For further information contact tto@idiap.ch
- *
- * Alternatively, this file may be used under the terms of the GNU Affero
- * Public License version 3 as published by the Free Software and appearing
- * in the file LICENSE.AGPL included in the packaging of this file.
- * The BEAT platform is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE.
- *
- * You should have received a copy of the GNU Affero Public License along
- * with the BEAT platform. If not, see http://www.gnu.org/licenses/.
- */
-
-/*
- * groupItemContainer
- * Desc:
- * 	container for an item in the group.
- * 	depending of the type of the item (found in the item's id)
- * 	it creates a table, plot, or text item
- */
-angular.module('reportApp')
-.directive("groupItemContainer", [function(){
-	return {
-		scope: {
-			group: '=',
-			reportItem: '='
-		},
-		link: function(scope){
-			scope.item = scope.reportItem;
-			// report-wide-unique prefix for the item to use
-			scope.domId = `${scope.group.name}_${scope.id}`;
-		},
-		template: `
-<div
-	group-table-item
-	ng-if="item.id.includes('table')"
-	class='panel panel-default'
-	group='group'
-	item-id='item.id'
-	content='item.content'
-	>
-</div>
-<div
-	group-plot-item
-	ng-if="item.id.includes('plot')"
-	class='panel panel-default'
-	group='group'
-	item-id='item.id'
-	content='item.content'
-	>
-</div>
-<div
-	group-text-item
-	ng-if="item.id.includes('text')"
-	class='panel panel-default'
-	group='group'
-	report-item='item'
-	item-id='item.id'
-	>
-</div>
-`
-	};
-}]);
diff --git a/beat/web/reports/static/reports/app/directives/edit/layout.js b/beat/web/reports/static/reports/app/directives/edit/layout.js
deleted file mode 100644
index ddedd21165ccf8abf4c54f0cdbc544865f65301f..0000000000000000000000000000000000000000
--- a/beat/web/reports/static/reports/app/directives/edit/layout.js
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/
- * Contact: beat.support@idiap.ch
- *
- * This file is part of the beat.web module of the BEAT platform.
- *
- * Commercial License Usage
- * Licensees holding valid commercial BEAT licenses may use this file in
- * accordance with the terms contained in a written agreement between you
- * and Idiap. For further information contact tto@idiap.ch
- *
- * Alternatively, this file may be used under the terms of the GNU Affero
- * Public License version 3 as published by the Free Software and appearing
- * in the file LICENSE.AGPL included in the packaging of this file.
- * The BEAT platform is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE.
- *
- * You should have received a copy of the GNU Affero Public License along
- * with the BEAT platform. If not, see http://www.gnu.org/licenses/.
- */
-
-/*
- * groupsLayout
- * Desc:
- * 	controls the layout of the reports content,
- * 	generating group panels using the GroupsService data,
- * 	and holding the menu for adding a group
- */
-angular.module('reportApp').directive("groupsLayout", ['GroupsService', function(GroupsService){
-	return {
-		scope: {
-		},
-		link: function(scope, el, attr){
-			scope.groups = GroupsService.groups;
-			scope.GroupsService = GroupsService;
-
-			// drag handle CSS selector
-			scope.sortableOptions = {
-				handle: '.panel-heading > .panel-title > .action-buttons > .drag-handle'
-			};
-		},
-		template: `
-<experiments-table></experiments-table>
-<div group-add-group-menu class='panel'></div>
-
-<div ui-sortable='sortableOptions' ng-model='GroupsService.groups' id='groupsLayout' class='panel-group'>
-	<div
-		group-panel-content
-		class='panel panel-default'
-		ng-repeat='group in GroupsService.groups'
-		group='group'
-		>
-	</div>
-</div>
-`
-	};
-}]);
diff --git a/beat/web/reports/static/reports/app/directives/edit/plotItem.js b/beat/web/reports/static/reports/app/directives/edit/plotItem.js
deleted file mode 100644
index 614a6999ea2f13115977d9698bc94d717d43877d..0000000000000000000000000000000000000000
--- a/beat/web/reports/static/reports/app/directives/edit/plotItem.js
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/
- * Contact: beat.support@idiap.ch
- *
- * This file is part of the beat.web module of the BEAT platform.
- *
- * Commercial License Usage
- * Licensees holding valid commercial BEAT licenses may use this file in
- * accordance with the terms contained in a written agreement between you
- * and Idiap. For further information contact tto@idiap.ch
- *
- * Alternatively, this file may be used under the terms of the GNU Affero
- * Public License version 3 as published by the Free Software and appearing
- * in the file LICENSE.AGPL included in the packaging of this file.
- * The BEAT platform is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE.
- *
- * You should have received a copy of the GNU Affero Public License along
- * with the BEAT platform. If not, see http://www.gnu.org/licenses/.
- */
-
-/*
- * groupPlotItem
- * Desc:
- * 	displays a plot report item (basically a container for the plots code to insert into)
- */
-angular.module('reportApp')
-.directive("groupPlotItem", ['ExperimentsService', 'PlotService', '$timeout', function(ExperimentsService, PlotService, $timeout){
-	return {
-		scope: {
-			group: '=',
-			itemId: '=',
-			content: '='
-		},
-		link: function(scope){
-			const group = scope.group;
-			scope.domId = `${scope.group.name}_${scope.itemId}`;
-
-			// container for the plots applet to insert into
-			scope.renderDivId = `${scope.domId}-render`;
-
-			// the callback for when the plot renders
-			// (called every time the plot re-renders, e.g. user changes config/merged
-			const updatePlotConfig = (selectedPlotter, selectedConfig, isMerged) => {
-				scope.content.merged = isMerged;
-				scope.content.savedPlotter = selectedPlotter;
-				scope.content.savedConfig = selectedConfig;
-			};
-
-			// wait until the container html element is rendered.
-			// angular will run these functions called with $timeout
-			// after everything has been rendered
-			$timeout(function() {
-				PlotService.addPlot(scope.group, scope.itemId, scope.renderDivId, updatePlotConfig);
-			});
-
-			let plotTimer;
-
-			const updatePlot = () => {
-				clearTimeout(plotTimer);
-
-				const queueUpdate = () => {
-					let el = document.querySelector(`#${scope.renderDivId}`);
-					// if the container is rendered and it already has had a render,
-					// redo the render
-					if(el && el.childNodes.length > 0){
-						el.innerHTML = '';
-						return PlotService.addPlot(scope.group, scope.itemId, scope.renderDivId, updatePlotConfig);
-					}
-				};
-
-				plotTimer = setTimeout(queueUpdate, 1000);
-			};
-
-
-			// if the group has exps added/removed, rerender the plot
-			scope.$watch(
-				// angular doesnt watch arrays properly (i.e. watching scope.group._experimentNames),
-				// as it doesnt register array changes.
-				// it also doesnt watch getters properly (i.e. watching scope.group.experiments),
-				// because it compares shallowly and getters commonly return a new obj each time.
-				// so, watch the getters length instead
-				() => scope.group.experiments.length,
-				updatePlot
-			);
-
-			// if the group has aliases changed, rerender the plot
-			scope.$watch(
-				() => JSON.stringify(scope.group._aliases),
-				updatePlot
-			);
-
-			// if the user selected different plotter or config, rerender
-			scope.$watch(
-				() => `${scope.content.savedPlotter}|${scope.content.savedConfig}`,
-				updatePlot
-			);
-
-			scope.toggleMerged = () => {
-				scope.content.merged = !scope.content.merged;
-				updatePlot();
-			};
-
-			scope.getPlotter = () => PlotService.getPlotter(scope.content);
-			scope.getPossiblePlotters = () => PlotService.plotters.filter(p => p.dataformat === scope.content.type).map(p => p.name);
-			scope.getPossibleConfigs = () => PlotService.getPossibleConfigs(scope.getPlotter());
-		},
-		template: `
-<div id="{{domId}}-heading" class="panel-heading" role="tab">
-	<h4 class="panel-title">
-		<a
-			class=''
-			role="button"
-			data-toggle="collapse"
-			data-parent="#{{domId}}-heading"
-			href="#collapse-{{domId}}"
-			aria-expanded="true"
-			aria-controls="collapse-{{domId}}">
-		</a>
-		<editable-label obj='content' field='itemName'></editable-label>
-		<div class='btn-group action-buttons'>
-			<span
-				ng-click='group.removeReportItem(itemId)'
-				class="btn btn-default btn-delete"
-				data-toggle="tooltip" data-placement="top" title="Delete Plot">
-				<i class="fa fa-times fa-lg"></i>
-			</span>
-			<span
-				class='btn btn-default drag-handle'
-				data-toggle='tooltip' data-placement='top' title='Drag to re-order item'>
-				<i class='fa fa-arrows fa-lg'></i>
-			</span>
-		</div>
-		<div class='btn-group' role='group'>
-			<download-link
-				class='btn-group'
-				dom-id='{{ domId }}'
-				group='group'
-				item-id='itemId'>
-			</download-link>
-			<button ng-if='group.experiments.length > 1' class='btn btn-default' ng-click='toggleMerged()'>
-				<span ng-if='content.merged'><i class='fa fa-expand'></i> Expand</span>
-				<span ng-if='!content.merged'><i class='fa fa-compress'></i> Merge</span>
-			</button>
-			<div class='btn-group' role='group'>
-				<button
-					ng-class='{disabled: getPossiblePlotters().length === 1}'
-					class='btn btn-default dropdown-toggle'
-					type="button"
-					data-toggle="dropdown"
-					aria-haspopup="true"
-					aria-expanded="false">
-					Plotter: {{ content.savedPlotter }}
-					<span class="caret"></span>
-				</button>
-				<ul class="dropdown-menu">
-					<li ng-click='content.savedPlotter = p' ng-repeat='p in getPossiblePlotters()'><a>{{ p }}</a></li>
-				</ul>
-			</div>
-			<div class='btn-group' role='group'>
-				<button
-					ng-class='{disabled: getPossibleConfigs().length === 1}'
-					class='btn btn-default dropdown-toggle'
-					type="button"
-					data-toggle="dropdown"
-					aria-haspopup="true"
-					aria-expanded="false">
-					Config: {{ content.savedConfig }}
-					<span class="caret"></span>
-				</button>
-				<ul class="dropdown-menu">
-					<li ng-click='content.savedConfig = c' ng-repeat='c in getPossibleConfigs()'><a>{{ c }}</a></li>
-				</ul>
-			</div>
-		</div>
-	</h4>
-</div>
-<div id="collapse-{{domId}}"
-	class="panel-collapse collapse in"
-	role="tabpanel"
-	aria-labelledby="{{domId}}-heading">
-	<div class='panel-body'>
-		<div id='{{ renderDivId }}'></div>
-	</div>
-</div>
-`
-	};
-}]);
diff --git a/beat/web/reports/static/reports/app/directives/edit/tableItem.js b/beat/web/reports/static/reports/app/directives/edit/tableItem.js
deleted file mode 100644
index 70bf876207bffa1782b4a5d4b7240cd66e806808..0000000000000000000000000000000000000000
--- a/beat/web/reports/static/reports/app/directives/edit/tableItem.js
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/
- * Contact: beat.support@idiap.ch
- *
- * This file is part of the beat.web module of the BEAT platform.
- *
- * Commercial License Usage
- * Licensees holding valid commercial BEAT licenses may use this file in
- * accordance with the terms contained in a written agreement between you
- * and Idiap. For further information contact tto@idiap.ch
- *
- * Alternatively, this file may be used under the terms of the GNU Affero
- * Public License version 3 as published by the Free Software and appearing
- * in the file LICENSE.AGPL included in the packaging of this file.
- * The BEAT platform is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE.
- *
- * You should have received a copy of the GNU Affero Public License along
- * with the BEAT platform. If not, see http://www.gnu.org/licenses/.
- */
-
-/*
- * groupTableItem
- * Desc:
- * 	displays a table report item and lets the user
- * 	manage this table's selected cols and float precision
- */
-angular.module('reportApp')
-.directive("groupTableItem", ['GroupsService', 'ExperimentsService', function(GroupsService, ExperimentsService){
-	return {
-		scope: {
-			group: '=',
-			itemId: '=',
-			content: '='
-		},
-		link: function(scope){
-			// aliases
-			scope.fields = scope.content.fields;
-			// ids
-			scope.domId = `${scope.group.name}_${scope.itemId}`;
-			scope.colSelectorId = `${scope.domId}_columnSelector`;
-
-			// 1 - 10
-			// probably the most concise way of generating 1-10 computationally
-			// also vastly more complex than writing [1,2,3,4,5,6,7,9,10]
-			scope.floatingPointRange = [...(new Array(10)).keys()].map(i => i + 1);
-
-			// init the chosen cols for the table with the saved cols
-			scope.chosenCols = Array.from(scope.fields);
-
-			// save new cols choice
-			// due to how angular handles functions passed as props to an element,
-			// it must return a function that does the actual work.
-			scope.saveChosenCols = () => () => {
-				// we want to keep the selected columns in order,
-				// so try to add new columns in their correct indices,
-				// and rm cols by mutating the array
-
-				const newCols = scope.chosenCols
-				.filter(c => !scope.fields.includes(c));
-
-				const rmCols = scope.fields
-				.filter(f => !scope.chosenCols.includes(f) && f !== 'Experiment');
-
-				rmCols.forEach(rf => scope.fields.splice(scope.fields.indexOf(rf), 1));
-
-				newCols.forEach(nf => scope.fields.push(nf));
-			};
-
-			// toggle val for viewing CSV
-			scope.isViewingCSV = { val: false };
-			scope.toggleViewingCSV = () => {
-				scope.isViewingCSV.val = !scope.isViewingCSV.val;
-			};
-		},
-		template: `
-<div id="{{domId}}-heading" class="panel-heading" role="tab">
-	<h4 class="panel-title">
-		<a
-			class=''
-			role="button"
-			data-toggle="collapse"
-			data-parent="#{{domId}}-heading"
-			href="#collapse-{{domId}}"
-			aria-expanded="true"
-			aria-controls="collapse-{{domId}}">
-		</a>
-		<editable-label obj='content' field='itemName'></editable-label>
-		<div class='btn-group action-buttons'>
-			<span
-				ng-click='group.removeReportItem(itemId)'
-				class="btn btn-default btn-delete"
-				data-toggle="tooltip" data-placement="top" title="Delete Table">
-				<i class="fa fa-times fa-lg"></i>
-			</span>
-			<span
-				class='btn btn-default drag-handle'
-				data-toggle='tooltip' data-placement='top' title='Drag to re-order item'>
-				<i class='fa fa-arrows fa-lg'></i>
-			</span>
-		</div>
-		<div class="btn-group" role="group" role='tab'>
-			<div class="btn-group" role="group"
-				group-table-field-selector
-				id='colSelectorId'
-				group='group'
-				cols-selected='chosenCols'
-				button-action='saveChosenCols()'
-				title="Choose Columns"
-				button-text="Save Column Choices"
-				>
-			</div>
-			<div class='btn-group' role='group'>
-				<button class='btn btn-default' id="{{domId}}-precision" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-					Float Precision: {{ content.precision }}
-					<span class="caret"></span>
-				</button>
-				<ul class="dropdown-menu" aria-labelledby="{{domId}}-precision">
-					<li ng-click='content.precision = i' ng-repeat='i in floatingPointRange'><a>{{ i }}</a></li>
-				</ul>
-			</div>
-			<button class='btn btn-default' ng-click='toggleViewingCSV()'>
-				Toggle CSV View
-			</button>
-		</div>
-	</h4>
-</div>
-<div id="collapse-{{domId}}"
-	class="panel-collapse collapse in"
-	role="tabpanel"
-	aria-labelledby="{{domId}}-heading">
-	<div table-item class='panel-body' group='group' content='content' is-viewing-csv='isViewingCSV'></div>
-</div>
-`
-	};
-}]);
diff --git a/beat/web/reports/static/reports/app/directives/editableLabel.js b/beat/web/reports/static/reports/app/directives/editableLabel.js
index 5fe652eeae078468b931cd32a9df2d864b9ebdf2..01f9eecd3ef2664dba66a86995ad93f1a916b858 100644
--- a/beat/web/reports/static/reports/app/directives/editableLabel.js
+++ b/beat/web/reports/static/reports/app/directives/editableLabel.js
@@ -25,17 +25,20 @@
  * Desc:
  * 	represents an editable label (group name, report item name, etc.)
  */
-angular.module('reportApp').directive("editableLabel", [function(){
+angular.module('reportApp').directive("editableLabel", ['UrlService', function(UrlService){
 	return {
 		scope: {
 			obj: '=',
 			field: '@'
 		},
 		link: function(scope){
+			scope.isViewmode = UrlService.isViewmode;
 		},
 		template: `
 <span style='display: inline-block;'>
+	<span ng-if='isViewmode()'>{{ obj[field] }}</span>
 	<input
+		ng-if='!isViewmode()'
 		style='display: inline;'
 		required
 		type='text'
diff --git a/beat/web/reports/static/reports/app/directives/view/layout.js b/beat/web/reports/static/reports/app/directives/layout.js
similarity index 70%
rename from beat/web/reports/static/reports/app/directives/view/layout.js
rename to beat/web/reports/static/reports/app/directives/layout.js
index ba578adaffb082ef97f7621aae92d92262aa802e..f32d9a5271abcba1d23fe006a55a0ed0e1b934b9 100644
--- a/beat/web/reports/static/reports/app/directives/view/layout.js
+++ b/beat/web/reports/static/reports/app/directives/layout.js
@@ -27,25 +27,37 @@
  * 	generating group panels using the GroupsService data,
  * 	and holding the menu for adding a group
  */
-angular.module('reportApp').directive("groupsLayout", ['GroupsService', function(GroupsService){
+angular.module('reportApp').directive("groupsLayout", ['GroupsService', 'UrlService', function(GroupsService, UrlService){
 	return {
 		scope: {
 		},
 		link: function(scope, el, attr){
 			scope.GroupsService = GroupsService;
-			console.log(scope.GroupsService.groups);
+			scope.isViewmode = UrlService.isViewmode;
+
+			// drag handle CSS selector
+			scope.sortableOptions = {
+				handle: '.dragGroup .drag-handle'
+			};
 		},
 		template: `
 <experiments-table></experiments-table>
+<div ng-if='!isViewmode()' group-add-group-menu class='panel'></div>
 
 <div id='groupsLayout' class='panel-group'>
-	<div ng-if='GroupsService.groups.length > 1' group-panel-content
-		class='panel panel-default'
-		ng-repeat='group in GroupsService.groups'
-		group='group'
-		>
+	<div ng-if='!isViewmode() || GroupsService.groups.length > 1'
+		ui-sortable='sortableOptions'
+		ng-model='GroupsService.groups'
+		id='groupsLayout'
+		class='panel-group'>
+		<div
+			group-panel-content
+			style='margin-bottom: 5px;'
+			ng-repeat='group in GroupsService.groups'
+			group='group'>
+		</div>
 	</div>
-	<div ng-if='GroupsService.groups.length == 1'>
+	<div ng-if='isViewmode() && GroupsService.groups.length == 1'>
 		<group-panel-items group='GroupsService.groups[0]'></group-panel-items>
 	</div>
 </div>
diff --git a/beat/web/reports/static/reports/app/directives/view/tableItem.js b/beat/web/reports/static/reports/app/directives/panelContainer.js
similarity index 65%
rename from beat/web/reports/static/reports/app/directives/view/tableItem.js
rename to beat/web/reports/static/reports/app/directives/panelContainer.js
index c357900edca1ad7cc372e3057c8190dac3ad9c34..e971fe661eafad4686000287745cbda4d403c313 100644
--- a/beat/web/reports/static/reports/app/directives/view/tableItem.js
+++ b/beat/web/reports/static/reports/app/directives/panelContainer.js
@@ -21,27 +21,24 @@
  */
 
 /*
- * groupTableItem
+ * panelContainer
  * Desc:
- * 	displays a table report item and lets the user view the CSV
+ * 	displays a Bootstrap panel with the specified header & body content
  */
 angular.module('reportApp')
-.directive("groupTableItem", ['GroupsService', 'ExperimentsService', function(GroupsService, ExperimentsService){
+.directive("panelContainer", [function(){
 	return {
 		scope: {
-			group: '=',
-			itemId: '=',
-			content: '='
+			noPanelBody: '=',
+			domId: '='
 		},
-		link: function(scope){
-			// ids
-			scope.domId = `${scope.group.name}_${scope.itemId}`;
-
-			// toggle val for viewing CSV
-			scope.isViewingCSV = { val: false };
-			scope.toggleViewingCSV = () => {
-				scope.isViewingCSV.val = !scope.isViewingCSV.val;
-			};
+		restrict: 'A',
+		transclude: {
+			'headerSlot': 'header',
+			'contentSlot': 'content'
+		},
+		link: function(scope, el){
+			el.addClass('panel panel-default');
 		},
 		template: `
 <div id="{{domId}}-heading" class="panel-heading" role="tab">
@@ -54,22 +51,16 @@ angular.module('reportApp')
 			href="#collapse-{{domId}}"
 			aria-expanded="true"
 			aria-controls="collapse-{{domId}}">
-			{{ content.itemName }}&nbsp;
 		</a>
-
-		<div class="btn-group" role="group" role='tab'>
-			<button class='btn btn-primary' ng-click='toggleViewingCSV()'>
-				Toggle CSV View
-			</button>
-		</div>
+		<span ng-transclude='headerSlot'>Header Content</span>
 	</h4>
 </div>
 <div id="collapse-{{domId}}"
 	class="panel-collapse collapse in"
 	role="tabpanel"
 	aria-labelledby="{{domId}}-heading">
-	<div class='panel-body'>
-		<table-item group='group' content='content' is-viewing-csv='isViewingCSV'></table-item>
+	<div ng-class='{ "panel-body": !noPanelBody }' ng-transclude='contentSlot'>
+		Body Content with hasPanelBody: {{ hasPanelBody }}
 	</div>
 </div>
 `
diff --git a/beat/web/reports/static/reports/app/directives/edit/panelContent.js b/beat/web/reports/static/reports/app/directives/panelContent.js
similarity index 77%
rename from beat/web/reports/static/reports/app/directives/edit/panelContent.js
rename to beat/web/reports/static/reports/app/directives/panelContent.js
index 881b0161e833c453009d239ae06d6baacd70635b..9f6319b036b2e8e4c739cc3502f38410a8d73bed 100644
--- a/beat/web/reports/static/reports/app/directives/edit/panelContent.js
+++ b/beat/web/reports/static/reports/app/directives/panelContent.js
@@ -29,21 +29,18 @@
  * 	- panel header also contains the buttons to add report items
  * 	- two sub-panels are added: the experiments list, and the items list
  */
-angular.module('reportApp').directive("groupPanelContent", ['GroupsService', function(GroupsService){
+angular.module('reportApp').directive("groupPanelContent", ['GroupsService', 'UrlService', function(GroupsService, UrlService){
 	return {
 		scope: {
 			group: '='
 		},
 		link: function(scope){
 			scope.deleteGroup = GroupsService.deleteGroup;
-			scope.editingGroupName = false;
-			scope.toggleEditingGroupName = () => {
-				scope.editingGroupName = !scope.editingGroupName;
-			};
+			scope.isViewmode = UrlService.isViewmode;
 		},
 		template: `
-<div id="{{group.name}}-heading" class="panel-heading" role="tab">
-	<h4 class="panel-title">
+<div panel-container class='dragGroup' domId='group.name'>
+	<header>
 		<a
 			class=''
 			role="button"
@@ -54,31 +51,22 @@ angular.module('reportApp').directive("groupPanelContent", ['GroupsService', fun
 			aria-controls="collapse-{{group.name}}">
 		</a>
 		<editable-label obj='group' field='_name'></editable-label>
-		<div class='btn-group action-buttons'>
+		<div ng-if='!isViewmode()' class='btn-group action-buttons'>
 			<span
 				ng-click='deleteGroup(group.name)'
 				class="btn btn-default btn-delete"
 				data-toggle="tooltip" data-placement="top" title="Delete Group">
 				<i class="fa fa-times fa-lg"></i>
 			</span>
-			<span
-				class='btn btn-default drag-handle'
-				data-toggle='tooltip' data-placement='top' title='Drag to re-order group'>
-				<i class='fa fa-arrows fa-lg'></i>
-			</span>
+			<span drag-handle handle-helper-class='dragGroup'></span>
 		</div>
-		<div
+		<div ng-if='!isViewmode()'
 			group-add-items-menu
 			class="btn-group" role="group" role='tab'
 			group='group'>
 		</div>
-	</h4>
-</div>
-<div id="collapse-{{group.name}}"
-	class="panel-collapse collapse in"
-	role="tabpanel"
-	aria-labelledby="{{group.name}}-heading">
-	<div class="panel-body panel-group">
+	</header>
+	<content>
 		<div group-panel-experiments group='group' class='panel panel-default'></div>
 		<div
 			style='margin-top: 5px;'
@@ -87,7 +75,7 @@ angular.module('reportApp').directive("groupPanelContent", ['GroupsService', fun
 			group='group'
 			>
 		</div>
-	</div>
+	</content>
 </div>
 `
 	};
diff --git a/beat/web/reports/static/reports/app/directives/edit/panelItems.js b/beat/web/reports/static/reports/app/directives/panelItems.js
similarity index 72%
rename from beat/web/reports/static/reports/app/directives/edit/panelItems.js
rename to beat/web/reports/static/reports/app/directives/panelItems.js
index 0c73d467701c8372a7cd9881484d6426f211f8bd..358862dd6011b54b7979a23511a80f8491d41a08 100644
--- a/beat/web/reports/static/reports/app/directives/edit/panelItems.js
+++ b/beat/web/reports/static/reports/app/directives/panelItems.js
@@ -36,18 +36,33 @@ angular.module('reportApp').directive("groupPanelItems", [function(){
 			// TODO: this needs to be changed each time the HTML hierarchy changes.
 			// Make it hierarchy-independent
 			scope.sortableOptions = {
-				handle: '.panel-heading > .panel-title > .action-buttons > .drag-handle'
+				handle: '.dragItem .drag-handle'
 			};
 		},
 		template: `
-<div ui-sortable='sortableOptions' ng-model='group._reportItems'>
-	<div
-		group-item-container
-		ng-repeat='item in group.reportItems'
-		group='group'
-		report-item='item'
-		style='margin-bottom: 5px;'
-		>
+<div ui-sortable='sortableOptions' ng-model='group._reportItems' class='panel-group'>
+	<div ng-repeat='item in group.reportItems'>
+		<div group-table-item
+			style='margin-bottom: 5px;'
+			ng-if="item.id.includes('table')"
+			group='group'
+			item-id='item.id'
+			content='item.content'>
+		</div>
+		<div group-plot-item
+			style='margin-bottom: 5px;'
+			ng-if="item.id.includes('plot')"
+			group='group'
+			item-id='item.id'
+			content='item.content'>
+		</div>
+		<div group-text-item
+			style='margin-bottom: 5px;'
+			ng-if="item.id.includes('text')"
+			group='group'
+			report-item='item'
+			item-id='item.id'>
+		</div>
 	</div>
 </div>
 `
diff --git a/beat/web/reports/static/reports/app/directives/view/plotItem.js b/beat/web/reports/static/reports/app/directives/plotItem.js
similarity index 88%
rename from beat/web/reports/static/reports/app/directives/view/plotItem.js
rename to beat/web/reports/static/reports/app/directives/plotItem.js
index 41766da6b5bba88e512083e2baaf70b57e90fcdf..4c7655b1ba4429d745673cf24f0112a195eca054 100644
--- a/beat/web/reports/static/reports/app/directives/view/plotItem.js
+++ b/beat/web/reports/static/reports/app/directives/plotItem.js
@@ -26,7 +26,7 @@
  * 	displays a plot report item (basically a container for the plots code to insert into)
  */
 angular.module('reportApp')
-.directive("groupPlotItem", ['ExperimentsService', 'PlotService', '$timeout', function(ExperimentsService, PlotService, $timeout){
+.directive("groupPlotItem", ['ExperimentsService', 'PlotService', '$timeout', 'UrlService', function(ExperimentsService, PlotService, $timeout, UrlService){
 	return {
 		scope: {
 			group: '=',
@@ -87,20 +87,21 @@ angular.module('reportApp')
 			scope.getPlotter = () => PlotService.getPlotter(scope.content);
 			scope.getPossiblePlotters = () => PlotService.plotters.filter(p => p.dataformat === scope.content.type).map(p => p.name);
 			scope.getPossibleConfigs = () => PlotService.getPossibleConfigs(scope.getPlotter());
+			scope.isViewmode = UrlService.isViewmode;
 		},
 		template: `
-<div id="{{domId}}-heading" class="panel-heading" role="tab">
-	<h4 class="panel-title">
-		<a
-			class=''
-			role="button"
-			data-toggle="collapse"
-			data-parent="#{{domId}}-heading"
-			href="#collapse-{{domId}}"
-			aria-expanded="true"
-			aria-controls="collapse-{{domId}}">
-		</a>
-		{{ content.itemName }}
+<div panel-container dom-id='domId'>
+	<header>
+		<editable-label obj='content' field='itemName'></editable-label>
+		<div ng-if='!isViewmode()' class='btn-group action-buttons'>
+			<span
+				ng-click='group.removeReportItem(itemId)'
+				class="btn btn-default btn-delete"
+				data-toggle="tooltip" data-placement="top" title="Delete Plot">
+				<i class="fa fa-times fa-lg"></i>
+			</span>
+			<span drag-handle handle-helper-class='dragItem'></span>
+		</div>
 		<div class='btn-group' role='group'>
 			<download-link
 				class='btn-group'
@@ -143,15 +144,10 @@ angular.module('reportApp')
 				</ul>
 			</div>
 		</div>
-	</h4>
-</div>
-<div id="collapse-{{domId}}"
-	class="panel-collapse collapse in"
-	role="tabpanel"
-	aria-labelledby="{{domId}}-heading">
-	<div class='panel-body'>
+	</header>
+	<content>
 		<div id='{{ renderDivId }}'></div>
-	</div>
+	</content>
 </div>
 `
 	};
diff --git a/beat/web/reports/static/reports/app/directives/tableItem.js b/beat/web/reports/static/reports/app/directives/tableItem.js
index 343a7b8b8f79d4fbc2a9477c64a594fd94e83ca9..e157b69f6039b476303bbedc5baa28ea4c27317b 100644
--- a/beat/web/reports/static/reports/app/directives/tableItem.js
+++ b/beat/web/reports/static/reports/app/directives/tableItem.js
@@ -23,17 +23,57 @@
 /*
  * tableItem
  * Desc:
- * 	displays a table report item
+ * 	displays a table report item and lets the user
+ * 	manage this table's selected cols and float precision
  */
 angular.module('reportApp')
-.directive("tableItem", ['GroupsService', 'ExperimentsService', 'UrlService', function(GroupsService, ExperimentsService, UrlService){
+.directive("groupTableItem", ['GroupsService', 'ExperimentsService', 'UrlService', function(GroupsService, ExperimentsService, UrlService){
 	return {
 		scope: {
 			group: '=',
-			content: '=',
-			isViewingCsv: '='
+			itemId: '=',
+			content: '='
 		},
 		link: function(scope){
+			// aliases
+			scope.fields = scope.content.fields;
+			// ids
+			scope.domId = `${scope.group.name}_${scope.itemId}`;
+			scope.colSelectorId = `${scope.domId}_columnSelector`;
+
+			// 1 - 10
+			// probably the most concise way of generating 1-10 computationally
+			// also vastly more complex than writing [1,2,3,4,5,6,7,9,10]
+			scope.floatingPointRange = [...(new Array(10)).keys()].map(i => i + 1);
+
+			// init the chosen cols for the table with the saved cols
+			scope.chosenCols = Array.from(scope.fields);
+
+			// save new cols choice
+			// due to how angular handles functions passed as props to an element,
+			// it must return a function that does the actual work.
+			scope.saveChosenCols = () => () => {
+				// we want to keep the selected columns in order,
+				// so try to add new columns in their correct indices,
+				// and rm cols by mutating the array
+
+				const newCols = scope.chosenCols
+				.filter(c => !scope.fields.includes(c));
+
+				const rmCols = scope.fields
+				.filter(f => !scope.chosenCols.includes(f) && f !== 'Experiment');
+
+				rmCols.forEach(rf => scope.fields.splice(scope.fields.indexOf(rf), 1));
+
+				newCols.forEach(nf => scope.fields.push(nf));
+			};
+
+			// toggle val for viewing CSV
+			scope.isViewingCSV = { val: false };
+			scope.toggleViewingCSV = () => {
+				scope.isViewingCSV.val = !scope.isViewingCSV.val;
+			};
+
 			// aliases
 			scope.fields = scope.content.fields;
 
@@ -165,44 +205,88 @@ angular.module('reportApp')
 			scope.sortableOptions = {
 				items: `th:not(:first-child)`
 			};
+
+			scope.isViewmode = UrlService.isViewmode;
 		},
 		template: `
-<div ng-if='isViewingCsv.val'>
-	<pre>{{ getCSV() }}</pre>
-</div>
-<div ng-if='!isViewingCsv.val' style='height: 100%; overflow-x: auto;'>
-	<table class="table table-striped table-hover">
-		<thead>
-			<tr ui-sortable='sortableOptions' ng-model='fields'>
-				<th ng-repeat='field in fields'>
-					<span
-						ng-if="sortField.val == field"
-						class='glyphicon'
-						ng-class="{
-							'glyphicon-chevron-up': sortField.isReversed,
-							'glyphicon-chevron-down': !sortField.isReversed
-							}"
-						>
-					</span>
-					<a role='button' ng-click='setSortField(field)'>
-						{{ field }} <i>({{ getFieldType(field) }})</i>
-					</a>
-				</th>
-			</tr>
-		</thead>
-		<tbody>
-			<tr ng-repeat="exp in group.experiments | orderBy:sortFunc:sortField.isReversed">
-				<td ng-repeat='field in fields'>
-					<a ng-if='$index == 0 && getExperimentUrl(exp)' href='{{ getExperimentUrl(exp) }}'>
-						{{ getFieldVal(exp, field) }}
-					</a>
-					<span ng-if='!$index == 0 || !getExperimentUrl(exp)'>
-						{{ getFieldVal(exp, field) }}
-					</span>
-				</td>
-			</tr>
-		</tbody>
-	</table>
+<div panel-container dom-id='domId'>
+	<header>
+		<editable-label obj='content' field='itemName'></editable-label>
+		<div ng-if='!isViewmode()' class='btn-group action-buttons'>
+			<span
+				ng-click='group.removeReportItem(itemId)'
+				class="btn btn-default btn-delete"
+				data-toggle="tooltip" data-placement="top" title="Delete Table">
+				<i class="fa fa-times fa-lg"></i>
+			</span>
+			<span drag-handle handle-helper-class='dragItem'></span>
+		</div>
+		<div class="btn-group" role="group" role='tab'>
+			<div ng-if='!isViewmode()' class="btn-group" role="group"
+				group-table-field-selector
+				id='colSelectorId'
+				group='group'
+				cols-selected='chosenCols'
+				button-action='saveChosenCols()'
+				title="Choose Columns"
+				button-text="Save Column Choices"
+				>
+			</div>
+			<div class='btn-group' role='group'>
+				<button class='btn btn-default' id="{{domId}}-precision" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+					Float Precision: {{ content.precision }}
+					<span class="caret"></span>
+				</button>
+				<ul class="dropdown-menu" aria-labelledby="{{domId}}-precision">
+					<li ng-click='content.precision = i' ng-repeat='i in floatingPointRange'><a>{{ i }}</a></li>
+				</ul>
+			</div>
+			<button class='btn btn-default' ng-click='toggleViewingCSV()'>
+				Toggle CSV View
+			</button>
+		</div>
+	</header>
+	<content>
+		<div class='panel-body'>
+			<div ng-if='isViewingCSV.val'>
+				<pre>{{ getCSV() }}</pre>
+			</div>
+			<div ng-if='!isViewingCSV.val' style='height: 100%; overflow-x: auto;'>
+				<table class="table table-striped table-hover">
+					<thead>
+						<tr ui-sortable='sortableOptions' ng-model='fields'>
+							<th ng-repeat='field in fields'>
+								<span
+									ng-if="sortField.val == field"
+									class='glyphicon'
+									ng-class="{
+										'glyphicon-chevron-up': sortField.isReversed,
+										'glyphicon-chevron-down': !sortField.isReversed
+										}"
+									>
+								</span>
+								<a role='button' ng-click='setSortField(field)'>
+									{{ field }} <i>({{ getFieldType(field) }})</i>
+								</a>
+							</th>
+						</tr>
+					</thead>
+					<tbody>
+						<tr ng-repeat="exp in group.experiments | orderBy:sortFunc:sortField.isReversed">
+							<td ng-repeat='field in fields'>
+								<a ng-if='$index == 0 && getExperimentUrl(exp)' href='{{ getExperimentUrl(exp) }}'>
+									{{ getFieldVal(exp, field) }}
+								</a>
+								<span ng-if='!$index == 0 || !getExperimentUrl(exp)'>
+									{{ getFieldVal(exp, field) }}
+								</span>
+							</td>
+						</tr>
+					</tbody>
+				</table>
+			</div>
+		</div>
+	</content>
 </div>
 `
 	};
diff --git a/beat/web/reports/static/reports/app/directives/edit/textItem.js b/beat/web/reports/static/reports/app/directives/textItem.js
similarity index 77%
rename from beat/web/reports/static/reports/app/directives/edit/textItem.js
rename to beat/web/reports/static/reports/app/directives/textItem.js
index c7af4cd4f09dde6e2ad51492a6bae16bfefa4798..72a2c9967bb523a1fdefbad820dc022c0748a2b5 100644
--- a/beat/web/reports/static/reports/app/directives/edit/textItem.js
+++ b/beat/web/reports/static/reports/app/directives/textItem.js
@@ -43,9 +43,12 @@ angular.module('reportApp')
 			scope.item = scope.reportItem;
 			scope.domId = `${scope.group.name}_${scope.item.id}`;
 
+			scope.isViewmode = UrlService.isViewmode;
+
 			// codemirror options
 			scope.uicmOptions = {
 				mode: 'rst',
+				readOnly: scope.isViewmode()
 			};
 
 			// handle compiling content
@@ -53,7 +56,8 @@ angular.module('reportApp')
 			scope.compiledContent = { val: '' };
 			scope.compileContent = () => {
 				let url = UrlService.getCompileRstUrl();
-				let content = scope.item.content.text;
+				let content = !scope.isViewmode() ? scope.item.content.text :
+					`${scope.group.name}|${scope.group.reportItems.indexOf(scope.reportItem)}`;
 
 				return reportFactory.compileRST(url, content)
 				.then(data => {
@@ -63,61 +67,46 @@ angular.module('reportApp')
 			};
 
 			// handle edit/save/cancel buttons
-			scope.isEditing = { val: false };
+			scope.isSrcMode = { val: false };
 			// when editing, use a buffer to hold the raw text
 			scope.unsavedContent = { val: `${scope.item.content.text}` };
 			// save the buffer to the actual report item content
 			scope.saveAction = () => {
 				scope.item.content.text = scope.unsavedContent.val;
 				scope.compileContent();
-				scope.isEditing.val = false;
+				scope.isSrcMode.val = false;
 			};
 			// discard buffer and use report item content
 			scope.cancelAction = () => {
 				scope.unsavedContent.val = `${scope.item.content.text}`;
-				scope.isEditing.val = false;
+				scope.isSrcMode.val = false;
 			};
 
 			// compile the content when loaded
 			scope.compileContent();
 		},
 		template: `
-<div id="{{domId}}-heading" class="panel-heading" role="tab">
-	<h4 class="panel-title">
-		<a
-			class=''
-			role="button"
-			data-toggle="collapse"
-			data-parent="#{{domId}}-heading"
-			href="#collapse-{{domId}}"
-			aria-expanded="true"
-			aria-controls="collapse-{{domId}}">
-		</a>
+<div panel-container dom-id='domId'>
+	<header>
 		<editable-label obj='item.content' field='itemName'></editable-label>
-		<div class='btn-group action-buttons'>
+		<div ng-if='!isViewmode()' class='btn-group action-buttons'>
 			<span
 				ng-click='group.removeReportItem(item.id)'
 				class="btn btn-default btn-delete"
 				data-toggle="tooltip" data-placement="top" title="Delete Text Block">
 				<i class="fa fa-times fa-lg"></i>
 			</span>
-			<span
-				class='btn btn-default drag-handle'
-				data-toggle='tooltip' data-placement='top' title='Drag to re-order item'>
-				<i class='fa fa-arrows fa-lg'></i>
-			</span>
+			<span drag-handle handle-helper-class='dragItem'></span>
 		</div>
-	</h4>
-</div>
-<div id="collapse-{{domId}}"
-	class="panel-collapse collapse in"
-	role="tabpanel"
-	aria-labelledby="{{domId}}-heading">
-	<div class='panel-body'>
-		<div class='row'>
+		<button ng-if='isViewmode()' class='btn btn-primary' ng-click='isSrcMode.val = !isSrcMode.val'>
+			Toggle Source View
+		</button>
+	</header>
+	<content>
+		<div ng-if='!isViewmode()' class='row'>
 			<div class='col-sm-10'>
-				<div ng-if='!isEditing.val' ng-bind-html='trustAsHtml(compiledContent.val)'></div>
-				<div ng-if='isEditing.val'>
+				<div ng-if='!isSrcMode.val' ng-bind-html='trustAsHtml(compiledContent.val)'></div>
+				<div ng-if='isSrcMode.val'>
 					<ui-codemirror ng-model='unsavedContent.val' ui-codemirror-opts='uicmOptions'></ui-codemirror>
 					<p class='help-block'>
 					Describe the object thoroughly using <a href="http://docutils.sourceforge.net/rst.html">reStructuredText mark-up</a><br><i class="fa fa-thumbs-up"></i> The ruler at 80 columns indicate suggested <a href="https://en.wikipedia.org/wiki/POSIX">POSIX line breaks</a> (for readability).<br><i class="fa fa-thumbs-up"></i> The editor will automatically enlarge to accomodate the entirety of your input<br><i class="fa fa-thumbs-up"></i> Use <a href="http://codemirror.net/doc/manual.html#commands">keyboard shortcuts</a> for search/replace and faster editing. For example, use Ctrl-F (PC) or Cmd-F (Mac) to search through this box
@@ -127,19 +116,19 @@ angular.module('reportApp')
 			<div class='col-sm-2'>
 				<div class="pull-right action-buttons">
 					<a
-						ng-if='!isEditing.val'
-						ng-click='isEditing.val = !isEditing.val'
+						ng-if='!isSrcMode.val'
+						ng-click='isSrcMode.val = !isSrcMode.val'
 						class="btn btn-primary btn-sm">
 						<i class="fa fa-edit fa-lg"></i> Edit
 					</a>
 					<a
-						ng-if='isEditing.val'
+						ng-if='isSrcMode.val'
 						ng-click='cancelAction()'
 						class="btn btn-danger btn-sm">
 						<i class="fa fa-times fa-lg"></i> Cancel
 					</a>
 					<a
-						ng-if='isEditing.val'
+						ng-if='isSrcMode.val'
 						ng-click='saveAction()'
 						class="btn btn-success btn-sm">
 						<i class="fa fa-save fa-lg"></i> Save
@@ -147,7 +136,16 @@ angular.module('reportApp')
 				</div>
 			</div>
 		</div>
-	</div>
+		<div ng-if='isViewmode()' class='row'>
+			<div class='col-sm-12'>
+				<div ng-if='!isSrcMode.val' ng-bind-html='trustAsHtml(compiledContent.val)'></div>
+				<div ng-if='isSrcMode.val'>
+					<i>Readonly view</i>
+					<ui-codemirror ng-model='item.content.text' ui-codemirror-opts='uicmOptions'></ui-codemirror>
+				</div>
+			</div>
+		</div>
+	</content>
 </div>
 `
 	};
diff --git a/beat/web/reports/static/reports/app/directives/view/itemContainer.js b/beat/web/reports/static/reports/app/directives/view/itemContainer.js
deleted file mode 100644
index ad80156fc0bec09e50026c2cfb6a7b73fee49cd6..0000000000000000000000000000000000000000
--- a/beat/web/reports/static/reports/app/directives/view/itemContainer.js
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/
- * Contact: beat.support@idiap.ch
- *
- * This file is part of the beat.web module of the BEAT platform.
- *
- * Commercial License Usage
- * Licensees holding valid commercial BEAT licenses may use this file in
- * accordance with the terms contained in a written agreement between you
- * and Idiap. For further information contact tto@idiap.ch
- *
- * Alternatively, this file may be used under the terms of the GNU Affero
- * Public License version 3 as published by the Free Software and appearing
- * in the file LICENSE.AGPL included in the packaging of this file.
- * The BEAT platform is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE.
- *
- * You should have received a copy of the GNU Affero Public License along
- * with the BEAT platform. If not, see http://www.gnu.org/licenses/.
- */
-
-/*
- * groupItemContainer
- * Desc:
- * 	container for an item in the group.
- * 	depending of the type of the item (found in the item's id)
- * 	it creates a table, plot, or text item
- */
-angular.module('reportApp')
-.directive("groupItemContainer", [function(){
-	return {
-		scope: {
-			group: '=',
-			reportItem: '='
-		},
-		link: function(scope){
-			scope.item = scope.reportItem;
-			// report-wide-unique prefix for the item to use
-			scope.domId = `${scope.group.name}_${scope.id}`;
-		},
-		template: `
-<div
-	group-table-item
-	ng-if="item.id.includes('table')"
-	class='panel panel-default'
-	group='group'
-	item-id='item.id'
-	content='item.content'
-	>
-</div>
-<div
-	group-plot-item
-	ng-if="item.id.includes('plot')"
-	class='panel panel-default'
-	group='group'
-	item-id='item.id'
-	content='item.content'
-	>
-</div>
-<div
-	group-text-item
-	ng-if="item.id.includes('text')"
-	class='panel panel-default'
-	group='group'
-	report-item='item'
-	item-id='item.id'
-	>
-</div>
-`
-	};
-}]);
diff --git a/beat/web/reports/static/reports/app/directives/view/panelContent.js b/beat/web/reports/static/reports/app/directives/view/panelContent.js
deleted file mode 100644
index e3a58c72ce198bd0d8d07a7bbf0a3b659f6ba734..0000000000000000000000000000000000000000
--- a/beat/web/reports/static/reports/app/directives/view/panelContent.js
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/
- * Contact: beat.support@idiap.ch
- *
- * This file is part of the beat.web module of the BEAT platform.
- *
- * Commercial License Usage
- * Licensees holding valid commercial BEAT licenses may use this file in
- * accordance with the terms contained in a written agreement between you
- * and Idiap. For further information contact tto@idiap.ch
- *
- * Alternatively, this file may be used under the terms of the GNU Affero
- * Public License version 3 as published by the Free Software and appearing
- * in the file LICENSE.AGPL included in the packaging of this file.
- * The BEAT platform is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE.
- *
- * You should have received a copy of the GNU Affero Public License along
- * with the BEAT platform. If not, see http://www.gnu.org/licenses/.
- */
-
-/*
- * groupPanelContent
- * Desc:
- * 	presents the group to the user in logical blocks:
- * 	- a panel holds content
- * 	- panel header contains an (editable) group name label
- * 	- panel header also contains the buttons to add report items
- * 	- two sub-panels are added: the experiments list, and the items list
- */
-angular.module('reportApp').directive("groupPanelContent", ['GroupsService', function(GroupsService){
-	return {
-		scope: {
-			group: '='
-		},
-		link: function(scope){
-		},
-		template: `
-<div id="{{group.name}}-heading" class="panel-heading" role="tab">
-	<h4 class="panel-title">
-		<a
-			class='collapsed'
-			role="button"
-			data-toggle="collapse"
-			data-parent="#{{group.name}}-heading"
-			href="#collapse-{{group.name}}"
-			aria-expanded="false"
-			aria-controls="collapse-{{group.name}}">
-			{{ group.name }}
-		</a>
-	</h4>
-</div>
-<div id="collapse-{{group.name}}"
-	class="panel-collapse collapse"
-	role="tabpanel"
-	aria-labelledby="{{group.name}}-heading">
-	<div class="panel-body panel-group">
-		<div group-panel-experiments class='panel panel-default' group='group'></div>
-		<group-panel-items group='group'></group-panel-items>
-	</div>
-</div>
-`
-	};
-}]);
-
diff --git a/beat/web/reports/static/reports/app/directives/view/textItem.js b/beat/web/reports/static/reports/app/directives/view/textItem.js
deleted file mode 100644
index e03d56a804b5f40089f2ec61d1603bf708698f7e..0000000000000000000000000000000000000000
--- a/beat/web/reports/static/reports/app/directives/view/textItem.js
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/
- * Contact: beat.support@idiap.ch
- *
- * This file is part of the beat.web module of the BEAT platform.
- *
- * Commercial License Usage
- * Licensees holding valid commercial BEAT licenses may use this file in
- * accordance with the terms contained in a written agreement between you
- * and Idiap. For further information contact tto@idiap.ch
- *
- * Alternatively, this file may be used under the terms of the GNU Affero
- * Public License version 3 as published by the Free Software and appearing
- * in the file LICENSE.AGPL included in the packaging of this file.
- * The BEAT platform is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE.
- *
- * You should have received a copy of the GNU Affero Public License along
- * with the BEAT platform. If not, see http://www.gnu.org/licenses/.
- */
-
-/*
- * groupTextItem
- * Desc:
- * 	displays a text report item:
- * 	- sends the raw RST to the server to be compiled,
- * 		and displays the returned HTML
- * 	- the user can edit the RST using codemirror
- * 	- the compile is async and doesnt require refreshing the page
- */
-angular.module('reportApp')
-.directive("groupTextItem", ['GroupsService', '$sce', 'UrlService', 'reportFactory', function(GroupsService, $sce, UrlService, reportFactory){
-	return {
-		scope: {
-			group: '=',
-			reportItem: '=',
-		},
-		link: function(scope){
-			// aliases
-			// angular requires that compiling raw html be sanitized
-			scope.trustAsHtml = $sce.trustAsHtml;
-			scope.item = scope.reportItem;
-			scope.domId = `${scope.group.name}_${scope.item.id}`;
-
-			// readonly codemirror options
-			scope.srccmOptions = {
-				mode: 'rst',
-				readOnly: true
-			};
-
-			// handle compiling content
-			// holds the last response from the server
-			scope.compiledContent = { val: '' };
-			scope.compileContent = () => {
-				let url = UrlService.getCompileRstUrl();
-				let mapToTextBlock = `${scope.group.name}|${scope.group.reportItems.indexOf(scope.reportItem)}`;
-
-				return reportFactory.compileRST(url, mapToTextBlock)
-				.then(data => {
-					// when compiled, save the raw html
-					scope.compiledContent.val = data.data.html_str;
-				});
-			};
-
-			// handle toggling between html & rst view
-			scope.isViewingSrc = { val: false };
-
-			// compile the content when loaded
-			scope.compileContent();
-		},
-		template: `
-<div id="{{domId}}-heading" class="panel-heading" role="tab">
-	<h4 class="panel-title">
-		<a
-			class=''
-			role="button"
-			data-toggle="collapse"
-			data-parent="#{{domId}}-heading"
-			href="#collapse-{{domId}}"
-			aria-expanded="true"
-			aria-controls="collapse-{{domId}}">
-			{{ item.content.itemName }}&nbsp;
-		</a>
-		<button class='btn btn-primary' ng-click='isViewingSrc.val = !isViewingSrc.val'>
-			Toggle Source View
-		</button>
-	</h4>
-</div>
-<div id="collapse-{{domId}}"
-	class="panel-collapse collapse in"
-	role="tabpanel"
-	aria-labelledby="{{domId}}-heading">
-	<div class='panel-body'>
-		<div class='row'>
-			<div class='col-sm-12'>
-				<div ng-if='!isViewingSrc.val' ng-bind-html='trustAsHtml(compiledContent.val)'></div>
-				<div ng-if='isViewingSrc.val'>
-					<i>Readonly view</i>
-					<ui-codemirror ng-model='item.content.text' ui-codemirror-opts='srccmOptions'></ui-codemirror>
-				</div>
-			</div>
-		</div>
-	</div>
-</div>
-`
-	};
-}]);
diff --git a/beat/web/reports/templates/reports/report.html b/beat/web/reports/templates/reports/report.html
index 35eb58321825cc82720b245c8e48893934a38175..bdaebcf58feb42cf19d3fab46a5e721222b725f5 100644
--- a/beat/web/reports/templates/reports/report.html
+++ b/beat/web/reports/templates/reports/report.html
@@ -95,7 +95,6 @@
     <!-- directives -->
 
     <!-- new -->
-    <script src="{% fingerprint "reports/app/directives/tableItem.js" %}" type="text/javascript" charset="utf-8"></script>
     <script src="{% fingerprint "reports/app/directives/downloadLink.js" %}" type="text/javascript" charset="utf-8"></script>
     <script src="{% fingerprint "reports/app/directives/bootstrapModal.js" %}" type="text/javascript" charset="utf-8"></script>
     <script src="{% fingerprint "reports/app/directives/publish.js" %}" type="text/javascript" charset="utf-8"></script>
@@ -106,28 +105,22 @@
     <script src="{% fingerprint "reports/app/directives/lastEdited.js" %}" type="text/javascript" charset="utf-8"></script>
     <script src="{% fingerprint "reports/app/directives/editableLabel.js" %}" type="text/javascript" charset="utf-8"></script>
     <script src="{% fingerprint "reports/app/directives/panelExperiments.js" %}" type="text/javascript" charset="utf-8"></script>
+    <script src="{% fingerprint "reports/app/directives/panelContainer.js" %}" type="text/javascript" charset="utf-8"></script>
+    <script src="{% fingerprint "reports/app/directives/dragHandle.js" %}" type="text/javascript" charset="utf-8"></script>
+    <script src="{% fingerprint "reports/app/directives/panelContent.js" %}" type="text/javascript" charset="utf-8"></script>
+    <script src="{% fingerprint "reports/app/directives/panelItems.js" %}" type="text/javascript" charset="utf-8"></script>
+    <script src="{% fingerprint "reports/app/directives/layout.js" %}" type="text/javascript" charset="utf-8"></script>
+    <script src="{% fingerprint "reports/app/directives/tableItem.js" %}" type="text/javascript" charset="utf-8"></script>
+    <script src="{% fingerprint "reports/app/directives/plotItem.js" %}" type="text/javascript" charset="utf-8"></script>
+    <script src="{% fingerprint "reports/app/directives/textItem.js" %}" type="text/javascript" charset="utf-8"></script>
 
     <!-- edit view -->
     {% if not report_number and report.get_status_display == 'Editable' and owner %}
     <script src="{% fingerprint "reports/app/directives/edit/addGroupMenu.js" %}" type="text/javascript" charset="utf-8"></script>
     <script src="{% fingerprint "reports/app/directives/edit/addItemsMenu.js" %}" type="text/javascript" charset="utf-8"></script>
-    <script src="{% fingerprint "reports/app/directives/edit/itemContainer.js" %}" type="text/javascript" charset="utf-8"></script>
-    <script src="{% fingerprint "reports/app/directives/edit/layout.js" %}" type="text/javascript" charset="utf-8"></script>
-    <script src="{% fingerprint "reports/app/directives/edit/panelContent.js" %}" type="text/javascript" charset="utf-8"></script>
-    <script src="{% fingerprint "reports/app/directives/edit/panelItems.js" %}" type="text/javascript" charset="utf-8"></script>
-    <script src="{% fingerprint "reports/app/directives/edit/plotItem.js" %}" type="text/javascript" charset="utf-8"></script>
     <script src="{% fingerprint "reports/app/directives/edit/tableFieldSelector.js" %}" type="text/javascript" charset="utf-8"></script>
-    <script src="{% fingerprint "reports/app/directives/edit/tableItem.js" %}" type="text/javascript" charset="utf-8"></script>
-    <script src="{% fingerprint "reports/app/directives/edit/textItem.js" %}" type="text/javascript" charset="utf-8"></script>
     {% else %}
     <!-- readonly view -->
-    <script src="{% fingerprint "reports/app/directives/view/layout.js" %}" type="text/javascript" charset="utf-8"></script>
-    <script src="{% fingerprint "reports/app/directives/view/panelContent.js" %}" type="text/javascript" charset="utf-8"></script>
-    <script src="{% fingerprint "reports/app/directives/view/panelItems.js" %}" type="text/javascript" charset="utf-8"></script>
-    <script src="{% fingerprint "reports/app/directives/view/itemContainer.js" %}" type="text/javascript" charset="utf-8"></script>
-    <script src="{% fingerprint "reports/app/directives/view/plotItem.js" %}" type="text/javascript" charset="utf-8"></script>
-    <script src="{% fingerprint "reports/app/directives/view/tableItem.js" %}" type="text/javascript" charset="utf-8"></script>
-    <script src="{% fingerprint "reports/app/directives/view/textItem.js" %}" type="text/javascript" charset="utf-8"></script>
     {% endif %}
 
     <script src="{% fingerprint "ui/js/smartselector.js" %}" type="text/javascript" charset="utf-8"></script>