Index: SRUAggregator/trunk/src/main/resources/assets/js/components/layeroptions.jsx
===================================================================
--- SRUAggregator/trunk/src/main/resources/assets/js/components/layeroptions.jsx	(revision 7222)
+++ SRUAggregator/trunk/src/main/resources/assets/js/components/layeroptions.jsx	(revision 7222)
@@ -0,0 +1,749 @@
+settings.senseAutoComplete = "<autoc model='model' placeholder='placeholder' type='sense' text-in-field='textInField'/>";
+
+var karpLemgramLink = "https://spraakbanken.gu.se/karp/#?mode=DEFAULT&search=extended||and|lemgram|equals|<%= val.replace(/:\\d+/, '') %>";
+
+var liteOptions = {
+    "is": "=",
+    "is_not": "!="
+}
+var setOptions = {
+    "is": "contains",
+    "is_not": "not contains"
+};
+var probabilitySetOptions = {
+    "is": "highest_rank",
+    "is_not": "not_highest_rank",
+    "contains": "rank_contains",
+    "contains_not": "not_rank_contains",
+};
+
+var defaultContext = {
+    "1 sentence": "1 sentence"
+};
+
+var spContext = {
+    "1 sentence": "1 sentence",
+    "1 paragraph": "1 paragraph"
+};
+var spWithin = {
+    "sentence": "sentence",
+    "paragraph": "paragraph"
+};
+
+var attrs = {};  // positional attributes
+var sattrs = {}; // structural attributes
+
+attrs.pos = {
+    label: "pos",
+    translationKey: "pos_",
+    dataset: {
+        "AB": "AB",
+        "MID|MAD|PAD": "DL",
+        "DT": "DT",
+        "HA": "HA",
+        "HD": "HD",
+        "HP": "HP",
+        "HS": "HS",
+        "IE": "IE",
+        "IN": "IN",
+        "JJ": "JJ",
+        "KN": "KN",
+        "NN": "NN",
+        "PC": "PC",
+        "PL": "PL",
+        "PM": "PM",
+        "PN": "PN",
+        "PP": "PP",
+        "PS": "PS",
+        "RG": "RG",
+        "RO": "RO",
+        "SN": "SN",
+        "UO": "UO",
+        "VB": "VB"
+    },
+    opts: liteOptions,
+    extendedComponent: "datasetSelect",
+    escape: false,
+    order: 0
+};
+
+attrs.msd = {
+    label: "msd",
+    opts: settings.defaultOptions,
+    extendedTemplate: '<input ng-model="input" class="arg_value" escaper ng-model-options=\'{debounce : {default : 300, blur : 0}, updateOn: "default blur"}\'>' +
+    '<span ng-click="onIconClick()" class="fa fa-info-circle"></span>',
+    extendedController: function($scope, $uibModal) {
+        var modal = null;
+
+        $scope.onIconClick = function() {
+            var msdHTML = settings.markup.msd;
+            modal = $uibModal.open({
+                template: '<div>' +
+                                '<div class="modal-header">' +
+                                    '<h3 class="modal-title">{{\'msd_long\' | loc:lang}}</h3>' +
+                                    '<span ng-click="clickX()" class="close-x">Ã</span>' +
+                                '</div>' +
+                                '<div class="modal-body msd-modal" ng-click="msdClick($event)" ng-include="' + msdHTML + '"></div>' +
+                            '</div>',
+                scope: $scope
+            })
+        }
+        $scope.clickX = function(event) {
+            modal.close()
+        }
+        $scope.msdClick = function(event) {
+            val = $(event.target).parent().data("value")
+            if(!val) return;
+            $scope.input = val;
+            modal.close();
+        }
+    }
+};
+attrs.baseform = {
+    label: "baseform",
+    type: "set",
+    opts: settings.defaultOptions,
+    extendedTemplate: "<input ng-model='model' >",
+    order: 1
+};
+attrs.lemgram = {
+    label: "lemgram",
+    type: "set",
+    opts: setOptions,
+    stringify: function(lemgram) {
+        // TODO: what if we're getting more than one consequtive lemgram back?
+        return util.lemgramToString(_.trim(lemgram), true);
+    },
+    externalSearch: karpLemgramLink,
+    internalSearch: true,
+    extendedTemplate: "<autoc model='model' placeholder='placeholder' type='lemgram' typeahead-close-callback='checkForError(valueSelected)' text-in-field='textInField'/>"
+                        + "<span ng-if='valueError' style='color: red; position: relative; top: 3px; margin-left: 6px'>{{'choose_lemgram' | loc:lang}}</span>",
+    extendedController: function($scope) {
+        $scope.valueError = false;
+
+        $scope.checkForError = function(valueSelected) {
+            $scope.valueError = !valueSelected;
+        }
+    },
+    order: 2
+};
+attrs.dalinlemgram = {
+    label: "dalin-lemgram",
+    type: "set",
+    opts: setOptions,
+    stringify: function(lemgram) {
+        // TODO: what if we're getting more than one consequtive lemgram back?
+        return util.lemgramToString(_.trim(lemgram), true);
+    },
+    externalSearch: karpLemgramLink,
+    internalSearch: true,
+    extendedTemplate: "<autoc model='model' placeholder='placeholder' type='lemgram' variant='dalin' text-in-field='textInField'/>",
+    order: 2
+};
+attrs.saldo = {
+    label: "saldo",
+    type: "set",
+    opts: setOptions,
+    stringify: function(saldo) {
+        return util.saldoToString(saldo, true);
+    },
+    externalSearch: "https://spraakbanken.gu.se/karp/#?mode=DEFAULT&search=extended||and|sense|equals|<%= val %>",
+    internalSearch: true,
+    extendedTemplate: settings.senseAutoComplete,
+    order: 3
+};
+attrs.dephead = {
+    label: "dephead",
+    displayType: "hidden"
+};
+attrs.deprel = {
+    label: "deprel",
+    translationKey: "deprel_",
+    extendedComponent: "datasetSelect",
+    dataset: {
+        "++": "++",
+        "+A": "+A",
+        "+F": "+F",
+        "AA": "AA",
+        "AG": "AG",
+        "AN": "AN",
+        "AT": "AT",
+        "CA": "CA",
+        "DB": "DB",
+        "DT": "DT",
+        "EF": "EF",
+        "EO": "EO",
+        "ES": "ES",
+        "ET": "ET",
+        "FO": "FO",
+        "FP": "FP",
+        "FS": "FS",
+        "FV": "FV",
+        "I?": "I?",
+        "IC": "IC",
+        "IG": "IG",
+        "IK": "IK",
+        "IM": "IM",
+        "IO": "IO",
+        "IP": "IP",
+        "IQ": "IQ",
+        "IR": "IR",
+        "IS": "IS",
+        "IT": "IT",
+        "IU": "IU",
+        "IV": "IV",
+        "JC": "JC",
+        "JG": "JG",
+        "JR": "JR",
+        "JT": "JT",
+        "KA": "KA",
+        "MA": "MA",
+        "MS": "MS",
+        "NA": "NA",
+        "OA": "OA",
+        "OO": "OO",
+        "OP": "OP",
+        "PL": "PL",
+        "PR": "PR",
+        "PT": "PT",
+        "RA": "RA",
+        "SP": "SP",
+        "SS": "SS",
+        "TA": "TA",
+        "TT": "TT",
+        "UK": "UK",
+        "VA": "VA",
+        "VO": "VO",
+        "VS": "VS",
+        "XA": "XA",
+        "XF": "XF",
+        "XT": "XT",
+        "XX": "XX",
+        "YY": "YY",
+        "CJ": "CJ",
+        "HD": "HD",
+        "IF": "IF",
+        "PA": "PA",
+        "UA": "UA",
+        "VG": "VG",
+        "ROOT": "ROOT"
+    },
+    opts: liteOptions
+};
+attrs.prefix = {
+    label: "prefix",
+    type: "set",
+    opts: setOptions,
+    stringify: function(lemgram) {
+        return util.lemgramToString(lemgram, true);
+    },
+    externalSearch: karpLemgramLink,
+    internalSearch: true,
+    extendedTemplate: "<autoc model='model' placeholder='placeholder' type='lemgram' variant='affix' text-in-field='textInField'/>"
+};
+attrs.suffix = {
+    label: "suffix",
+    type: "set",
+    opts: setOptions,
+    stringify: function(lemgram) {
+        return util.lemgramToString(lemgram, true);
+    },
+    externalSearch: karpLemgramLink,
+    internalSearch: true,
+    extendedTemplate: "<autoc model='model' placeholder='placeholder' type='lemgram' variant='affix' text-in-field='textInField'/>"
+};
+attrs.ref = {
+    label: "ref",
+    displayType: "hidden"
+};
+attrs.link = {
+    label: "sentence_link"
+};
+attrs.ne_ex = {
+    label: "ne_expr",
+    translationKey: "ne_expr_",
+    extendedComponent: "datasetSelect",
+    isStructAttr: true,
+    dataset: [
+       "ENAMEX",
+       "TIMEX",
+       "NUMEX",
+   ]
+};
+attrs.ne_type = {
+    label: "ne_type",
+    translationKey: "ne_type_",
+    extendedComponent: "datasetSelect",
+    isStructAttr: true,
+    dataset: [
+       "LOC",
+       "PRS",
+       "ORG",
+       "EVN",
+       "WRK",
+       "OBJ",
+       "MSR",
+       "TME"
+   ]
+};
+attrs.ne_subtype = {
+    label: "ne_subtype",
+    translationKey: "ne_subtype_",
+    extendedComponent: "datasetSelect",
+    isStructAttr: true,
+    dataset: [
+        "AST",
+        "GPL",
+        "PPL",
+        "FNC",
+        "STR",
+        "HUM",
+        "MTH",
+        "ANM",
+        "CLC",
+        "FIN",
+        "ATH",
+        "CLT",
+        "PLT",
+        "TVR",
+        "EDU",
+        "TRN",
+        "CRP",
+        "HPL",
+        "WTH",
+        "CLU",
+        "ATL",
+        "RLG",
+        "WRT",
+        "RTV",
+        "WAO",
+        "PRJ",
+        "WMD",
+        "WAE",
+        "MDC",
+        "FWP",
+        "CMP",
+        "VHA",
+        "VHG",
+        "VHW",
+        "PRZ",
+        "PRD",
+        "VLM",
+        "TMP",
+        "INX",
+        "DST",
+        "PRC",
+        "CUR",
+        "DEN",
+        "DSG",
+        "SPD",
+        "FRQ",
+        "AGE",
+        "MSU",
+        "WMU",
+        "CMU",
+        "WEB",
+        "PSS",
+        "CVU",
+        "IDX",
+        "LST",
+        "DAT",
+        "PER"
+   ],
+   stringify: function(val) {
+       lString = util.getLocaleStringUndefined("ne_subtype_" + val)
+       return lString || val;
+   }
+};
+attrs.ne_name = {
+    label: "ne_name",
+    isStructAttr: true
+};
+sattrs.date = {
+    label: "date"
+};
+
+var modernAttrsOld = {
+    pos: attrs.pos,
+    msd: attrs.msd,
+    lemma: attrs.baseform,
+    lex: attrs.lemgram,
+    saldo: attrs.saldo,
+    dephead: attrs.dephead,
+    deprel: attrs.deprel,
+    ref: attrs.ref,
+    prefix: attrs.prefix,
+    suffix: attrs.suffix
+};
+
+
+var modernAttrs = {
+    pos: attrs.pos,
+    msd: attrs.msd,
+    lemma: attrs.baseform,
+    lex: attrs.lemgram,
+    dephead: attrs.dephead,
+    deprel: attrs.deprel,
+    ref: attrs.ref,
+    prefix: attrs.prefix,
+    suffix: attrs.suffix,
+    ne_ex: attrs.ne_ex,
+    ne_type: attrs.ne_type,
+    ne_subtype: attrs.ne_subtype,
+    ne_name: attrs.ne_name,
+    complemgram: {
+        label: "complemgram",
+        internalSearch: true,
+        ranked: true,
+        display: {
+            expandList: {
+                splitValue: function(value) { return value.split("+"); },
+                searchKey: "lex",
+                joinValues: " + ",
+                stringify: function(lemgram) { return util.lemgramToString(lemgram, true); },
+                linkAllValues: true
+            }
+        },
+        type: "set",
+        hideStatistics: true,
+        hideExtended: true,
+        hideCompare: true
+    },
+    compwf: {
+        label: "compwf",
+        display: {
+            "expandList": {}
+        },
+        type: "set",
+        hideStatistics: true,
+        hideExtended: true,
+        hideCompare: true
+    },
+    sense: {
+        label: "sense",
+        type: "set",
+        ranked: true,
+        display: {
+            expandList: {
+                internalSearch: function(key, value) { return "[" + key + " highest_rank '" + regescape(value) + "']"}
+            }
+        },
+        stringify: function(sense) { return util.saldoToString(sense, true); },
+        opts: probabilitySetOptions,
+        externalSearch: "https://spraakbanken.gu.se/karp/#?mode=DEFAULT&search=extended||and|sense|equals|<%= val %>",
+        internalSearch: true,
+        extendedTemplate: settings.senseAutoComplete
+    }
+};
+
+var modernAttrs2 = {
+    pos: attrs.pos,
+    msd: attrs.msd,
+    lemma: attrs.baseform,
+    lex: attrs.lemgram,
+    dephead: attrs.dephead,
+    deprel: attrs.deprel,
+    ref: attrs.ref,
+    prefix: attrs.prefix,
+    suffix: attrs.suffix,
+    ne_ex: attrs.ne_ex,
+    ne_type: attrs.ne_type,
+    ne_subtype: attrs.ne_subtype,
+    ne_name: attrs.ne_name,
+    complemgram: modernAttrs.complemgram,
+    compwf: modernAttrs.compwf,
+    sense: modernAttrs.sense,
+    sentiment: {
+        label: "sentiment"
+    },
+    blingbring: {
+        label: "blingbring",
+        type: "set",
+        internalSearch: true
+    },
+    swefn: {
+        label: "swefn",
+        type: "set",
+        externalSearch: "https://spraakbanken.gu.se/karp/#?mode=swefn&search=sense%7C%7Cswefn--<%= val %>",
+        internalSearch: true
+    }
+};
+
+var lexClassesText = {
+    text_blingbring: {
+        label: "blingbring",
+        type: "set",
+        isStructAttr: true,
+        ranked: true,
+        order: 500,
+        display: {
+            expandList: {
+                internalSearch: function(key, value) { return "[_.text_blingbring highest_rank '" + regescape(value) + "']"},
+                linkAllValues: true,
+                showAll: true
+            }
+        },
+        internalSearch: true
+    },
+    text_swefn: {
+        label: "swefn",
+        type: "set",
+        isStructAttr: true,
+        ranked: true,
+        order: 501,
+        display: {
+            expandList: {
+                internalSearch: function(key, value) { return "[_.text_swefn highest_rank '" + regescape(value) + "']"},
+                linkAllValues: true,
+                showAll: true
+            }
+        },
+        externalSearch: "https://spraakbanken.gu.se/karp/#?mode=swefn&search=sense%7C%7Cswefn--<%= val %>",
+        internalSearch: true
+    }
+};
+
+var readability = {
+    lix: {
+        label: "lix",
+        isStructAttr: true,
+        order: 600
+    },
+    ovix: {
+        label: "ovix",
+        isStructAttr: true,
+        order: 601
+    },
+    nk: {
+        label: "nk",
+        isStructAttr: true,
+        order: 602
+    }
+};
+
+settings.posset = {
+   type: "set",
+   label: "posset",
+   opts: setOptions,
+   translationKey: "pos_",
+   extendedComponent: "datasetSelect",
+   dataset:  {
+        "AB": "AB",
+        "MID|MAD|PAD": "DL",
+        "DT": "DT",
+        "HA": "HA",
+        "HD": "HD",
+        "HP": "HP",
+        "HS": "HS",
+        "IE": "IE",
+        "IN": "IN",
+        "JJ": "JJ",
+        "KN": "KN",
+        "NN": "NN",
+        "PC": "PC",
+        "PL": "PL",
+        "PM": "PM",
+        "PN": "PN",
+        "PP": "PP",
+        "PS": "PS",
+        "RG": "RG",
+        "RO": "RO",
+        "SN": "SN",
+        "UO": "UO",
+        "VB": "VB"
+    },
+    order: 0
+};
+
+settings.fsvlemma = {
+    type: "set",
+    label: "baseform",
+    opts: setOptions,
+    extendedTemplate: "<input ng-model='model' >"
+};
+settings.fsvlex = {
+    type: "set",
+    label: "lemgram",
+    opts: setOptions,
+    extendedTemplate: "<autoc model='model' placeholder='placeholder' type='lemgram' text-in-field='textInField'/>",
+    stringify: function(str) {
+        return util.lemgramToString(str, true);
+    },
+    externalSearch: karpLemgramLink,
+    internalSearch: true
+};
+settings.fsvvariants = {
+    type: "set",
+    label: "variants",
+    stringify: function(str) {
+        return util.lemgramToString(str, true);
+    },
+    extendedTemplate: "<autoc model='model' placeholder='placeholder' type='lemgram' text-in-field='textInField'/>",
+    opts: setOptions,
+    externalSearch: karpLemgramLink,
+    internalSearch: true,
+    order: 4
+};
+
+settings.fsvdescription ='<a target="_blank" href="http://project2.sol.lu.se/fornsvenska/">Fornsvenska textbanken</a> Ã€r ett projekt som digitaliserar fornsvenska texter och gÃ¶r dem tillgÃ€ngliga Ã¶ver webben. Projektet leds av Lars-Olof Delsing vid Lunds universitet.';
+
+var fsv_yngrelagar = {
+    morphology: 'fsvm',
+    id: "fsv-yngrelagar",
+    title: "Yngre lagar â Fornsvenska textbankens material",
+    description: settings.fsvdescription,
+    within: settings.defaultWithin,
+    context: spContext,
+    attributes: {
+        posset: settings.posset,
+        lemma: settings.fsvlemma,
+        lex: settings.fsvlex,
+        variants: settings.fsvvariants
+        },
+    structAttributes: {
+        text_title: {
+            label: "title",
+            extendedComponent: "datasetSelect",
+            dataset: [
+                "Kristoffers Landslag, nyskrivna flockar i fÃ¶rhÃ¥llande till MEL",
+                "Kristoffers Landslag, innehÃ¥llsligt Ã€ndrade flockar i fÃ¶rhÃ¥llande til MEL",
+                "Kristoffers Landslag, flockar direkt hÃ€mtade frÃ¥n MEL",
+                "Kristoffers Landslag"
+                ],
+        },
+        text_date: {label: "date"}
+    }
+};
+
+var fsv_aldrelagar = {
+    morphology: 'fsvm',
+    id: "fsv-aldrelagar",
+    title: "Ãldre lagar â Fornsvenska textbankens material",
+    description: settings.fsvdescription,
+    within: settings.defaultWithin,
+    context: spContext,
+    attributes: {
+        posset: settings.posset,
+        lemma: settings.fsvlemma,
+        lex: settings.fsvlex,
+        variants: settings.fsvvariants
+                },
+    structAttributes: {
+        text_title: {
+            label: "title",
+            extendedComponent: "datasetSelect",
+            dataset: [
+                "Yngre VÃ€stgÃ¶talagens Ã€ldsta fragment, Lydekini excerpter och anteckningar",
+                "TillÃ€gg till Upplandslagen, hskr A (Ups B 12)",
+                "SÃ¶dermannalagen, enligt Codex iuris Sudermannici",
+                "ÃstgÃ¶talagen, fragment H, ur Kyrkobalken ur Skokloster Avdl I 145",
+                "Yngre VÃ€stmannalagen, enl Holm B 57",
+                "VidhemsprÃ€stens anteckningar",
+                "Magnus Erikssons Stadslag, exklusiva stadslagsflockar",
+                "SÃ¶dermannalagens additamenta, efter NKS 2237",
+                "HÃ€lsingelagen",
+                "Yngre VÃ€stgÃ¶talagen, tillÃ€gg, enligt Holm B 58",
+                "ÃstgÃ¶talagen, fragment C, ur Holm B 1709",
+                "Yngre VÃ€stgÃ¶talagen, enligt Holm B 58",
+                "Upplandslagen enl Schlyters utgÃ¥va och Codex Ups C 12, hskr A",
+                "SkÃ¥nelagen, enligt Holm B 76",
+                "ÃstgÃ¶talagen, fragment D, ur Holm B 24",
+                "ÃstgÃ¶talagen A, ur Holm B 50",
+                "Ãldre VÃ€stgÃ¶talagen",
+                "ÃstgÃ¶talagen, fragment M, ur Holm B 196",
+                "Gutalagen enligt Holm B 64",
+                "Upplandslagen enligt Codex Holm B 199, Schlyters hskr B",
+                "SmÃ¥landslagens kyrkobalk",
+                "Dalalagen (Ãldre VÃ€stmannalagen)",
+                "Gutalagens additamenta enligt AM 54",
+                "BjÃ€rkÃ¶arÃ€tten",
+                "Magnus Erikssons Landslag",
+                "ÃstgÃ¶talagen, fragment N, ur KÃ¶penhamn AM 1056",
+                "SÃ¶dermannalagen stadsfÃ€stelse - Confirmatio, enligt NKS 2237",
+                "ÃstgÃ¶talagen, fragment E, ur Ups B 22"
+                            ],
+        },
+        text_date: {label: "date"}
+    }
+};
+
+settings.commonStructTypes = {
+    date_interval: {
+        label: "date_interval",
+        hideSidebar: "true",
+        hideCompare: "true",
+        hideStatistics: "true",
+        opts: false,
+        extendedTemplate: '<div class="date_interval_arg_type"> <div class="section"> <button class="btn btn-default btn-sm" popper no-close-on-click my="left top" at="right top"> <i class="fa fa-calendar"></i> FrÃ¥n </button> {{combined.format("YYYY-MM-DD HH:mm")}} <time-interval ng-click="from_click($event)" class="date_interval popper_menu dropdown-menu" date-model="from_date" time-model="from_time" model="combined" min-date="minDate" max-date="maxDate"> </time-interval> </div> <div class="section"> <button class="btn btn-default btn-sm" popper no-close-on-click my="left top" at="right top"> <i class="fa fa-calendar"></i> Till </button> {{combined2.format("YYYY-MM-DD HH:mm")}} <time-interval ng-click="from_click($event)" class="date_interval popper_menu dropdown-menu" date-model="to_date" time-model="to_time" model="combined2" my="left top" at="right top" min-date="minDate" max-date="maxDate"> </time-interval> </div> </div>',
+        extendedController: [
+            "$scope", "searches", "$timeout", function($scope, searches, $timeout) {
+                var cl, getTime, getYear, ref, ref1, ref2, s, updateIntervals;
+                s = $scope;
+                cl = settings.corpusListing;
+
+                updateIntervals = function() {
+                    var from, moments, ref, ref1, to;
+                    moments = cl.getMomentInterval();
+                    if (moments.length) {
+                        return ref = _.invokeMap(moments, "toDate"), s.minDate = ref[0], s.maxDate = ref[1], ref;
+                    } else {
+                        ref1 = cl.getTimeInterval(), from = ref1[0], to = ref1[1];
+                        s.minDate = moment(from.toString(), "YYYY").toDate();
+                        return s.maxDate = moment(to.toString(), "YYYY").toDate();
+                    }
+                };
+
+                s.$on("corpuschooserchange", function() {
+                  return updateIntervals();
+                });
+
+                updateIntervals();
+
+                s.from_click = function(event) {
+                  event.originalEvent.preventDefault();
+                  return event.originalEvent.stopPropagation();
+                };
+
+                getYear = function(val) {
+                  return moment(val.toString(), "YYYYMMDD").toDate();
+                };
+
+                getTime = function(val) {
+                  return moment(val.toString(), "HHmmss").toDate();
+                };
+
+                if (!s.model) {
+                    s.from_date = s.minDate;
+                    s.to_date = s.maxDate;
+                    ref = _.invokeMap(cl.getMomentInterval(), "toDate"), s.from_time = ref[0], s.to_time = ref[1];
+                } else if (s.model.length === 4) {
+                    ref1 = _.map(s.model.slice(0, 3), getYear), s.from_date = ref1[0], s.to_date = ref1[1];
+                    ref2 = _.map(s.model.slice(2), getTime), s.from_time = ref2[0], s.to_time = ref2[1];
+                }
+                return s.$watchGroup(["combined", "combined2"], function(arg) {
+                    var combined, combined2;
+                    combined = arg[0], combined2 = arg[1];
+                    return s.model = [moment(s.from_date).format("YYYYMMDD"), moment(s.to_date).format("YYYYMMDD"), moment(s.from_time).format("HHmmss"), moment(s.to_time).format("HHmmss")];
+                });
+            }
+        ]
+    }
+};
+
+module.exports = {
+  spWithin,
+  spContext,
+  modernAttrs,
+  modernAttrs2,
+  defaultContext,
+  attrs,
+  sattrs,
+  modernAttrsOld,
+  setOptions,
+  liteOptions,
+  lexClassesText,
+  readability,
+  fsv_aldrelagar,
+  fsv_yngrelagar
+}
+
Index: SRUAggregator/trunk/src/main/resources/assets/js/components/queryinput.jsx
===================================================================
--- SRUAggregator/trunk/src/main/resources/assets/js/components/queryinput.jsx	(revision 7221)
+++ SRUAggregator/trunk/src/main/resources/assets/js/components/queryinput.jsx	(revision 7222)
@@ -3,50 +3,84 @@
 import PropTypes from "prop-types";
 import createReactClass from "create-react-class";
+import PureRenderMixin from 'react-addons-pure-render-mixin';
 import {CSSTransition, TransitionGroup} from "react-transition-group";
+import {Controlled as CodeMirror} from 'react-codemirror2';
+require('codemirror/mode/fcs-ql/fcs-ql');
+require('codemirror/mode/javascript/javascript');
 
 var PT = PropTypes;
+
+function nextId() {
+    return nextId.id++;
+}
+nextId.id = 0;
 
 var QueryInput = createReactClass({
     //fixme! - class QueryInput extends React.Component {
     propTypes: {
-	searchedLanguage: PT.array,
-	queryTypeId: PT.string.isRequired, 
-	query: PT.string,
-	embedded: PT.bool.isRequired,
-	placeholder: PT.string,
-	onChange: PT.func.isRequired,
-	onQuery: PT.func.isRequired,
-	onKeyDown: PT.func.isRequired
+        searchedLanguage: PT.array,
+        queryTypeId: PT.string.isRequired, 
+        query: PT.string,
+        embedded: PT.bool.isRequired,
+        placeholder: PT.string,
+        onQueryChange: PT.func.isRequired,
+        onKeyDown: PT.func.isRequired
     },
 
     render: function() {
-	//if (this.props.queryTypeId == "cql") {
+	if (this.props.queryTypeId == "cql") {
 	    return (
-		<input className="form-control input-lg search" 
-		       id="query-cql" name="query-cql" type="text"
-		       value={this.props.query} placeholder={this.props.placeholder}
-		       tabIndex="1" onChange={this.props.onChange} 
-		       //onQuery={this.props.onQuery}
-		       onKeyDown={this.props.onKeyDown} 
-		       ref="cqlOrEmbeddedQuery"/>
+		    <input className="form-control input-lg search" 
+		          id="query-cql" name="query-cql" type="text"
+		          value={this.props.query} placeholder={this.props.placeholder}
+		          tabIndex="1" onChange={ (evt) => this.props.onQueryChange(evt.target.value) } 
+		          //onQuery={this.props.onQuery}
+		          onKeyDown={this.props.onKeyDown} 
+		          ref="cqlOrEmbeddedQuery"/>
+		      /*
+//		     <CodeMirror
+//		         value={this.props.placeholder || this.props.query}
+//		         options={{
+//		             mode: 'fcs-ql',
+//		             theme: 'default',
+//		             lineNumbers: false,
+//		             matchBrackets: true,
+//		             viewportMargin: 1
+//		         }}
+//		         className="form-control input-lg search"
+//		         onBeforeChange={(editor, data, value) => {
+//		             this.setState({value});
+//		         }}
+//		         onChange={(editor, data, value) => {
+//		             this.props.onChange(value);
+//		         }}
+//		         onQuery={this.props.onQuery}
+//		         onKeyDown={(editor, event) => {
+//		             this.props.onKeyDown(editor, event)
+//		         }}
+//		         ref="cqlOrEmbeddedQuery"
+//		         >
+//		      </CodeMirror>
+              */
 	    );
-	// } else if (this.props.embedded && this.props.queryTypeId == "fcs") {
-	//     return (
-	// 	<textarea className="form-control input-lg search"
-	// 	       id="query-fcs" name="query-fcs"
-	// 	       type="text" rows="1"
-	// 	       value={this.props.query} placeholder={this.props.placeholder}
-	// 	       tabIndex="1" onChange={this.props.onChange} 
-	// 	       //onQuery={this.props.onQuery}
-	// 	       onKeyDown={this.props.onKeyDown} 
-	// 	       ref="fcsOrEmbeddedQuery" />
-	//     );
-	// }
-	// return (<div id="adv_query_input_group" className="input-group-addon">
-	// 	    <ADVTokens
-	//                 query={this.props.query}
-	//                 ref="fcsGQB"
-	//             />
-	// </div>);
+	} else if (this.props.embedded && this.props.queryTypeId == "fcs") {
+	     return (
+	 	<textarea className="form-control input-lg search"
+	 	       id="query-fcs" name="query-fcs"
+	 	       type="text" rows="1"
+	 	       value={this.props.query} placeholder={this.props.placeholder}
+	 	       tabIndex="1" onChange={ (evt) => this.props.onQueryChange(evt.target.value) } 
+	 	       //onQuery={this.props.onQuery}
+	 	       onKeyDown={this.props.onKeyDown} 
+	 	       ref="fcsOrEmbeddedQuery" />
+	     );
+	 }
+	return (<div id="adv_query_input_group">
+	 	    <ADVTokens
+	                 query={this.props.query}
+	                 ref="fcsGQB"
+	                 onQueryChange={this.props.onQueryChange}
+	             />
+	 </div>);
     }
 });
@@ -55,49 +89,80 @@
 
     propTypes: {
-	query: PT.string
+        query: PT.string, // initial state query. If you want to set a new one, change the 'key' prop on ADVTokens.
+        onQueryChange: PT.func.isRequired
+    },
+    
+    getDefaultProps() {
+        return {
+            query: '[ word = "Elefant" ]',
+        }
     },
 
     getInitialState: function () {
-	return { 
-	    tokenCounter: 1,
-	    tokens: ["token1"] 
-	};
+        this.queryStrCache = {};
+        
+        console.log('ADVTokens:', this.props.query);
+        
+        var match = queryToTokens(this.props.query);
+        console.log('ADVTokens 2:', match);
+        if (match === null) {
+            return { tokens: ['token-'+nextId()] }
+        }
+        
+        var tokens = [];
+        match.forEach((m) => {
+            var id = 'token-'+nextId();
+            tokens.push(id);
+            this.queryStrCache[id] = m;
+        });
+        
+        return { tokens }
     },
 
     addADVToken: function() {
-	var i = this.state.tokenCounter + 1;
-	this.state.tokens.push('token' + i);
-	this.setState({tokenCounter: i, tokens: this.state.tokens});
+        this.setState( (oldSt) => {
+	        oldSt.tokens.push('token-'+nextId());
+	        return {tokens: oldSt.tokens}
+	    });
     },
     
     removeADVToken: function(id) {
-	var tokens = this.state.tokens;
-	var i = tokens.indexOf(id);
-	if (tokens.length > 1) {
-	    var one = tokens;
-	    var two = one.slice(0, i - 1)
-			 .concat(one.slice(i));;
-	    this.setState({tokens: two});
-	}
+	    this.setState( (oldSt) => {
+	        delete this.queryStrCache[id];
+	        oldSt.tokens.splice(oldSt.tokens.indexOf(id), 1);
+	        return {tokens: oldSt.tokens}
+	    }, this.fireQueryChange);
+    },
+    
+    fireQueryChange() {
+        var tokenParts = this.state.tokens.map( (id) => this.queryStrCache[id] );
+        const queryString = tokenParts.join(' ')
+        this.props.onQueryChange(queryString);
+    },
+    
+    onQueryChange(tokenId, queryStr) {
+        this.queryStrCache[tokenId] = queryStr;
+        this.fireQueryChange();
     },
 
     render: function() {
-	var i = 0;
-	var tokens = this.state.tokens.map(function (token, i) {
-	    return (
-	       <CSSTransition key={i} classNames="token" timeout={{enter: 250, exit: 250}}>
-		  <ADVToken 
-			key={token}
-			parentToken={token}
-		        handleRemoveADVToken={this.removeADVToken} />
-	       </CSSTransition>);
-	}.bind(this));
-
-	return (<div>
-	    <TransitionGroup>{tokens}</TransitionGroup>
-		<button className="btn btn-xs btn-default image_button insert_token" type="button" onClick={this.addADVToken} ref="addToken">
-		    <i className="glyphicon glyphicon-plus"></i>
-		</button>
-	</div>);
+        var i = 0;
+        var tokens = this.state.tokens.map( (tokenId, i) => {
+            return (
+               <CSSTransition key={tokenId} classNames="token" timeout={{enter: 250, exit: 250}}>
+                  <ADVToken
+                     query={this.queryStrCache[tokenId]}
+                     onQueryChange={(qs) => this.onQueryChange(tokenId, qs)}
+                     handleRemoveADVToken={() => this.removeADVToken(tokenId)} />
+               </CSSTransition>
+            );
+        });
+
+        return (<div id="adv-tokens">
+            <TransitionGroup>{tokens}</TransitionGroup>
+            <button className="btn btn-xs btn-default image_button insert_token" type="button" onClick={this.addADVToken}>
+                <i className="glyphicon glyphicon-plus"></i>
+            </button>
+        </div>);
     }
 });
@@ -105,115 +170,228 @@
 var ADVToken = createReactClass({
     propTypes: {
-	parentToken: PT.string.isRequired,
-	handleRemoveADVToken: PT.func.isRequired,
-    },
+        query: PT.string,
+        onQueryChange: PT.func.isRequired,
+	    handleRemoveADVToken: PT.func.isRequired,
+    },
+    
     render: function() {
-	return (<div className="token query_token inline btn-group" style={{display:"inline-block"}}>
-	    <div className="token_header">
-	       <button className="btn btn-xs btn-default image_button close_btn" type="button" onClick={this.props.handleRemoveADVToken(this.props.parentToken)} ref="removeToken">
-	          <i className="glyphicon glyphicon-remove-circle" />
-	       </button>
-	       <div style={{clear:"both"}} />
-	       </div>
-	       <div className="args">
-	       { /* and.query_arg* and token_footer */ }
-	         <ANDQueryArgs />
-	       <div className="lower_footer">
-	       </div>
-	    </div>
-	</div>);
+	    return (<div className="token query_token inline btn-group" style={{display:"inline-block"}}>
+	        <div className="token_header">
+	           <span className="image_button close_btn" type="button" onClick={this.props.handleRemoveADVToken} ref="removeToken">
+	              <i className="glyphicon glyphicon-remove-circle" />
+	           </span>
+	           <div style={{clear:"both"}} />
+	           </div>
+	           <div className="args">
+	           { /* and.query_arg* and token_footer */ }
+	             <ANDQueryArgs onQueryChange={this.props.onQueryChange} query={this.props.query} />
+	           <div className="lower_footer">
+	           </div>
+	        </div>
+	    </div>);
     }
 });
 
 var ADVTokenMenu = createReactClass({
+    mixins: [PureRenderMixin],
+    
+    propTypes: {
+        onChange: PT.func.isRequired,
+        repeat1: PT.string,
+        repeat2: PT.string,
+    },
+    
 	getInitialState: function() {
-	    return {"hideRepeatMenu": true};
+	    var repeat1 = this.props.repeat1||'';
+	    var repeat2 = this.props.repeat2||'';
+	    return {
+	        repeat1,
+	        repeat2,
+	        hideMenu: (repeat1||repeat2) ? false : true,
+	        isStart: false,
+	        isEnd: false,
+	   };
+	},
+	
+	getMenuState() {
+	    if (this.state.hideMenu) {
+	        return {};
+	    } else {
+	        return $.extend({}, this.state); // copy of state
+	    }
 	},
 
 	toggleRepeatMenu: function(e) {
-	    this.setState({"hideRepeatMenu": !this.state.hideRepeatMenu});
-	    e.preventDefault();
+	    this.setState((st) =>({hideMenu: !st.hideMenu}));
+	},
+	toggleStart: function(e) {
+	    this.setState((st) => ({isStart: !st.isStart}));
+	},
+	toggleEnd: function(e) {
+	    this.setState((st) => ({isEnd: !st.isEnd}));
+	},
+	componentDidMount() {
+	    // make this compoent controlled to so that this awkward ref.getMenuState() stuff can be removed
+	    this.props.onChange(this.getMenuState());
+	},
+	componentDidUpdate() {
+	    // safe because of pure render mixin: will only update on state change.
+	    this.props.onChange(this.getMenuState());
 	},
 	
 	render: function() {
-	    return (<div>
-	    <button className="btn btn-xs btn-default image_button repeat_menu" onClick={this.toggleRepeatMenu} ref="repeatMenu">
-		<i className="fa fa-cog" />
-	    </button>
-	    <div id="repeatMenu" className={"repeat hide-" + this.state.hideRepeatMenu}>
-		<span>repeat</span>
-		<input type="number" id="repeat1" value={this.state.repeat1} ref="repeat1"/>
-		<span>to</span>
-		<input type="number" id="repeat2" value={this.state.repeat2} ref="repeat2"/>
-		<span>times</span>
-	    </div>
+	    return (<div id="ADVtokenMenu">
+	        <button className="btn btn-xs btn-default image_button repeat_menu" onClick={this.toggleRepeatMenu} ref="repeatMenu">
+		        <i className="fa fa-cog" />
+            </button>
+            
+            <div id="ADVtokenMenu-items" className={"hide-" + this.state.hideMenu}>
+                <div id="repeatMenu" className={"repeat"}>
+	                <span>Repeat</span>
+	                <input type="text" id="repeat1" value={this.state.repeat1} onChange={(evt) => this.setState({repeat1: evt.target.value})} ref="repeat1"/>
+	                <span>to</span>
+	                <input type="text" id="repeat2" value={this.state.repeat2} onChange={(evt) => this.setState({repeat2: evt.target.value})} ref="repeat2"/>
+	                <span>times</span>
+                </div>
+                <div id="start-end-menu">
+	                <div><label><input type="checkbox" checked={this.state.isStart} onChange={this.toggleStart} /> Sentence start</label></div>
+	                <div><label><input type="checkbox" checked={this.state.isEnd} onChange={this.toggleEnd} /> Sentence end</label></div>
+                </div>
+            </div>
 	    </div>);
 	}
-    });
-
-    var ANDQueryArgs = createReactClass({
-
+});
+
+var ANDQueryArgs = createReactClass({
+    propTypes: {
+        query: PT.string,
+        onQueryChange: PT.func.isRequired,
+    },
+    
 	getInitialState: function() {
-	    return {
-		andCounter: 1,
-		ands: ["and1"]
-	    };
-	},
 	
-	setADVTokenLayer: function(layer) {
-	    //fixme! - check agains valid layers
-	    return;
+	    this.queryStrCache = {};
+	    
+	    console.log('ANDQueryArgs:', this.props.query);
+        
+	    var repeat1, repeat2;
+	    var qlean = this.props.query;
+	    if (qlean) {
+            var repeatMatch = qlean.match(/{ *(\d)* *(?:, *(\d)*)? *} *$/);
+            
+            if (repeatMatch !== null) {
+                repeat1 = repeatMatch[1];
+                repeat2 = repeatMatch[2] || repeat1;
+                qlean = qlean.substring(0, qlean.length - repeatMatch[0].length);
+	        }
+	        
+           
+            // replace token's [ and ]
+            qlean = qlean.replace(/^\s*\[\s*/, '').replace(/\s*\]\s*$/, '');
+	    }
+	    
+	    var match = queryToANDArgs(qlean);
+	    console.log('ANDQueryArgs 2:', match);
+        if (match === null) {
+            return {
+	            ands: ["and-"+nextId()]
+            };
+        }
+        
+        var ands = [];
+        match.forEach((m) => {
+            var id = 'and-'+nextId();
+            ands.push(id);
+            this.queryStrCache[id] = m;
+        });
+        
+        return {
+            ands,
+            repeat1,
+            repeat2,
+        }
 	},
 
 	addADVAnd: function() {
-	    var i = this.state.andCounter + 1;
-	    this.state.ands.push('and' + i);
-	    this.setState({andCounter: i, ands: this.state.ands});
-
+	    this.setState( (oldSt) => {
+	        oldSt.ands.push('and-'+nextId());
+	        return {ands: this.state.ands}
+	    });
 	},
 
 	removeADVAnd: function(id) {
-	    var ands = this.state.ands;
-	    var i = ands.indexOf(id);
-	    if (ands.length > 1) {
-		var one = ands;
-		var two = one.slice(0, i - 1)
-			     .concat(one.slice(i));;
-		this.setState({ands: two});
-	    }
-	},
+	    this.setState( (oldSt) => {
+	        delete this.queryStrCache[id];
+	        oldSt.ands.splice(oldSt.ands.indexOf(id), 1);
+	        return {ands: oldSt.ands}
+	    }, this.fireQueryChange);
+	},
+	
+	onMenuChange(menust) {
+	    this.setState({
+	        menuState_isStart: menust.isStart,
+	        menuState_isEnd: menust.isEnd,
+	        menuState_repeat1: menust.repeat1,
+	        menuState_repeat2: menust.repeat2,
+	    }, () =>  this.fireQueryChange() );
+	},
+    
+    fireQueryChange() {
+        var andParts = this.state.ands.map( (id) => this.queryStrCache[id] );
+        
+        if (this.state.menuState_isStart) {
+            andParts.push('lbound(sentence)')
+        }
+        if (this.state.menuState_isEnd) {
+            andParts.push('rbound(sentence)')
+        }
+        
+        
+        var queryString = andParts.length >= 2 ? andParts.join(' & ') : andParts[0];
+        queryString = `[ ${queryString} ]`;
+        
+        if (this.state.menuState_repeat1 || this.state.menuState_repeat2 ) {
+            queryString = queryString + '{' + (this.state.menuState_repeat1||this.state.menuState_repeat2 ) + ',' + (this.state.menuState_repeat2 || this.state.menuState_repeat1) + '}'
+        }
+        
+        this.props.onQueryChange(queryString);
+    },
+    
+    onQueryChange(andId, queryStr) {
+        this.queryStrCache[andId] = queryStr;
+        this.fireQueryChange();
+    },
 
 	renderANDTokenFooter: function () {
-	    return (<div className="token_footer">
-		<button className="btn btn-xs btn-default image_button insert_arg" onClick={this.addADVAnd} ref="addAndButton">
-		    <i className="glyphicon glyphicon-plus"/>
-		</button>
-		<ADVTokenMenu/>
-		<div style={{clear:"both"}}/>
-	    </div>);
-	},
-
-	renderANDQueryArg: function (and) {
-	    return (<div className="and query_arg">
-		<span className="hidden">and</span>
-		<ANDQueryORArgs 
-		numAnds={this.state.ands.length}
-		parentAnd={and}
-		handleRemoveADVAnd={this.removeADVAnd}/>
+	    return (
+	    <div className="token_footer">
+		    <button className="btn btn-xs btn-default image_button insert_arg" onClick={this.addADVAnd} ref="addAndButton">
+		        <i className="glyphicon glyphicon-plus"/>
+		    </button>
+		    <ADVTokenMenu ref={(ref) => this.menuRef = ref} onChange={this.onMenuChange} repeat1={this.state.repeat1} repeat2={this.state.repeat2} />
+		    <div style={{clear:"both"}}/>
 	    </div>);
 	},
 	
 	render: function () {
-	    var andQueryArgs = this.state.ands.map(function (and, i) {
-	    return (
-		<CSSTransition key={i} classNames="fade" timeout={{enter: 200, exit: 200}}>
-		   <div key={and}>{this.renderANDQueryArg(and)}</div>
-		</CSSTransition>);
-	    }.bind(this));
+	    var andQueryArgs = this.state.ands.map(( andId ) => {
+	        return (
+		    <CSSTransition key={andId} classNames="fade" timeout={{enter: 200, exit: 200}}>
+		        <div className="and query_arg">
+		            <span className="hidden">and</span>
+		            <ANDQueryORArgs
+		                query={this.queryStrCache[andId]}
+		                onQueryChange={(qs) => this.onQueryChange(andId, qs)}
+		                handleRemoveADVAnd={() => this.removeADVAnd(andId)}
+		                />
+	            </div>
+		    </CSSTransition>);
+	        }
+	   );
 	    return (<div>
-		<TransitionGroup>
-		   {andQueryArgs}
-		</TransitionGroup>
-		{this.renderANDTokenFooter()}
+		    <TransitionGroup>
+		       {andQueryArgs}
+		    </TransitionGroup>
+		    {this.renderANDTokenFooter()}
 		</div>);
     }
@@ -222,129 +400,540 @@
 var ANDQueryORArgs = createReactClass({
     propTypes: {
-	numAnds: PT.number.isRequired,
-	parentAnd: PT.string.isRequired,
-	handleRemoveADVAnd: PT.func.isRequired,
-    },
+        query: PT.string,
+        onQueryChange: PT.func.isRequired,
+        handleRemoveADVAnd: PT.func.isRequired,
+    },
+    
     getInitialState: function() {
-	return {
-	    orCounter: 1,
-	    ors: [{id: "or1", layerType: "string:lemma", placeholder: "Bagdad"}]
-	};
-    },
-
-    //shouldComponentUpdate: function (nextProps, nextState) {
-    //	return nextState.ors.length > 1; //!== this.state.ors.length;
-    //},
-
-    setADVTokenOp: function(op) {
-	//fixme! - check agains valid layers
-	return;
-    },
-
-    setADVInputDefault: function(or) {
-	//fixme! - disable SearchButton if not atleast 1 token is in the query filter
-	return;
+        this.queryStrCache = {};
+        
+        console.log('ANDQueryORArgs:', this.props.query);
+        var match = queryToORArgs(this.props.query);
+        console.log('ANDQueryORArgs 2:', match);
+	    
+        if (match === null) {
+            return {
+	            ors: ["or-"+nextId()]
+            };
+        }
+        
+        var ors = [];
+        match.forEach((m) => {
+            var id = 'or-'+nextId();
+            ors.push(id);
+            this.queryStrCache[id] = m;
+        });
+        
+        return {
+            ors
+        }
     },
 
     validateADV: function(value) {
-	//fixme! - disable SearchButton if not atleast 1 token is in the query filter
-	return;
+	    //fixme! - disable SearchButton if not atleast 1 token is in the query filter
+	    return;
+    },
+    
+    fireQueryChange() {
+        var orParts = this.state.ors.map( (id) => this.queryStrCache[id] );
+        const queryString = orParts.length >= 2 ? '( ' + orParts.join(' | ') + ' )' : orParts[0];
+        this.props.onQueryChange(queryString);
     },
 
     addADVOr: function(e) {
-	var i = this.state.orCounter + 1;
-	this.state.ors.push({id: 'or' + i, layerType: "string:pos", placeholder: "PROPN"});
-	this.setState({orCounter: i, ors: this.state.ors});
-    },
-
-    removeADVOr: function(id, e) {
-	var ors = this.state.ors;
-	var i = ors.indexOf(id);
-	if (ors.length > 1) {
-	    var one = ors;
-	    var two = one.slice(0, i - 1)
-			 .concat(one.slice(i));;
-	    this.setState({ors: two});
-	} else if (ors.length === 1 && this.props.numAnds > 1) {
-	    this.props.handleRemoveADVAnd(this.props.parentAnd);
-	}
-    },
-
-    render: function () {
-	var orArgs = this.state.ors.map(function (or, i) {
-	    return ( 		
-		<CSSTransition key={i} classNames="fade" timeout={{enter: 200, exit: 200}}>
-		   <ORArg key={or.id} 
-	                  data={or} 
-                          handleRemoveADVOr={this.removeADVOr}
-	                  handleSetADVInputDefault={this.setADVInputDefault}
-	                  handleSetADVTokenOp={this.setADVTokenOp}
-	                  handleValidateADV={this.validateADV}
-	           />
-		</CSSTransition>
-	    )
-	}.bind(this));
-	return (<div>
-	    <div className="or_container">
-	       <TransitionGroup>
-	          {orArgs}
-	       </TransitionGroup>
-	    </div>
-	    <div className="arg_footer">
-		<span className="link" onClick={this.addADVOr} ref={'addOR' + this.props.numAnds}>or</span>
-		<div style={{clear:"both"}}/>
-	    </div>
-	</div>);
+	    this.setState( (oldSt) => {
+	        oldSt.ors.push('or-'+nextId());
+	        return {ors: this.state.ors}
+	    });
+    },
+
+    removeADVOr: function(id) {
+        this.setState( (oldSt) => {
+            delete this.queryStrCache[id];
+	        oldSt.ors.splice(oldSt.ors.indexOf(id), 1);
+	        return {ors: oldSt.ors}
+	    }, () => {
+	        if (this.state.ors.length===0) {
+	            this.props.handleRemoveADVAnd();
+	        } else {
+	            this.fireQueryChange();
+	        }
+	   });
+    },
+    
+    onQueryChange(orId, queryStr) {
+        this.queryStrCache[orId] = queryStr;
+        this.fireQueryChange();
+    },
+
+    render() {
+	    var orArgs = this.state.ors.map((orId)  => {
+	        return ( 		
+		    <CSSTransition key={orId} classNames="fade" timeout={{enter: 200, exit: 200}}>
+	                <ORArg
+	                      query={this.queryStrCache[orId]}
+                          handleRemoveADVOr={() => this.removeADVOr(orId)}
+	                      handleValidateADV={() => {return true}}
+	                      onQueryChange={(qs) => this.onQueryChange(orId, qs)}
+	               />
+		    </CSSTransition>
+	        )
+	    });
+	    return (<div>
+	        <div className="or_container">
+	           <TransitionGroup>
+	              {orArgs}
+	           </TransitionGroup>
+	        </div>
+	        <div className="arg_footer">
+		        <span className="link" onClick={this.addADVOr} >or</span>
+		        <div style={{clear:"both"}}/>
+	        </div>
+	    </div>);
     }
 });
 
 var ORArg = createReactClass({
+    mixins: [PureRenderMixin],
+    
     propTypes: {
-	data: PT.object.isRequired,
-	handleRemoveADVOr: PT.func.isRequired,
-	handleSetADVInputDefault: PT.func.isRequired,
-	handleSetADVTokenOp: PT.func.isRequired,
-	handleValidateADV: PT.func.isRequired,
+        query: PT.string,
+	    handleValidateADV: PT.func.isRequired,
+	    handleRemoveADVOr: PT.func.isRequired,
+	    onQueryChange: PT.func.isRequired,
+    },
+    
+    getInitialState() {
+        console.log('ORArg:', this.props.query);
+        var qp = queryParse(this.props.query);
+        console.log('ORArg 2:', qp);
+        
+        if (qp !== null) {
+            var layer = qp.layer;
+            var op = qp.op;
+            var val = qp.val;
+        }
+        return {
+            layer: layer||'word',
+            argOpt: op||'is',
+            argValue: val||'',
+            
+            editingText: false,
+        };
+    },
+    
+    fireQueryChange() {
+        const queryString = layerToQueryString(this.state.layer, this.state.argOpt, this.state.argValue);
+        this.props.onQueryChange(queryString);
+    },
+    
+    onlayerChange (evt) {
+        var layer = evt.target.value;
+        this.setState((st) => {
+            var argOpt  = getLayerArgOpts(layer)[0].value;
+            var lvo = getLayerValueOptions(layer, argOpt, st.argValue);
+	    var argValue = '';
+            if (lvo === undefined) argValue = '';
+            else argValue= lvo[0].value;
+            
+            return {
+                layer,
+                argOpt,
+                argValue,
+            }
+        })
+    },
+    
+    onArgOptChange (evt) {
+        var argOpt = evt.target.value;
+        this.setState({argOpt});
+    },
+    
+    onArgValueChange (evt) {
+        var value = evt.target.value;
+        //console.log("picked arg value", value);
+        
+        this.setState({argValue: value});
+    },
+    
+    
+    componentDidMount(){
+        this.fireQueryChange();
+    },
+    
+    componentDidUpdate(prevProps, prevState) {
+        // after state update.
+        if (prevState.layer !== this.state.layer
+            || prevState.argOpt !== this.state.argOpt
+            || prevState.argValue !== this.state.argValue
+            /*|| (!this.state.editingText && prevState.argValue !== this.state.argValue) // only update text-value input on field blur 
+            || (prevState.editingText !== this.state.editingText && prevState.editingText) // stopped editing text field.
+            */
+        ) {
+            this.fireQueryChange();
+        }
+    },
+    
+    renderInput() {
+        
+        var valueOptions = getLayerValueOptions(this.state.layer, this.state.argOpt, this.state.argValue);
+        
+        if (valueOptions === undefined) {
+            return <input type="text" className="form-control"
+                    value={this.state.argValue}
+                    onChange={this.onArgValueChange}
+                    onFocus={() => this.setState({editingText:true})}
+                    onBlur={() => this.setState({editingText: false})}
+                    />
+	    } else {
+	        return <select className="form-control" value={this.state.argValue} onChange={this.onArgValueChange} onFocus={() => this.setState({editingText:true})} onBlur={() => this.setState({editingText: false})}>
+	        {
+	            valueOptions.map( (valOpt) => {
+                    return <option value={valOpt.value}>{valOpt.label}</option>
+                })
+	        }
+	        </select>
+	    }
     },
 
     render: function() {
-	return (<div className="or or_arg">
-	    <div className="left_col" >
-		<button className="btn btn-xs btn-default image_button remove_arg" onClick={this.props.handleRemoveADVOr.bind(null, this.props.data.id)} ref={'removeADVOr_' + this.props.data.id}>
-		    <i className="glyphicon glyphicon-minus"></i>
-		</button>
-	    </div>
-	    <div className="right_col inline_block" style={{display:"inline-block"}}> { /* , margin-left: "5px" */ }
-		<div className="arg_selects lemma">
-		    <select className="arg_type" onChange={this.props.handleSetADVInputDefault("or")} defaultValue={this.props.data.layerType} ref={'ANDLayerType_' + this.props.data.id}>
-			{ /* onChange={this.handleSetADVTokenLayer("value")} */}
-			<optgroup label="word">
-			    { /* ::before */ }
-			    <option value="string:word" label="word">word</option>
-			</optgroup>
-			<optgroup label="wordAttribute">
-			    { /* ::before */ }
-			    <option value="string:pos">part-of-speech</option>
-			    <option value="string:lemma">lemma</option>
-			</optgroup>
-			<optgroup label="textAttribute">
-			    <option value="string:_.text_language" label="language">language</option>
-			</optgroup>
-		    </select>
-		    <select className="arg_opts" defaultValue="string:contains" onChange={this.props.handleSetADVTokenOp("op")}>
-			<option value="string:contains" label="is">is</option>
-			<option value="string:not contains" label="is not">is not</option>
-		    </select>
-		</div>
-		<div className="arg_val_container">
-	 	    <input id={'inputADV_' + this.props.data.id} type="text" defaultValue={this.props.data.placeholder} onChange={this.props.handleValidateADV} ref={'textEntry_' + this.props.data.id}/>
-		</div>
-	       <select> 
-		<option label="PROPN" value="string:PROPN">Proper Noun</option>
-	       </select>
-	    </div>
-	    </div>);
+	    return (<div className="or or_arg">
+	        <div className="left_col" >
+		        <button className="btn btn-xs btn-default image_button remove_arg" onClick={this.props.handleRemoveADVOr}>
+		            <i className="glyphicon glyphicon-minus"></i>
+		        </button>
+	            </div>
+	            <div className="right_col inline_block" style={{display:"inline-block"}}> { /* , margin-left: "5px" */ }
+		            
+		            <div className="arg_selects form-inline">
+		                <select className="arg_type form-control" value={this.state.layer} onChange={this.onlayerChange}>
+		                {
+		                    layerCategories.map( (cat) => {
+		                        return (
+		                            <optgroup label={cat.label}>
+	                                {
+	                                    cat.layers.map( (layer) => {
+	                                        return <option value={layer}>{getLayerLabel(layer)}</option>;
+	                                    })
+	                                }
+		                            </optgroup>
+		                        );
+		                    })
+		                 }
+	                    </select>
+	                    
+	                    <select className="arg_opts form-control" value={this.state.argOpt} onChange={this.onArgOptChange}>
+	                    {
+	                        getLayerArgOpts(this.state.layer).map( (argOpt) => {
+	                            return <option value={argOpt.value}>{argOpt.label}</option>;
+	                        })
+		                }
+		                </select>
+		            </div>
+		            
+		            <div className="arg_val_container">
+	             	    { this.renderInput() }
+		            </div>
+                </div>
+            </div>
+        );
     }
 });
 
+function getLayers() {
+    const layers_arr = [];
+    for (var l in layers) {
+        layers_arr.push(l);
+    }
+    return layers_arr;
+}
+
+function getLayerArgOpts(layer) {
+    return layers[layer].argOpts;
+}
+
+function isValidLayerOperator(layer, operator) {
+    return !!layers[layer].argOpts.find((e) => e.value===operator);
+}
+function isValidLayerValue(layer, operator, value) {
+    var valopts = getLayerValueOptions(layer);
+    if (!valopts) {
+        return true
+    }
+    return valopts.indexOf(value) !== -1;
+}
+
+function layerToQueryString(layer, operator, value) {
+    var toStr = layers[layer].toQueryString
+    if (! toStr) {
+        toStr = getLayerArgOpts(layer).defaultToStr;
+    }
+    if (! toStr) {
+        toStr = wordOptions.defaultToStr;
+        console.warn("layerToQueryString: couldnt find a toQueryString method!");
+    }
+    var qstr = toStr(layer, operator, value);
+    if (qstr === undefined) {
+        console.warn("layerToQueryString: qstr undefined!");
+        return 'undefined';
+    }
+    return qstr;
+}
+
+function getLayerLabel(layer) {
+    return layers[layer].label;
+}
+
+function getOperatorLabel(layer, operator) {
+    return layers[layer].argOpts[operator].label;
+}
+
+function getLayerValueOptions(layer, operator, value) {
+    var valopts = layers[layer].valueOptions;
+    if (! valopts) {
+        return;
+    }
+    if (typeof valopts === 'function') {
+        return valopts(layer, operator, value);
+    }
+    else if (valopts.length) {
+        return valopts; // array
+    }
+}
+
+
+// a RE string matching a "double-quote"d string
+const quotedStringRE = /(?:"(?:\\"|[^"])*")/.source;
+
+// in: 'word = "zebra" '
+// out: ['word', '=', 'zebra']
+function queryParse(q) {
+    if (!q) return null;
+    var match = q.match(/^\s*(\w+) *(=|!=) *"((?:\\"|.)*?)"/);
+    if(match===null) {
+        return null;
+    }
+    
+    const layer = match[1];
+    const op = match[2];
+    const value = match[3];
+    
+    var fromStr = getLayerArgOpts(layer).fromQueryString
+    if (! fromStr) {
+        fromStr = getLayerArgOpts(layer).defaultFromString;
+    }
+    if (! fromStr) {
+        fromStr = wordOptions.defaultFromStr;
+        log.error("queryParse: couldnt find a fromQueryString method!");
+    }
+    
+    return fromStr(layer, op, value);
+}
+
+// in: '(word = "zebra" | word = "zoo" ...)'
+// out: ['word = "zebra" ', ' (word = "zoo" ...)']
+function queryToORArgs(q) {
+    if (!q) return null;
+    var match = q.trim().match(queryToORArgs.re);
+    return match;
+}
+queryToORArgs.re = RegExp('(?:'+quotedStringRE+'|[^()|])+', 'g')
+
+// in: '[word = "zebra" & (word = "zoo" ...)]'
+// out: ['word = "zebra" ', ' (word = "zoo" ...)']
+function queryToANDArgs(q) {
+    if (!q) return null
+    
+    var match = q.trim().match(queryToANDArgs.re);
+    return match;
+}
+queryToANDArgs.re = RegExp('(?:'+quotedStringRE+'|[^&])+', 'g')
+
+
+// in: '[word = "zebra"] [word = "zoo"]'
+// out: ['[word = "zebra"]', '[word = "zoo"]']
+function queryToTokens(q) {
+    if (!q) return null;
+    var match = q.match(queryToTokens.re);
+    return match;
+}
+queryToTokens.re = RegExp('\\[(?:'+quotedStringRE+'|.)*?\\] *(?:\\{.*?\\})?', 'g');
+
+
+/*To simplify matching regex filter out words within "quotemarks". This help to not stumble on any special characters that can occur there. */
+function filterWords(s, f) {
+    const filteredString = s.replace(/("(?:\\"|[^"])*")/g, (m) => {
+        filteredWords.push(m)
+        return '""'
+    })
+    const ret = f(filteredString)
+    // restore words
+    
+    // return return value
+    return ret;
+}
+
+var wordOptions = [
+    {value: 'is', label: 'is'},
+    {value: 'is_not', label: 'is not'},
+    {value: 'contains', label: 'contains'},
+    {value: 'starts_with', label: 'starts with'},
+    {value: 'ends_with', label: 'ends with'},
+    {value: 'regex', label: 'regex'},
+    {value: 'not_regex', label: 'not regex'},
+]
+var liteOptions = [
+    {value: "is", label: "is"},
+    {value: "is_not", label: "is not"},
+]
+var setOptions = [
+    {value: "is", label: "is"},
+    {value: "is_not", label: "is not"},
+]
+var probabilitySetOptions = [
+    {value: "is", label: "highest_rank"},
+    {value: "is_not", label: "not_highest_rank"},
+    {value: "contains", label: "rank_contains"},
+    {value: "contains_not", label: "not_rank_contains"},
+]
+
+setOptions.defaultToStr = (layer, op, val) => {
+    switch(op) {
+    case 'is': 
+        return `${layer} = "${val}"`
+    case 'is_not': 
+        return `${layer} != "${val}"`
+    }
+}
+setOptions.defaultFromString = (layer, op, val) => {
+    return {layer, op: op==='!='?'is_not':'is', val}
+}
+
+wordOptions.defaultToStr = (layer, op, val) => {
+    var unescVal = val;
+    var val = escapeRegExp(val);
+    switch(op) {
+    case 'is': 
+        return `${layer} = "${val}"`
+    case 'is_not': 
+        return `${layer} != "${val}"`
+    case 'contains': 
+        return `${layer} = ".*${val}.*"`
+    case 'starts_with': 
+        return `${layer} = "${val}.*"`
+    case 'ends_with': 
+        return `${layer} = ".*${val}"`
+    case 'regex': 
+        return `${layer} = "${unescVal}"`
+    case 'not_regex': 
+        return `${layer} != "${unescVal}"`
+    }
+}
+wordOptions.defaultFromString = (layer, op, val) => {
+    // layer should be good. Now figure out op, and if val is escaped or not
+    if (op === '=') {
+        var strippedOfSemiRE = val.replace(/^\.\*/, '').replace(/\.\*$/, '');
+        if ( strippedOfSemiRE.length !== val.length ) {
+            // could be one of: startswith, contains, endswith.
+            if ( ! guessIfRegexp(strippedOfSemiRE) ) {
+                // Ok, it is one of: startswith, contains, endswith.
+                if (val.startsWith('.*') && val.endsWith('.*')) {
+                    var op2 = 'contains';
+                } else if (val.startsWith('.*')) {
+                    op2 = 'starts_with';
+                } else if (val.endsWith('.*')) {
+                    op2 = 'ends_with'
+                } else {
+                    console.error("parsing query failed due to coding error");
+                    return null;
+                }
+                return {
+                    layer: layer,
+                    op: op2,
+                    val: unescapeRegExp(strippedOfSemiRE)
+                }
+            }
+            // its regexp.
+        }
+    }
+    
+    if (guessIfRegexp(val)) {
+        // its regexp
+        return {layer, op: op==='='?'regex':'not_regex', val: val};
+    }
+    
+    // its not regexp
+    return {layer, op: op==='='?'is':'is_not', val: unescapeRegExp(val)};
+}
+function guessIfRegexp(s) {
+    return !! s.match(/[^\\][-[\]{}()*+\\?.,^$|#]/); // find if it contains any unescaped regex characters
+}
+function unescapeRegExp(text) {
+  return text.replace(/\\([-[\]{}()*+?.,\\^$|#])/g, '$1');
+}
+function escapeRegExp(text) {
+  return text.replace(/[-[\]{}()*+?.,\\^$|#]/g, '\\$&');
+}
+
+const layers = {
+    'word': {
+        label: 'word',
+        argOpts: wordOptions,
+    },
+    'pos': {
+        label: 'part-of-speech UD v2.0 tagset',
+        argOpts: setOptions,
+        valueOptions: [
+            {value: "ADJ", label: "Adjective"},
+            {value: "ADV", label: "Adverb"},
+            {value: "INTJ", label: "Interjection"},
+            {value: "NOUN", label: "Noun"},
+            {value: "PROPN", label: "Proper noun"},
+            {value: "VERB", label: "Verb"},
+            {value: "ADP", label: "Adposition"},
+            {value: "AUX", label: "Auxiliary"},
+            {value: "CCONJ", label: "Coordinating conjunction"},
+            {value: "DET", label: "Determiner"},
+            {value: "NUM", label: "Numeral"},
+            {value: "PART", label: "Particle"},
+            {value: "PRON", label: "Pronoun"},
+            {value: "SCONJ", label: "Subordinating conjunction"},
+            {value: "PUNCT", label: "Punctuation"},
+            {value: "SYM", label: "Symbol"},
+            {value: "X", label: "Other"},
+        ],
+    },
+    'lemma': {
+        label: 'lemmatization of tokens',
+        argOpts: wordOptions,
+    },
+    'orth': {
+        label: 'orthographic transcription',
+        argOpts: wordOptions,
+    },
+    'norm': {
+        label: 'orthographic normalization',
+        argOpts: wordOptions,
+    },
+    'phonetic': {
+        label: 'phonetic transcription SAMPA',
+        argOpts: wordOptions, // TODO special toString/parse? (probably due to regex character handling)
+    },
+    'text': {
+        label: 'Layer only for Basic Search',
+        argOpts: wordOptions,
+    },
+    '_.text_language': {
+        label: 'language',
+        argOpts: wordOptions,
+    },
+};
+
+const layerCategories = [
+    {cat: 'word', label: 'Word', layers: ['word']},
+    {cat: 'wordAttribute', label: 'Word attribute', layers: ['pos', 'lemma', 'orth', 'norm', 'phonetic', 'text']},
+    {cat: 'textAttribute', label: 'Text attribute', layers: ['_.text_language']},
+];
+
+
 module.exports = QueryInput;
