diff --git a/beat/web/reports/static/reports/app/directives/groupAddItemsMenu.js b/beat/web/reports/static/reports/app/directives/groupAddItemsMenu.js
new file mode 100644
index 0000000000000000000000000000000000000000..6909978c75a79f0d4b8dbdff29cb5cfee4917363
--- /dev/null
+++ b/beat/web/reports/static/reports/app/directives/groupAddItemsMenu.js
@@ -0,0 +1,103 @@
+/*
+ * 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/.
+ */
+
+/*
+ * groupAddItemsMenu
+ * Desc:
+ * 	represents the menu for adding report items to a group
+ */
+angular.module('reportApp')
+.directive("groupAddItemsMenu", ['ExperimentsService', 'GroupsService', function(ExperimentsService, GroupsService){
+	return {
+		scope: {
+			group: '='
+		},
+		link: function(scope){
+			const getNextItemId = (type) => {
+				let count = Object.values(scope.group.reportItems)
+				.filter(v => v.id.includes(type))
+				.length
+				;
+				let nextId = `${type}_${count}`;
+				return nextId;
+			};
+
+			scope.plottables = ExperimentsService.plottables;
+
+			// use the data sub-obj to hold the selected fields
+			// to keep twoway binding
+			scope.newTable = { data: [] };
+
+			scope.addNewTable = function() {
+				return () => {
+					let id = getNextItemId('table');
+					scope.group.addReportItem(id, Array.from(scope.newTable.data));
+					scope.newTable.data.length = 0;
+				};
+			};
+
+			scope.addNewPlot = (content) => {
+				let id = getNextItemId('plot');
+				scope.group.addReportItem(id, content);
+			};
+
+			scope.addNewText = () => {
+				let id = getNextItemId('text');
+				scope.group.addReportItem(id, '');
+			};
+
+			scope.tableFieldSelectorId = `${scope.group.name}_newTableDropdownToggle`;
+		},
+		template: `
+<div class="btn-group" role="group">
+	<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+		Add Plot
+		<span class="caret"></span>
+	</button>
+	<ul class='dropdown-menu' ng-repeat='(expName, plots) in plottables' ng-if='expName === group.experiments[0]'>
+		<li ng-repeat='plot in plots'>
+			<a href='#' ng-click='addNewPlot(plot.type)'>{{ plot.type }}</a>
+		</li>
+	</ul>
+</div>
+<div class="btn-group" role="group"
+	group-table-field-selector
+	id='tableFieldSelectorId'
+	group='group'
+	cols-selected='newTable.data'
+	button-action='addNewTable()'
+	title="Add Table"
+	button-text="Add Table"
+	>
+</div>
+<div class="btn-group" role="group">
+	<button
+		ng-click='addNewText()'
+		type="button"
+		class="btn btn-default"
+		>
+		Add Text Block
+	</button>
+</div>
+`
+	}
+}]);
diff --git a/beat/web/reports/static/reports/app/directives/groupItemContainer.js b/beat/web/reports/static/reports/app/directives/groupItemContainer.js
new file mode 100644
index 0000000000000000000000000000000000000000..012be32a75a0ee58a3f5bcc6a577246ca2437083
--- /dev/null
+++ b/beat/web/reports/static/reports/app/directives/groupItemContainer.js
@@ -0,0 +1,68 @@
+/*
+ * 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
+ */
+angular.module('reportApp')
+.directive("groupItemContainer", [function(){
+	return {
+		scope: {
+			group: '=',
+			reportItem: '='
+		},
+		link: function(scope){
+			scope.item = scope.reportItem;
+			scope.domId = `${scope.group.name}_${scope.id}`;
+		},
+		template: `
+<div
+	group-table-item
+	ng-if="item.id.includes('table')"
+	class='panel panel-default'
+	group='group'
+	id='item.id'
+	fields='item.content'
+	>
+</div>
+<div
+	group-plot-item
+	ng-if="item.id.includes('plot')"
+	class='panel panel-default'
+	group='group'
+	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'
+	>
+</div>
+`
+	}
+}]);
diff --git a/beat/web/reports/static/reports/app/directives/groupPanelAliases.js b/beat/web/reports/static/reports/app/directives/groupPanelAliases.js
new file mode 100644
index 0000000000000000000000000000000000000000..9e663b962833ec99088ccbd11aa2a2d35c8b9f69
--- /dev/null
+++ b/beat/web/reports/static/reports/app/directives/groupPanelAliases.js
@@ -0,0 +1,71 @@
+/*
+ * 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/.
+ */
+
+/*
+ * groupPanelAliases
+ * Desc:
+ * 	displays & manages the aliases of the group
+ */
+angular.module('reportApp').directive("groupPanelAliases", [function(){
+	return {
+		scope: {
+			group: '='
+		},
+		link: function(scope){
+		},
+		template: `
+<div id="{{group.name}}-aliaslist-heading" class="panel-heading" role="tab">
+	<h4 class="panel-title">
+		<a
+			class=''
+			role="button"
+			data-toggle="collapse"
+			data-parent="#{{group.name}}-aliaslist-heading"
+			href="#collapse-{{group.name}}-aliaslist"
+			aria-expanded="true"
+			aria-controls="collapse-{{group.name}}-aliaslist">
+			Aliases
+		</a>
+	</h4>
+</div>
+<div id="collapse-{{group.name}}-aliaslist"
+	class="panel-collapse collapse in"
+	role="tabpanel"
+	aria-labelledby="{{group.name}}-aliaslist-heading">
+	<table class="table table-striped table-hover">
+		<thead>
+			<tr>
+				<th>Experiment</th>
+				<th>Alias</th>
+			</tr>
+		</thead>
+		<tbody>
+			<tr ng-repeat='(expName, alias) in group.aliases'>
+				<td>{{ expName }}</td>
+				<td><input ng-model='group.aliases[expName]'></input></td>
+			</tr>
+		</tbody>
+	</table>
+</div>
+`
+	}
+}]);
diff --git a/beat/web/reports/static/reports/app/directives/groupPanelContent.js b/beat/web/reports/static/reports/app/directives/groupPanelContent.js
new file mode 100644
index 0000000000000000000000000000000000000000..f80c5ddd55374227772aabd25b1ec0f3ccc9ca7c
--- /dev/null
+++ b/beat/web/reports/static/reports/app/directives/groupPanelContent.js
@@ -0,0 +1,68 @@
+/*
+ * 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:
+ * 	lays out the content of a group in a panel
+ */
+angular.module('reportApp').directive("groupPanelContent", ['GroupsService', function(GroupsService){
+	return {
+		scope: {
+			group: '='
+		},
+		link: function(scope){
+			scope.deleteGroup = GroupsService.deleteGroup;
+		},
+		template: `
+<div id="{{group.name}}-heading" class="panel-heading" role="tab">
+	<h4 class="panel-title">
+		<a
+			class=''
+			role="button"
+			data-toggle="collapse"
+			data-parent="#{{group.name}}-heading"
+			href="#collapse-{{group.name}}"
+			aria-expanded="true"
+			aria-controls="collapse-{{group.name}}">
+			{{ group.name }}
+		</a>
+		<div class='btn-group'>
+			<button class='btn btn-danger' ng-click='deleteGroup(group.name)'>Delete Group</button>
+		</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">
+		<i ng-if='group.analyzer.length > 0'>Analyzer: {{ group.analyzer }}</i>
+		<div group-panel-experiments group='group' class='panel panel-default'></div>
+		<div ng-if='group.experiments.length > 0' group-panel-aliases group='group' class='panel panel-default'></div>
+		<div ng-if='group.experiments.length > 0' group-panel-items group='group' class='panel panel-default'></div>
+	</div>
+</div>
+`
+	}
+}]);
+
diff --git a/beat/web/reports/static/reports/app/directives/groupPanelExperiments.js b/beat/web/reports/static/reports/app/directives/groupPanelExperiments.js
new file mode 100644
index 0000000000000000000000000000000000000000..83c0b5e4037b8c46a27844a6c000553b3a0e6474
--- /dev/null
+++ b/beat/web/reports/static/reports/app/directives/groupPanelExperiments.js
@@ -0,0 +1,90 @@
+/*
+ * 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/.
+ */
+
+/*
+ * groupPanelExperiments
+ * Desc:
+ * 	displays the experiments panel of the group
+ */
+angular.module('reportApp').directive("groupPanelExperiments", ['ExperimentsService', function(ExperimentsService){
+	return {
+		scope: {
+			group: '='
+		},
+		link: function(scope){
+			scope.dropdownId = `${scope.group.name}_exp_add_dropdown`;
+			scope.expsNotInGroup = () => {
+				return Object.keys(ExperimentsService.experiments)
+				// exps not in group
+				.filter(e => !scope.group.experiments.includes(e))
+				// exp has compatible analyzer
+				.filter(e => scope.group.analyzer === '' || ExperimentsService.getAnalyzerFromExpName(e) === scope.group.analyzer)
+				;
+			};
+			scope.getAnalyzerFromExpName = ExperimentsService.getAnalyzerFromExpName;
+		},
+		template: `
+<div id="{{group.name}}-explist-heading" class="panel-heading" role="tab">
+	<h4 class="panel-title">
+		<a
+			class=''
+			role="button"
+			data-toggle="collapse"
+			data-parent="#{{group.name}}-explist-heading"
+			href="#collapse-{{group.name}}-explist"
+			aria-expanded="true"
+			aria-controls="collapse-{{group.name}}-explist">
+			Experiments
+		</a>
+		<div class='btn-group'>
+		<div class="dropdown">
+			<button class="btn btn-default dropdown-toggle" ng-class='{disabled: expsNotInGroup().length === 0}' type="button" id="{{ dropdownId }}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
+				Add Experiment
+				<span class="caret"></span>
+			</button>
+			<ul class="dropdown-menu" aria-labelledby="{{ dropdownId }}">
+				<li
+					ng-repeat='exp in expsNotInGroup()'
+					ng-click='group.addExperiment(exp, getAnalyzerFromExpName(exp))'>
+					<a href="#">{{ exp }}</a>
+				</li>
+			</ul>
+		</div>
+		</div>
+	</h4>
+</div>
+<div id="collapse-{{group.name}}-explist"
+	class="panel-collapse collapse in"
+	role="tabpanel"
+	aria-labelledby="{{group.name}}-explist-heading">
+	<div class="panel-body">
+		<ul class='list-group'>
+			<li class='list-group-item' ng-repeat='exp in group.experiments'>
+				<button class="btn btn-danger" aria-hidden="true" ng-click='group.removeExperiment(exp)'>Remove</button>
+				<span>{{ exp }}</span>
+			</li>
+		</ul>
+	</div>
+</div>
+`
+	}
+}]);
diff --git a/beat/web/reports/static/reports/app/directives/groupPanelItems.js b/beat/web/reports/static/reports/app/directives/groupPanelItems.js
new file mode 100644
index 0000000000000000000000000000000000000000..916f24b26e8273acb1a630e2685359d7f064d1eb
--- /dev/null
+++ b/beat/web/reports/static/reports/app/directives/groupPanelItems.js
@@ -0,0 +1,74 @@
+/*
+ * 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/.
+ */
+
+/*
+ * groupPanelItems
+ * Desc:
+ * 	displays the panel of report items of the group
+ */
+angular.module('reportApp').directive("groupPanelItems", [function(){
+	return {
+		scope: {
+			group: '='
+		},
+		link: function(scope){
+		},
+		template: `
+<div id="{{group.name}}-itemlist-heading" class="panel-heading" role="tab">
+	<h4 class="panel-title">
+		<a
+			class=''
+			role="button"
+			data-toggle="collapse"
+			data-parent="#{{group.name}}-itemlist-heading"
+			href="#collapse-{{group.name}}-itemlist"
+			aria-expanded="true"
+			aria-controls="collapse-{{group.name}}-itemlist">
+			Items
+		</a>
+		<div
+			group-add-items-menu
+			class="btn-group" role="group" role='tab'
+			group='group'
+		></div>
+	</h4>
+</div>
+<div id="collapse-{{group.name}}-itemlist"
+	class="panel-collapse collapse in"
+	role="tabpanel"
+	aria-labelledby="{{group.name}}-itemlist-heading">
+	<div class="panel-body">
+		<div class='panel-group'>
+			<div
+				group-item-container
+				ng-repeat='item in group._reportItems'
+				group='group'
+				report-item='item'
+				style='margin-bottom: 5px;'
+				>
+			</div>
+		</div>
+	</div>
+</div>
+`
+	}
+}]);
diff --git a/beat/web/reports/static/reports/app/directives/groupPlotItem.js b/beat/web/reports/static/reports/app/directives/groupPlotItem.js
new file mode 100644
index 0000000000000000000000000000000000000000..7681ff63ed71db986aeeabd480ffc066a0577bd4
--- /dev/null
+++ b/beat/web/reports/static/reports/app/directives/groupPlotItem.js
@@ -0,0 +1,71 @@
+/*
+ * 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
+ */
+angular.module('reportApp')
+.directive("groupPlotItem", ['ExperimentsService', function(ExperimentsService){
+	return {
+		scope: {
+			group: '=',
+			id: '=',
+			content: '='
+		},
+		link: function(scope){
+			scope.domId = `${scope.group.name}_${scope.id}`;
+		},
+		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}}">
+			{{ domId }}
+		</a>
+
+		<div class="btn-group" role="group" role='tab'>
+			<button class='btn btn-default' ng-click='hardRefresh()'>
+				<span class='glyphicon glyphicon-refresh'></span>
+			</button>
+			<button class='btn btn-danger' ng-click='group.removeReportItem(id)'>
+				Delete Plot
+			</button
+		</div>
+	</h4>
+</div>
+<div id="collapse-{{domId}}"
+	class="panel-collapse collapse in"
+	role="tabpanel"
+	aria-labelledby="{{domId}}-heading">
+	{{ id }} content
+</div>
+`
+	}
+}]);
diff --git a/beat/web/reports/static/reports/app/directives/groupTableFieldSelector.js b/beat/web/reports/static/reports/app/directives/groupTableFieldSelector.js
new file mode 100644
index 0000000000000000000000000000000000000000..d1340426869ca1f2ab070f6587ff41f243487c04
--- /dev/null
+++ b/beat/web/reports/static/reports/app/directives/groupTableFieldSelector.js
@@ -0,0 +1,93 @@
+/*
+ * 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/.
+ */
+
+/*
+ * GroupTableFieldSelector
+ * Desc:
+ * 	Handles the choosing of table columns
+ */
+angular.module('reportApp')
+.directive("groupTableFieldSelector", ['ExperimentsService', function(ExperimentsService){
+	return {
+		scope: {
+			id: '=',
+			group: '=',
+			colsSelected: '=',
+			buttonAction: '&',
+			title: '@',
+			buttonText: '@'
+		},
+		link: function(scope){
+			// bootstrap's auto dropdown toggling is disabled for the table creation dropdown
+			// add a click handler for the table creation dropdown submit button to toggle
+			// manually
+			scope.clickButton = (e) => {
+				$(`#${scope.id}`).dropdown('toggle');
+				scope.buttonAction()();
+			};
+
+			scope.tableables = () => {
+				const tableables = ExperimentsService.tableables;
+				const fieldArr = Object.entries(tableables)
+				.filter(([e, fields]) => scope.group.experiments.includes(e))
+				.map(([e, fields]) => Object.keys(fields))
+				.reduce((arr, fArr) => [...arr, ...fArr], [])
+				;
+
+				return Array.from(new Set(fieldArr));
+			};
+
+			scope.isUniqueTableable = (expName, fieldName) => {
+				const tableables = ExperimentsService.tableables;
+				const concatNames = (eName, fName) => `${eName}.${fName}`;
+
+				const isRepeat = Object.entries(tableables)
+				.filter(([e, fields]) => {
+					let isInGroup = scope.group.experiments.includes(e);
+					let alreadyChecked = Object.keys(tableables).indexOf(e) < Object.keys(tableables).indexOf(expName);
+					return isInGroup && alreadyChecked;
+				})
+				.map(([e, fields]) => Object.keys(fields))
+				.reduce((arr, fArr) => [...arr, ...fArr], [])
+				.includes(fieldName)
+				;
+
+				return !isRepeat;
+			};
+
+			console.log(scope.buttonText);
+		},
+		template: `
+<button id='{{ id }}' type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+	{{ title }}
+	<span class="caret"></span>
+</button>
+<div class='dropdown-menu' ng-click="$event.stopPropagation();">
+	<h4>Select columns to show in Table</h4>
+	<select multiple ng-model='colsSelected'>
+		<option ng-repeat='fName in tableables()' ng-if='isUniqueTableable(fName)'>{{ fName }}</option>
+	</select>
+	<button class='btn btn-default' ng-click='clickButton($event)'>{{ buttonText }}</button>
+</div>
+`
+	}
+}]);
diff --git a/beat/web/reports/static/reports/app/directives/groupTableItem.js b/beat/web/reports/static/reports/app/directives/groupTableItem.js
new file mode 100644
index 0000000000000000000000000000000000000000..9d436334975ff2a44e0889c5a60011142643d371
--- /dev/null
+++ b/beat/web/reports/static/reports/app/directives/groupTableItem.js
@@ -0,0 +1,180 @@
+/*
+ * 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
+ */
+angular.module('reportApp')
+.directive("groupTableItem", ['ExperimentsService', function(ExperimentsService){
+	return {
+		scope: {
+			group: '=',
+			id: '=',
+			fields: '='
+		},
+		link: function(scope){
+			// add 'expName' to the beginning of the fields to show in the table
+			// if it isnt already there
+			if(scope.fields.length === 0 || scope.fields[0] !== 'Experiment'){
+				scope.fields.unshift('Experiment');
+			}
+
+			console.log(scope.fields);
+			scope.tableables = ExperimentsService.tableables || {};
+			scope.getFieldType = (field) => {
+				if(field === scope.fields[0]){
+					return 'string';
+				}
+
+				let hasFieldObj = Object.values(scope.tableables)
+				.find(o => o[field]);
+				let fVal = hasFieldObj ? hasFieldObj[field] : {};
+				let type;
+				if(fVal.type){
+					type = fVal.type;
+				} else if(Number.isSafeInteger(fVal)){
+					type = 'integer';
+				} else if(Number.isFinite(fVal)){
+					type = 'float';
+				} else if(typeof fVal === 'string'){
+					type = 'string';
+				} else {
+					type = undefined;
+				}
+
+				return type
+			};
+			scope.getFieldVal = (expName, field) => {
+				let fVal = scope.tableables[expName] ?
+					scope.tableables[expName][field] : undefined;
+				let val;
+				if(field === scope.fields[0]){
+					val = scope.group.aliases[expName].length > 0 ? scope.group.aliases[expName] : expName;
+				} else if(fVal.value){
+					val = fVal.value;
+				} else if (fVal){
+					val = fVal;
+				} else {
+					val = '-';
+				}
+
+				return val;
+			};
+
+			scope.chosenCols = Array.from(scope.fields);
+			scope.saveChosenCols = () => () => {
+				scope.fields.length = 1;
+				scope.chosenCols.forEach(f => scope.fields.push(f));
+			};
+
+			// need to nest actual value in an obj to get angular
+			// to watch it correctly
+			scope.sortField = { val: 'Experiment', isReversed: false };
+			scope.sortFunc = (expName) => {
+				return scope.getFieldType(scope.sortField.val) ?
+					scope.getFieldVal(expName, scope.sortField.val) : expName;
+			};
+			scope.setSortField = (field) => {
+				if(scope.sortField.val === field){
+					scope.sortField.isReversed = !scope.sortField.isReversed;
+				} else {
+					scope.sortField.val = field;
+					scope.sortField.isReversed = false;
+				}
+			};
+
+			scope.domId = `${scope.group.name}_${scope.id}`;
+			scope.colSelectorId = `${scope.domId}_columnSelector`;
+		},
+		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}}">
+			{{ domId }}
+		</a>
+
+		<div class="btn-group" role="group" role='tab'>
+			<button class='btn btn-default' ng-click='hardRefresh()'>
+				<span class='glyphicon glyphicon-refresh'></span>
+			</button>
+			<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>
+		<button class='btn btn-danger' ng-click='group.removeReportItem(id)'>
+			Delete Table
+		</button
+	</h4>
+</div>
+<div id="collapse-{{domId}}"
+	class="panel-collapse collapse in"
+	role="tabpanel"
+	aria-labelledby="{{domId}}-heading">
+	<div style='height: 100%; overflow-x: auto;'>
+		<table class="table table-striped table-hover">
+			<thead>
+				<tr>
+					<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'>
+						{{ tableables[exp] ? getFieldVal(exp, field) : '' }}
+					</td>
+				</tr>
+			</tbody>
+		</table>
+	</div>
+</div>
+`
+	}
+}]);
diff --git a/beat/web/reports/static/reports/app/directives/groupTextItem.js b/beat/web/reports/static/reports/app/directives/groupTextItem.js
new file mode 100644
index 0000000000000000000000000000000000000000..98364dc4e0ed8b1c9bc810f228c94437a039732b
--- /dev/null
+++ b/beat/web/reports/static/reports/app/directives/groupTextItem.js
@@ -0,0 +1,115 @@
+/*
+ * 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 plot report item
+ */
+angular.module('reportApp')
+.directive("groupTextItem", [function(){
+	return {
+		scope: {
+			group: '=',
+			reportItem: '='
+		},
+		link: function(scope){
+			scope.item = scope.reportItem;
+			scope.domId = `${scope.group.name}_${scope.item.id}`;
+			const editorOptions = {
+				debug: 'info',
+				modules: {
+					toolbar: [
+						[{ header: [1, 2, false] }],
+						['bold', 'italic', 'underline'],
+						['image', 'code-block']
+					]
+				},
+				theme: 'snow'
+			};
+
+			// the text editor
+			scope.editor;
+			scope.hasUnsavedContent = { val: false, change () { this.val = true; } };
+
+			let setupEditor = (el) => {
+				scope.editor = new Quill(el, editorOptions);
+				if(typeof scope.item.content === 'object'){
+					scope.editor.setContents(scope.item.content);
+				}
+				scope.editor.on('text-change', (newDelta) => {
+					scope.hasUnsavedContent.change();
+					console.log(scope.hasUnsavedContent);
+				});
+			};
+
+			let editorId = `${scope.domId}-text-editor`;
+			scope.$watch(() => document.querySelector(`#${editorId}`),
+				(oldEl, newEl) => {
+					if(newEl){
+						setupEditor(newEl);
+					}
+				}
+			);
+
+			scope.saveContent = () => {
+				let newContent = scope.editor.getContents();
+				scope.hasUnsavedContent.val = false;
+				scope.item.content = newContent;
+			};
+		},
+		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}}">
+			{{ domId }}
+		</a>
+		<button class='btn btn-success' ng-click='saveContent()'>
+			Save Content
+		</button>
+
+		<div class="btn-group" role="group" role='tab'>
+			<button class='btn btn-danger' ng-click='group.removeReportItem(item.id)'>
+				Delete Text Block
+			</button
+		</div>
+	<div ng-if='hasUnsavedContent.val'>
+		<strong>Warning:</strong> Press the 'Save Content' button to save changes.
+	</div>
+	</h4>
+</div>
+<div id="collapse-{{domId}}"
+	class="panel-collapse collapse in"
+	role="tabpanel"
+	aria-labelledby="{{domId}}-heading">
+	<div id='{{domId}}-text-editor'></div>
+</div>
+`
+	};
+}]);
diff --git a/beat/web/reports/static/reports/app/directives/groupsLayout.js b/beat/web/reports/static/reports/app/directives/groupsLayout.js
new file mode 100644
index 0000000000000000000000000000000000000000..458e17d56e321fb3aa53ab452f34636c8f8db192
--- /dev/null
+++ b/beat/web/reports/static/reports/app/directives/groupsLayout.js
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+angular.module('reportApp').directive("groupsLayout", ['GroupsService', function(GroupsService){
+	return {
+		// isolate scope for modularity & less coupling
+		scope: {
+		},
+		link: function(scope, el, attr){
+			scope.groups = GroupsService.groups;
+		},
+		template: `
+<div id='groupsLayout' class='panel-group'>
+	<div
+		ng-if='true || groups.length > 1'
+		group-panel-content
+		class='panel panel-default'
+		ng-repeat='group in groups track by $index'
+		group='group'
+		>
+	</div>
+	<!--
+	<div
+		group-table-item
+		ng-if='groups.length == 1'
+		class='panel panel-default'
+		ng-repeat='table in groups[0]._reportItems'
+		group='groups[0]'
+		id='table.id'
+		fields='table.content'
+		>
+	</div>
+	!-->
+</div>
+`
+	}
+}]);
diff --git a/beat/web/reports/templates/reports/report.html b/beat/web/reports/templates/reports/report.html
index 6a38aa008d47f0bfcb50cebf51479f27c0ebdda9..32b21caf380ffd76cf24749b1c8370c23356aee5 100644
--- a/beat/web/reports/templates/reports/report.html
+++ b/beat/web/reports/templates/reports/report.html
@@ -42,6 +42,8 @@
     <link rel="stylesheet" href="{% fingerprint "chosen-bootstrap/chosen.bootstrap.min.css" %}" type="text/css" media="screen" />
     <link rel="stylesheet" href="{% fingerprint "datatables/media/css/dataTables.bootstrap.min.css" %}" type="text/css" media="screen" />
     <link rel="stylesheet" href="{% fingerprint "jquery-ui/themes/base/minified/jquery-ui.min.css" %}" type="text/css" media="screen" />
+    <link href="//cdn.quilljs.com/1.2.2/quill.snow.css" rel="stylesheet">
+    <link href="//cdn.quilljs.com/1.2.2/quill.bubble.css" rel="stylesheet">
     {% code_editor_css %}
 {% endblock %}
 
@@ -53,6 +55,7 @@
     <script src="{% fingerprint "chosen/chosen.jquery.min.js" %}" type="text/javascript" charset="utf-8"></script>
     <script src="{% fingerprint "datatables/media/js/jquery.dataTables.min.js" %}" type="text/javascript" charset="utf-8"></script>
     <script src="{% fingerprint "datatables/media/js/dataTables.bootstrap.min.js" %}" type="text/javascript" charset="utf-8"></script>
+    <script src="//cdn.quilljs.com/1.2.2/quill.min.js"></script>
 
     <!-- Use Google's CDN for angular-js with a local fallback -->
     <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js"></script>
@@ -83,6 +86,7 @@
 
     <!-- services -->
     <script src="{% fingerprint "reports/app/services/groupsService.js" %}" type="text/javascript" charset="utf-8"></script>
+    <script src="{% fingerprint "reports/app/services/experimentsService.js" %}" type="text/javascript" charset="utf-8"></script>
 
     <!-- directives
     -->
@@ -102,6 +106,17 @@
     <script src="{% fingerprint "reports/app/directives/tableDynamic.js" %}" type="text/javascript" charset="utf-8"></script>
     <script src="{% fingerprint "reports/app/directives/tablePrecision.js" %}" type="text/javascript" charset="utf-8"></script>
     <script src="{% fingerprint "reports/app/directives/theColumn.js" %}" type="text/javascript" charset="utf-8"></script>
+    <script src="{% fingerprint "reports/app/directives/groupsLayout.js" %}" type="text/javascript" charset="utf-8"></script>
+    <script src="{% fingerprint "reports/app/directives/groupPanelContent.js" %}" type="text/javascript" charset="utf-8"></script>
+    <script src="{% fingerprint "reports/app/directives/groupPanelExperiments.js" %}" type="text/javascript" charset="utf-8"></script>
+    <script src="{% fingerprint "reports/app/directives/groupPanelItems.js" %}" type="text/javascript" charset="utf-8"></script>
+    <script src="{% fingerprint "reports/app/directives/groupAddItemsMenu.js" %}" type="text/javascript" charset="utf-8"></script>
+    <script src="{% fingerprint "reports/app/directives/groupItemContainer.js" %}" type="text/javascript" charset="utf-8"></script>
+    <script src="{% fingerprint "reports/app/directives/groupTableItem.js" %}" type="text/javascript" charset="utf-8"></script>
+    <script src="{% fingerprint "reports/app/directives/groupPlotItem.js" %}" type="text/javascript" charset="utf-8"></script>
+    <script src="{% fingerprint "reports/app/directives/groupTextItem.js" %}" type="text/javascript" charset="utf-8"></script>
+    <script src="{% fingerprint "reports/app/directives/groupTableFieldSelector.js" %}" type="text/javascript" charset="utf-8"></script>
+    <script src="{% fingerprint "reports/app/directives/groupPanelAliases.js" %}" type="text/javascript" charset="utf-8"></script>
 
 
     <script src="{% fingerprint "ui/js/smartselector.js" %}" type="text/javascript" charset="utf-8"></script>