diff --git a/ma-impl.sublime-workspace b/ma-impl.sublime-workspace
index a8cbaab1212047178a35328674ba735badf78cdc..eac89659bc2779cbc08064ded16afa2761036267 100644
--- a/ma-impl.sublime-workspace
+++ b/ma-impl.sublime-workspace
@@ -275,30 +275,6 @@
 	},
 	"buffers":
 	[
-		{
-			"file": "vipra-ui/app/routes/articles/show.js",
-			"settings":
-			{
-				"buffer_size": 643,
-				"line_ending": "Unix"
-			}
-		},
-		{
-			"file": "vipra-ui/app/templates/articles/show.hbs",
-			"settings":
-			{
-				"buffer_size": 540,
-				"line_ending": "Unix"
-			}
-		},
-		{
-			"file": "vipra-ui/app/models/article.js",
-			"settings":
-			{
-				"buffer_size": 168,
-				"line_ending": "Unix"
-			}
-		}
 	],
 	"build_system": "",
 	"build_system_choices":
@@ -480,22 +456,50 @@
 		"/home/eike/repos/master/ma-impl",
 		"/home/eike/repos/master/ma-impl/vipra-ui",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/components",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/models",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/routes",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/routes/articles",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/routes/topics",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates",
-		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates/articles"
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates/articles",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates/components",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates/topics",
+		"/home/eike/repos/master/ma-impl/vipra-ui/tests/helpers",
+		"/home/eike/repos/master/ma-impl/vipra-ui/tests/integration",
+		"/home/eike/repos/master/ma-impl/vipra-ui/tests/integration/components",
+		"/home/eike/repos/master/ma-impl/vipra-ui/tests/unit",
+		"/home/eike/repos/master/ma-impl/vipra-ui/tests/unit/adapters",
+		"/home/eike/repos/master/ma-impl/vipra-ui/tests/unit/controllers",
+		"/home/eike/repos/master/ma-impl/vipra-ui/tests/unit/helpers",
+		"/home/eike/repos/master/ma-impl/vipra-ui/tests/unit/models",
+		"/home/eike/repos/master/ma-impl/vipra-ui/tests/unit/routes"
 	],
 	"file_history":
 	[
-		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates/articles/show.hbs",
-		"/home/eike/repos/master/ma-impl/vipra-ui/app/routes/topics/list.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates/articles.hbs",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates/topics.hbs",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/components/topics-list.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/components/articles-list.js",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates/articles/list.hbs",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates/components/articles-list.hbs",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates/topics/list.hbs",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates/components/topics-list.hbs",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/models/article.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/models/topic.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/routes/topics/list.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/components/filter-list.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates/articles/show.hbs",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/components/topic-link.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/helpers/topic-numi.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates/components/topic-link.hbs",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/routes/articles/show.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/helpers/topicname.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/helpers/topic-name.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/styles/app.css",
 		"/home/eike/repos/master/ma-impl/vm/bootstrap.sh",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates/topics/show.hbs",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/router.js",
-		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates/topics/list.hbs",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates/index.hbs",
 		"/home/eike/.config/sublime-text-3/Packages/User/Preferences.sublime-settings",
 		"/home/eike/.config/sublime-text-3/Packages/Default/Preferences.sublime-settings",
@@ -600,23 +604,7 @@
 		"/home/eike/Repositories/niels_website/web/src/intergeo-2015.html",
 		"/home/eike/Repositories/niels_website/web/src/less/main.less",
 		"/home/eike/Downloads/intellij/idea-IU-141.2735.5/Install-Linux-tar.txt",
-		"/home/eike/Downloads/JetBrains.IntelliJ.IDEA.Ultimate.v14.1.5.141.2735.5.Linux.Incl.KeyMaker-DVT/dvt.nfo",
-		"/home/eike/Repositories/niels_website/web/src/sftp-config.json",
-		"/home/eike/Repositories/niels_website/web/src/impressum.html",
-		"/home/eike/Repositories/niels_website/web/gulpfile.js",
-		"/home/eike/.config/sublime-text-3/Packages/SFTP/SFTP.sublime-settings",
-		"/home/eike/.config/sublime-text-3/Packages/User/SFTP.sublime-settings",
-		"/home/eike/.config/sublime-text-3/Packages/SFTP/SFTP.py",
-		"/home/eike/.config/sublime-text-3/Packages/SFTP/license.txt",
-		"/home/eike/Repositories/niels_website/web/dist/sftp-config.json",
-		"/home/eike/Repositories/fu/ss15/ki/exercise-09/task01a.pl",
-		"/home/eike/Repositories/fu/ss15/ki/exercise-07/task3b.pl",
-		"/home/eike/Repositories/fu/ss15/ki/exercise-07/task3a.pl",
-		"/home/eike/Downloads/analyze.r",
-		"/home/eike/Repositories/fu/ss15/ebi/exercise-03/exercise-03.r",
-		"/home/eike/Repositories/fu/ss15/ebi/exercise-02/exercise-02.r",
-		"/home/eike/Downloads/Formularantworten150621.csv",
-		"/home/eike/Downloads/analyze.r.Rout"
+		"/home/eike/Downloads/JetBrains.IntelliJ.IDEA.Ultimate.v14.1.5.141.2735.5.Linux.Incl.KeyMaker-DVT/dvt.nfo"
 	],
 	"find":
 	{
@@ -938,96 +926,8 @@
 	"groups":
 	[
 		{
-			"selected": 2,
 			"sheets":
 			[
-				{
-					"buffer": 0,
-					"file": "vipra-ui/app/routes/articles/show.js",
-					"semi_transient": false,
-					"settings":
-					{
-						"buffer_size": 643,
-						"regions":
-						{
-						},
-						"selection":
-						[
-							[
-								342,
-								342
-							]
-						],
-						"settings":
-						{
-							"open_with_edit": true,
-							"syntax": "Packages/JavaScript/JavaScript.tmLanguage",
-							"tab_size": 2,
-							"translate_tabs_to_spaces": true
-						},
-						"translation.x": 0.0,
-						"translation.y": 0.0,
-						"zoom_level": 1.0
-					},
-					"stack_index": 2,
-					"type": "text"
-				},
-				{
-					"buffer": 1,
-					"file": "vipra-ui/app/templates/articles/show.hbs",
-					"semi_transient": false,
-					"settings":
-					{
-						"buffer_size": 540,
-						"regions":
-						{
-						},
-						"selection":
-						[
-							[
-								299,
-								299
-							]
-						],
-						"settings":
-						{
-							"syntax": "Packages/Handlebars/grammars/Handlebars.tmLanguage"
-						},
-						"translation.x": 0.0,
-						"translation.y": 0.0,
-						"zoom_level": 1.0
-					},
-					"stack_index": 1,
-					"type": "text"
-				},
-				{
-					"buffer": 2,
-					"file": "vipra-ui/app/models/article.js",
-					"semi_transient": false,
-					"settings":
-					{
-						"buffer_size": 168,
-						"regions":
-						{
-						},
-						"selection":
-						[
-							[
-								168,
-								168
-							]
-						],
-						"settings":
-						{
-							"syntax": "Packages/JavaScript/JavaScript.tmLanguage"
-						},
-						"translation.x": 0.0,
-						"translation.y": 0.0,
-						"zoom_level": 1.0
-					},
-					"stack_index": 0,
-					"type": "text"
-				}
 			]
 		}
 	],
@@ -1194,7 +1094,7 @@
 	"show_open_files": true,
 	"show_tabs": true,
 	"side_bar_visible": true,
-	"side_bar_width": 247.0,
+	"side_bar_width": 254.0,
 	"status_bar_visible": true,
 	"template_settings":
 	{
diff --git a/vipra-rest/src/main/java/de/vipra/rest/model/TopicDefinition.java b/vipra-rest/src/main/java/de/vipra/rest/model/Topic.java
similarity index 87%
rename from vipra-rest/src/main/java/de/vipra/rest/model/TopicDefinition.java
rename to vipra-rest/src/main/java/de/vipra/rest/model/Topic.java
index 0541f4a1625aa9376a895e3a9ddd7e791f92c984..58856e7f2b2098769d4356199c2d74f791f0af9a 100644
--- a/vipra-rest/src/main/java/de/vipra/rest/model/TopicDefinition.java
+++ b/vipra-rest/src/main/java/de/vipra/rest/model/Topic.java
@@ -4,7 +4,7 @@ import java.net.URI;
 import java.util.HashMap;
 import java.util.Map;
 
-public class TopicDefinition extends de.vipra.util.model.Topic implements Linked {
+public class Topic extends de.vipra.util.model.Topic implements Linked {
 
 	private Map<String, String> links;
 
diff --git a/vipra-rest/src/main/java/de/vipra/rest/provider/ObjectMapperProvider.java b/vipra-rest/src/main/java/de/vipra/rest/provider/ObjectMapperProvider.java
index b748acc0cbc1dc60921593b937a78c20508f02a3..512a18eae4561263ef13051b888181673b8754ad 100644
--- a/vipra-rest/src/main/java/de/vipra/rest/provider/ObjectMapperProvider.java
+++ b/vipra-rest/src/main/java/de/vipra/rest/provider/ObjectMapperProvider.java
@@ -14,8 +14,11 @@ import com.fasterxml.jackson.databind.SerializationFeature;
 import com.fasterxml.jackson.databind.module.SimpleModule;
 
 import de.vipra.rest.model.Article;
+import de.vipra.rest.model.Topic;
 import de.vipra.rest.serializer.ArticleDeserializer;
 import de.vipra.rest.serializer.ArticleSerializer;
+import de.vipra.rest.serializer.TopicDeserializer;
+import de.vipra.rest.serializer.TopicSerializer;
 import de.vipra.util.Constants;
 
 @Provider
@@ -38,6 +41,8 @@ public class ObjectMapperProvider implements ContextResolver<ObjectMapper> {
 		SimpleModule module = new SimpleModule();
 		module.addSerializer(Article.class, new ArticleSerializer());
 		module.addDeserializer(Article.class, new ArticleDeserializer());
+		module.addSerializer(Topic.class, new TopicSerializer());
+		module.addDeserializer(Topic.class, new TopicDeserializer());
 
 		final ObjectMapper mapper = new ObjectMapper();
 		mapper.enable(SerializationFeature.INDENT_OUTPUT);
diff --git a/vipra-rest/src/main/java/de/vipra/rest/resource/TopicResource.java b/vipra-rest/src/main/java/de/vipra/rest/resource/TopicResource.java
index f2f94cb023fcd37574c9875a536af06017dc06ad..68b222461ba24916f6ebde77b4b917d699012306 100644
--- a/vipra-rest/src/main/java/de/vipra/rest/resource/TopicResource.java
+++ b/vipra-rest/src/main/java/de/vipra/rest/resource/TopicResource.java
@@ -20,7 +20,7 @@ import de.vipra.rest.APIMediaType;
 import de.vipra.rest.Messages;
 import de.vipra.rest.model.APIError;
 import de.vipra.rest.model.ResponseWrapper;
-import de.vipra.rest.model.TopicDefinition;
+import de.vipra.rest.model.Topic;
 import de.vipra.rest.service.TopicService;
 import de.vipra.util.Config;
 import de.vipra.util.Mongo;
@@ -45,8 +45,8 @@ public class TopicResource {
 	@Produces(APIMediaType.APPLICATION_JSONAPI)
 	public Response getTopics(@QueryParam("skip") @DefaultValue("0") int skip,
 			@QueryParam("limit") @DefaultValue("0") int limit) {
-		List<TopicDefinition> topics = service.getMultiple(uri.getAbsolutePath(), skip, limit, null);
-		ResponseWrapper<List<TopicDefinition>> res = new ResponseWrapper<>(topics);
+		List<Topic> topics = service.getMultiple(uri.getAbsolutePath(), skip, limit, null);
+		ResponseWrapper<List<Topic>> res = new ResponseWrapper<>(topics);
 		res.addLink("self", uri.getAbsolutePath().toString());
 		return Response.ok().entity(res).build();
 	}
@@ -56,13 +56,13 @@ public class TopicResource {
 	@Consumes(APIMediaType.APPLICATION_JSONAPI)
 	@Path("{id}")
 	public Response getTopic(@PathParam("id") String id) {
-		ResponseWrapper<TopicDefinition> res = new ResponseWrapper<>();
+		ResponseWrapper<Topic> res = new ResponseWrapper<>();
 		if (id == null || id.trim().length() == 0) {
 			res.addError(new APIError(Response.Status.BAD_REQUEST, "ID is empty",
 					String.format(Messages.BAD_REQUEST, "id cannot be empty")));
 			return Response.status(Response.Status.BAD_REQUEST).entity(res).build();
 		}
-		TopicDefinition topic = service.getSingle(uri.getAbsolutePath(), id);
+		Topic topic = service.getSingle(uri.getAbsolutePath(), id);
 		if (topic != null) {
 			res.setData(topic);
 			return Response.ok().entity(res).build();
@@ -77,8 +77,8 @@ public class TopicResource {
 	@Consumes(APIMediaType.APPLICATION_JSONAPI)
 	@Produces(APIMediaType.APPLICATION_JSONAPI)
 	@Path("{id}")
-	public Response updateTopic(@PathParam("id") String id, TopicDefinition topic) {
-		ResponseWrapper<TopicDefinition> res = new ResponseWrapper<>();
+	public Response updateTopic(@PathParam("id") String id, Topic topic) {
+		ResponseWrapper<Topic> res = new ResponseWrapper<>();
 		try {
 			long updated = service.updateSingle(topic);
 			int updatedInt = updated > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) updated;
diff --git a/vipra-rest/src/main/java/de/vipra/rest/serializer/JsonHelper.java b/vipra-rest/src/main/java/de/vipra/rest/serializer/JsonHelper.java
index a15ff8ae64aca885be54c03ba35879b04a2ea42b..ebda14f7a65edbcdd8e1909fb60358071dd2e17b 100644
--- a/vipra-rest/src/main/java/de/vipra/rest/serializer/JsonHelper.java
+++ b/vipra-rest/src/main/java/de/vipra/rest/serializer/JsonHelper.java
@@ -46,6 +46,14 @@ public class JsonHelper {
 		return getLong(node, name, 0L);
 	}
 
+	public static int getInt(JsonNode node, String name, int defaultValue) {
+		return get(node, name, defaultValue, Integer.class);
+	}
+
+	public static int getInt(JsonNode node, String name) {
+		return getInt(node, name, 0);
+	}
+
 	public static String dateToString(Date date) {
 		DateFormat df = new SimpleDateFormat(Constants.DATETIME_FORMAT);
 		return df.format(date);
diff --git a/vipra-rest/src/main/java/de/vipra/rest/serializer/TopicDeserializer.java b/vipra-rest/src/main/java/de/vipra/rest/serializer/TopicDeserializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..8fcf38b63194365f2d05bde86ff6c17f2876746c
--- /dev/null
+++ b/vipra-rest/src/main/java/de/vipra/rest/serializer/TopicDeserializer.java
@@ -0,0 +1,44 @@
+package de.vipra.rest.serializer;
+
+import static de.vipra.rest.serializer.JsonHelper.getString;
+import static de.vipra.rest.serializer.JsonHelper.getInt;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+
+import de.vipra.rest.model.Topic;
+
+public class TopicDeserializer extends JsonDeserializer<Topic> {
+
+	@Override
+	public Topic deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
+		Topic topic = null;
+
+		JsonNode node = p.readValueAsTree();
+		if (node != null) {
+			topic = new Topic();
+			if (node.has("id"))
+				topic.setId(getString(node, "id"));
+
+			if (node.has("attributes")) {
+				JsonNode attrs = node.get("attributes");
+				if (attrs.has("name"))
+					topic.setName(getString(attrs, "name"));
+				if (attrs.has("index"))
+					topic.setIndex(getInt(attrs, "index"));
+				if (attrs.has("words")) {
+					JsonNode wordsNode = attrs.get("words");
+					// TODO implement
+				}
+			}
+		}
+
+		return topic;
+	}
+
+}
diff --git a/vipra-rest/src/main/java/de/vipra/rest/serializer/TopicSerializer.java b/vipra-rest/src/main/java/de/vipra/rest/serializer/TopicSerializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..38ef064de549cb44771a0ee5cccf6bd4c30e957d
--- /dev/null
+++ b/vipra-rest/src/main/java/de/vipra/rest/serializer/TopicSerializer.java
@@ -0,0 +1,35 @@
+package de.vipra.rest.serializer;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+import de.vipra.rest.model.Topic;
+
+public class TopicSerializer extends JsonSerializer<Topic> {
+
+	@Override
+	public void serialize(Topic value, JsonGenerator gen, SerializerProvider serializer)
+			throws IOException, JsonProcessingException {
+		gen.writeStartObject();
+		gen.writeStringField("id", value.getId());
+		gen.writeStringField("type", "topic");
+
+		if (value.getLinks() != null)
+			gen.writeObjectField("links", value.getLinks());
+
+		gen.writeObjectFieldStart("attributes");
+		gen.writeNumberField("index", value.getIndex());
+		if (value.getName() != null)
+			gen.writeStringField("name", value.getName());
+		if (value.getWords() != null)
+			gen.writeObjectField("words", value.getWords());
+		gen.writeEndObject();
+
+		gen.writeEndObject();
+	}
+
+}
diff --git a/vipra-rest/src/main/java/de/vipra/rest/service/TopicService.java b/vipra-rest/src/main/java/de/vipra/rest/service/TopicService.java
index 577c6575e3c9aec2c5059d2dbf39fb60f65dd9f3..9599b521d9f58c8abb8cd7ba4dc9dbbea2347bad 100644
--- a/vipra-rest/src/main/java/de/vipra/rest/service/TopicService.java
+++ b/vipra-rest/src/main/java/de/vipra/rest/service/TopicService.java
@@ -3,19 +3,19 @@ package de.vipra.rest.service;
 import java.net.URI;
 import java.util.List;
 
-import de.vipra.rest.model.TopicDefinition;
+import de.vipra.rest.model.Topic;
 import de.vipra.util.Constants;
 import de.vipra.util.Mongo;
 
-public class TopicService extends DatabaseService<TopicDefinition> {
+public class TopicService extends DatabaseService<Topic> {
 
 	public TopicService(Mongo mongo) {
-		super(mongo, Constants.Collection.TOPICS, TopicDefinition.class);
+		super(mongo, Constants.Collection.TOPICS, Topic.class);
 	}
 
-	public List<TopicDefinition> getMultiple(URI base, int skip, int limit, String sortBy) {
-		List<TopicDefinition> topics = super.getMultiple(skip, limit, sortBy);
-		for (TopicDefinition topic : topics) {
+	public List<Topic> getMultiple(URI base, int skip, int limit, String sortBy) {
+		List<Topic> topics = super.getMultiple(skip, limit, sortBy);
+		for (Topic topic : topics) {
 			topic.setWords(null);
 		}
 		return topics;
diff --git a/vipra-ui/app/components/article-list.js b/vipra-ui/app/components/article-list.js
deleted file mode 100644
index afa75e431c88c2ffe6b51b07470e0f53a4be44be..0000000000000000000000000000000000000000
--- a/vipra-ui/app/components/article-list.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import Ember from 'ember';
-
-    export default Ember.Component.extend({
-
-      filteredArticles: Ember.computed('articles', 'filter', function() {
-        var keyword = this.get('filter');
-        var filtered = this.get('articles');
-        if (keyword) {
-          keyword = keyword.toLowerCase().trim();
-          filtered = this.get('articles').filter((item) => item.get('title').toLowerCase().includes(keyword));
-        }
-        return filtered;
-      })
-
-    });
diff --git a/vipra-ui/app/components/articles-list.js b/vipra-ui/app/components/articles-list.js
new file mode 100644
index 0000000000000000000000000000000000000000..88005348a07bb3392c807c181fac5b46e8e5311d
--- /dev/null
+++ b/vipra-ui/app/components/articles-list.js
@@ -0,0 +1,15 @@
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+
+  filteredItems: Ember.computed('items', 'filter', function() {
+    var keyword = this.get('filter');
+    var filtered = this.get('items');
+    if (keyword) {
+      keyword = keyword.toLowerCase().trim();
+      filtered = this.get('items').filter((item) => item.get('_name').toLowerCase().includes(keyword));
+    }
+    return filtered;
+  })
+
+});
\ No newline at end of file
diff --git a/vipra-ui/app/components/topic-link.js b/vipra-ui/app/components/topic-link.js
new file mode 100644
index 0000000000000000000000000000000000000000..506e7122192595698fafe9eb384b0ca4a8e22dfa
--- /dev/null
+++ b/vipra-ui/app/components/topic-link.js
@@ -0,0 +1,15 @@
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+
+  tagName: 'span',
+
+  text: Ember.computed('topic', function() {
+    var topic = this.get('topic');
+    var text = topic.name ? topic.name : topic.id;
+    if(topic.hasOwnProperty('count'))
+      text += ` (${topic.count})`;
+    return text;
+  })
+
+});
diff --git a/vipra-ui/app/components/topics-list.js b/vipra-ui/app/components/topics-list.js
new file mode 100644
index 0000000000000000000000000000000000000000..88005348a07bb3392c807c181fac5b46e8e5311d
--- /dev/null
+++ b/vipra-ui/app/components/topics-list.js
@@ -0,0 +1,15 @@
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+
+  filteredItems: Ember.computed('items', 'filter', function() {
+    var keyword = this.get('filter');
+    var filtered = this.get('items');
+    if (keyword) {
+      keyword = keyword.toLowerCase().trim();
+      filtered = this.get('items').filter((item) => item.get('_name').toLowerCase().includes(keyword));
+    }
+    return filtered;
+  })
+
+});
\ No newline at end of file
diff --git a/vipra-ui/app/models/article.js b/vipra-ui/app/models/article.js
index 94797c67807646591f2f23a88cb5b83d90ded21a..0c23451f312a782c6fd7ee54314145af85dc0b14 100644
--- a/vipra-ui/app/models/article.js
+++ b/vipra-ui/app/models/article.js
@@ -5,5 +5,11 @@ export default DS.Model.extend({
   text: DS.attr(),
   url: DS.attr(),
   date: DS.attr('date'),
-  stats: DS.attr()
+  stats: DS.attr(),
+  topics: DS.attr(),
+
+  _name: function() {
+    var title = this.get('title');
+    return title ? title : this.get('id');
+  }.property('title')
 });
diff --git a/vipra-ui/app/models/topic.js b/vipra-ui/app/models/topic.js
index af2db9b44b47931b9b77cab2e68a284afdc2e57a..34cf33dc7693478ef9064a94e43a52fca97db0bd 100644
--- a/vipra-ui/app/models/topic.js
+++ b/vipra-ui/app/models/topic.js
@@ -1,5 +1,11 @@
 import DS from 'ember-data';
 
 export default DS.Model.extend({
-  
+  name: DS.attr(),
+  index: DS.attr(),
+
+  _name: function() {
+    var name = this.get('name');
+    return name ? name : this.get('id');
+  }.property('name')
 });
diff --git a/vipra-ui/app/routes/topics/list.js b/vipra-ui/app/routes/topics/list.js
index ef14ad4bccdd995300f8c0ec7ec1f248a840a00c..0e1edf569710acb5c60bc7305f443dca29f22353 100644
--- a/vipra-ui/app/routes/topics/list.js
+++ b/vipra-ui/app/routes/topics/list.js
@@ -4,6 +4,6 @@ export default Ember.Route.extend({
   model() {
     return Ember.RSVP.hash({
       topics: this.store.findAll('topic')
-    });
+    })
   }
 });
\ No newline at end of file
diff --git a/vipra-ui/app/templates/articles.hbs b/vipra-ui/app/templates/articles.hbs
index c42a140eac368bb9661e1e5e77fe78ab056bc47c..c4ddd8803c909fc67c701ac09d62f5819cf07a6c 100644
--- a/vipra-ui/app/templates/articles.hbs
+++ b/vipra-ui/app/templates/articles.hbs
@@ -1,4 +1,5 @@
 <h1>Articles</h1>
+{{#link-to 'index'}}Top{{/link-to}}
 {{#link-to 'articles.list'}}All{{/link-to}}
 <hr>
 
diff --git a/vipra-ui/app/templates/articles/list.hbs b/vipra-ui/app/templates/articles/list.hbs
index dcb0456ae2ebb26a8c3660bfd62ea7c2bb6bd32a..86df0159b9c403a4a892787ff0344a30d9265d07 100644
--- a/vipra-ui/app/templates/articles/list.hbs
+++ b/vipra-ui/app/templates/articles/list.hbs
@@ -2,6 +2,6 @@
 
 <h2>Found articles</h2>
 
-{{debounced-input placeholder='Filter' size='50' value='filter' debounce='150'}}
+{{debounced-input placeholder='Filter' size='50' valueBinding='filter' debounce='150'}}
 
-{{article-list articles=model.articles filter=filter}}
\ No newline at end of file
+{{articles-list items=model.articles filter=filter}}
\ No newline at end of file
diff --git a/vipra-ui/app/templates/articles/show.hbs b/vipra-ui/app/templates/articles/show.hbs
index d38231dddf0dcba90c9c966b51f1c75e29d3767c..564d86f7b331578e1082e57e89a821f686233e44 100644
--- a/vipra-ui/app/templates/articles/show.hbs
+++ b/vipra-ui/app/templates/articles/show.hbs
@@ -9,6 +9,12 @@
   <dd><a href="{{model.article.url}}">{{model.article.url}}</a></dd>
 </dl>
 
+<h3>Topics</h3>
+
+{{#each model.article.topics as |topic|}}
+  {{topic-link topic=topic}}
+{{/each}}
+
 <h3>Statistics</h3>
 
 <table>
diff --git a/vipra-ui/app/templates/components/article-list.hbs b/vipra-ui/app/templates/components/articles-list.hbs
similarity index 76%
rename from vipra-ui/app/templates/components/article-list.hbs
rename to vipra-ui/app/templates/components/articles-list.hbs
index 8d3269ffb87a2266baece0abf9056f021d25a6c3..c4c3d1af7f4072cd95842c58da187c0f406a1f9e 100644
--- a/vipra-ui/app/templates/components/article-list.hbs
+++ b/vipra-ui/app/templates/components/articles-list.hbs
@@ -1,5 +1,5 @@
 <ol>
-  {{#each filteredArticles as |article|}}
+  {{#each filteredItems as |article|}}
     <li>{{#link-to 'articles.show' article.id}}{{text-marker text=article.title mark=filter}}{{/link-to}}</li>
   {{/each}}
 </ol>
\ No newline at end of file
diff --git a/vipra-ui/app/templates/components/topic-link.hbs b/vipra-ui/app/templates/components/topic-link.hbs
new file mode 100644
index 0000000000000000000000000000000000000000..69c3ccb06acf50d57cf09f92b47e23354236b400
--- /dev/null
+++ b/vipra-ui/app/templates/components/topic-link.hbs
@@ -0,0 +1 @@
+{{#link-to 'topics.show' topic.id}}{{text}}{{/link-to}}
\ No newline at end of file
diff --git a/vipra-ui/app/templates/components/topics-list.hbs b/vipra-ui/app/templates/components/topics-list.hbs
new file mode 100644
index 0000000000000000000000000000000000000000..5ab07c256db216380b2dc55cb8825ba7a8052105
--- /dev/null
+++ b/vipra-ui/app/templates/components/topics-list.hbs
@@ -0,0 +1,5 @@
+<ol>
+  {{#each filteredItems as |topic|}}
+    <li>{{#link-to 'topics.show' topic.id}}{{text-marker text=topic._name mark=filter}}{{/link-to}}</li>
+  {{/each}}
+</ol>
\ No newline at end of file
diff --git a/vipra-ui/app/templates/topics.hbs b/vipra-ui/app/templates/topics.hbs
index c24cd68950a9a5c52c59d7020c3a30a00adb094d..3da2b0f7f70e3b9551d248f2839177405469ab0f 100644
--- a/vipra-ui/app/templates/topics.hbs
+++ b/vipra-ui/app/templates/topics.hbs
@@ -1 +1,6 @@
-{{outlet}}
+<h1>Topics</h1>
+{{#link-to 'index'}}Top{{/link-to}}
+{{#link-to 'topics.list'}}All{{/link-to}}
+<hr>
+
+{{outlet}}
\ No newline at end of file
diff --git a/vipra-ui/app/templates/topics/list.hbs b/vipra-ui/app/templates/topics/list.hbs
index 5db134dd36dc5d29533065860e48cd8bd34fe030..f7cabdd7052be50106761da749d418fda9acf5f6 100644
--- a/vipra-ui/app/templates/topics/list.hbs
+++ b/vipra-ui/app/templates/topics/list.hbs
@@ -1 +1,5 @@
-<h2>Found topics</h2>
\ No newline at end of file
+<h2>Found topics</h2>
+
+{{debounced-input placeholder='Filter' size='50' valueBinding='filter' debounce='150'}}
+
+{{topics-list items=model.topics filter=filter}}
\ No newline at end of file
diff --git a/vipra-ui/tests/integration/components/article-list-test.js b/vipra-ui/tests/integration/components/item-list-test.js
similarity index 100%
rename from vipra-ui/tests/integration/components/article-list-test.js
rename to vipra-ui/tests/integration/components/item-list-test.js
diff --git a/vipra-ui/tests/integration/components/topic-link-test.js b/vipra-ui/tests/integration/components/topic-link-test.js
new file mode 100644
index 0000000000000000000000000000000000000000..d3ea4cf9e5aac1a12db07f7813d91d626bdf74cd
--- /dev/null
+++ b/vipra-ui/tests/integration/components/topic-link-test.js
@@ -0,0 +1,25 @@
+import { moduleForComponent, test } from 'ember-qunit';
+import hbs from 'htmlbars-inline-precompile';
+
+moduleForComponent('topic-link', 'Integration | Component | topic link', {
+  integration: true
+});
+
+test('it renders', function(assert) {
+  
+  // Set any properties with this.set('myProperty', 'value');
+  // Handle any actions with this.on('myAction', function(val) { ... });" + EOL + EOL +
+
+  this.render(hbs`{{topic-link}}`);
+
+  assert.equal(this.$().text().trim(), '');
+
+  // Template block usage:" + EOL +
+  this.render(hbs`
+    {{#topic-link}}
+      template block text
+    {{/topic-link}}
+  `);
+
+  assert.equal(this.$().text().trim(), 'template block text');
+});