--- a/server/src/main/java/org/iri_research/renkan/controller/RenkanRootController.java Thu Apr 04 15:35:15 2013 +0200
+++ b/server/src/main/java/org/iri_research/renkan/controller/RenkanRootController.java Thu Apr 04 18:20:59 2013 +0200
@@ -23,6 +23,7 @@
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.servlet.ModelAndView;
@@ -52,7 +53,7 @@
}
@RequestMapping(value="/s/{space_id}", method = RequestMethod.GET, produces={"text/html;charset=UTF-8"})
- public ModelAndView spaceIndex(@PathVariable("space_id") String spaceId, @PageableDefaults(sort={"updated","created"}, sortDir=Direction.DESC, pageNumber=0, value=Constants.PAGINATION_SIZE) Pageable p, HttpServletRequest request) {
+ public ModelAndView spaceIndex(@PathVariable("space_id") String spaceId, @RequestParam(required=false) String filter, @PageableDefaults(sort={"updated","created"}, sortDir=Direction.DESC, pageNumber=0, value=Constants.PAGINATION_SIZE) Pageable p, HttpServletRequest request) {
logger.debug("SpaceId : " + (spaceId== null ? "null" : spaceId));
@@ -69,9 +70,14 @@
}
model.put("space", space);
-
- Page<Project> page = this.projectsRepository.findBySpaceId(spaceId, p);
-
+ Page<Project> page;
+ if(filter != null && !filter.isEmpty()) {
+ page = this.projectsRepository.findBySpaceIdAndTitleRegex(spaceId, filter, p);
+ }
+ else {
+ page = this.projectsRepository.findBySpaceId(spaceId, p);
+ }
+
model.put("page", page);
model.put("baseUrl", Utils.buildBaseUrl(request));
--- a/server/src/main/java/org/iri_research/renkan/repositories/ProjectsRepository.java Thu Apr 04 15:35:15 2013 +0200
+++ b/server/src/main/java/org/iri_research/renkan/repositories/ProjectsRepository.java Thu Apr 04 18:20:59 2013 +0200
@@ -5,9 +5,15 @@
import org.iri_research.renkan.models.Project;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
+import org.springframework.data.mongodb.repository.Query;
public interface ProjectsRepository extends IRenkanRepository<Project, String>, ProjectsRepositoryCustom {
List<Project> findBySpaceId(String spaceId);
Page<Project> findBySpaceId(String spaceId, Pageable p);
+
+ @Query("{ 'space_id' : ?0, 'title' : { '$regex':?1, '$options': 'i'} }")
+ List<Project> findBySpaceIdAndTitleRegex(String spaceId, String title);
+ @Query("{ 'space_id' : ?0, 'title' : { '$regex':?1, '$options': 'i'} }")
+ Page<Project> findBySpaceIdAndTitleRegex(String spaceId, String title, Pageable p);
}
--- a/server/src/main/webapp/WEB-INF/i18n/messages_en.properties Thu Apr 04 15:35:15 2013 +0200
+++ b/server/src/main/webapp/WEB-INF/i18n/messages_en.properties Thu Apr 04 18:20:59 2013 +0200
@@ -17,6 +17,7 @@
renkanIndex.project_delete_link = Delete renkan
renkanIndex.project_render_link = View renkan
renkanIndex.project_delete_confirm = Delete renkan "<%= title %>" ?
+renkanIndex.project_filter = Filter title
renkanIndex.space_exp = Create a space
renkanIndex.renkan_spaces = Renkan Spaces
--- a/server/src/main/webapp/WEB-INF/i18n/messages_fr.properties Thu Apr 04 15:35:15 2013 +0200
+++ b/server/src/main/webapp/WEB-INF/i18n/messages_fr.properties Thu Apr 04 18:20:59 2013 +0200
@@ -18,6 +18,7 @@
renkanIndex.project_delete_link = Eff. renkan
renkanIndex.project_render_link = Consult. renkan
renkanIndex.project_delete_confirm = Voulez-vous effacer le renkan "<%= title %>" ?
+renkanIndex.project_filter = Filtre titre
renkanIndex.space_exp = Créer un espace
renkanIndex.renkan_spaces = Espaces Renkan
--- a/server/src/main/webapp/WEB-INF/templates/projectIndex.html Thu Apr 04 15:35:15 2013 +0200
+++ b/server/src/main/webapp/WEB-INF/templates/projectIndex.html Thu Apr 04 18:20:59 2013 +0200
@@ -30,46 +30,54 @@
<button type="submit">OK</button>
</form>
</div>
- <h2 th:text="#{renkanIndex.project_list}">Project list</h2>
- <div th:include="fragment/paginationFragment :: paginationFragment" class="pagination-container">
- <div>
- <a href="#?p.page=1"><<</a>
- <a href="#?p.page=3"><</a>
- <span>...</span>
- <a href="#?p.page=2">2</a>
- <a href="#?p.page=3">3</a>
- <span>4</span>
- <a href="#?p.page=5">5</a>
- <a href="#?p.page=6">6</a>
- <span>...</span>
- <a href="#?p.page=5">></a>
- <a href="#?p.page=7">>></a>
- </div>
- </div>
- <table th:with="columnSort=${param['p.sort']}?${param['p.sort'][0]}:'updated',sortDir=${param['p.sort.dir']}?${param['p.sort.dir'][0]}:'desc'">
- <thead th:with="sortDirInv=${sortDir}=='desc'?'asc':'desc'">
- <tr>
- <th th:with="sorted=(${columnSort}=='title')"><span th:text="#{renkanIndex.project_name}">Name</span><form method="get" class="proj-sort-form"><input type="hidden" name="p.sort" value="title"/><input type="hidden" name="p.sort.dir" th:value="${sorted}?${sortDirInv}:'desc'"/><input type="submit" class="proj-sortable-col" th:class="${sorted}?'proj-sort-'+${sortDir}+'-col':'proj-sortable-col'" value=""/></form></th>
- <th th:with="sorted=(${columnSort}=='updated')"><span th:text="#{renkanIndex.project_updated}">Updated</span><form method="get" class="proj-sort-form"><input type="hidden" name="p.sort" value="updated"/><input type="hidden" name="p.sort.dir" th:value="(${sorted})?${sortDirInv}:'desc'"/><input type="submit" class="proj-sort-desc-col" th:class="${sorted}?'proj-sort-'+${sortDir}+'-col':'proj-sortable-col'" value=""/></form></th>
- <th th:with="sorted=(${columnSort}=='created')"><span th:text="#{renkanIndex.project_creation}" >Creation</span><form method="get" class="proj-sort-form"><input type="hidden" name="p.sort" value="created"/><input type="hidden" name="p.sort.dir" th:value="${sorted}?${sortDirInv}:'desc'"/><input type="submit" class="proj-sort-asc-col" th:class="${sorted}?'proj-sort-'+${sortDir}+'-col':'proj-sortable-col'" value=""/></form></th>
- <th th:text="#{renkanIndex.project_edit}">Edit</th>
- <th th:text="#{renkanIndex.project_copy}">Copy</th>
- <th th:text="#{renkanIndex.project_delete}">Del.</th>
- <th th:text="#{renkanIndex.project_render}">View</th>
- </tr>
- </thead>
- <tbody>
- <tr th:each="project: ${page}">
- <th th:text="${project.title}">title</th>
- <td th:text="${project.updated}?${#dates.format(project.updated, #messages.msg('date.format'))}:''">update</td>
- <td th:text="${#dates.format(project.created, #messages.msg('date.format'))}">date</td>
- <td><a href="#" th:href="@{'/p/'+${project.id}(cowebkey=${project.getKey(2)})}" th:text="#{renkanIndex.project_edit_link}">Edit project</a></td>
- <td><a href="#" th:text="#{renkanIndex.project_copy_link}" th:attr="data-project_id=${project.id}" class="copy_project">Copy project</a></td>
- <td><a href="#" th:text="#{renkanIndex.project_delete_link}" th:attr="data-project_id=${project.id},data-project_title=${project.title}" class="delete_project">Delete project</a></td>
- <td><a href="#" th:href="@{'/p/pub/'+${project.id}(cowebkey=${project.getKey(1)})}" th:text="#{renkanIndex.project_render_link}">View project</a></td>
- </tr>
- </tbody>
- </table>
+ <div id="project-list-container">
+ <h2 th:text="#{renkanIndex.project_list}">Project list</h2>
+ <div id="project-filter-container">
+ <form method="get">
+ <input type="text" id="project-filter" name="filter" placeholder="filter title" th:placeholder="#{renkanIndex.project_filter}" th:value="${param['filter']}?${param['filter'][0]}:''" />
+ <button type="submit">OK</button>
+ </form>
+ </div>
+ <div th:include="fragment/paginationFragment :: paginationFragment" class="pagination-container">
+ <div>
+ <a href="#?p.page=1"><<</a>
+ <a href="#?p.page=3"><</a>
+ <span>...</span>
+ <a href="#?p.page=2">2</a>
+ <a href="#?p.page=3">3</a>
+ <span>4</span>
+ <a href="#?p.page=5">5</a>
+ <a href="#?p.page=6">6</a>
+ <span>...</span>
+ <a href="#?p.page=5">></a>
+ <a href="#?p.page=7">>></a>
+ </div>
+ </div>
+ <table th:with="columnSort=${param['p.sort']}?${param['p.sort'][0]}:'updated',sortDir=${param['p.sort.dir']}?${param['p.sort.dir'][0]}:'desc'">
+ <thead th:with="sortDirInv=${sortDir}=='desc'?'asc':'desc'">
+ <tr>
+ <th th:with="sorted=(${columnSort}=='title')"><span th:text="#{renkanIndex.project_name}">Name</span><form method="get" class="proj-sort-form"><input type="hidden" name="p.sort" value="title"/><input type="hidden" name="p.sort.dir" th:value="${sorted}?${sortDirInv}:'desc'"/><input th:if="${param['filter']}" name="filter" type="hidden" th:value="${param['filter'][0]}"/><input type="submit" class="proj-sortable-col" th:class="${sorted}?'proj-sort-'+${sortDir}+'-col':'proj-sortable-col'" value=""/></form></th>
+ <th th:with="sorted=(${columnSort}=='updated')"><span th:text="#{renkanIndex.project_updated}">Updated</span><form method="get" class="proj-sort-form"><input type="hidden" name="p.sort" value="updated"/><input type="hidden" name="p.sort.dir" th:value="(${sorted})?${sortDirInv}:'desc'"/><input th:if="${param['filter']}" name="filter" type="hidden" th:value="${param['filter'][0]}"/><input type="submit" class="proj-sort-desc-col" th:class="${sorted}?'proj-sort-'+${sortDir}+'-col':'proj-sortable-col'" value=""/></form></th>
+ <th th:with="sorted=(${columnSort}=='created')"><span th:text="#{renkanIndex.project_creation}" >Creation</span><form method="get" class="proj-sort-form"><input type="hidden" name="p.sort" value="created"/><input type="hidden" name="p.sort.dir" th:value="${sorted}?${sortDirInv}:'desc'"/><input th:if="${param['filter']}" name="filter" type="hidden" th:value="${param['filter'][0]}"/><input type="submit" class="proj-sort-asc-col" th:class="${sorted}?'proj-sort-'+${sortDir}+'-col':'proj-sortable-col'" value=""/></form></th>
+ <th th:text="#{renkanIndex.project_edit}">Edit</th>
+ <th th:text="#{renkanIndex.project_copy}">Copy</th>
+ <th th:text="#{renkanIndex.project_delete}">Del.</th>
+ <th th:text="#{renkanIndex.project_render}">View</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr th:each="project: ${page}">
+ <th th:text="${project.title}">title</th>
+ <td th:text="${project.updated}?${#dates.format(project.updated, #messages.msg('date.format'))}:''">update</td>
+ <td th:text="${#dates.format(project.created, #messages.msg('date.format'))}">date</td>
+ <td><a href="#" th:href="@{'/p/'+${project.id}(cowebkey=${project.getKey(2)})}" th:text="#{renkanIndex.project_edit_link}">Edit project</a></td>
+ <td><a href="#" th:text="#{renkanIndex.project_copy_link}" th:attr="data-project_id=${project.id}" class="copy_project">Copy project</a></td>
+ <td><a href="#" th:text="#{renkanIndex.project_delete_link}" th:attr="data-project_id=${project.id},data-project_title=${project.title}" class="delete_project">Delete project</a></td>
+ <td><a href="#" th:href="@{'/p/pub/'+${project.id}(cowebkey=${project.getKey(1)})}" th:text="#{renkanIndex.project_render_link}">View project</a></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
</div>
<footer id="footer" th:include="fragment/pageFragment :: footerFragment">
<div id="version">© <span class="version-date">2013</span> <a href="http://www.iri.centrepompidou.fr" target="_blanck">IRI</a> - Version <span class="version-version">0.0</span></div>
--- a/server/src/main/webapp/static/css/index.css Thu Apr 04 15:35:15 2013 +0200
+++ b/server/src/main/webapp/static/css/index.css Thu Apr 04 18:20:59 2013 +0200
@@ -131,24 +131,26 @@
background: #444;
}
- #label {
+#label {
font: 30px verdana,arial,sans-serif bold;
text-align: center;
text-shadow: 0 1px 1px #fff;
height: 70px;
line-height: 70px;
margin: 16px auto 0;
- }
- form {
+}
+
+form {
height: 38px;
position: relative;
- }
- button, input {
+}
+
+button, input {
font-weight: bold;
font-size: 15px;
- }
+}
- #inner input[type="text"] {
+#inner input[type="text"] {
background: #fff;
border: 1px solid #bbb;
border-radius: 3px;
@@ -162,7 +164,7 @@
position: absolute;
}
- #inner button[type="submit"] {
+#inner button[type="submit"] {
position: absolute;
right: -45px;
width: 45px;
@@ -278,6 +280,26 @@
margin-right: 12px;
}
+#project-filter-container {
+ margin: 12px 0 0 15px;
+}
+
+#project-filter-container input[type="text"] {
+ background: #fff;
+ border: 1px solid #bbb;
+ border-radius: 3px;
+ padding: 2px 10px 3px;
+ box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ outline: none;
+ font-weight: normal;
+ font-size: 15px;
+}
+
+#project-filter-container button[type="submit"] {
+
+}
+
.proj-sort-form {
float: right;
height: auto;
--- a/server/src/test/java/org/iri_research/renkan/test/repositories/ProjectsRepositoryTest.java Thu Apr 04 15:35:15 2013 +0200
+++ b/server/src/test/java/org/iri_research/renkan/test/repositories/ProjectsRepositoryTest.java Thu Apr 04 18:20:59 2013 +0200
@@ -26,6 +26,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.geo.Point;
import org.springframework.test.context.ContextConfiguration;
@@ -88,7 +90,7 @@
testSpace = new Space(this.spaceIds.get(i), "test space " + i, "Test space " + i, null, null, null, "test_user", null, this.creationDate);
testSpace = spacesRepository.save(testSpace);
for(int j=0; j<SPACE_NB-1-i; j++) {
- pl.add(new Project(testSpace.getId(), UUID.randomUUID().toString(), "test"+((SPACE_NB-1)*i+j+1), "desc"+((SPACE_NB-1)*i+j+1), "http://localhost:8080/rest/projects/id"+((SPACE_NB-1)*i+j+1), this.creationDate));
+ pl.add(new Project(testSpace.getId(), UUID.randomUUID().toString(), "test project "+((SPACE_NB-1)*i+j+1), "desc"+((SPACE_NB-1)*i+j+1), "http://localhost:8080/rest/projects/id"+((SPACE_NB-1)*i+j+1), this.creationDate));
}
}
for(Project p: projectsRepository.save(pl)) {
@@ -425,4 +427,64 @@
}
+ @Test
+ public void testFindBySpaceIdAndTitleRegex() {
+
+ List<Project> res = this.projectsRepository.findBySpaceIdAndTitleRegex(this.spaceIds.get(0), ".*Project.*");
+
+ Assert.assertEquals("res must have length " + (SPACE_NB -1) , SPACE_NB-1, res.size());
+ for(Project p: res) {
+ Assert.assertEquals("project must belong to the same space", this.spaceIds.get(0), p.getSpaceId());
+ Assert.assertNotNull("project title must not be null", p.getTitle());
+ Assert.assertTrue("project tilte must contains title", p.getTitle().matches(".*project.*"));
+ }
+ }
+
+ @Test
+ public void testFindBySpaceIdAndTitleRegexBad() {
+
+ List<Project> res = this.projectsRepository.findBySpaceIdAndTitleRegex(this.spaceIds.get(0), ".*foo.*");
+
+ Assert.assertEquals("res must have zero length", 0, res.size());
+ }
+
+ @Test
+ public void testFindBySpaceIdAndTitleRegexPageable() {
+
+ PageRequest pr = new PageRequest(0, 1);
+
+ Page<Project> res = this.projectsRepository.findBySpaceIdAndTitleRegex(this.spaceIds.get(0), ".*Project.*", pr);
+
+ Assert.assertEquals("res must have length 1", 1, res.getNumberOfElements());
+ for(Project p: res) {
+ Assert.assertEquals("project must belong to the same space", this.spaceIds.get(0), p.getSpaceId());
+ Assert.assertNotNull("project title must not be null", p.getTitle());
+ Assert.assertTrue("project tilte must contains title", p.getTitle().matches(".*project.*"));
+ }
+ }
+
+ @Test
+ public void testFindBySpaceIdAndTitleRegexPageableBadSize() {
+
+ PageRequest pr = new PageRequest(0, 3);
+
+ Page<Project> res = this.projectsRepository.findBySpaceIdAndTitleRegex(this.spaceIds.get(0), ".*Project.*", pr);
+
+ Assert.assertEquals("res must have length 2", 2, res.getNumberOfElements());
+ for(Project p: res) {
+ Assert.assertEquals("project must belong to the same space", this.spaceIds.get(0), p.getSpaceId());
+ Assert.assertNotNull("project title must not be null", p.getTitle());
+ Assert.assertTrue("project tilte must contains title", p.getTitle().matches(".*project.*"));
+ }
+ }
+
+ @Test
+ public void testFindBySpaceIdAndTitleRegexPageableBadPage() {
+
+ PageRequest pr = new PageRequest(1, 3);
+
+ Page<Project> res = this.projectsRepository.findBySpaceIdAndTitleRegex(this.spaceIds.get(0), ".*Project.*", pr);
+
+ Assert.assertEquals("res must have length 0", 0, res.getNumberOfElements());
+ }
}