Changeset 5784 for SRUAggregator


Ignore:
Timestamp:
11/06/14 18:36:44 (10 years ago)
Author:
emanuel.dima@uni-tuebingen.de
Message:

more code cleanup + UI improvements: kwic checkbox, info popover

Location:
SRUAggregator/trunk
Files:
1 added
1 deleted
21 edited

Legend:

Unmodified
Added
Removed
  • SRUAggregator/trunk/build.sh

    r5771 r5784  
    66        node_modules/bower/bin/bower install jquery bootstrap react react-addons react-bootstrap
    77
    8         cp bower_components/bootstrap/dist/css/bootstrap.min.css src/main/webapp/lib/bootstrap.min.css
    9         cp bower_components/jquery/dist/jquery.min.js src/main/webapp/lib/jquery.min.js
    10         cp bower_components/react/react-with-addons.js src/main/webapp/lib/react-with-addons.js
    11         cp bower_components/react/react-with-addons.min.js src/main/webapp/lib/react-with-addons.min.js
    12         cp bower_components/react-bootstrap/react-bootstrap.min.js src/main/webapp/lib/react-bootstrap.min.js
     8        cp bower_components/bootstrap/dist/css/bootstrap.min.css src/main/webapp/lib/
     9        cp bower_components/bootstrap/dist/js/bootstrap.min.js src/main/webapp/lib/
     10        cp bower_components/jquery/dist/jquery.min.js src/main/webapp/lib/
     11        cp bower_components/react/react-with-addons.js src/main/webapp/lib/
     12        cp bower_components/react/react-with-addons.min.js src/main/webapp/lib/
     13        cp bower_components/react-bootstrap/react-bootstrap.min.js src/main/webapp/lib/
    1314fi
    1415
  • SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/app/Aggregator.java

    r5771 r5784  
    7676 * @author edima
    7777 *
    78  * TODO: result panes with animation and more info
    79  *
    80  * TODO: highlighted/kwic hits: toggle for now
    81  *
    82  * TODO: show multiple hits on the same result in multiple rows, linked visually
    83  *
    84  * TODO: new UI element to specify layer we search in
    85  *
    8678 * TODO: good UI for tree view corpus selection, with instant search form
    8779 *
     
    9587 * TODO: atomic replace of cached corpora (file)
    9688 *
     89 * TODO: show multiple hits on the same result in multiple rows, linked visually
     90 *
     91 * TODO: new UI element to specify layer we search in
     92 *
    9793 * TODO: test json deserialization
    9894 *
     
    10298        private static final org.slf4j.Logger log = LoggerFactory.getLogger(Aggregator.class);
    10399
    104         public static final int WAITING_TIME_FOR_SHUTDOWN_MS = 2000;
    105100        public static final String DE_TOK_MODEL = "/tokenizer/de-tuebadz-8.0-token.bin";
    106         private static final String DEFAULT_DATA_LOCATION = "/data";
    107101
    108102        private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
    109103        private static Aggregator instance;
     104
     105        public static int ENDPOINTS_TIMEOUT_MS = 50 * 1000;
     106        public static int EXECUTOR_SHUTDOWN_TIMEOUT_MS = (50 + 10) * 1000;
     107        private final EndpointUrlFilter filter = new EndpointUrlFilter();
    110108
    111109        private AtomicReference<Corpora> scanCacheAtom = new AtomicReference<Corpora>(new Corpora());
     
    129127        @Override
    130128        public void contextInitialized(ServletContextEvent servletContextEvent) {
    131                 log.info("Aggregator is starting now.");
     129                log.info("Aggregator initialization started.");
    132130                instance = this;
    133131                try {
    134132                        params = new Params();
     133                        detectAndConfigureEnvironment();
    135134
    136135                        sruClient = new ClarinFCSClientBuilder()
    137                                         .setConnectTimeout(5000)
    138                                         .setSocketTimeout(5000)
     136                                        .setConnectTimeout(ENDPOINTS_TIMEOUT_MS)
     137                                        .setSocketTimeout(ENDPOINTS_TIMEOUT_MS)
    139138                                        .addDefaultDataViewParsers()
    140139                                        .enableLegacySupport()
     
    152151                        model = setUpTokenizers();
    153152
    154                         EndpointUrlFilter filter = new EndpointUrlFilter();//.deny("leipzig"); // ~5k corpora
    155153                        ScanCrawlTask task = new ScanCrawlTask(sruClient, params.centerRegistryUrl,
    156154                                        params.cacheMaxDepth, filter, scanCacheAtom, corporaCacheFile);
     
    206204                        sruClient.shutdown();
    207205                        scheduler.shutdown();
    208                         Thread.sleep(WAITING_TIME_FOR_SHUTDOWN_MS);
     206                        Thread.sleep(EXECUTOR_SHUTDOWN_TIMEOUT_MS);
    209207                        sruClient.shutdownNow();
    210208                        scheduler.shutdownNow();
    211                         Thread.sleep(WAITING_TIME_FOR_SHUTDOWN_MS);
     209                        Thread.sleep(EXECUTOR_SHUTDOWN_TIMEOUT_MS);
    212210                } catch (InterruptedException ie) {
    213211                        sruClient.shutdownNow();
     
    228226                return model;
    229227        }
     228
     229        private void detectAndConfigureEnvironment() {
     230                if (!"Development".equals(System.getProperty("Environment"))) {
     231                        log.info(" *** Production Environment detected, using default settings *** ");
     232                        return;
     233                }
     234
     235                log.warn(" *** Development Environment detected, using custom settings *** ");
     236
     237                ENDPOINTS_TIMEOUT_MS = 10 * 1000;
     238                EXECUTOR_SHUTDOWN_TIMEOUT_MS = 1000;
     239
     240                filter.deny("leipzig"); // ~5k corpora
     241                params.aggregatorFilePath = System.getProperty("user.home") + File.separator + "fcsAggregatorCorpora.json";
     242                params.cacheMaxDepth = 1;
     243        }
    230244}
  • SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/app/Params.java

    r5771 r5784  
    1515
    1616        public final String centerRegistryUrl;
    17         public final int cacheMaxDepth;
    1817        public final TimeUnit cacheUpdateIntervalUnit;
    1918        public final int cacheUpdateInterval;
    20         public final String aggregatorFilePath;
     19        public int cacheMaxDepth;
     20        public String aggregatorFilePath;
    2121
    2222        public Params() throws NamingException {
  • SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/cache/Corpora.java

    r5771 r5784  
    44import eu.clarin.sru.fcs.aggregator.registry.Institution;
    55import java.util.ArrayList;
     6import java.util.Collections;
    67import java.util.HashMap;
    78import java.util.HashSet;
     
    2930
    3031        public List<Institution> getInstitutions() {
    31                 return institutions;
     32                return Collections.unmodifiableList(institutions);
    3233        }
    3334
    3435        public List<Corpus> getCorpora() {
    35                 return corpora;
     36                return Collections.unmodifiableList(corpora);
    3637        }
    3738
    38         public void addInstitution(Institution institution) {
     39        public synchronized void addInstitution(Institution institution) {
    3940                institutions.add(institution);
    4041        }
    4142
    42         public synchronized boolean addRootCorpus(final Corpus c) {
     43        public synchronized boolean addCorpus(Corpus c, Corpus parentCorpus) {
    4344                if (findByHandle(c.getHandle()) != null) {
    4445                        return false;
    4546                }
    46                 corpora.add(c);
    47                 for (String lang : c.getLanguages()) {
    48                         if (!langToRootCorpora.containsKey(lang)) {
    49                                 langToRootCorpora.put(lang, new HashSet<Corpus>());
     47                if (parentCorpus == null) { //i.e it's a root corpus
     48                        corpora.add(c);
     49                        for (String lang : c.getLanguages()) {
     50                                if (!langToRootCorpora.containsKey(lang)) {
     51                                        langToRootCorpora.put(lang, new HashSet<Corpus>());
     52                                }
     53                                langToRootCorpora.get(lang).add(c);
    5054                        }
    51                         langToRootCorpora.get(lang).add(c);
    52                 }
    53                 return true;
    54         }
    55 
    56         public synchronized boolean addSubCorpus(Corpus c, Corpus parentCorpus) {
    57                 if (findByHandle(c.getHandle()) != null) {
    58                         return false;
    59                 }
    60 
    61                 if (parentCorpus == null) { //i.e it's a root corpus
    62                         addRootCorpus(c);
    6355                } else {
    6456                        parentCorpus.addCorpus(c);
  • SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/cache/EndpointFilter.java

    r5720 r5784  
    11package eu.clarin.sru.fcs.aggregator.cache;
    2 
    3 import eu.clarin.sru.fcs.aggregator.registry.Endpoint;
    42
    53/**
     
    1210public interface EndpointFilter {
    1311
    14     Iterable<Endpoint> filter(Iterable<Endpoint> endpoints);
     12        Iterable<String> filter(Iterable<String> endpoints);
    1513   
    1614}
  • SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/cache/EndpointUrlFilter.java

    r5758 r5784  
    11package eu.clarin.sru.fcs.aggregator.cache;
    22
    3 import eu.clarin.sru.fcs.aggregator.registry.Endpoint;
    43import java.util.ArrayList;
    54import java.util.Collections;
     
    3130
    3231        @Override
    33         public Iterable<Endpoint> filter(Iterable<Endpoint> endpoints) {
    34                 List<Endpoint> filtered = new ArrayList<Endpoint>();
     32        public Iterable<String> filter(Iterable<String> endpoints) {
     33                List<String> filtered = new ArrayList<String>();
    3534
    36                 for (Endpoint endp : endpoints) {
     35                for (String endp : endpoints) {
    3736                        if (allow.isEmpty()) {
    3837                                filtered.add(endp);
    3938                        }
    4039                        for (String urlSubstring : allow) {
    41                                 if (endp.getUrl().contains(urlSubstring)) {
     40                                if (endp.contains(urlSubstring)) {
    4241                                        filtered.add(endp);
    4342                                        break;
     
    4544                        }
    4645                        for (String urlSubstring : deny) {
    47                                 if (endp.getUrl().contains(urlSubstring)) {
     46                                if (endp.contains(urlSubstring)) {
    4847                                        filtered.remove(endp);
    4948                                }
  • SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/cache/ScanCrawler.java

    r5771 r5784  
    1010import eu.clarin.sru.fcs.aggregator.registry.CenterRegistry;
    1111import eu.clarin.sru.fcs.aggregator.registry.Corpus;
    12 import eu.clarin.sru.fcs.aggregator.registry.Endpoint;
    1312import eu.clarin.sru.fcs.aggregator.registry.Institution;
    1413import eu.clarin.sru.fcs.aggregator.util.SRUCQL;
     
    4847                for (Institution institution : centerRegistry.getCQLInstitutions()) {
    4948                        cache.addInstitution(institution);
    50                         Iterable<Endpoint> endpoints = institution.getEndpoints();
     49                        Iterable<String> endpoints = institution.getEndpoints();
    5150                        if (filter != null) {
    5251                                endpoints = filter.filter(endpoints);
    5352                        }
    54                         for (Endpoint endp : endpoints) {
    55                                 addCorpora(endp.getUrl(), institution, null, cache, 0);
     53                        for (String endp : endpoints) {
     54                                addCorpora(institution, endp, null, cache, 0);
    5655                        }
    5756                }
     
    6867        }
    6968
    70         private void addCorpora(final String endpointUrl, final Institution institution,
     69        private void addCorpora(final Institution institution, final String endpointUrl,
    7170                        final Corpus parentCorpus, final Corpora corpora, final int depth) {
    7271                if (depth > maxDepth) {
     
    7776                try {
    7877                        scanRequest = new SRUScanRequest(endpointUrl);
    79                         StringBuilder scanClause = new StringBuilder(SRUCQL.SCAN_RESOURCE_PARAMETER);
    80                         scanClause.append("=");
    81                         String normalizedHandle = normalizeHandle(parentCorpus, parentCorpus == null);
    82                         scanClause.append(normalizedHandle);
    83                         scanRequest.setScanClause(scanClause.toString());
     78                        scanRequest.setScanClause(SRUCQL.SCAN_RESOURCE_PARAMETER
     79                                        + "=" + normalizeHandle(parentCorpus));
    8480                        scanRequest.setExtraRequestData(SRUCQL.SCAN_RESOURCE_INFO_PARAMETER,
    8581                                        SRUCQL.SCAN_RESOURCE_INFO_PARAMETER_DEFAULT_VALUE);
     
    10197                                                        for (SRUTerm term : response.getTerms()) {
    10298                                                                Corpus c = createCorpus(institution, endpointUrl, term);
    103                                                                 checkedAdd(corpora, parentCorpus, c, depth);
     99                                                                if (corpora.addCorpus(c, parentCorpus)) {
     100                                                                        addCorpora(institution, endpointUrl, c, corpora, depth + 1);
     101                                                                }
    104102                                                        }
    105103                                                } else if (parentCorpus == null) {
    106                                                         // create default root corpus
    107                                                         Corpus c = new Corpus(institution, endpointUrl);
    108                                                         checkedAdd(corpora, parentCorpus, c, depth);
     104                                                        Corpus c = createCorpus(institution, endpointUrl, null);
     105                                                        if (corpora.addCorpus(c, parentCorpus)) {
     106                                                                addCorpora(institution, endpointUrl, c, corpora, depth + 1);
     107                                                        }
    109108                                                }
    110109
     
    119118
    120119                                @Override
     120
    121121                                public void onError(SRUScanRequest request, SRUClientException error) {
    122122                                        latch.decrement();
    123123                                        log.error("{} Error while scanning {}: {} : {}", latch.get(), endpointUrl, error, error.getCause());
    124124                                }
    125                         });
     125                        }
     126                        );
    126127                } catch (SRUClientException ex) {
    127128                        latch.decrement();
     
    130131        }
    131132
    132         private void checkedAdd(Corpora corpora, Corpus parentCorpus, Corpus c, int depth) {
    133                 if (corpora.addSubCorpus(c, parentCorpus)) {
    134                         addCorpora(c.getEndpointUrl(), c.getInstitution(), c, corpora, depth + 1);
    135                 } else {
    136                         // log.warn("Cyclic reference in corpus " + c.getHandle() + " of endpoint " + c.getEndpointUrl());
    137                 }
    138         }
    139 
    140         private static String normalizeHandle(Corpus corpus, boolean root) {
    141                 if (root) {
     133        private static String normalizeHandle(Corpus corpus) {
     134                if (corpus == null) {
    142135                        return Corpus.ROOT_HANDLE;
    143136                }
     
    152145        private static Corpus createCorpus(Institution institution, String endpointUrl, SRUTerm term) {
    153146                Corpus c = new Corpus(institution, endpointUrl);
    154                 c.setHandle(term.getValue());
    155                 c.setDisplayName(term.getDisplayTerm());
    156                 if (term.getNumberOfRecords() > 0) {
    157                         c.setNumberOfRecords(term.getNumberOfRecords());
    158                 }
    159                 addExtraInfo(c, term);
     147                if (term == null) {
     148                        c.setDisplayName("[" + endpointUrl + "]");
     149                } else {
     150                        c.setDisplayName(term.getDisplayTerm());
     151                        c.setHandle(term.getValue());
     152                        if (term.getNumberOfRecords() > 0) {
     153                                c.setNumberOfRecords(term.getNumberOfRecords());
     154                        }
     155                        addExtraInfo(c, term);
     156                }
    160157                return c;
    161158        }
  • SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/registry/Corpus.java

    r5771 r5784  
    2323        private String handle;
    2424        private Integer numberOfRecords;
    25         private String displayTerm;
     25        private String displayName;
    2626        private Set<String> languages = new HashSet<String>();
    2727        private String landingPage;
    2828        private String title;
    2929        private String description;
    30         boolean temp = false;
    3130        public List<Corpus> subCorpora = Collections.synchronizedList(new ArrayList<Corpus>());
    3231
    3332        public Corpus() {
    34                 temp = true;
    3533        }
    3634
     
    4846        }
    4947
    50         public boolean isTemporary() {
    51                 return temp;
     48        public void setSubCorpora(List<Corpus> subCorpora) {
     49                this.subCorpora = subCorpora;
    5250        }
     51
    5352
    5453        public String getHandle() {
     
    6059        }
    6160
    62         public void setNumberOfRecords(int numberOfRecords) {
    63                 this.numberOfRecords = numberOfRecords;
    64         }
    65 
    6661        public Integer getNumberOfRecords() {
    6762                return numberOfRecords;
    6863        }
    6964
     65        public void setNumberOfRecords(Integer numberOfRecords) {
     66                this.numberOfRecords = numberOfRecords;
     67        }
     68
    7069        public String getDisplayName() {
    71                 return displayTerm;
     70                return displayName;
    7271        }
    7372
    7473        public void setDisplayName(String displayName) {
    75                 this.displayTerm = displayName;
     74                this.displayName = displayName;
    7675        }
    7776
     
    8079        }
    8180
     81        public void setEndpointUrl(String endpointUrl) {
     82                this.endpointUrl = endpointUrl;
     83        }
     84
    8285        public Institution getInstitution() {
    8386                return institution;
     87        }
     88
     89        public void setInstitution(Institution institution) {
     90                this.institution = institution;
    8491        }
    8592
  • SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/registry/Institution.java

    r5771 r5784  
    1313        private String name;
    1414        private String link;
    15         private ArrayList<Endpoint> endpoints;
     15        private Set<String> endpoints;
    1616
    1717        // for JSON deserialization
     
    2222                this.name = name;
    2323                this.link = link;
    24                 this.endpoints = new ArrayList<Endpoint>();
     24                this.endpoints = new LinkedHashSet<String>();
    2525        }
    2626
    27         public Endpoint add(String endpointUrl) {
    28                 Endpoint ep = getEndpoint(endpointUrl);
    29                 if (ep == null) {
    30                         ep = new Endpoint(endpointUrl, this);
    31                         endpoints.add(ep);
    32                 }
    33                 return ep;
     27        public String add(String endpointUrl) {
     28                endpoints.add(endpointUrl);
     29                return endpointUrl;
    3430        }
    3531
     
    4238        }
    4339
    44         public List<Endpoint> getEndpoints() {
     40        public Set<String> getEndpoints() {
    4541                return this.endpoints;
    46         }
    47 
    48         public Endpoint getEndpoint(int index) {
    49                 if (index >= endpoints.size()) {
    50                         return null;
    51                 }
    52                 return endpoints.get(index);
    53         }
    54 
    55         public Endpoint getEndpoint(String endpointUrl) {
    56                 for (Endpoint ep : endpoints) {
    57                         if (ep.getUrl().equals(endpointUrl)) {
    58                                 return ep;
    59                         }
    60                 }
    61                 return null;
    6242        }
    6343
  • SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/rest/RestService.java

    r5771 r5784  
    9494        @POST
    9595        @Path("search")
    96         public Response postSearch(@FormParam("query") String query) throws Exception {
     96        public Response postSearch(
     97                        @FormParam("query") String query,
     98                        @FormParam("language") String language,
     99                        @FormParam("numHits") Integer numHits) throws Exception {
    97100                if (query == null || query.isEmpty()) {
    98101                        return Response.status(400).entity("'query' parameter expected").build();
    99102                }
    100103                List<Corpus> corpora = Aggregator.getInstance().getCorpora().getCorpora();
    101                 Search search = Aggregator.getInstance().startSearch(SRUVersion.VERSION_1_2, corpora, query, "eng", 10);
     104                if (corpora == null || corpora.isEmpty()) {
     105                        return Response.status(503).entity("No corpora, please wait for the server to finish scanning").build();
     106                }
     107                if (numHits == null || numHits < 10) {
     108                        numHits = 10;
     109                }
     110                Search search = Aggregator.getInstance().startSearch(SRUVersion.VERSION_1_2, corpora, query, language, numHits);
     111                if (search == null) {
     112                        return Response.status(500).entity("Initiating search failed").build();
     113                }
    102114                URI uri = URI.create("" + search.getId());
    103115                return Response.created(uri).entity(uri).build();
  • SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/search/Request.java

    r5718 r5784  
    4040
    4141        public boolean hasCorpusHandler() {
    42                 if (corpus != null && corpus.getHandle() != null) {
    43                         return true;
    44                 }
    45                 return false;
     42                return corpus != null && corpus.getHandle() != null;
    4643        }
    4744}
  • SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/search/Search.java

    r5758 r5784  
    1818import java.util.logging.Logger;
    1919import opennlp.tools.tokenize.TokenizerModel;
     20import org.slf4j.LoggerFactory;
    2021
    2122/**
     
    2728public class Search {
    2829
    29         private static final Logger LOGGER = Logger.getLogger(Search.class.getName());
     30        private static final org.slf4j.Logger log = LoggerFactory.getLogger(Search.class);
    3031
    3132        private static final String SEARCH_RESULTS_ENCODING = "UTF-8";
     
    5051        private Request executeSearch(SRUThreadedClient searchClient, SRUVersion version, Corpus corpus, String searchString, int startRecord, int maxRecords) {
    5152                final Request request = new Request(corpus, searchString, startRecord, startRecord + maxRecords - 1);
    52                 LOGGER.log(Level.INFO, "Executing search for {0} query={1} maxRecords={2}",
    53                                 new Object[]{corpus.toString(), searchString, maxRecords});
     53                log.info("Executing search for {0} query={1} maxRecords={2}", corpus, searchString, maxRecords);
    5454
    5555                SRUSearchRetrieveRequest searchRequest = new SRUSearchRetrieveRequest(corpus.getEndpointUrl());
     
    7979                        });
    8080                } catch (SRUClientException ex) {
    81                         LOGGER.log(Level.SEVERE, "SearchRetrieve failed for {0} {1} {2}",
    82                                         new String[]{corpus.getEndpointUrl(), ex.getClass().getName(), ex.getMessage()});
     81                        log.error("SearchRetrieve exception for " + corpus.getEndpointUrl(), ex);
     82                } catch (Throwable xc) {
     83                        log.error("SearchRetrieve error for " + corpus.getEndpointUrl(), xc);
    8384                }
    8485                return request;
  • SRUAggregator/trunk/src/main/webapp/CLARIN.css

    r5720 r5784  
    1111  -moz-osx-font-smoothing: grayscale;
    1212  color: #222;
    13   /*font-size: 13px;*/
     13  /*font-size: 12px;*/
    1414  /*word-wrap: break-word;*/
    1515}
  • SRUAggregator/trunk/src/main/webapp/base.css

    r5758 r5784  
    131131}
    132132
     133div.panel-title {
     134        font-size:16px;
     135}
     136
    133137.unselectable {
    134138        -webkit-touch-callout: none;
     
    145149
    146150
    147 .panel span {
     151.panel-body span {
    148152        color: #222;
    149153}
    150154 
    151 .panel span.keyword {
     155.panel-body span.keyword {
     156        /*color: #00406F;*/
     157        /*font-weight: bold*/
     158        font-size: 12px;
     159        color: #fff;
     160        background-color: #00406F;
     161}
     162 
     163.panel-body td.keyword {
    152164        color: #00406F;
     165        font-weight: bold
    153166}
    154167 
    155168.bs-callout {
    156169        padding: 0 20px;
    157         margin: 20px 0;
     170        margin: 0 0 20px 0;
    158171        border: 1px solid #ddd;
    159172        /*border-left: 5px solid rgb(17, 65, 111);*/
     
    162175}
    163176.bs-callout .panel-heading {
     177        margin-top: 0;
     178        margin-bottom: 5px;
     179}
     180.bs-callout .panel-title {
    164181        color: #806a52;
    165182        /*color: #222;*/
    166         margin-top: 0;
    167         margin-bottom: 5px;
    168183}
    169184.panel-body {
    170185        border-top: 1px solid #ddd;     
     186}
     187
     188div.popover {
     189        max-width:552px;
    171190}
    172191
  • SRUAggregator/trunk/src/main/webapp/index.html

    r5771 r5784  
    6363                                <a title="about" id="aboutlink">
    6464                                        <span class="glyphicon glyphicon-info-sign"></span>
    65                                         <!-- <span>VERSION ${pom.version} </span> -->
    66                                         <span>VERSION 2.0.0-ALPHA2 </span>
     65                                        <span>VERSION 2.0.0.α3 </span>
    6766                                </a>
    6867                        </div>
     
    8786        <![endif]-->   
    8887        <script src="lib/react-with-addons.js"></script>
     88        <script src="lib/bootstrap.min.js"></script>
    8989        <script src="js/components.js"></script>
    9090        <script src="js/search.js"></script>
  • SRUAggregator/trunk/src/main/webapp/js/components.js

    r5771 r5784  
    55var ReactTransitionGroup = React.addons.TransitionGroup;
    66
     7var PopoverMixin = {
     8        getDefaultProps: function(){
     9                return {hasPopover: true};
     10        },
     11 
     12        componentDidMount: function() {
     13                var content;
     14                if (Array.isArray(this.props.children))
     15                        content = this.props.children.map(React.renderToString).join(" ");
     16                else
     17                        content = React.renderToString(this.props.children);
     18                $(this.getDOMNode()).popover({
     19                        content: content,
     20                        animation: this.props.animation,
     21                        placement: this.props.placement,
     22                        title: this.props.title,
     23                        trigger: 'click',
     24                        html: true,
     25                });
     26        }
     27};
     28
     29var InfoPopover = React.createClass({displayName: 'InfoPopover',
     30        propTypes: {
     31                title: PT.string.isRequired,
     32        },
     33        mixins: [PopoverMixin],
     34
     35        handleClick: function(e) {
     36                e.stopPropagation();
     37        },
     38
     39        render: function() {
     40                return  React.createElement("button", {className: "btn btn-default btn-xs", onClick: this.handleClick},
     41                                        React.createElement("span", {className: "glyphicon glyphicon-info-sign"})
     42                                );
     43        }
     44});
     45
    746window.MyReact = {};
    847window.MyReact.Panel = React.createClass({displayName: 'Panel',
    948        propTypes: {
    10                 key:  PT.oneOfType([PT.string, PT.number]).isRequired,
    11                 header: PT.string.isRequired,
     49                corpus:PT.object.isRequired,
    1250        },
    13  
     51
    1452        getInitialState: function() {
    1553                return {
     
    2260        },
    2361
     62        renderBody: function() {
     63                return this.state.open ?
     64                                        React.createElement("div", {className: "panel-body"}, this.props.children) :
     65                                        false;
     66        },
     67
     68        renderInfo: function() {
     69                return  React.createElement("dl", {className: "dl-horizontal"},
     70                                        React.createElement("dt", null, "Institution"),
     71                                        React.createElement("dd", null, this.props.corpus.institution.name),
     72
     73                                        this.props.corpus.description ? React.createElement("dt", null, "Description"):false,
     74                                        this.props.corpus.description ? React.createElement("dd", null, this.props.corpus.description): false,
     75
     76                                        this.props.corpus.landingPage ? React.createElement("dt", null, "Landing Page") : false,
     77                                        this.props.corpus.landingPage ?
     78                                                React.createElement("dd", null, React.createElement("a", {href: this.props.corpus.landingPage}, this.props.corpus.landingPage)):
     79                                                false,
     80
     81                                        React.createElement("dt", null, "Languages"),
     82                                        React.createElement("dd", null, this.props.corpus.languages.join(", "))
     83                                );
     84        },
     85
    2486        render: function() {
    2587                var chevron = "glyphicon glyphicon-chevron-" + (this.state.open ? "down":"right");
    2688                var chevronStyle={fontSize:12};
    27                 return  React.createElement("div", {key: this.props.key, className: "bs-callout bs-callout-info"},
     89                var right={float:"right"};
     90                return  React.createElement("div", {className: "bs-callout bs-callout-info"},
    2891                                        React.createElement("div", {className: "panel"},
    29                                                 React.createElement("div", {className: "panel-heading unselectable", onClick: this.toggleState},
    30                                                         React.createElement("p", {className: "panel-title unselectable"},
     92                                                React.createElement("div", {className: "panel-heading unselectable row", onClick: this.toggleState},
     93                                                        React.createElement("div", {className: "panel-title unselectable col-sm-11"},
    3194                                                                React.createElement("span", {className: chevron, style: chevronStyle}), " ",
    32                                                                 this.props.header
     95                                                                this.props.corpus.displayName
     96                                                        ),
     97                                                        React.createElement("div", {style: right},
     98                                                                React.createElement(InfoPopover, {placement: "left", title: this.props.corpus.displayName},
     99                                                                        this.renderInfo()
     100                                                                )
    33101                                                        )
    34102                                                ),
    35                                                 React.createElement("div", {className: "panel-body"},
    36                                                         React.createElement(ReactTransitionGroup, {transitionName: "display"},
    37                                                                 this.state.open ? this.props.children : false
    38                                                         )
    39                                                 )
     103                                                this.renderBody()
    40104                                        )
    41105                                );
  • SRUAggregator/trunk/src/main/webapp/js/components.jsx

    r5771 r5784  
    55var ReactTransitionGroup = React.addons.TransitionGroup;
    66
     7var PopoverMixin = {
     8        getDefaultProps: function(){
     9                return {hasPopover: true};
     10        },
     11 
     12        componentDidMount: function() {
     13                var content;
     14                if (Array.isArray(this.props.children))
     15                        content = this.props.children.map(React.renderToString).join(" ");
     16                else
     17                        content = React.renderToString(this.props.children);
     18                $(this.getDOMNode()).popover({
     19                        content: content,
     20                        animation: this.props.animation,
     21                        placement: this.props.placement,
     22                        title: this.props.title,
     23                        trigger: 'click',
     24                        html: true,
     25                });
     26        }
     27};
     28
     29var InfoPopover = React.createClass({
     30        propTypes: {
     31                title: PT.string.isRequired,
     32        },
     33        mixins: [PopoverMixin],
     34
     35        handleClick: function(e) {
     36                e.stopPropagation();
     37        },
     38
     39        render: function() {
     40                return  <button className="btn btn-default btn-xs" onClick={this.handleClick}>
     41                                        <span className="glyphicon glyphicon-info-sign"/>
     42                                </button>;
     43        }
     44});
     45
    746window.MyReact = {};
    847window.MyReact.Panel = React.createClass({
    948        propTypes: {
    10                 key:  PT.oneOfType([PT.string, PT.number]).isRequired,
    11                 header: PT.string.isRequired,
     49                corpus:PT.object.isRequired,
    1250        },
    13  
     51
    1452        getInitialState: function() {
    1553                return {
     
    2260        },
    2361
     62        renderBody: function() {
     63                return this.state.open ?
     64                                        <div className="panel-body">{this.props.children}</div> :
     65                                        false;
     66        },
     67
     68        renderInfo: function() {
     69                return  <dl className="dl-horizontal">
     70                                        <dt>Institution</dt>
     71                                        <dd>{this.props.corpus.institution.name}</dd>
     72
     73                                        {this.props.corpus.description ? <dt>Description</dt>:false}
     74                                        {this.props.corpus.description ? <dd>{this.props.corpus.description}</dd>: false}
     75
     76                                        {this.props.corpus.landingPage ? <dt>Landing Page</dt> : false }
     77                                        {this.props.corpus.landingPage ?
     78                                                <dd><a href={this.props.corpus.landingPage}>{this.props.corpus.landingPage}</a></dd>:
     79                                                false}
     80
     81                                        <dt>Languages</dt>
     82                                        <dd>{this.props.corpus.languages.join(", ")}</dd>
     83                                </dl>;
     84        },
     85
    2486        render: function() {
    2587                var chevron = "glyphicon glyphicon-chevron-" + (this.state.open ? "down":"right");
    2688                var chevronStyle={fontSize:12};
    27                 return  <div key={this.props.key} className="bs-callout bs-callout-info">
     89                var right={float:"right"};
     90                return  <div className="bs-callout bs-callout-info">
    2891                                        <div className="panel">
    29                                                 <div className="panel-heading unselectable" onClick={this.toggleState}>
    30                                                         <p className="panel-title unselectable">
    31                                                                 <span className={chevron} style={chevronStyle}></span>&nbsp;
    32                                                                 {this.props.header}
    33                                                         </p>
     92                                                <div className="panel-heading unselectable row" onClick={this.toggleState}>
     93                                                        <div className="panel-title unselectable col-sm-11">
     94                                                                <span className={chevron} style={chevronStyle} />&nbsp;
     95                                                                {this.props.corpus.displayName}
     96                                                        </div>
     97                                                        <div style={right}>
     98                                                                <InfoPopover placement="left" title={this.props.corpus.displayName}>
     99                                                                        {this.renderInfo()}
     100                                                                </InfoPopover>
     101                                                        </div>
    34102                                                </div>
    35                                                 <div className="panel-body">
    36                                                         <ReactTransitionGroup transitionName="display">
    37                                                                 {this.state.open ? this.props.children : false}
    38                                                         </ReactTransitionGroup>
    39                                                 </div>
     103                                                {this.renderBody()}
    40104                                        </div>
    41105                                </div>;
  • SRUAggregator/trunk/src/main/webapp/js/main.js

    r5771 r5784  
    88var HitNumber = window.MyAggregator.HitNumber;
    99var Results = window.MyAggregator.Results;
     10
     11var globals = {};
    1012
    1113var Main = React.createClass({displayName: 'Main',
     
    3032                        success: function(json, textStatus, jqXHR) {
    3133                                that.setState({corpora:json});
     34                                console.log("corpora", json);
    3235                        },
    3336                        error: function(jqXHR, textStatus, error) {
     
    6164                                console.log("search ["+query+"] ok: ", searchId);
    6265                                that.setState({searchId : searchId});
    63                                 that.setState({timerId : setInterval(that.refreshSearchResults, 1000)});
     66                                globals.timeout = 250;
     67                                setTimeout(that.refreshSearchResults, globals.timeout);
    6468                        },
    6569                        error: function(jqXHR, textStatus, error) {
     
    7983                        success: function(json, textStatus, jqXHR) {
    8084                                if (json.requests.length === 0) {
    81                                         clearInterval(that.state.timerId);
    82                                         console.log("cleaned up timer");
     85                                        console.log("search ended");
     86                                } else {
     87                                        globals.timeout = 1.5 * globals.timeout;
     88                                        setTimeout(that.refreshSearchResults, globals.timeout);
     89                                        // console.log("new search in: " + globals.timeout+ "ms");
    8390                                }
    8491                                that.setState({hits:json});
     92                                console.log("hits:", json);
    8593                        },
    8694                        error: function(jqXHR, textStatus, error) {
     
    114122                                                React.createElement("div", {style: margin},
    115123                                                        React.createElement("form", {className: "form-inline", role: "form"},
    116                                                                 React.createElement("label", {htmlFor: "dropdownCorpus", className: "muted"}, "search in "),
     124                                                                React.createElement("label", {className: "muted"}, "search in "),
    117125                                                                React.createElement("div", {id: "corpusSelection", style: inlinew},
    118126                                                                        this.renderCorpusSelection()
    119127                                                                ),
    120                                                                 React.createElement("label", {htmlFor: "dropdownLanguage", className: "muted"}, " for results in "),
     128                                                                React.createElement("label", {className: "muted"}, " for results in "),
    121129                                                                React.createElement("div", {id: "languageSelection", style: inlinew},
    122130                                                                        React.createElement(LanguageSelection, {languages: this.state.languages})
     
    126134                                                                        React.createElement(HitNumber, {onChange: this.setNumberOfResults, numberOfResults: this.state.numberOfResults})
    127135                                                                ),
    128                                                                 React.createElement("label", {htmlFor: "hits", className: "muted"}, " hits")
     136                                                                React.createElement("label", {className: "muted"}, " hits")
    129137                                                        )
    130138                                                )
  • SRUAggregator/trunk/src/main/webapp/js/main.jsx

    r5771 r5784  
    88var HitNumber = window.MyAggregator.HitNumber;
    99var Results = window.MyAggregator.Results;
     10
     11var globals = {};
    1012
    1113var Main = React.createClass({
     
    3032                        success: function(json, textStatus, jqXHR) {
    3133                                that.setState({corpora:json});
     34                                console.log("corpora", json);
    3235                        },
    3336                        error: function(jqXHR, textStatus, error) {
     
    6164                                console.log("search ["+query+"] ok: ", searchId);
    6265                                that.setState({searchId : searchId});
    63                                 that.setState({timerId : setInterval(that.refreshSearchResults, 1000)});
     66                                globals.timeout = 250;
     67                                setTimeout(that.refreshSearchResults, globals.timeout);
    6468                        },
    6569                        error: function(jqXHR, textStatus, error) {
     
    7983                        success: function(json, textStatus, jqXHR) {
    8084                                if (json.requests.length === 0) {
    81                                         clearInterval(that.state.timerId);
    82                                         console.log("cleaned up timer");
     85                                        console.log("search ended");
     86                                } else {
     87                                        globals.timeout = 1.5 * globals.timeout;
     88                                        setTimeout(that.refreshSearchResults, globals.timeout);
     89                                        // console.log("new search in: " + globals.timeout+ "ms");
    8390                                }
    8491                                that.setState({hits:json});
     92                                console.log("hits:", json);
    8593                        },
    8694                        error: function(jqXHR, textStatus, error) {
     
    114122                                                <div style={margin}>
    115123                                                        <form className="form-inline" role="form">
    116                                                                 <label htmlFor="dropdownCorpus" className="muted">search in </label>
     124                                                                <label className="muted">search in </label>
    117125                                                                <div id="corpusSelection" style={inlinew}>
    118126                                                                        {this.renderCorpusSelection()}
    119127                                                                </div>
    120                                                                 <label htmlFor="dropdownLanguage" className="muted"> for results in </label>
     128                                                                <label className="muted"> for results in </label>
    121129                                                                <div id="languageSelection" style={inlinew}>
    122130                                                                        <LanguageSelection languages={this.state.languages} />
     
    126134                                                                        <HitNumber onChange={this.setNumberOfResults} numberOfResults={this.state.numberOfResults} />
    127135                                                                </div>
    128                                                                 <label htmlFor="hits" className="muted"> hits</label>
     136                                                                <label className="muted"> hits</label>
    129137                                                        </form>
    130138                                                </div>
  • SRUAggregator/trunk/src/main/webapp/js/search.js

    r5771 r5784  
    6868        },
    6969
     70        handleKey: function(event) {
     71        if (event.keyCode==13) {
     72                this.search();
     73        }
     74        },
     75
    7076        search: function() {
    7177                this.props.search(this.state.query);
     
    7682                                        React.createElement("input", {name: "query", type: "text", className: "form-control input-lg search",
    7783                                                value: this.state.query, placeholder: "Search", tabIndex: "1",
    78                                                 onChange: this.handleChange}),
     84                                                onChange: this.handleChange,
     85                                                onKeyDown: this.handleKey
     86                                                }),
    7987                                        React.createElement("div", {className: "input-group-btn"},
    8088                                                React.createElement("button", {className: "btn btn-default input-lg search", type: "submit", tabIndex: "2", onClick: this.search},
     
    105113        },
    106114
    107         renderCorpora: function() {
     115        renderCorpus: function(level, corpus) {
    108116                var that = this;
    109                 return this.props.corpora.map(function(corpus) {
    110                         if (corpus.subCorpora.length > 0) {
    111                                 console.log("big corpus: ", corpus);
    112                         }
    113                         function toggle() {
    114                                 corpus.checked = !corpus.checked;
    115                                 that.setState({corpora : corpora});
    116                         }
    117                         var bold = {fontWeight:"bold"};
    118                         var spaced = {marginRight:"20px"};
    119                         var topline = {borderTop:"1px solid #ddd", paddingTop:10};
    120                         return  React.createElement("div", {className: "row", style: topline, key: corpus.displayTerm},
    121                                                 React.createElement("div", {className: "col-sm-2"}, that.renderCheckbox(corpus.checked, corpus.displayTerm, toggle)),
     117
     118                if (corpus.subCorpora.length > 0) {
     119                        console.log("big corpus: ", corpus);
     120                }
     121                function toggle() {
     122                        corpus.checked = !corpus.checked;
     123                        that.setState({corpora : corpora});
     124                }
     125                var bold = {fontWeight:"bold"};
     126                var spaced = {marginRight:"20px"};
     127                var topline = {borderTop:"1px solid #ddd", paddingTop:10};
     128                return  React.createElement("div", {style: topline, key: corpus.displayName},
     129                                        React.createElement("div", {className: "row"},
     130                                                React.createElement("div", {className: "col-sm-2"}, that.renderCheckbox(corpus.checked, corpus.displayName, toggle)),
    122131                                                React.createElement("div", {className: "col-sm-6"},
    123                                                         React.createElement("p", null, corpus.description)
     132                                                        React.createElement("p", null, level, " ", corpus.description)
    124133                                                ),
    125134                                                React.createElement("div", {className: "col-sm-4"},
     
    128137                                                        React.createElement("p", null, " ", React.createElement("span", null, corpus.numberOfRecords ? (corpus.numberOfRecords+" records") : ""))
    129138                                                )
    130                                         );
    131                 });
     139                                        ),
     140                                        corpus.subCorpora.map(this.renderCorpus.bind(this,level+1))
     141                                );
    132142        },
    133143
     
    138148                                                React.createElement("div", {className: "col-sm-10"}, React.createElement("h3", null, "Description"))
    139149                                        ),
    140                                         this.renderCorpora()
     150                                        this.props.corpora.map(this.renderCorpus.bind(this,0))
    141151                                );
    142152        }
     
    151161        },
    152162
     163        getInitialState: function () {
     164                return { displayKwic: false };
     165        },
     166
     167        toggleKwic: function() {
     168                this.setState({displayKwic:!this.state.displayKwic});
     169        },
     170
    153171        renderResultPanels: function(corpusHit) {
    154                 function renderRows(hit,i) {
     172                function renderRowsAsHits(hit,i) {
    155173                        function renderTextFragments(tf, idx) {
    156                                 return React.createElement("span", {key: idx, className: tf.hit?"keyword":""}, tf.text);
     174                                return React.createElement("span", {key: idx, className: tf.hit?"keyword label label-primary":""}, tf.text);
    157175                        }
    158176                        return  React.createElement("p", {key: i},
     
    161179                }
    162180
    163                 console.log(corpusHit);
     181                function renderRowsAsKwic(hit,i) {
     182                        var sleft={textAlign:"left", verticalAlign:"middle", width:"50%"};
     183                        var scenter={textAlign:"center", verticalAlign:"middle", maxWidth:"50%"};
     184                        var sright={textAlign:"right", verticalAlign:"middle", maxWidth:"50%"};
     185                        return  React.createElement("tr", {key: i},
     186                                                React.createElement("td", {style: sright}, hit.left),
     187                                                React.createElement("td", {style: scenter, className: "keyword"}, hit.keyword),
     188                                                React.createElement("td", {style: sleft}, hit.right)
     189                                        );
     190                }
     191
    164192                if (corpusHit.kwics.length === 0) {
    165                         return React.createElement("span", {key: corpusHit.corpus.displayName});
    166                 }
    167                 return  React.createElement(Panel, {header: corpusHit.corpus.displayName, key: corpusHit.corpus.displayName},
    168                                         corpusHit.kwics.map(renderRows)
     193                        return false;
     194                }
     195                var fulllength = {width:"100%"};               
     196                var body = this.state.displayKwic ?
     197                        React.createElement("table", {className: "table table-condensed table-hover", style: fulllength},
     198                                React.createElement("tbody", null, corpusHit.kwics.map(renderRowsAsKwic))
     199                        ) :
     200                        React.createElement("div", null, corpusHit.kwics.map(renderRowsAsHits));
     201                return  React.createElement(Panel, {corpus: corpusHit.corpus, key: corpusHit.corpus.displayName},
     202                                        body
    169203                                );
    170204        },
     
    178212                                React.createElement("div", {className: "progress-bar progress-bar-striped active", role: "progressbar",
    179213                                        'aria-valuenow': sperc, 'aria-valuemin': "0", 'aria-valuemax': "100", style: styleperc})
    180                         ) : React.createElement("span", null);
     214                        ) :
     215                        React.createElement("span", null);
    181216        },
    182217
    183218        renderMessage: function() {
    184219                var noHits = this.props.results.filter(function(corpusHit) { return corpusHit.kwics.length === 0; });
    185                 return noHits.length > 0 ? (noHits.length + " other collections returned no results") : "";
     220                return noHits.length > 0 ? (noHits.length + " other collections did not return any results") : "";
     221        },
     222
     223        renderKwicCheckbox: function() {
     224                var inline = {display:"inline-block"};
     225                return  React.createElement("div", {className: "row"},
     226                                        React.createElement("div", {className: "col-sm-3 col-sm-offset-9"},
     227                                                React.createElement("div", {className: "btn-group", style: inline},
     228                                                        React.createElement("label", {forHtml: "inputKwic", className: "btn-default"},
     229                                                                 this.state.displayKwic ?
     230                                                                        React.createElement("input", {id: "inputKwic", type: "checkbox", value: "kwic", checked: true, onChange: this.toggleKwic}) :
     231                                                                        React.createElement("input", {id: "inputKwic", type: "checkbox", value: "kwic", onChange: this.toggleKwic}),
     232                                                               
     233                                                                " " + ' ' +
     234                                                                "Display as Key Word In Context"
     235                                                        )
     236                                                )
     237                                        )
     238                                );
    186239        },
    187240
    188241        render: function() {
    189242                var margintop = {marginTop:"10px"};
     243                var margin = {marginTop:"0", padding:"20px"};
     244                var inlinew = {display:"inline-block", margin:"0 5px 0 0", width:"240px;"};
     245                var right= {float:"right"};
    190246                return  React.createElement("div", null,
     247                                        this.props.results.length > 0 ? this.renderKwicCheckbox() : false,
    191248                                        React.createElement(ReactCSSTransitionGroup, {transitionName: "fade"},
    192249                                                this.props.results.map(this.renderResultPanels),
  • SRUAggregator/trunk/src/main/webapp/js/search.jsx

    r5771 r5784  
    6868        },
    6969
     70        handleKey: function(event) {
     71        if (event.keyCode==13) {
     72                this.search();
     73        }
     74        },
     75
    7076        search: function() {
    7177                this.props.search(this.state.query);
     
    7682                                        <input name="query" type="text" className="form-control input-lg search"
    7783                                                value={this.state.query} placeholder="Search" tabIndex="1"
    78                                                 onChange={this.handleChange}></input>
     84                                                onChange={this.handleChange}
     85                                                onKeyDown={this.handleKey}
     86                                                ></input>
    7987                                        <div className="input-group-btn">
    8088                                                <button className="btn btn-default input-lg search" type="submit" tabIndex="2" onClick={this.search}>
     
    105113        },
    106114
    107         renderCorpora: function() {
     115        renderCorpus: function(level, corpus) {
    108116                var that = this;
    109                 return this.props.corpora.map(function(corpus) {
    110                         if (corpus.subCorpora.length > 0) {
    111                                 console.log("big corpus: ", corpus);
    112                         }
    113                         function toggle() {
    114                                 corpus.checked = !corpus.checked;
    115                                 that.setState({corpora : corpora});
    116                         }
    117                         var bold = {fontWeight:"bold"};
    118                         var spaced = {marginRight:"20px"};
    119                         var topline = {borderTop:"1px solid #ddd", paddingTop:10};
    120                         return  <div className="row" style={topline} key={corpus.displayTerm}>
    121                                                 <div className="col-sm-2">{that.renderCheckbox(corpus.checked, corpus.displayTerm, toggle)}</div>
     117
     118                if (corpus.subCorpora.length > 0) {
     119                        console.log("big corpus: ", corpus);
     120                }
     121                function toggle() {
     122                        corpus.checked = !corpus.checked;
     123                        that.setState({corpora : corpora});
     124                }
     125                var bold = {fontWeight:"bold"};
     126                var spaced = {marginRight:"20px"};
     127                var topline = {borderTop:"1px solid #ddd", paddingTop:10};
     128                return  <div style={topline} key={corpus.displayName}>
     129                                        <div className="row">
     130                                                <div className="col-sm-2">{that.renderCheckbox(corpus.checked, corpus.displayName, toggle)}</div>
    122131                                                <div className="col-sm-6">
    123                                                         <p>{corpus.description}</p>
     132                                                        <p>{level} {corpus.description}</p>
    124133                                                </div>
    125134                                                <div className="col-sm-4">
     
    128137                                                        <p>     <span>{corpus.numberOfRecords ? (corpus.numberOfRecords+" records") : ""}</span></p>
    129138                                                </div>
    130                                         </div>;
    131                 });
     139                                        </div>
     140                                        {corpus.subCorpora.map(this.renderCorpus.bind(this,level+1))}
     141                                </div>;
    132142        },
    133143
     
    138148                                                <div className="col-sm-10"><h3>Description</h3></div>
    139149                                        </div>
    140                                         {this.renderCorpora()}
     150                                        {this.props.corpora.map(this.renderCorpus.bind(this,0))}
    141151                                </div>;
    142152        }
     
    151161        },
    152162
     163        getInitialState: function () {
     164                return { displayKwic: false };
     165        },
     166
     167        toggleKwic: function() {
     168                this.setState({displayKwic:!this.state.displayKwic});
     169        },
     170
    153171        renderResultPanels: function(corpusHit) {
    154                 function renderRows(hit,i) {
     172                function renderRowsAsHits(hit,i) {
    155173                        function renderTextFragments(tf, idx) {
    156                                 return <span key={idx} className={tf.hit?"keyword":""}>{tf.text}</span>;
     174                                return <span key={idx} className={tf.hit?"keyword label label-primary":""}>{tf.text}</span>;
    157175                        }
    158176                        return  <p key={i}>
     
    161179                }
    162180
    163                 console.log(corpusHit);
     181                function renderRowsAsKwic(hit,i) {
     182                        var sleft={textAlign:"left", verticalAlign:"middle", width:"50%"};
     183                        var scenter={textAlign:"center", verticalAlign:"middle", maxWidth:"50%"};
     184                        var sright={textAlign:"right", verticalAlign:"middle", maxWidth:"50%"};
     185                        return  <tr key={i}>
     186                                                <td style={sright}>{hit.left}</td>
     187                                                <td style={scenter} className="keyword">{hit.keyword}</td>
     188                                                <td style={sleft}>{hit.right}</td>
     189                                        </tr>;
     190                }
     191
    164192                if (corpusHit.kwics.length === 0) {
    165                         return <span key={corpusHit.corpus.displayName}></span>;
    166                 }
    167                 return  <Panel header={corpusHit.corpus.displayName} key={corpusHit.corpus.displayName}>
    168                                         {corpusHit.kwics.map(renderRows)}
     193                        return false;
     194                }
     195                var fulllength = {width:"100%"};               
     196                var body = this.state.displayKwic ?
     197                        <table className="table table-condensed table-hover" style={fulllength}>
     198                                <tbody>{corpusHit.kwics.map(renderRowsAsKwic)}</tbody>
     199                        </table> :
     200                        <div>{corpusHit.kwics.map(renderRowsAsHits)}</div>;
     201                return  <Panel corpus={corpusHit.corpus} key={corpusHit.corpus.displayName}>
     202                                        {body}
    169203                                </Panel>;
    170204        },
     
    178212                                <div className="progress-bar progress-bar-striped active" role="progressbar"
    179213                                        aria-valuenow={sperc} aria-valuemin="0" aria-valuemax="100" style={styleperc} />
    180                         </div> : <span />;
     214                        </div> :
     215                        <span />;
    181216        },
    182217
    183218        renderMessage: function() {
    184219                var noHits = this.props.results.filter(function(corpusHit) { return corpusHit.kwics.length === 0; });
    185                 return noHits.length > 0 ? (noHits.length + " other collections returned no results") : "";
     220                return noHits.length > 0 ? (noHits.length + " other collections did not return any results") : "";
     221        },
     222
     223        renderKwicCheckbox: function() {
     224                var inline = {display:"inline-block"};
     225                return  <div className="row">
     226                                        <div className="col-sm-3 col-sm-offset-9">
     227                                                <div className="btn-group" style={inline}>
     228                                                        <label forHtml="inputKwic" className="btn-default">
     229                                                                { this.state.displayKwic ?
     230                                                                        <input id="inputKwic" type="checkbox" value="kwic" checked onChange={this.toggleKwic} /> :
     231                                                                        <input id="inputKwic" type="checkbox" value="kwic" onChange={this.toggleKwic} />
     232                                                                }
     233                                                                &nbsp;
     234                                                                Display as Key Word In Context
     235                                                        </label>
     236                                                </div>
     237                                        </div>
     238                                </div>;
    186239        },
    187240
    188241        render: function() {
    189242                var margintop = {marginTop:"10px"};
     243                var margin = {marginTop:"0", padding:"20px"};
     244                var inlinew = {display:"inline-block", margin:"0 5px 0 0", width:"240px;"};
     245                var right= {float:"right"};
    190246                return  <div>
     247                                        {this.props.results.length > 0 ? this.renderKwicCheckbox() : false}
    191248                                        <ReactCSSTransitionGroup transitionName="fade">
    192249                                                {this.props.results.map(this.renderResultPanels)}
Note: See TracChangeset for help on using the changeset viewer.