Add filter on projects
authorymh <ymh.work@gmail.com>
Thu, 04 Apr 2013 18:20:59 +0200
changeset 137 efc0fce30814
parent 136 a822178766ce
child 138 e5be5cc9f09b
Add filter on projects
server/src/main/java/org/iri_research/renkan/controller/RenkanRootController.java
server/src/main/java/org/iri_research/renkan/repositories/ProjectsRepository.java
server/src/main/webapp/WEB-INF/i18n/messages_en.properties
server/src/main/webapp/WEB-INF/i18n/messages_fr.properties
server/src/main/webapp/WEB-INF/templates/projectIndex.html
server/src/main/webapp/static/css/index.css
server/src/test/java/org/iri_research/renkan/test/repositories/ProjectsRepositoryTest.java
--- 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">&lt;&lt;</a>                    
-                    <a href="#?p.page=3">&lt;</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">&gt;</a>
-                    <a href="#?p.page=7">&gt;&gt;</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">&lt;&lt;</a>                    
+                      <a href="#?p.page=3">&lt;</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">&gt;</a>
+                      <a href="#?p.page=7">&gt;&gt;</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());
+	}
 }