Use statemanager for toggle states

Change-Id: Iafbdf69140b69ffabd5c26ffab3b2e055d2e3fe3
diff --git a/Changes b/Changes
index ec02d21..e5851b4 100755
--- a/Changes
+++ b/Changes
@@ -1,8 +1,10 @@
-0.44 2022-01-04
+0.44 2022-01-18
         - Fixed autosecrets migration. (diewald)
-        - Format page numbers in pagination (diewald).
-        - Introduce tei2korapxml command via plugin (diewald).
-        - Introduce korapxml2tei command via plugin (diewald).
+        - Format page numbers in pagination. (diewald)
+        - Introduce tei2korapxml command via plugin. (diewald)
+        - Introduce korapxml2tei command via plugin. (diewald)
+        - Plugin toggle states are now managed by the state
+          manager and can therefore survive URL changes. (diewald)
 
 0.43 2021-11-05
         - New menu class that has an entry at the very end,
diff --git a/dev/js/spec/pluginSpec.js b/dev/js/spec/pluginSpec.js
index 973ad16..acadeac 100644
--- a/dev/js/spec/pluginSpec.js
+++ b/dev/js/spec/pluginSpec.js
@@ -230,14 +230,14 @@
       p.element().querySelector("span.close").click();
 
       expect(p.element().querySelectorAll("iframe").length).toEqual(0);
-
+      
       b.click();
 
       expect(p.element().querySelectorAll("iframe").length).toEqual(1);
       expect(p.element().querySelectorAll("div.view.widget").length).toEqual(1);
       expect(p.element().querySelectorAll("div.view.show.widget").length).toEqual(1);
       expect(p.element().querySelector("iframe").getAttribute('sandbox')).toEqual('');
-      
+
       manager.destroy();
 
       KorAP.Panel["result"] = undefined;
@@ -254,6 +254,7 @@
           panel : 'result',
           title : 'Glemm',
           onClick : {
+            state : 'check',
             template : 'about:blank',
             action : 'toggle',
             'default' : false
@@ -271,8 +272,12 @@
 
       expect(p.element().querySelectorAll("iframe").length).toEqual(0);
 
+      expect(manager.states().toString()).toEqual("");
+      
       b.click();
 
+      expect(manager.states().toString()).toEqual("\"check\":true");
+      
       expect(b.getAttribute("title")).toEqual("Glemm");
       expect(b.firstChild.classList.contains('button-icon')).toBeTruthy();
       expect(b.firstChild.classList.contains('check')).toBeTruthy();
diff --git a/dev/js/src/init.js b/dev/js/src/init.js
index 724cfb3..48fb112 100644
--- a/dev/js/src/init.js
+++ b/dev/js/src/init.js
@@ -25,6 +25,7 @@
   'vc/array',
   'lib/alertify',
   'session',
+  'state/manager',
   'selectMenu',
   'panel/result',
   'panel/query',
@@ -44,6 +45,7 @@
              vcArray,
              alertifyClass,
              sessionClass,
+             stateManagerClass,
              selectMenuClass,
              resultPanelClass,
              queryPanelClass,
@@ -471,6 +473,13 @@
       if (url !== undefined) {
         KorAP.API.getPluginList(url, function (json) {
           if (json && json.length > 0) {
+
+            // Add state manager
+            const input = d.getElementById("searchform").addE("input");
+            input.setAttribute("name","state");
+            KorAP.States = stateManagerClass.create(input);
+            
+            
             // Load Plugin Server first
             KorAP.Plugin = pluginClass.create();
 
@@ -480,7 +489,7 @@
             // Add pipe form
             KorAP.Pipe = pipeClass.create();
             d.getElementById("searchform").appendChild(KorAP.Pipe.element());
-            
+
             try {
               
               // Register all plugins
diff --git a/dev/js/src/plugin/server.js b/dev/js/src/plugin/server.js
index 1fa0002..589263f 100644
--- a/dev/js/src/plugin/server.js
+++ b/dev/js/src/plugin/server.js
@@ -9,10 +9,16 @@
  */
 "use strict";
 
-define(['plugin/widget', 'plugin/service', 'state', 'pageInfo', 'util'], function (widgetClass, serviceClass, stateClass, pageInfoClass) {
+define(['plugin/widget', 'plugin/service', 'state', 'state/manager', 'pageInfo', 'util'], function (widgetClass, serviceClass, stateClass, stateManagerClass, pageInfoClass) {
 
   KorAP.Panel = KorAP.Panel || {};
 
+  // State manager undefined
+  const states = KorAP.States ? KorAP.States :
+
+        // Not serialized state manager
+        stateManagerClass.create(document.createElement('input'));
+  
   // Contains all servicess to address with
   // messages to them
   var services = {};
@@ -104,7 +110,7 @@
       // TODO:
       //   These fields need to be localized for display by a structure like
       //   { de : { name : '..' }, en : { .. } }
-      var name = obj["name"];
+      const name = obj["name"];
 
       if (!name)
         throw new Error("Missing name of plugin");
@@ -154,7 +160,7 @@
             // a intermediate object to toggle the view
             if ('state' in this.button && this.button.state.associates() > 0) {
 
-              let s = this.button.state;
+              const s = this.button.state;
 
               // The associated service is existent
               if (services[this.button['widgetID']]) {
@@ -203,8 +209,10 @@
 
           if (onClick["action"] && onClick["action"] == "setWidget") {
 
-            // Create a boolean state value, that initializes to true == opened
+            // Create a boolean state value,
+            // that initializes to true == opened
             obj['state'] = stateClass.create([true, false]);
+            obj['state'].setIfNotYet(true);
           };
           
           // Add to dynamic button list (e.g. for matches)
@@ -230,12 +238,11 @@
           //   Accept a "value" list here for toggling, which should
           //   also allow for "rolling" through states via CSS classes
           //   as 'toggle-true', 'toggle-false' etc.
-
-          let state = stateClass.create([true, false]);
-
-          if (onClick["default"] !== undefined) {
-            state.setIfNotYet(onClick["default"]);
-          };
+          let state = states.newState(
+            (onClick["state"] ? onClick["state"] : name),
+            [true, false],
+            onClick["default"]
+          );
 
           // TODO:
           //   Lazy registration (see above!)
@@ -544,9 +551,10 @@
     // Close the service
     _closeService : function (id) {
       delete limits[id];
-      
+
       // Close the iframe
       if (services[id] && services[id]._closeIframe) {
+
         services[id]._closeIframe();
 
         // Remove from list
@@ -583,7 +591,13 @@
       }
       return this._el;
     },
-    
+
+
+    // Return states object
+    states : function () {
+      return states;
+    },
+
     // Destructor, just for testing scenarios
     destroy : function () {
       limits = {};
@@ -605,7 +619,6 @@
         };
         this._el = null;
       };
-      
       this._removeListener();
     }
   };
diff --git a/dev/js/src/state.js b/dev/js/src/state.js
index f68408f..a4254d6 100644
--- a/dev/js/src/state.js
+++ b/dev/js/src/state.js
@@ -49,8 +49,11 @@
 
       // Check if the object has a setState() method
       if (obj.hasOwnProperty("setState")) {
+
         this._assoc.push(obj);
-        obj.setState(this.value);
+        if (this.value != undefined) {
+          obj.setState(this.value);
+        };
       } else {
         console.log("Object " + obj + " has no setState() method");
       }
diff --git a/dev/js/src/state/manager.js b/dev/js/src/state/manager.js
index 4376b27..339503d 100644
--- a/dev/js/src/state/manager.js
+++ b/dev/js/src/state/manager.js
@@ -63,11 +63,6 @@
       const t = this;
       let s = stateClass.create(values);
 
-      // Load state
-      if (t._states[name] !== undefined) {
-        s.set(t._states[name]);
-      };
-
       // Set default value
       // TODO: It would be better to make this part
       // of the state and serialize correctly using TOJSON()
@@ -88,6 +83,11 @@
         }
       });
       
+      // Load state
+      if (t._states[name] !== undefined) {
+        s.set(t._states[name]);
+      };
+
       return s;
     }
   };