From f5a3a60fed1d72bc45c258844808388684e1a91c Mon Sep 17 00:00:00 2001
From: Moises Sacal <moisbo@gmail.com>
Date: Fri, 4 Oct 2019 19:28:23 +1000
Subject: [PATCH] added faceting ability eresearch/rdm#1303

---
 src/components/FacetController.js | 34 ++++++++++++++++++++++
 src/components/Router.js          | 48 ++++++++++++++++++++++---------
 src/components/SolrService.js     | 41 +++++++++++---------------
 src/components/views/Facets.js    | 35 ++++++++++++++++++++++
 src/components/views/ListDocs.js  | 11 +++++--
 src/components/views/Main.js      | 21 ++++++--------
 src/index.js                      |  7 +++++
 src/styles/media.scss             |  2 +-
 src/styles/theme.scss             | 14 +++++----
 9 files changed, 155 insertions(+), 58 deletions(-)
 create mode 100644 src/components/FacetController.js
 create mode 100644 src/components/views/Facets.js

diff --git a/src/components/FacetController.js b/src/components/FacetController.js
new file mode 100644
index 0000000..1795311
--- /dev/null
+++ b/src/components/FacetController.js
@@ -0,0 +1,34 @@
+const FacetController = {
+  get: function({data:arr, toJSON:toJSON}) {
+    const facets = [];
+    arr.map(function(value, index, Arr) {
+      if(index % 2 == 0) {
+        if(toJSON){
+          try{
+            value = JSON.parse(value)
+          }catch(e){console.error(e.message);}}
+          const facet = {
+            value: value, count: arr[index + 1]
+          }
+          facets.push(facet);
+        }
+      });
+      return facets;
+    },
+    display: function ({data: data, config: config}) {
+      const displays = [];
+      data.forEach(function(d){
+        const displayFacet = {
+          count:d.count,
+          name: config.name,
+          route: config.route,
+          searchUrl: d.value[config.searchUrl],
+          searchText: d.value[config.searchText]
+        }
+        displays.push(displayFacet)
+      });
+      return displays;
+    }
+  }
+
+  module.exports = FacetController;
diff --git a/src/components/Router.js b/src/components/Router.js
index 5d14a77..3f4b6ad 100644
--- a/src/components/Router.js
+++ b/src/components/Router.js
@@ -6,6 +6,8 @@ const Footer = require('./views/footer');
 const Search = require('./views/search');
 const ViewDoc = require('./views/ViewDoc');
 const ViewError = require('./views/ViewError');
+const FacetController = require('./FacetController');
+
 const solrService = require('./SolrService');
 
 const Router = async function (state) {
@@ -30,19 +32,20 @@ const Router = async function (state) {
             start: 0,
             page: 1,
             searchParam: 'author_id%3A',
-            text: state.main.doc.id
+            text: state.main.doc.id,
+            facets: false
           });
           if (status === 200) {
             state.main.related = res.data.docs || [];
           }
         }
-        app.innerHTML = [Header(state), Menu(state), Container([Search(state), ViewDoc(state)]), Footer(state)].join('');
+        app.innerHTML = [Container([Header(state), Menu(state), Search(state), ViewDoc(state), Footer(state)])].join('');
       } else {
-        app.innerHTML = [Header(state), Menu(state), Container([Search(state), ViewError(state)]), Footer(state)].join('');
+        app.innerHTML = [Container([Header(state), Menu(state), Search(state), ViewError(state), Footer(state)])].join('');
       }
     }
     if (verb === '#search/') {
-      //TODO: make this better
+      //TODO: make the split better
       const splits = match[2].split('/');
       const start = splits[0] || state.main.start;
       const page = splits[1] || '';
@@ -51,30 +54,49 @@ const Router = async function (state) {
         start: start,
         page: page,
         searchParam: 'main_search%3A',
-        text: searchText
+        text: searchText,
+        facets: state.facets,
+        facetLimit: state.facetLimit
       });
       if (status === 200) {
         state.main.docs = data.docs;
         state.main.numFound = data.numFound;
         state.main.searchText = searchText;
-        app.innerHTML = [Header(state), Menu(state), Container([Search(state), Main(state)]), Footer(state)].join('');
+        app.innerHTML = [Container([Header(state), Menu(state), Search(state), Main(state), Footer(state)])].join('');
         const input = document.getElementById('text-to-search');
         if (input) {
           input.value = searchText;
         }
       } else {
-        app.innerHTML = [Header(state), Menu(state), Container([Search(state), ViewError(state)]), Footer(state)].join('');
+        app.innerHTML = [Container([Header(state), Menu(state), Search(state), ViewError(state), Footer(state)])].join('');
       }
     }
   } else {
-    const {data, status} = await solrService.searchAll({api: state.config.api});
-    if (status === 200) {
-      state.main.docs = data.docs;
-      state.main.numFound = data.numFound;
+    const res = await solrService.search({api: state.config.api}, {
+      start: state.main.start,
+      page: state.main.page,
+      searchParam: '*%3A',
+      text: '*',
+      facets: state.facets,
+      facetLimit: state.facetLimit
+    });
+    if (res.status === 200) {
+      state.main.docs = res.data.docs;
+      state.main.numFound = res.data.numFound;
       state.main.searchText = '';
-      app.innerHTML = [Header(state), Menu(state), Container([Search(state), Main(state)]), Footer(state)].join('');
+      state.facetResult = res.facets;
+      const facetContent = FacetController.get({data: state.facetResult['facet_fields'][state.facets[0]], toJSON: true});
+      const facetData = FacetController.display({data: facetContent, config: {
+        name: 'Dataset_author_facetmulti',
+        route: '#view/',
+        searchUrl: '@id',
+        searchText: 'name'
+        }
+      });
+      state.facetData = facetData;
+      app.innerHTML = [Container([Header(state), Menu(state), Search(state), Main(state), Footer(state)])].join('');
     } else {
-      app.innerHTML = [Header(state), Menu(state), Container([Search(state), ViewError(state)]), Footer(state)].join('');
+      app.innerHTML = [Container([Header(state), Menu(state), Search(state), ViewError(state), Footer(state)])].join('');
     }
   }
 };
diff --git a/src/components/SolrService.js b/src/components/SolrService.js
index 1567a10..502132a 100644
--- a/src/components/SolrService.js
+++ b/src/components/SolrService.js
@@ -1,43 +1,36 @@
 const axios = require('axios');
 
 const SolrService = {
-  searchAll: async function (config) {
-    try {
-      const req = await axios.get(`${config.api}/select?q=*%3A*`);
-      if (req.data) {
-        return {data: req.data['response'], status: req.status};
-      } else {
-        return {data: [], status: req.status};
-      }
-    } catch (e) {
-      return {data: [], status: e.message};
-    }
-  },
   get: async function (config, data) {
     try {
       let param = `get?id=`;
       data = encodeURIComponent(`${data}`);
-      const req = await axios.get(`${config.api}/${param}${data}`);
-      if (req.data) {
-        return {data: req.data['doc'], status: req.status};
+      const res = await axios.get(`${config.api}/${param}${data}`);
+      if (res.data) {
+        return {data: res.data['doc'], status: res.status};
       } else {
-        return {data: {}, status: req.status};
+        return {data: {}, status: res.status};
       }
     } catch (e) {
       return {data: {}, status: e.message};
     }
   },
-  search: async function (config, {start: start, page: page, searchParam: searchParam, text: text}) {
+  search: async function (config, {start: start, page: page, searchParam: searchParam, text: text, facets: facets, facetLimit: facetLimit}) {
     try {
-      let param = `select?q=`;
-      if (text === '' || !text) {
+      let param = `select?q=`;      
+      if (text === '' || !text ) {
         text = '*';
       }
-      const req = await axios.get(`${config.api}/${param}${searchParam}${text}&start=${start}&page=${page}`);
-      if (req.data) {
-        return {data: req.data['response'], status: req.status};
+      let query = `${param}${searchParam}${text}&start=${start}&page=${page}`;
+
+      if(facets) {
+        query += `&facet=true%20&facet.field=${[...facets].join('&facet.field')}&facet.limit=${facetLimit || 5}`;
+      }
+      const res = await axios.get(`${config.api}/${query}`);
+      if (res.data) {
+        return {data: res.data['response'], facets: res.data['facet_counts'], status: res.status};
       } else {
-        return {data: [], status: req.status};
+        return {data: [], status: res.status};
       }
     } catch (e) {
       return {data: [], status: e.message};
@@ -45,4 +38,4 @@ const SolrService = {
   }
 };
 
-module.exports = SolrService;
\ No newline at end of file
+module.exports = SolrService;
diff --git a/src/components/views/Facets.js b/src/components/views/Facets.js
new file mode 100644
index 0000000..d381228
--- /dev/null
+++ b/src/components/views/Facets.js
@@ -0,0 +1,35 @@
+const $ = require("jquery");
+const isIterable = require('../isIterable');
+
+const Facets = function (data) {
+  let html = `<ul class="list-group col-sm-4">`;
+
+  if(isIterable(data.facetsDisplay)){
+    for(let fd of data.facetsDisplay){
+      html += `<li class="list-group-item">
+      <div>
+         <h4>${fd.displayText}</h4>
+         <hr/>
+         <ul class="list-group">`;
+         const currentFacet = data.facetData.filter((f)=>{
+           return f.name === fd.name;
+         });
+        if(isIterable(currentFacet)){
+           for(let facet of currentFacet){            
+             html += `<li class="row">
+             <div class="col-sm-4">${facet['count']}</div>
+             <div class="col-sm-8"><a href="/${facet['route']}${facet['searchUrl']}">${facet['searchText']}</a></div>
+             </li>`;
+           }
+        }
+         html += `</ul>
+      </div>
+      </li>`
+    }
+  };
+
+  html += `</ul>`;
+  return html;
+};
+
+module.exports = Facets;
diff --git a/src/components/views/ListDocs.js b/src/components/views/ListDocs.js
index 9e85d11..00ce87d 100644
--- a/src/components/views/ListDocs.js
+++ b/src/components/views/ListDocs.js
@@ -1,7 +1,11 @@
+const Facets = require('./Facets');
+
 const ListDocs = function (data) {
   var html = '';
   const docs = data.main.docs;
-  html += `<ul class="list-group">`;
+  html += `<div class="container col-sm-12"><div class="row">`
+  html += Facets(data);
+  html += `<ul class="list-group col-sm-8">`;
   if (docs.length > 0) {
     docs.forEach((d) => {
       var url = `${data.config.repo}${d['uri_id']}/`;
@@ -13,8 +17,9 @@ const ListDocs = function (data) {
   } else {
     html += `<div class="text-center"> No data found</div>`;
   }
-  html += `<div></div><br/></div></ul>`;
+  html += `</ul><div><br/></div>`;
+  html += `</div></div>`;
   return html;
 };
 
-module.exports = ListDocs;
\ No newline at end of file
+module.exports = ListDocs;
diff --git a/src/components/views/Main.js b/src/components/views/Main.js
index d312824..4fbf293 100644
--- a/src/components/views/Main.js
+++ b/src/components/views/Main.js
@@ -3,23 +3,20 @@ const ListDocs = require('./ListDocs');
 const Pagination = require('./Pagination');
 
 const Main = function (data) {
-  let html = `<div class="maincontent-body">
-<div><br/></div>
-
-`;
-  html = [ListDocs(data), Pagination(data)].join('');
-
+  let html = `<div><br/></div>`;
+  html = [ListDocs(data), '<div><br/></div>' ,Pagination(data)].join('');
   html += `
-   <div class="container">
-    <div class="text-center">
-    <p>Results: ${data.main.numFound}</p>
+  <div class="row">
+    <div class="container">
+      <div class="text-center">
+        <p>Results: ${data.main.numFound}</p>
+      </div>
     </div>
   </div>
   <div><br/></div>
-  </div>
-`;
+  `;
 
   return html;
 };
 
-module.exports = Main;
\ No newline at end of file
+module.exports = Main;
diff --git a/src/index.js b/src/index.js
index 5ad51be..ec3816d 100644
--- a/src/index.js
+++ b/src/index.js
@@ -43,6 +43,13 @@ let state = {
       {display: "SubDoc", field: "contactPoint", fieldName: 'Contact Point', template: '${item.name} ${item.email}'},
     ]
   },
+  facets: ['Dataset_author_facetmulti'],
+  facetsDisplay: [
+    {name: 'Dataset_author_facetmulti', displayText: 'Top Authors'},
+    //{name: 'Keywords', displayText: 'Top Keywords'}
+  ],
+  facetData: [],
+  facetLimit: 5,
   footer:{
     text: '2019 University of Technology Sydney'
   },
diff --git a/src/styles/media.scss b/src/styles/media.scss
index 0ce0b90..dfd5913 100644
--- a/src/styles/media.scss
+++ b/src/styles/media.scss
@@ -13,7 +13,7 @@
   }
 }
 
-@media only screen and (max-width: 430px) {
+@media only screen and (max-width: 490px) {
   .header-main a {
     font-size: 23px !important;
     bottom: 23px !important;
diff --git a/src/styles/theme.scss b/src/styles/theme.scss
index 1cccca2..c569751 100644
--- a/src/styles/theme.scss
+++ b/src/styles/theme.scss
@@ -22,15 +22,20 @@ html, body {
 }
 
 .content-inside {
-  //padding: 20px;
-  padding-bottom: 70px;
+  min-height: 100vh; /* will cover the 100% of viewport */
+  overflow: hidden;
+  display: block;
+  position: relative;
+  padding-bottom: 100px; /* height of your footer */
 }
 
 .footer {
-  height: 70px;
-  margin-top: -70px;
+  position: absolute;
+  bottom: 0;
+  width: 100%;
   background-color: #000;
   color: #fff;
+  height: 70px;
 }
 
 .footer p {
@@ -93,4 +98,3 @@ html, body {
   width: 500px;
   background-color: $refiner-card-bg-color;
 }
-
-- 
GitLab