From ac4f1331b5216f8857911d6ece76e19267552592 Mon Sep 17 00:00:00 2001 From: "moandji.ezana" Date: Tue, 30 Oct 2012 09:34:48 +0200 Subject: [PATCH] Using pushState to navigate from essay list to single essay --- .../moandjiezana/essayist/utils/Tasks.java | 16 +++++ .../moandjiezana/tent/essayist/Essays.java | 13 ++++ .../tent/essayist/MyFeedServlet.java | 60 +++-------------- .../com/moandjiezana/tent/essayist/Users.java | 24 ++++++- .../tent/essayist/config/JamonContext.java | 2 + .../tent/essayist/config/Routes.java | 4 ++ .../tent/essayist/tent/Entities.java | 2 +- .../tent/essayist/EssaysTemplate.jamon | 4 +- .../moandjiezana/tent/essayist/Layout.jamon | 10 +-- .../tent/essayist/partials/EssayLink.jamon | 54 ++++++++++----- src/main/webapp/assets/essayist.js | 65 +++++++++++++++++++ 11 files changed, 176 insertions(+), 78 deletions(-) create mode 100644 src/main/java/com/moandjiezana/essayist/utils/Tasks.java create mode 100644 src/main/webapp/assets/essayist.js diff --git a/src/main/java/com/moandjiezana/essayist/utils/Tasks.java b/src/main/java/com/moandjiezana/essayist/utils/Tasks.java new file mode 100644 index 0000000..9f42a37 --- /dev/null +++ b/src/main/java/com/moandjiezana/essayist/utils/Tasks.java @@ -0,0 +1,16 @@ +package com.moandjiezana.essayist.utils; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +public class Tasks { + + private final ExecutorService executor = Executors.newCachedThreadPool(); + + public Future run(Runnable runnable) { + return executor.submit(runnable); + } + + +} diff --git a/src/main/java/com/moandjiezana/tent/essayist/Essays.java b/src/main/java/com/moandjiezana/tent/essayist/Essays.java index 5093e18..72be257 100644 --- a/src/main/java/com/moandjiezana/tent/essayist/Essays.java +++ b/src/main/java/com/moandjiezana/tent/essayist/Essays.java @@ -1,6 +1,7 @@ package com.moandjiezana.tent.essayist; import com.google.common.base.Throwables; +import com.moandjiezana.tent.client.TentClient; import com.moandjiezana.tent.client.TentClientAsync; import com.moandjiezana.tent.client.posts.Post; import com.moandjiezana.tent.client.posts.PostQuery; @@ -45,4 +46,16 @@ public class Essays { return posts; } + + public List getFeed(User user) { + TentClient tentClient = new TentClient(user.getProfile()); + tentClient.getAsync().setAccessToken(user.getAccessToken()); + tentClient.getAsync().setRegistrationResponse(user.getRegistration()); + + return tentClient.getPosts(new PostQuery().postTypes(Post.Types.essay("v0.1.0"))); + } + + public Post get(String essayId) { + return null; + } } diff --git a/src/main/java/com/moandjiezana/tent/essayist/MyFeedServlet.java b/src/main/java/com/moandjiezana/tent/essayist/MyFeedServlet.java index f1ce87a..2243846 100644 --- a/src/main/java/com/moandjiezana/tent/essayist/MyFeedServlet.java +++ b/src/main/java/com/moandjiezana/tent/essayist/MyFeedServlet.java @@ -1,21 +1,15 @@ package com.moandjiezana.tent.essayist; -import com.moandjiezana.tent.client.TentClient; +import com.moandjiezana.essayist.utils.Tasks; import com.moandjiezana.tent.client.posts.Post; -import com.moandjiezana.tent.client.posts.PostQuery; import com.moandjiezana.tent.client.users.Profile; import com.moandjiezana.tent.essayist.auth.Authenticated; import com.moandjiezana.tent.essayist.tent.Entities; import java.io.IOException; -import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import javax.inject.Inject; import javax.inject.Provider; @@ -32,17 +26,18 @@ import org.slf4j.LoggerFactory; public class MyFeedServlet extends HttpServlet { private static final Logger LOGGER = LoggerFactory.getLogger(MyFeedServlet.class); - public static final ExecutorService EXECUTOR = Executors.newCachedThreadPool(); private final Templates templates; private Essays essays; private Users users; private Provider sessions; + private Tasks tasks; @Inject - public MyFeedServlet(Users users, Essays essays, Provider sessions, Templates templates) { + public MyFeedServlet(Users users, Essays essays, Tasks tasks, Provider sessions, Templates templates) { this.users = users; this.essays = essays; + this.tasks = tasks; this.sessions = sessions; this.templates = templates; } @@ -52,10 +47,7 @@ public class MyFeedServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { User user = sessions.get().getUser(); - TentClient tentClient = new TentClient(user.getProfile()); - tentClient.getAsync().setAccessToken(user.getAccessToken()); - tentClient.getAsync().setRegistrationResponse(user.getRegistration()); - List essays = tentClient.getPosts(new PostQuery().postTypes(Post.Types.essay("v0.1.0"))); + List essaysFeed = essays.getFeed(user); List allUsers = users.getAll(); final Map profiles = new ConcurrentHashMap(); @@ -64,49 +56,13 @@ public class MyFeedServlet extends HttpServlet { profiles.put(aUser.getProfile().getCore().getEntity(), aUser.getProfile()); } - List> missingUsers = new ArrayList>(); - int missingUsersCount = 0; - - for (Post essay : essays) { + for (Post essay : essaysFeed) { if (!profiles.containsKey(essay.getEntity())) { - missingUsersCount++; + users.fetch(essay.getEntity()); } } - final CountDownLatch countDownLatch = new CountDownLatch(missingUsersCount); - - for (final Post essay : essays) { - if (profiles.containsKey(essay.getEntity())) { - continue; - } - - missingUsers.add(new Callable() { - @Override - public Profile call() throws Exception { - try { - TentClient tentClientAsync = new TentClient(essay.getEntity()); - Profile profile = tentClientAsync.getProfile(); - users.save(new User(profile)); - profiles.put(essay.getEntity(), profile); - - return profile; - } finally { - countDownLatch.countDown(); - } - } - }); - } - - if (!missingUsers.isEmpty()) { - try { - EXECUTOR.invokeAll(missingUsers); - countDownLatch.await(); - } catch (Exception e) { - LOGGER.error("Problem while fetching missing profiles", e); - } - } - - templates.read().setEssays(essays).render(resp.getWriter(), profiles); + templates.read().setEssays(essaysFeed).render(resp.getWriter(), profiles); } @Override diff --git a/src/main/java/com/moandjiezana/tent/essayist/Users.java b/src/main/java/com/moandjiezana/tent/essayist/Users.java index 876abd5..02d94de 100644 --- a/src/main/java/com/moandjiezana/tent/essayist/Users.java +++ b/src/main/java/com/moandjiezana/tent/essayist/Users.java @@ -5,6 +5,8 @@ import com.google.common.io.CharStreams; import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.moandjiezana.essayist.utils.Tasks; +import com.moandjiezana.tent.client.TentClient; import com.moandjiezana.tent.client.apps.RegistrationResponse; import com.moandjiezana.tent.client.users.Profile; import com.moandjiezana.tent.oauth.AccessToken; @@ -24,15 +26,21 @@ import org.apache.commons.dbutils.BeanProcessor; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanListHandler; import org.apache.commons.dbutils.handlers.MapHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @Singleton public class Users { + private static final Logger LOGGER = LoggerFactory.getLogger(Users.class); + private final QueryRunner queryRunner; private final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); + private Tasks tasks; @Inject - public Users(QueryRunner queryRunner) { + public Users(Tasks tasks, QueryRunner queryRunner) { + this.tasks = tasks; this.queryRunner = queryRunner; } @@ -91,6 +99,20 @@ public class Users { } } + public void fetch(final String entity) { + tasks.run(new Runnable() { + @Override + public void run() { + try { + Profile updatedProfile = new TentClient(entity).getProfile(); + save(new User(updatedProfile)); + } catch (Exception e) { + LOGGER.error("Could not fetch Profile", Throwables.getRootCause(e)); + } + } + }); + } + private T convert(Object value, Class objectClass) { String s; if (value instanceof String) { diff --git a/src/main/java/com/moandjiezana/tent/essayist/config/JamonContext.java b/src/main/java/com/moandjiezana/tent/essayist/config/JamonContext.java index ddc3b03..16dcbe2 100644 --- a/src/main/java/com/moandjiezana/tent/essayist/config/JamonContext.java +++ b/src/main/java/com/moandjiezana/tent/essayist/config/JamonContext.java @@ -4,6 +4,7 @@ import com.google.common.base.Splitter; import com.moandjiezana.tent.client.users.Profile; import com.moandjiezana.tent.essayist.EssayistSession; import com.moandjiezana.tent.essayist.User; +import com.moandjiezana.tent.essayist.security.Csrf; import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; @@ -14,6 +15,7 @@ public class JamonContext { public final String contextPath; public final Routes routes; + public final Csrf csrf = new Csrf(); private final HttpServletRequest req; public final String currentUrl; diff --git a/src/main/java/com/moandjiezana/tent/essayist/config/Routes.java b/src/main/java/com/moandjiezana/tent/essayist/config/Routes.java index 937527e..38f81dc 100644 --- a/src/main/java/com/moandjiezana/tent/essayist/config/Routes.java +++ b/src/main/java/com/moandjiezana/tent/essayist/config/Routes.java @@ -17,6 +17,10 @@ public class Routes { this.req = req; } + public String assets(String asset) { + return req.getContextPath() + "/assets/" + asset; + } + public String essay(Post essay) { return req.getContextPath() + "/" + Entities.getForUrl(essay.getEntity()) + "/essay/" + essay.getId(); } diff --git a/src/main/java/com/moandjiezana/tent/essayist/tent/Entities.java b/src/main/java/com/moandjiezana/tent/essayist/tent/Entities.java index 17be49b..33fb2cf 100644 --- a/src/main/java/com/moandjiezana/tent/essayist/tent/Entities.java +++ b/src/main/java/com/moandjiezana/tent/essayist/tent/Entities.java @@ -39,6 +39,6 @@ public class Entities { } public static String getName(Profile profile, String fallback) { - return profile.getBasic() != null && !Strings.isNullOrEmpty(profile.getBasic().getName()) ? profile.getBasic().getName() : fallback; + return profile != null && profile.getBasic() != null && !Strings.isNullOrEmpty(profile.getBasic().getName()) ? profile.getBasic().getName() : fallback; } } diff --git a/src/main/templates/com/moandjiezana/tent/essayist/EssaysTemplate.jamon b/src/main/templates/com/moandjiezana/tent/essayist/EssaysTemplate.jamon index 1c6b960..c357f17 100644 --- a/src/main/templates/com/moandjiezana/tent/essayist/EssaysTemplate.jamon +++ b/src/main/templates/com/moandjiezana/tent/essayist/EssaysTemplate.jamon @@ -12,7 +12,7 @@ Profile profile; String active = "My Feed"; <&| Layout; active = active; &> -
+
<%if profile.getBasic() != null && profile.getBasic().getAvatarUrl() != null %> @@ -29,6 +29,6 @@ String active = "My Feed";
<%for Post essay : essays %> - <& partials/EssayLink; essay = essay; &> + <& partials/EssayLink; essay = essay; profile = profile; showProfile = false; &> \ No newline at end of file diff --git a/src/main/templates/com/moandjiezana/tent/essayist/Layout.jamon b/src/main/templates/com/moandjiezana/tent/essayist/Layout.jamon index 768a3a1..5cf576b 100644 --- a/src/main/templates/com/moandjiezana/tent/essayist/Layout.jamon +++ b/src/main/templates/com/moandjiezana/tent/essayist/Layout.jamon @@ -2,6 +2,7 @@ <%import> com.moandjiezana.tent.essayist.tent.*; +com.moandjiezana.tent.essayist.config.*; <%frag body/> <%args> @@ -48,7 +49,7 @@ String url; font-family: 'Alegreya', Georgia, serif; font-size: 1.5em; } - .essaySummary { + .separator { border-bottom: 1px solid #eee; } @@ -59,6 +60,7 @@ String url; + @@ -90,11 +92,11 @@ String url;
-
+
<& body &> -
- Version: 20121025
+
+ Version: 20121025
diff --git a/src/main/templates/com/moandjiezana/tent/essayist/partials/EssayLink.jamon b/src/main/templates/com/moandjiezana/tent/essayist/partials/EssayLink.jamon index 175247d..d94fe6b 100644 --- a/src/main/templates/com/moandjiezana/tent/essayist/partials/EssayLink.jamon +++ b/src/main/templates/com/moandjiezana/tent/essayist/partials/EssayLink.jamon @@ -9,30 +9,48 @@ java.util.*; <%args> Post essay; Profile profile = null; +boolean showProfile = true; <%java> final SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy"); final EssayContent essayContent = essay.getContentAs(EssayContent.class); +String entityForUrl = Entities.getForUrl(essay.getEntity()); +String entityName = Entities.getName(profile, essay.getEntity()); +String authorPageUrl = jamonContext.contextPath + "/" + entityForUrl + "/essays"; +String formattedPublicationDate = dateFormat.format(new Date(essay.getPublishedAt() * 1000)); -
- <%if profile != null %> -
- <%if profile.getBasic() != null && profile.getBasic().getAvatarUrl() != null %> - - -
- -
-

<% essayContent.getTitle() %>

- <%if profile != null %><% Entities.getName(profile, essay.getEntity()) %> <% dateFormat.format(new Date(essay.getPublishedAt() * 1000)) %> -

<% essayContent.getExcerpt() %>

- <%if jamonContext.getCurrentUser().owns(essay) %> -
-
- - -
+
+
+ <%if showProfile %> +
+ <%if profile != null && profile.getBasic() != null && profile.getBasic().getAvatarUrl() != null %> + +
+
+

<% essayContent.getTitle() %>

+ <%if showProfile %><% entityName %> <% formattedPublicationDate %> +

<% essayContent.getExcerpt() %>

+ <%if jamonContext.getCurrentUser().owns(essay) %> +
+
+ + +
+
+ +
+
+ +
diff --git a/src/main/webapp/assets/essayist.js b/src/main/webapp/assets/essayist.js new file mode 100644 index 0000000..8194ec4 --- /dev/null +++ b/src/main/webapp/assets/essayist.js @@ -0,0 +1,65 @@ +function init() { + var essayContainers = document.querySelectorAll("[data-essay=container]"); + var i; + + var displaySection = function (section, target) { + var j; + var detailDisplay = section === "essay" ? "block" : "none"; + var listDisplay = section === "list" ? "block" : "none"; + + if (target !== document) { + target.querySelector("[data-essay=summary]").style.display = listDisplay; + target.querySelector("[data-essay=full]").style.display = detailDisplay; + } + + for (j = 0; j < essayContainers.length; j++) { + var essayContainer = essayContainers[j]; + if (essayContainer !== target) { + essayContainer.style.display = listDisplay; + essayContainer.querySelector("[data-essay=summary]").style.display = listDisplay; + essayContainer.querySelector("[data-essay=full]").style.display = detailDisplay; + } + } + + var intro = document.querySelector("[data-essay=intro]"); + if (intro !== null) { + intro.style.display = listDisplay; + } + }; + + var essayClickHandler = function (event) { + + if (event.target.dataset.essay !== "link") { + return; + } + + event.preventDefault(); + event.stopPropagation(); + + displaySection("essay", event.currentTarget); + + history.pushState({ essayId: event.currentTarget.dataset.essayId, essayAuthor: event.currentTarget.dataset.essayAuthor }, "essay title", event.target.href); + + return false; + }; + + var essayPopStateHandler = function (event) { + var essayContainer; + if (window.location.href.indexOf("/essays") > -1 || window.location.href.indexOf("/global") > -1 || window.location.href.indexOf("/read") > -1) { + displaySection("list", document); + } else if (event.state !== null) { + essayContainer = document.querySelector("[data-essay-author=\"" + event.state.essayAuthor + "\"][data-essay-id=\"" + event.state.essayId + "\"]"); + displaySection("essay", essayContainer); + } + }; + + for (i = 0; i < essayContainers.length; i++) { + essayContainers[i].addEventListener("click", essayClickHandler, false); + } + + window.addEventListener("popstate", essayPopStateHandler, false); +} + +if (history.pushState !== undefined) { + document.addEventListener("DOMContentLoaded", init, false); +}