Merge "Improve note regarding opt-out"
diff --git a/Changes b/Changes
index 8d88e0a..a0c1aca 100755
--- a/Changes
+++ b/Changes
@@ -1,6 +1,9 @@
-0.32 2018-12-19
+0.32 2019-01-29
         - Support attachements in metadata fields (#77).
         - Added ping request option to Piwik.
+        - Fix handling of login errors.
+        - Added Statistics reload option (hebasta, #66).
+        - Fixed VC query serialization.
 
 0.31 2018-11-30
         - Update to Mojolicious >= 8.06.
diff --git a/dev/demo/vcdemo.js b/dev/demo/vcdemo.js
index 0c66dd3..b9a7b1a 100644
--- a/dev/demo/vcdemo.js
+++ b/dev/demo/vcdemo.js
@@ -122,32 +122,36 @@
 
     // Create a new virtual collection by passing a based json object and
     // field menu information
-    var vc = vcClass.create([
+    KorAP.vc = vcClass.create([
       ['title', 'string'],
       ['subTitle', 'string'],
       ['pubDate', 'date'],
       ['author', 'text']
     ]).fromJson(json);
 
-    document.getElementById('vc-view').appendChild(vc.element());
+    document.addEventListener('vcChange', function (e) {
+      KorAP.vc.checkStatActive(e.detail);
+      }, false);
+    
+    document.getElementById('vc-view').appendChild(KorAP.vc.element());
 
-    vc.open();
+    KorAP.vc.open();
 
     // show the current JSON serialization
     KorAP.showJSON = function () {
       var json = document.getElementById("json");
-      json.innerHTML = JSON.stringify(vc.root().toJson(), null, '  ');
+      json.innerHTML = JSON.stringify(KorAP.vc.root().toJson(), null, '  ');
       hljs.highlightBlock(json);
     };
 
     // show the current query serialization
     KorAP.showQuery = function () {
-      document.getElementById("query").innerHTML = vc.root().toQuery();
+      document.getElementById("query").innerHTML = KorAP.vc.root().toQuery();
     };
 
     // make the current vc persistant
     KorAP.makeVCPersistant = function () {
-      vc.makePersistant();
+      KorAP.vc.makePersistant();
     };
     
     //get the corpus statistic (demo function)
diff --git a/dev/js/spec/statSpec.js b/dev/js/spec/statSpec.js
index cb9d713..25e55a2 100644
--- a/dev/js/spec/statSpec.js
+++ b/dev/js/spec/statSpec.js
@@ -7,7 +7,7 @@
 
 
 define(['vc', 'vc/statistic', 'view/corpstatv'], function(vcClass, statClass, corpStatVClass){
-	 
+  
 	var json = {
    	"@type":"koral:docGroup",
    	"operation":"operation:or",
@@ -77,8 +77,118 @@
   	return cb(preDefinedStat);
   }; 
   
- 	
-	describe('KorAP.CorpusStat', function(){
+
+  generateCorpusDocGr = function(){     
+   let vc = vcClass.create().fromJson({
+      "@type" : 'koral:docGroup',
+      'operation' : 'operation:or',
+      'operands' : [
+        {
+          '@type' : 'koral:doc',
+          'key' : 'title', 
+          'match': 'match:eq',
+          'value' : 'Hello World!'
+        },
+        {
+          '@type' : 'koral:doc',   
+          'match': 'match:eq',
+          'key' : 'foo',
+          'value' : 'bar'
+        }
+      ]
+    });  
+   return vc;
+  }
+  
+  /**
+   * Generate corpus doc group with more entries
+   */
+  generateBCorpusDocGr = function(){  
+    let vc = vcClass.create().fromJson({
+      "@type": "koral:docGroup",
+      "operation": "operation:or", 
+      "operands": [{
+        "@type" : 'koral:docGroup',
+        'operation' : 'operation:or',
+        'operands' : [
+          {
+            '@type' : 'koral:doc',
+            'key' : 'title', 
+            'match': 'match:eq',
+            'value' : 'Hello World!'
+          },
+          {
+            '@type' : 'koral:doc',   
+            'match': 'match:eq',
+            'key' : 'foo',
+            'value' : 'bar'
+          }
+          ]
+      },
+      {
+        '@type' : 'koral:doc',   
+        'match': 'match:eq',
+        'key' : 'author',
+        'value' : 'Goethe'
+      }
+      ]
+    });
+    
+   return vc;
+  }
+  
+  generateCorpusDoc = function(){
+    let vc= vcClass.create().fromJson({
+        '@type' : 'koral:doc',
+        'key' : 'title', 
+        'match': 'match:eq',
+        'value' : 'Hello World!',
+        'type'  : 'type:string'      
+    });
+    return vc;
+  };
+  
+  
+  /**
+   * Generate vc with docgroupref
+   */
+  generateCorpusRef = function(){  
+    let vc = vcClass.create().fromJson({
+      "@type" : "koral:docGroupRef",
+      "ref" : "@max/myCorpus"
+    });
+    return vc;
+  };
+  
+  
+  /**
+   * Checks is corpus statistic is active
+   */
+  checkStatActive = function(view, div){   
+    // corpus statistic exists, it is active and reloadStatButton is shown
+    if ((view.firstChild.classList.contains("stattable") === true) 
+      && (view.firstChild.classList.contains("stdisabled") === false)
+      && (div.getElementsByClassName("reloadStatB").length == 0) ) { 
+      return true;
+    } 
+    return false;
+  };
+ 
+  
+  /**
+   * Checks if corpus statistic is disabled
+   */
+  checkStatDisabled = function(view, div){
+    if ((view.firstChild.classList.contains("stattable") === true) 
+        && (view.firstChild.classList.contains("stdisabled") === true)
+        && (div.getElementsByClassName("reloadStatB").length === 1) ) {
+      return true;
+    }   
+    return false;
+    };
+    
+    
+  describe('KorAP.CorpusStat', function(){
 
 		it('should be initiable', function(){
 		  var stat = statClass.create(preDefinedStat);		
@@ -88,7 +198,7 @@
 		
 		it('should be parsed in a statistic view and displayed as HTML Description List', function(){
 		  var stat = statClass.create(preDefinedStat);		
-      var descL = stat.element();
+          var descL = stat.element();
 			expect(descL.tagName).toEqual('DL');		
 			expect(descL.children[0].tagName).toEqual('DIV');
 			expect(descL.children[0].children[0].tagName).toEqual('DT');
@@ -113,12 +223,14 @@
 	      ['author', 'text']
 	    ]).fromJson(json);
 
+		  KorAP.vc = vc;
+		  
 		  statView = corpStatVClass.create(vc);
-		  //corpStatVClass.show(vc);
+		  // corpStatVClass.show(vc);
 		  
 			var testDiv = document.createElement('div');
 			testDiv.appendChild(statView.show());
-			//statClass.showCorpStat(testDiv, vc);
+			// statClass.showCorpStat(testDiv, vc);
 			
 			expect(testDiv.children[0].tagName).toEqual('DIV');
 			expect(testDiv.children[0].getAttribute("class")).toEqual('stattable');   
@@ -232,5 +344,217 @@
       panel.lastChild.children[0].click();
       expect(panel.firstChild.children.length).toEqual(1);
     });
-  });		
+    
+   
+  });	
+	
+  
+  
+    /**
+     * Test disabling and reload of corpus statistic if vc is changed 
+     * in vc builder through user-interaction
+     */
+	describe ('KorAP.CorpusStat.Disable', function(){
+	
+	  document.addEventListener('vcChange', function (e) {
+	    if(KorAP.vc){
+        KorAP.vc.checkStatActive(e.detail);
+	    }
+        }, false);
+	  
+	  /**
+	   * If the user defines a new vc, the statistic should be disabled,
+	   * because it is out-of-date.
+	   * 
+	   * The user can choose to display an up-to-date corpus statistic. Here it is tested
+	   * if corpus statistic is disabled after a valid change of corpus statistic and if the corpus statistic is updatable.
+	   */ 	  	 
+      it ('should disable the corpus statistic if corpus definition is changed and display a functional reload  button', function(){
+        
+        KorAP.vc = generateCorpusDocGr();  
+        
+        //Show corpus statistic
+        let show = document.createElement('div');
+        show.appendChild(KorAP.vc.element());
+        let panel = show.firstChild.lastChild.firstChild;
+        panel.lastChild.children[0].click();
+        let view = panel.firstChild.firstChild;
+        
+        //corpus statistic is active
+        expect(checkStatActive(view, show)).toBe(true);
+        
+        //change vc, a line in vc builder is deleted
+        KorAP._delete.apply(KorAP.vc.root().getOperand(0));
+        expect(checkStatDisabled(view,show)).toBe(true);
+        
+        //click at reload button
+        let rlbutton = show.getElementsByClassName("refresh").item(0);
+        rlbutton.click();
+        
+        expect(checkStatActive(view,show)).toBe(true);
+      });
+      
+      
+      it('should disable corpus statistic if entries in vc builder are deleted', function(){
+        KorAP.vc = generateCorpusDocGr();
+        
+        // create corpus builder and corpus statistic;
+        let show = document.createElement('div');
+        show.appendChild(KorAP.vc.element());   
+        let panel = show.firstChild.lastChild.firstChild;
+        panel.lastChild.children[0].click();
+        let view = panel.firstChild.firstChild;
+
+        expect(checkStatActive(view, show)).toBe(true);
+        
+        //delete foo=bar
+        KorAP._delete.apply(KorAP.vc.root().getOperand(1));
+        expect(checkStatDisabled(view, show)).toBe(true);
+       
+        //refresh corpus statistic
+        let rlbutton = show.getElementsByClassName("refresh").item(0);
+        rlbutton.click();
+        expect(checkStatActive(view,show)).toBe(true);   
+        
+        KorAP._delete.apply(KorAP.vc.root());
+        view = panel.firstChild.firstChild;
+        expect(checkStatDisabled(view, show)).toBe(true);     
+        
+        KorAP.vc  = generateBCorpusDocGr();
+        // create corpus builder and corpus statistic;
+        show = document.createElement('div');
+        show.appendChild(KorAP.vc.element());   
+        panel = show.firstChild.lastChild.firstChild;
+        panel.lastChild.children[0].click();
+        view = panel.firstChild.firstChild;
+
+        expect(checkStatActive(view, show)).toBe(true);
+        KorAP._delete.apply(KorAP.vc.root().getOperand(1));
+        view = panel.firstChild.firstChild;
+        expect(checkStatDisabled(view, show)).toBe(true); 
+              
+      });
+      
+      
+      it('should disable corpus statistic if key, matchoperator or value is changed', function(){  
+        /*         
+         * Doc change of key, match operator and value 
+         */
+        KorAP.vc= generateCorpusDoc();
+        // show vc builder and open corpus statistic
+        let show = document.createElement('div');
+        show.appendChild(KorAP.vc.element());
+        let panel = show.firstChild.lastChild.firstChild;
+        panel.lastChild.children[0].click();
+        let view = panel.firstChild.firstChild;
+        expect(checkStatActive(view, show)).toBe(true);
+        
+        KorAP.vc.root().matchop("ne").update();
+        expect(checkStatDisabled(view, show)).toBe(true);
+        
+        let rlbutton = show.getElementsByClassName("refresh").item(0);
+        rlbutton.click();
+        
+        view = panel.firstChild.firstChild;
+        expect(checkStatActive(view, show)).toBe(true);
+        KorAP.vc.root().value("Hello tester").update();
+        expect(checkStatDisabled(view, show)).toBe(true);
+          
+        //refresh corpus statistic
+        rlbutton = show.getElementsByClassName("refresh").item(0);
+        rlbutton.click();
+        view = panel.firstChild.firstChild;
+        expect(checkStatActive(view, show)).toBe(true);
+        
+        KorAP.vc.root().key("author").update();
+        expect(checkStatDisabled(view, show)).toBe(true);
+        
+        
+        /*
+         * DocGroupRef change of value...
+         */  
+        KorAP.vc = generateCorpusRef();
+        show = document.createElement('div');
+        show.appendChild(KorAP.vc.element());
+        panel = show.firstChild.lastChild.firstChild;
+        panel.lastChild.children[0].click();
+        view = panel.firstChild.firstChild;
+        expect(checkStatActive(view, show)).toBe(true);
+        
+        KorAP.vc.root().ref("@anton/secondCorpus").update();
+        expect(checkStatDisabled(view, show)).toBe(true);
+        });
+      
+      
+      it('should not disable corpus statistic if docgroup definition is incomplete', function(){
+        
+        KorAP.vc = generateCorpusDocGr();
+        
+        //Show corpus statistic
+        let show = document.createElement('div');
+        show.appendChild(KorAP.vc.element());
+        let panel = show.firstChild.lastChild.firstChild;
+        panel.lastChild.children[0].click();
+        let view = panel.firstChild.firstChild;
+        
+        expect(checkStatActive(view, show)).toBe(true);
+
+        KorAP._and.apply(KorAP.vc.root());
+
+        let andbuild = show.getElementsByClassName("builder");
+        expect(andbuild[0].firstChild.classList.contains('docGroup')).toBeTruthy();
+        expect(andbuild[0].firstChild.getAttribute("data-operation")).toEqual("and");  
+        expect(checkStatActive(view, show)).toBe(true);
+      });
+
+      
+      it('should not disable corpus statistic if doc/docref definition is incomplete', function(){
+        
+        /*
+         * DOC incomplete
+         */
+        KorAP.vc = vcClass.create().fromJson();
+        expect(KorAP.vc.builder().firstChild.classList.contains('unspecified')).toBeTruthy();
+        
+        // show vc builder and open corpus statistic
+        let show = document.createElement('div');
+        show.appendChild(KorAP.vc.element());
+        let panel = show.firstChild.lastChild.firstChild;
+        panel.lastChild.children[0].click();
+        let view = panel.firstChild.firstChild;
+       
+        // corpus statistic should be shown and be up-to-date, reload button is not shown
+        expect(checkStatActive(view, show)).toBe(true);
+        
+        // open the menu
+        KorAP.vc.builder().firstChild.firstChild.click();
+        KorAP._vcKeyMenu._prefix.add("author");
+        let prefElement = KorAP.vc.builder().querySelector('span.pref');
+        // add key 'author' to VC
+        prefElement.click();
+        
+        expect(checkStatActive(view, show)).toBe(true); 
+       
+        
+        /*
+         * DOCREF incomplete
+         */
+        KorAP.vc = vcClass.create().fromJson();
+        expect(KorAP.vc.builder().firstChild.classList.contains('unspecified')).toBeTruthy();
+        
+        // show vc builder and open corpus statistic
+        show = document.createElement('div');
+        show.appendChild(KorAP.vc.element());
+        panel = show.firstChild.lastChild.firstChild;
+        panel.lastChild.children[0].click();
+        view = panel.firstChild.firstChild;
+        expect(checkStatActive(view, show)).toBe(true);
+
+        KorAP.vc.builder().firstChild.firstChild.click();
+        KorAP._vcKeyMenu._prefix.add("referTo");
+        prefElement = KorAP.vc.builder().querySelector('span.pref');
+        prefElement.click();
+        expect(checkStatActive(view, show)).toBe(true);
+        });     
+      });
 });
diff --git a/dev/js/spec/vcSpec.js b/dev/js/spec/vcSpec.js
index f657ee9..e7cc4fb 100644
--- a/dev/js/spec/vcSpec.js
+++ b/dev/js/spec/vcSpec.js
@@ -27,7 +27,7 @@
 
   KorAP._vcKeyMenu = undefined;
 
-
+   
   // Helper method for building factories
   buildFactory = function (objClass, defaults) {
     return {
@@ -146,6 +146,7 @@
       expect(doc.key()).toBeUndefined();
       expect(doc.value()).toBeUndefined();
       expect(doc.type()).toEqual("string");
+      expect(doc.incomplete()).toBeTruthy();
     });
 
     it('should be definable', function () {
@@ -161,6 +162,7 @@
       expect(doc.key()).toEqual("title");
       expect(doc.type()).toEqual("string");
       expect(doc.value()).toEqual("Der alte Mann");
+      expect(doc.incomplete()).toBeFalsy();
     });
 
 
@@ -173,6 +175,7 @@
       expect(doc.key()).toEqual("author");
       expect(doc.type()).toEqual("string");
       expect(doc.value()).toEqual("Max Birkendale");
+      expect(doc.incomplete()).toBeFalsy();
 
       // No valid string
       doc = stringFactory.create({
@@ -195,6 +198,7 @@
       expect(doc.key()).toEqual("author");
       expect(doc.type()).toEqual("string");
       expect(doc.value()).toEqual("Max Birkendale");
+      expect(doc.incomplete()).toBeFalsy();
 
       // Invalid match type
       doc = stringFactory.create({
@@ -216,6 +220,7 @@
       });
       expect(doc.matchop()).toEqual('ne');
       expect(doc.rewrites()).toBeUndefined();
+      expect(doc.incomplete()).toBeFalsy();
 
       // Invalid matcher
       doc = regexFactory.create({
@@ -345,6 +350,12 @@
       doc = stringFactory.create();
       expect(doc.toQuery()).toEqual('author = "Max Birkendale"');
 
+      // Check for incompletion
+      expect(doc.incomplete()).toBeFalsy();
+      doc.value("");
+      expect(doc.incomplete()).toBeTruthy();
+      expect(doc.toQuery()).toEqual('');
+
       // Serialize string with quotes
       doc = stringFactory.create({ "value" : 'Max "Der Coole" Birkendate'});
       expect(doc.toQuery()).toEqual('author = "Max \\"Der Coole\\" Birkendate"');
@@ -570,6 +581,17 @@
           '(pubDate since 2014-05-12 & ' +
           'pubDate until 2014-12-05 & foo != /[a]?bar/)'
       );
+
+
+      // Check for incompletion and only serialize complete operands
+      expect(docGroup.incomplete()).toBeFalsy();
+      var op1 = docGroup.getOperand(0);
+      op1.value("");
+      expect(docGroup.incomplete()).toBeFalsy();
+      expect(docGroup.toQuery()).toEqual(
+        '(pubDate since 2014-05-12 & ' +
+          'pubDate until 2014-12-05 & foo != /[a]?bar/)'
+      );
     });
   });
 
@@ -625,6 +647,12 @@
       expect(vcRef.toQuery()).toEqual(
         "referTo \"@peter/myCorpus2\""
       );
+
+      // Check for incompletion and only serialize complete operands
+      expect(vcRef.incomplete()).toBeFalsy();
+      vcRef.ref("");
+      expect(vcRef.incomplete()).toBeTruthy();
+      expect(vcRef.toQuery()).toEqual("");
     });
   });
 
@@ -637,6 +665,7 @@
       expect(docElement.firstChild.firstChild.data).toEqual(KorAP.Locale.EMPTY);
       expect(docElement.lastChild.lastChild.data).toEqual(KorAP.Locale.EMPTY);
       expect(doc.toQuery()).toEqual('');
+      expect(doc.incomplete()).toBeTruthy();
 
       // Only removable
       expect(docElement.lastChild.children.length).toEqual(0);
@@ -731,7 +760,9 @@
     });
     
     it('should be replaceable on root', function () {
+      
       var vc = vcClass.create();
+      KorAP.vc = vc;
       expect(vc.toQuery()).toEqual("");
 
       expect(vc.root().ldType()).toEqual("non");
@@ -756,6 +787,8 @@
       var vc = vcClass.create([
         ["pubDate", "date"]
       ]);
+      KorAP.vc = vc;
+
       expect(vc.toQuery()).toEqual("");
       expect(vc.builder().firstChild.textContent).toEqual(KorAP.Locale.EMPTY);
       expect(vc.builder().firstChild.classList.contains('unspecified')).toEqual(true);
@@ -805,6 +838,7 @@
         "value":"Baum",
         "match":"match:eq"
       });
+      KorAP.vc = vc;
       expect(vc.toQuery()).toEqual("Titel = \"Baum\"");
 
       var vcE = vc.builder();
@@ -837,6 +871,7 @@
         "match":"match:eq"
       });
 
+      KorAP.vc = vc;
       expect(vc.toQuery()).toEqual("Titel = \"Baum\"");
 
       var vcE = vc.builder();
@@ -870,6 +905,7 @@
         "match":"match:eq"
       });
 
+      KorAP.vc = vc;
       expect(vc.toQuery()).toEqual("Titel = \"Baum\"");
 
       var vcE = vc.builder();
@@ -911,6 +947,7 @@
         ]
       });
 
+    KorAP.vc = vc;
       expect(vc.toQuery()).toEqual("author = \"Max Birkendale\" & pubDate in 2014-12-05");
 
       var vcE = vc.builder();
@@ -2936,29 +2973,32 @@
 
 
     it('should be clickable', function () {
-      var vc = vcClass.create([
+      KorAP.vc = vcClass.create([
         ['a', null],
         ['b', null],
         ['c', null]
       ]).fromJson();
-      expect(vc.builder().firstChild.classList.contains('unspecified')).toBeTruthy();
+      
+      //vc = KorAP.vc;
+      
+      expect(KorAP.vc.builder().firstChild.classList.contains('unspecified')).toBeTruthy();
 
       // This should open up the menu
-      vc.builder().firstChild.firstChild.click();
-      expect(vc.builder().firstChild.firstChild.tagName).toEqual('UL');
+      KorAP.vc.builder().firstChild.firstChild.click();
+      expect(KorAP.vc.builder().firstChild.firstChild.tagName).toEqual('UL');
 
       KorAP._vcKeyMenu._prefix.clear();
       KorAP._vcKeyMenu._prefix.add('x');
 
-      var prefElement = vc.builder().querySelector('span.pref');
+      var prefElement = KorAP.vc.builder().querySelector('span.pref');
       expect(prefElement.innerText).toEqual('x');
 
       // This should add key 'x' to VC
       prefElement.click();
 
-      expect(vc.builder().firstChild.classList.contains('doc')).toBeTruthy();
-      expect(vc.builder().firstChild.firstChild.className).toEqual('key');
-      expect(vc.builder().firstChild.firstChild.innerText).toEqual('x');
+      expect(KorAP.vc.builder().firstChild.classList.contains('doc')).toBeTruthy();
+      expect(KorAP.vc.builder().firstChild.firstChild.className).toEqual('key');
+      expect(KorAP.vc.builder().firstChild.firstChild.innerText).toEqual('x');
     });
   });
 
diff --git a/dev/js/src/init.js b/dev/js/src/init.js
index 34a838a..d94b1b5 100644
--- a/dev/js/src/init.js
+++ b/dev/js/src/init.js
@@ -90,6 +90,10 @@
     var input = d.getElementById('collection');
 
     var vc = KorAP.vc;
+    
+    document.addEventListener('vcChange', function (e) {
+      vc.checkStatActive(e.detail);
+      }, false);
 
     // Add vc name object
     if (input) {
diff --git a/dev/js/src/panel/vcpanel.js b/dev/js/src/panel/vcpanel.js
index 98a9b4b..e12c20c 100644
--- a/dev/js/src/panel/vcpanel.js
+++ b/dev/js/src/panel/vcpanel.js
@@ -43,10 +43,19 @@
       if (this.statView === undefined || !this.statView.shown()) {
         this.statView = corpStatVClass.create(this.vc, this);
         this.add(this.statView);
+        this.vc.oldvcQuery = KorAP.vc.toQuery();
       }
 
     },
     
+    /**
+     * Reload corpus statistic 
+     *
+     */
+    reloadCorpStat: function(){
+      this.statView.close();
+      this.addCorpStat();
+    }
     
   }
 });
diff --git a/dev/js/src/vc.js b/dev/js/src/vc.js
index 711008f..4bc5e13 100644
--- a/dev/js/src/vc.js
+++ b/dev/js/src/vc.js
@@ -81,6 +81,8 @@
   // KorAP._validDateMatchRE is defined in datepicker.js!
 
   const loc = KorAP.Locale;
+  loc.SHOW_STAT        = loc.SHOW_STAT        || 'Statistics';
+  loc.VERB_SHOWSTAT    = loc.VERB_SHOWSTAT    || 'Corpus Statistics';
   loc.VC_allCorpora    = loc.VC_allCorpora    || 'all corpora';
   loc.VC_oneCollection = loc.VC_oneCollection || 'a virtual corpus';
   loc.MINIMIZE         = loc.MINIMIZE         || 'Minimize';
@@ -240,13 +242,15 @@
 
     
     /**
-     * Clean the virtual document to uspecified doc.
+     * Clean the virtual document to unspecified doc.
      */
     clean : function() {
       if (this._root.ldType() !== "non") {
         this._root.destroy();
         this.root(unspecDocClass.create(this));
       };
+      //update for graying corpus statistic by deleting the first line of the vc builder
+      this.update();
       return this;
     },
 
@@ -360,13 +364,14 @@
     
     /**
      * Update the whole object based on the underlying data structure
-     */ 
+     */    
     update : function() {
       this._root.update();
+      var vcchevent = new CustomEvent('vcChange', {'detail':this});
+      document.dispatchEvent(vcchevent);
+      
       return this;
     },
-    
- 
     /**
      * Make the vc persistant by injecting the current timestamp as a
      * creation date limit criterion.
@@ -478,8 +483,17 @@
       //Create panel  
       this.panel = vcPanelClass.create(this); 
       dv.appendChild(this.panel.element());
+      
     },
     
-    
+    /**
+     * Checks if corpus statistic has to be disabled,
+     * and to be updated after clicking at the "reload-button"
+     */
+    checkStatActive : function(){
+      if(this.panel !== undefined && this.panel.statView !==undefined){
+        this.panel.statView.checkStatActive();
+      }
+    }
   };
 });
diff --git a/dev/js/src/vc/doc.js b/dev/js/src/vc/doc.js
index 996c347..132c4a8 100644
--- a/dev/js/src/vc/doc.js
+++ b/dev/js/src/vc/doc.js
@@ -149,6 +149,13 @@
         e.appendChild(op.element());
       };
       
+      if(KorAP.vc){
+      //Replaced through Event
+      //KorAP.vc.checkGrayingStat(this); 
+      var vcchevent = new CustomEvent('vcChange', {'detail':this});
+      document.dispatchEvent(vcchevent);
+      }
+      
       return e;
     },
 
@@ -616,9 +623,12 @@
       };
     },
 
+    incomplete : function () {
+      return !(this.matchop() && this.key() && this.value());
+    },
 
     toQuery : function () {
-      if (!this.matchop() || !this.key())
+      if (this.incomplete())
         return "";
 
       // Build doc string based on key
diff --git a/dev/js/src/vc/docgroup.js b/dev/js/src/vc/docgroup.js
index 7ce2fed..858e847 100644
--- a/dev/js/src/vc/docgroup.js
+++ b/dev/js/src/vc/docgroup.js
@@ -210,6 +210,9 @@
 
       group.appendChild(op.element());
 
+      var vcchevent = new CustomEvent('vcChange', {'detail':this});
+      document.dispatchEvent(vcchevent);
+      
       return this;
     },
 
@@ -356,7 +359,7 @@
     toQuery : function (brackets) {
       var list = this._operands
 	        .filter(function (op) {
-	          return op.ldType() !== 'non';
+            return !op.incomplete();
 	        })
 	        .map(function (op) {
 	          return (op.ldType() === 'docGroup') ?
diff --git a/dev/js/src/vc/docgroupref.js b/dev/js/src/vc/docgroupref.js
index 7d3ecea..f72e27a 100644
--- a/dev/js/src/vc/docgroupref.js
+++ b/dev/js/src/vc/docgroupref.js
@@ -109,7 +109,10 @@
         // Append new operators
         e.appendChild(op.element());
       };  
-
+     
+      var vcchevent = new CustomEvent('vcChange', {'detail':this});
+      document.dispatchEvent(vcchevent);
+      
       return this.element();
     },
 
@@ -207,7 +210,6 @@
       return this;
     },
 
-
     /**
      * Click on the unspecified object
      */
@@ -261,9 +263,13 @@
       };
     },
 
+
+    incomplete : function () {
+      return this.ref() ? false : true
+    },
     
     toQuery : function () {
-      if (!this.ref())
+      if (this.incomplete())
         return "";
 
       // Build doc string based on key
diff --git a/dev/js/src/vc/jsonld.js b/dev/js/src/vc/jsonld.js
index 600a5bb..21260d4 100644
--- a/dev/js/src/vc/jsonld.js
+++ b/dev/js/src/vc/jsonld.js
@@ -99,6 +99,10 @@
       return null;
     },
 
+    incomplete : function () {
+      return false;
+    },
+
     toQuery : function () {
       return '';
     }
diff --git a/dev/js/src/vc/unspecified.js b/dev/js/src/vc/unspecified.js
index 6ffa0e3..416ad85 100644
--- a/dev/js/src/vc/unspecified.js
+++ b/dev/js/src/vc/unspecified.js
@@ -121,6 +121,11 @@
       return this._element;
     },
 
+
+    incomplete : function () {
+      return true;
+    },
+    
     /**
      * Click on the unspecified object
      */
diff --git a/dev/js/src/view/corpstatv.js b/dev/js/src/view/corpstatv.js
index 1cc6f0c..fa9a135 100644
--- a/dev/js/src/view/corpstatv.js
+++ b/dev/js/src/view/corpstatv.js
@@ -3,18 +3,20 @@
  * 
  * @author Helge Stallkamp
  */
+
 define([ 'view', 'vc/statistic' ], function(viewClass, statClass) {
 
   const d = document;
 
   return {
-    create : function(vc) {
+    create : function(vc, panel) {
       return Object.create(viewClass)._init([ 'vcstatistic' ]).upgradeTo(this)
-          ._init(vc);
+          ._init(vc, panel);
     },
 
-    _init : function(vc) {
+    _init : function(vc, panel) {
       this.vc = vc;
+      this.panel = panel;
       return this;
     },
 
@@ -71,9 +73,10 @@
      * Show corpus statistic view 
      */
     show : function() {
+      
       if (this._show)
-        return this._show;
-
+        return this._show; 
+      
       var statTable = document.createElement('div');
       statTable.classList.add('stattable', 'loading');
   
@@ -91,13 +94,71 @@
 
         statisticobj = statClass.create(statistic);
         statTable.appendChild(statisticobj.element());
+    
       });
 
       this._show = statTable;
       return statTable;
 
     },
+    
 
+    
+    /**
+     * Checks if statistic has to be disabled
+     */
+    checkStatActive : function (){
+     var newString = KorAP.vc.toQuery();
+     var oldString = this.vc.oldvcQuery;
+     /*
+      * Do ignore surrounding round brackets
+      * Definining an incomplete docGroup in the vc builder: 
+      * (foo = bar and author = Goethe) and ... 
+      * leads to 
+      * vc.toQuery() -> (foo = bar and author=Goethe)
+      */
+  
+     if(newString || newString === ''){
+       if(newString.startsWith('(')){
+         newString = newString.slice(1, newString.length-1);
+       }
+       
+       if(newString != oldString) {
+        this.disableStat();
+      }  
+     }
+     
+   },
+   
+    /**
+     * Disabling corpus statistic if in vc builder a different vc is choosen.
+     * After clicking at the reload-button the up-to-date corpus statistic is displayed.
+     */   
+
+    disableStat : function(){
+      var statt = this._show;
+  
+      if(statt.getElementsByClassName('reloadStatB').length == 0){
+        var reloadspan = document.createElement('div');
+        reloadspan.classList.add('reloadStatB'); 
+        reloadspan.classList.add('button-group');
+        reloadspan.classList.add('button-panel');    
+        reloadb = reloadspan.addE('span');
+        reloadb.classList.add('refresh');
+          
+        var that = this;          
+        reloadb.addEventListener("click", function (e){    
+        statt.classList.remove('stdisabled');
+        that.panel.reloadCorpStat(); 
+        });
+          
+
+        statt.appendChild(reloadspan);
+        statt.classList.add('stdisabled');        
+        }
+    },
+
+    
     /**
      * Close the view.
      */
diff --git a/dev/scss/header/statistics.scss b/dev/scss/header/statistics.scss
index a4d3f1e..3952e66 100644
--- a/dev/scss/header/statistics.scss
+++ b/dev/scss/header/statistics.scss
@@ -1,31 +1,65 @@
 @charset "utf-8";
 @import "../util";
 
+
+/* 
+Corpus statistic  
+Graying corpus statistic 
+*/
 div.stattable {
-	display: flex;
-	flex-direction: row;
+    display: flex;
+    flex-direction: row;
+}
+div.stattable {
+    > dl {
+        display: flex;
+        flex-flow: row wrap;
+        margin-top:4px;
+        margin-bottom:4px;
+        padding-bottom: 1px;   
+            > div {
+                border-color: $dark-green;
+                > dt {
+                    background-color: $middle-green;
+                    width: 15em;
+                    margin: 0;
+                    &:after {
+                    content: ":";
+                        }
+                    }   
+                > dd {
+                     background-color: $lightest-green;
+                     color: $dark-grey;
+                    }
+                }
+            }
+    &.stdisabled {
+        > dl 
+            > div {
+                > dt {
+                    @include vcinfo-inactive;
+                    }
+             > dd {
+                    @include vcinfo-inactive;
+                }
+            }
+        }
 }
 
-div.stattable > dl {
-  display: flex;
-  flex-flow: row wrap;
-  //margin-top: 4 * $border-size !important;
-  margin-top:4px;
-  margin-bottom:4px;
-  padding-bottom: 1px;   
-  > div {
-    border-color: $dark-green;
-    > dt {
-      background-color: $middle-green;
-      width: 15em;
-      margin: 0;
-      &:after {
-  		  content: ":";
-  	  }
+
+/* Corpus statistic reload button */
+div.reloadStatB {
+    font-family: 'FontAwesome';
+    padding-left: 2px;
+    z-index: 30;
     }
-   	> dd {
-      background-color: $lightest-green;
-      color: $dark-grey;
-	  }
-  }
-}
+span.refresh::after{
+    line-height: normal;
+    content : $fa-redo;
+    font-size: 15pt;
+    }
+
+/* Close-button should always be seen next to or above reload-button */
+.button-view.vcstatistic {
+    z-index: 30;
+    }
diff --git a/dev/scss/util.scss b/dev/scss/util.scss
index bb82ca0..90e20b1 100644
--- a/dev/scss/util.scss
+++ b/dev/scss/util.scss
@@ -38,6 +38,7 @@
 $middle-green:   lighten($ids-green-1, 9%);
 $light-green:    lighten($ids-green-1, 13%); // #7ba400;
 $lightest-green: #d8e38c; // lighten($ids-green-1, 26%);
+$grey-green: #bcc387;
 
 /**
  * Blue Colors
@@ -50,10 +51,10 @@
  * Grey Colors
  */
 $middle-grey:  $ids-grey-1; // #999;
+$semilight-grey: #8d8d8d;
 $light-grey:   $ids-grey-2; // #ddd;
 $dark-grey:    darken($middle-grey, 15%);
 $nearly-white: #fefefe;
-
 /**
  * Red Colors (no IDS relation)
  */
@@ -234,7 +235,15 @@
   appearance:none;
 }
 
-
+/** 
+* Mixin for the appearance of inactive elements in the vcinfo panel
+*/
+@mixin vcinfo-inactive{
+    background-color: $grey-green;
+    color: $semilight-grey;
+    border-color:   $semilight-grey; 
+    text-shadow: none;
+}
 /**
  * Font Awesome symbol table
  */
@@ -267,4 +276,5 @@
 $fa-to-query:     "\f102";
 $fa-cut:          "\f0c4";
 $fa-plugin:       "\f1e6";
-$fa-referto:      "\f0c5";
\ No newline at end of file
+$fa-referto:      "\f0c5";
+$fa-redo:         "\f01e";
diff --git a/lib/Kalamar/Plugin/Auth.pm b/lib/Kalamar/Plugin/Auth.pm
index 7693b45..640cc8b 100644
--- a/lib/Kalamar/Plugin/Auth.pm
+++ b/lib/Kalamar/Plugin/Auth.pm
@@ -173,9 +173,16 @@
 
           # There is an error here
           # Dealing with errors here
-          if (my $error = $jwt->{error}) {
+          if (my $error = $jwt->{error} // $jwt->{errors}) {
             if (ref $error eq 'ARRAY') {
-              $c->notify(error => $c->dumper($_));
+              foreach (@$error) {
+                unless ($_->[1]) {
+                  $c->notify(error => $c->loc('Auth_loginFail'));
+                }
+                else {
+                  $c->notify(error => $_->[0] . ($_->[1] ? ': ' . $_->[1] : ''));
+                };
+              };
             }
             else {
               $c->notify(error => 'There is an unknown JWT error');
diff --git a/package.json b/package.json
index 542e3b3..df09c86 100755
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
   "name": "Kalamar",
   "description": "Mojolicious-based Frontend for KorAP",
   "license": "BSD-2-Clause",
-  "version": "0.31.1",
+  "version": "0.32.1",
   "pluginVersion": "0.1",
   "repository" : {
     "type": "git",
diff --git a/t/plugin/auth.t b/t/plugin/auth.t
index 9828baf..b2089a1 100644
--- a/t/plugin/auth.t
+++ b/t/plugin/auth.t
@@ -58,6 +58,7 @@
 $t->get_ok('/')
   ->status_is(200)
   ->element_exists('div.notify-error')
+  ->text_is('div.notify-error', 'Bad CSRF token')
   ->element_exists('input[name=handle_or_email][value=test]')
   ->element_exists_not('div.button.top a')
   ;
@@ -76,6 +77,42 @@
 
 $t->post_ok('/user/login' => form => {
   handle_or_email => 'test',
+  pwd => 'ldaperr',
+  csrf_token => $csrf
+})
+  ->status_is(302)
+  ->content_is('')
+  ->header_is('Location' => '/');
+
+$csrf = $t->get_ok('/')
+  ->status_is(200)
+  ->element_exists('div.notify-error')
+  ->text_is('div.notify-error', '2022: LDAP Authentication failed due to unknown user or password!')
+  ->element_exists('input[name=handle_or_email][value=test]')
+  ->element_exists_not('div.button.top a')
+  ->tx->res->dom->at('input[name=csrf_token]')->attr('value')
+  ;
+
+$t->post_ok('/user/login' => form => {
+  handle_or_email => 'test',
+  pwd => 'unknown',
+  csrf_token => $csrf
+})
+  ->status_is(302)
+  ->content_is('')
+  ->header_is('Location' => '/');
+
+$csrf = $t->get_ok('/')
+  ->status_is(200)
+  ->element_exists('div.notify-error')
+  ->text_is('div.notify-error', 'Access denied')
+  ->element_exists('input[name=handle_or_email][value=test]')
+  ->element_exists_not('div.button.top a')
+  ->tx->res->dom->at('input[name=csrf_token]')->attr('value')
+  ;
+
+$t->post_ok('/user/login' => form => {
+  handle_or_email => 'test',
   pwd => 'pass',
   csrf_token => $csrf
 })
diff --git a/t/server/mock.pl b/t/server/mock.pl
index 18fd05d..79eeb90 100644
--- a/t/server/mock.pl
+++ b/t/server/mock.pl
@@ -229,6 +229,16 @@
           token_type => 'api_token'
         })
       );
+    }
+
+    elsif ($pwd eq 'ldaperr') {
+      return $c->render(
+        format => 'html',
+        status => 401,
+        json => {
+          "errors" => [[2022,"LDAP Authentication failed due to unknown user or password!"]]
+        }
+      );
     };
 
     return $c->render(