Ignore:
Timestamp:
01/15/15 16:01:26 (9 years ago)
Author:
emanuel.dima@uni-tuebingen.de
Message:
  1. alpha11: UI refactor
File:
1 edited

Legend:

Unmodified
Added
Removed
  • SRUAggregator/trunk/src/main/resources/assets/js/search.jsx

    r5900 r5919  
    33"use strict";
    44
     5window.MyAggregator = window.MyAggregator || {};
     6
    57var React = window.React;
    68var PT = React.PropTypes;
    7 var ReactCSSTransitionGroup = window.React.addons.CSSTransitionGroup;
    8 // own components
     9var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
     10
     11var CorpusSelection = window.MyAggregator.CorpusSelection;
     12var HitNumber = window.MyAggregator.HitNumber;
     13var CorpusView = window.MyAggregator.CorpusView;
    914var InfoPopover = window.MyReact.InfoPopover;
    1015var Panel = window.MyReact.Panel;
     16var Modal = window.MyReact.Modal;
     17
     18var multipleLanguageCode = "mul"; // see ISO-693-3
     19
     20var layers = [
     21        {
     22                id: "sampa",
     23                name: "Phonetic Transcriptions",
     24                searchPlaceholder: "stA:z",
     25                searchLabel: "SAMPA query",
     26                searchLabelBkColor: "#eef",
     27        },
     28        {
     29                id: "text",
     30                name: "Text Resources",
     31                searchPlaceholder: "Elephant",
     32                searchLabel: "Search text",
     33                searchLabelBkColor: "#fed",
     34        },
     35];
     36var layerMap = {
     37        sampa: layers[0],
     38        text: layers[1],
     39};
     40
     41function Corpora(corpora, updateFn) {
     42        var that = this;
     43        this.corpora = corpora;
     44        this.update = function() {
     45                updateFn(that);
     46        };
     47       
     48        var sortFn = function(x, y) {
     49                var r = x.institution.name.localeCompare(y.institution.name);
     50                if (r !== 0) {
     51                        return r;
     52                }
     53                var t1 = x.title ? x.title : x.displayName;
     54                var t2 = y.title ? y.title : y.displayName;
     55                return t1.toLowerCase().localeCompare(t2.toLowerCase());
     56        };
     57
     58        this.recurse(function(corpus) { corpus.subCorpora.sort(sortFn); });
     59        this.corpora.sort(sortFn);
     60
     61        this.recurse(function(corpus, index) {
     62                corpus.visible = true; // visible in the corpus view
     63                corpus.selected = true; // selected in the corpus view
     64                corpus.expanded = false; // not expanded in the corpus view
     65                corpus.priority = 1; // priority in corpus view
     66                corpus.index = index;
     67        });
     68}
     69
     70Corpora.prototype.recurseCorpus = function(corpus, fn) {
     71        if (false === fn(corpus)) {             
     72                // no recursion
     73        } else {
     74                this.recurseCorpora(corpus.subCorpora, fn);
     75        }
     76};
     77
     78Corpora.prototype.recurseCorpora = function(corpora, fn) {
     79        var recfn = function(corpus, index){
     80                if (false === fn(corpus)) {
     81                        // no recursion
     82                } else {
     83                        corpus.subCorpora.forEach(recfn);
     84                }
     85        };
     86        corpora.forEach(recfn);
     87};
     88
     89Corpora.prototype.recurse = function(fn) {
     90        this.recurseCorpora(this.corpora, fn);
     91};
     92
     93Corpora.prototype.getLanguageCodes = function() {
     94        var languages = {};
     95        this.recurse(function(corpus) {
     96                corpus.languages.forEach(function(lang) {
     97                        languages[lang] = true;
     98                });
     99                return true;
     100        });
     101        return languages;
     102};
     103
     104Corpora.prototype.isCorpusVisible = function(corpus, layerId, languageCode) {
     105        if (layerId !== "text") {
     106                return false;
     107        }
     108        // yes for any language
     109        if (languageCode === multipleLanguageCode) {
     110                return true;
     111        }
     112        // yes if the corpus is in only that language
     113        if (corpus.languages && corpus.languages.length === 1 && corpus.languages[0] === languageCode) {
     114                return true;
     115        }       
     116
     117        // ? yes if the corpus also contains that language
     118        if (corpus.languages && corpus.languages.indexOf(languageCode) >=0) {
     119                return true;
     120        }
     121
     122        // ? yes if the corpus has no language
     123        // if (!corpus.languages || corpus.languages.length === 0) {
     124        //      return true;
     125        // }
     126        return false;
     127};
     128
     129Corpora.prototype.setVisibility = function(layerId, languageCode) {
     130        // top level
     131        this.corpora.forEach(function(corpus) {
     132                corpus.visible = this.isCorpusVisible(corpus, layerId, languageCode);
     133                this.recurseCorpora(corpus.subCorpora, function(c) { c.visible = corpus.visible; });
     134        }.bind(this));
     135};
     136
     137Corpora.prototype.getSelectedIds = function() {
     138        var ids = [];
     139        this.recurse(function(corpus) {
     140                if (corpus.visible && corpus.selected) {
     141                        ids.push(corpus.id);
     142                        return false; // top-most collection in tree, don't delve deeper
     143                }
     144                return true;
     145        });
     146
     147        // console.log("ids: ", ids.length, {ids:ids});
     148        return ids;
     149};
     150
     151Corpora.prototype.getSelectedMessage = function() {
     152        var selected = this.getSelectedIds().length;
     153        if (this.corpora.length === selected) {
     154                return "All available collections";
     155        } else if (selected === 1) {
     156                return "1 selected collection";
     157        }
     158        return selected+" selected collections";
     159};
     160
     161
     162var AggregatorPage = window.MyAggregator.AggregatorPage = React.createClass({
     163        propTypes: {
     164                ajax: PT.func.isRequired
     165        },
     166
     167        mixins: [React.addons.LinkedStateMixin],
     168        timeout: 0,
     169        nohits: {
     170                requests: [],
     171                results: [],
     172        },
     173        anyLanguage: [multipleLanguageCode, "Any Language"],
     174
     175        getInitialState: function () {
     176                return {
     177                        corpora: new Corpora([], this.updateCorpora),
     178                        languageMap: {},
     179                        language: this.anyLanguage,
     180                        searchLayerId: "text",
     181                        numberOfResults: 10,
     182
     183                        searchId: null,
     184                        hits: this.nohits,
     185                };
     186        },
     187
     188        componentDidMount: function() {
     189                this.refreshCorpora();
     190                this.refreshLanguages();
     191        },
     192
     193        refreshCorpora: function() {
     194                this.props.ajax({
     195                        url: 'rest/corpora',
     196                        success: function(json, textStatus, jqXHR) {
     197                                this.setState({corpora : new Corpora(json, this.updateCorpora)});
     198                        }.bind(this),
     199                });
     200        },
     201
     202        refreshLanguages: function() {
     203                this.props.ajax({
     204                        url: 'rest/languages',
     205                        success: function(json, textStatus, jqXHR) {
     206                                this.setState({languageMap : json});
     207                        }.bind(this),
     208                });
     209        },
     210
     211        updateCorpora: function(corpora) {
     212                this.setState({corpora:corpora});
     213        },
     214
     215        search: function(query) {
     216                // console.log(query);
     217                if (!query) {
     218                        this.setState({ hits: this.nohits, searchId: null });
     219                        return;                 
     220                }
     221                this.props.ajax({
     222                        url: 'rest/search',
     223                        type: "POST",
     224                        data: {
     225                                layer: this.state.searchLayerId,
     226                                language: this.state.language[0],
     227                                query: query,
     228                                numberOfResults: this.state.numberOfResults,
     229                                corporaIds: this.state.corpora.getSelectedIds(),
     230                        },
     231                        success: function(searchId, textStatus, jqXHR) {
     232                                // console.log("search ["+query+"] ok: ", searchId, jqXHR);
     233                                this.setState({searchId : searchId});
     234                                this.timeout = 250;
     235                                setTimeout(this.refreshSearchResults, this.timeout);
     236                        }.bind(this),
     237                });
     238        },
     239
     240        refreshSearchResults: function() {
     241                if (!this.state.searchId) {
     242                        return;
     243                }
     244                this.props.ajax({
     245                        url: 'rest/search/'+this.state.searchId,
     246                        success: function(json, textStatus, jqXHR) {
     247                                if (json.requests.length > 0) {
     248                                        if (this.timeout < 10000) {
     249                                                this.timeout = 1.5 * this.timeout;
     250                                        }
     251                                        setTimeout(this.refreshSearchResults, this.timeout);
     252                                        // console.log("new search in: " + this.timeout+ "ms");
     253                                } else {
     254                                        // console.log("search ended");
     255                                }
     256                                this.setState({hits:json});
     257                                // console.log("hits:", json);
     258                        }.bind(this),
     259                });
     260        },
     261
     262        setLanguage: function(languageObj) {
     263                this.state.corpora.setVisibility(this.state.searchLayerId, languageObj[0]);
     264                this.setState({language: languageObj});
     265                this.state.corpora.update();
     266        },
     267
     268        setLayer: function(layerId) {
     269                this.state.corpora.setVisibility(layerId, this.state.language[0]);
     270                this.state.corpora.update();
     271                this.setState({searchLayerId: layerId});
     272        },
     273
     274        setNumberOfResults: function(e) {
     275                var n = e.target.value;
     276                if (n < 10) n = 10;
     277                if (n > 250) n = 250;
     278                this.setState({numberOfResults: n});
     279                e.preventDefault();
     280                e.stopPropagation();
     281        },
     282
     283        stop: function(e) {
     284                e.preventDefault();
     285                e.stopPropagation();
     286        },
     287
     288        toggleCorpusSelection: function(e) {
     289                $(this.refs.corporaModal.getDOMNode()).modal();
     290                e.preventDefault();
     291                e.stopPropagation();
     292        },
     293
     294        renderAggregator: function() {
     295                var layer = layerMap[this.state.searchLayerId];
     296                return  (
     297                        <div className="top-gap">
     298                                <div className="row">
     299                                        <div className="aligncenter" style={{marginLeft:16, marginRight:16}}>
     300                                                <div className="input-group">
     301                                                        <span className="input-group-addon" style={{backgroundColor:layer.searchLabelBkColor}}>
     302                                                                {layer.searchLabel}
     303                                                        </span>
     304
     305                                                        <SearchBox search={this.search} placeholder={layer.searchPlaceholder} />
     306                                                        <div className="input-group-btn">
     307                                                                <button className="btn btn-default input-lg" type="button" onClick={this.search}>
     308                                                                        <i className="glyphicon glyphicon-search"></i>
     309                                                                </button>
     310                                                        </div>
     311                                                </div>
     312                                        </div>
     313                                </div>
     314
     315                                <div className="wel" style={{marginTop:20}}>
     316                                        <div className="aligncenter" >
     317                                                <form className="form-inline" role="form">
     318
     319                                                        <div className="input-group">
     320                                                               
     321                                                                <span className="input-group-addon nobkg" >Search for</span>
     322                                                               
     323                                                                <div className="input-group-btn">
     324                                                                        <button className="form-control btn btn-default"
     325                                                                                        aria-expanded="false" data-toggle="dropdown">
     326                                                                                {this.state.language[1]} <span className="caret"/>
     327                                                                        </button>
     328                                                                        <ul ref="languageDropdownMenu" className="dropdown-menu">
     329                                                                                <li key={this.anyLanguage[0]}> <a tabIndex="-1" href="#"
     330                                                                                                onClick={this.setLanguage.bind(this, this.anyLanguage)}>
     331                                                                                        {this.anyLanguage[1]}</a>
     332                                                                                </li>
     333                                                                                {       _.pairs(this.state.languageMap).sort(function(l1, l2){
     334                                                                                                return l1[1].localeCompare(l2[1]);
     335                                                                                        }).map(function(l) {
     336                                                                                                var desc = l[1] + " [" + l[0] + "]";
     337                                                                                                return <li key={l[0]}> <a tabIndex="-1" href="#"
     338                                                                                                        onClick={this.setLanguage.bind(this, l)}>{desc}</a></li>;
     339                                                                                        }.bind(this))
     340                                                                                }
     341                                                                        </ul>
     342                                                                </div>
     343
     344                                                                <div className="input-group-btn">
     345                                                                        <ul ref="layerDropdownMenu" className="dropdown-menu">
     346                                                                                {       layers.map(function(l) {
     347                                                                                                return <li key={l.id}> <a tabIndex="-1" href="#"
     348                                                                                                        onClick={this.setLayer.bind(this, l.id)}> {l.name} </a></li>;
     349                                                                                        }.bind(this))
     350                                                                                }
     351                                                                        </ul>                                                           
     352                                                                        <button className="form-control btn btn-default"
     353                                                                                        aria-expanded="false" data-toggle="dropdown" >
     354                                                                                {layer.name} <span className="caret"/>
     355                                                                        </button>
     356                                                                </div>
     357
     358                                                        </div>
     359
     360                                                        <div className="input-group">
     361                                                                <span className="input-group-addon nobkg">in</span>
     362                                                                        <button type="button" className="btn btn-default" onClick={this.toggleCorpusSelection}>
     363                                                                                {this.state.corpora.getSelectedMessage()} <span className="caret"/>
     364                                                                        </button>
     365                                                        </div>                                                 
     366
     367                                                        <div className="input-group">
     368                                                                <span className="input-group-addon nobkg">and show up to</span>
     369                                                                <div className="input-group-btn">
     370                                                                        <input type="number" className="form-control input" min="10" max="250" step="5"
     371                                                                                style={{width:54}}
     372                                                                                onChange={this.setNumberOfResults} value={this.state.numberOfResults}
     373                                                                                onKeyPress={this.stop}/>
     374                                                                </div>
     375                                                                <span className="input-group-addon nobkg">hits</span>
     376                                                        </div>
     377                                                </form>
     378                                        </div>
     379                                </div>
     380
     381                    <Modal ref="corporaModal" title="Collections">
     382                                        <CorpusView corpora={this.state.corpora} languageMap={this.state.languageMap} />
     383                    </Modal>
     384
     385                                <div className="top-gap">
     386                                        <Results requests={this.state.hits.requests} results={this.state.hits.results} />
     387                                </div>
     388                        </div>
     389                        );
     390        },
     391        render: function() {
     392                return this.renderAggregator();
     393        }
     394});
     395
    11396
    12397
     
    67452        },
    68453
     454        renderRowLanguage: function(hit) {
     455                return <span style={{fontFace:"Courier",color:"black"}}>{hit.language}</span>;
     456        },
     457
    69458        renderRowsAsHits: function(hit,i) {
    70459                function renderTextFragments(tf, idx) {
     
    72461                }
    73462                return  <p key={i} className="hitrow">
     463                                        {this.renderRowLanguage(hit)}
    74464                                        {hit.fragments.map(renderTextFragments)}
    75465                                </p>;
     
    81471                var sright={textAlign:"right", verticalAlign:"middle", maxWidth:"50%"};
    82472                return  <tr key={i} className="hitrow">
     473                                        <td>{this.renderRowLanguage(hit)}</td>
    83474                                        <td style={sright}>{hit.left}</td>
    84475                                        <td style={scenter} className="keyword">{hit.keyword}</td>
     
    208599});
    209600
    210 if (!window.MyAggregator) {
    211         window.MyAggregator = {};
    212 }
    213 window.MyAggregator.SearchBox = SearchBox;
    214 window.MyAggregator.Results = Results;
     601var _ = window._ = window._ || {
     602        keys: function() {
     603                var ret = [];
     604                for (var x in o) {
     605                        if (o.hasOwnProperty(x)) {
     606                                ret.push(x);
     607                        }
     608                }
     609                return ret;
     610        },
     611
     612        pairs: function(o){
     613                var ret = [];
     614                for (var x in o) {
     615                        if (o.hasOwnProperty(x)) {
     616                                ret.push([x, o[x]]);
     617                        }
     618                }
     619                return ret;
     620        },
     621};
     622
    215623})();
Note: See TracChangeset for help on using the changeset viewer.