Introduce state manager (fixes #119)

Change-Id: Idfd71ea53a898c57e676a197776e2b019ef08561
diff --git a/Changes b/Changes
index c3356f3..6bcc53d 100755
--- a/Changes
+++ b/Changes
@@ -41,6 +41,7 @@
         - Introduce pagination panel.
         - Support for inactive buttongroup items.
         - Support default values for state.
+        - Introduce state manager (#119).
 
 0.42 2021-06-18
         - Added GitHub based CI for perl.
diff --git a/dev/js/spec/stateSpec.js b/dev/js/spec/stateSpec.js
index 09ab274..286cb1c 100644
--- a/dev/js/spec/stateSpec.js
+++ b/dev/js/spec/stateSpec.js
@@ -1,4 +1,4 @@
-define(['state'], function (stateClass) {
+define(['state','state/manager'], function (stateClass, stateManagerClass) {
 
   describe('KorAP.State', function () {
     it('should be initializable', function () {
@@ -137,4 +137,47 @@
       expect(s.get()).toEqual('mann');
     });
   });
+
+  describe('KorAP.State.Manager', function () {
+
+    const el = document.createElement('input');
+
+    it('should be initializable', function () {
+
+      let sm = stateManagerClass.create(el);
+      expect(sm).toBeTruthy();
+
+      expect(sm.toString()).toEqual("{}");
+    });
+
+
+    it('should be extensible', function () {
+      const sm = stateManagerClass.create(el);
+      expect(sm).toBeTruthy();
+
+      const s1 = sm.newState('test', [1,2,3]);
+      
+      expect(sm.toString()).toEqual("{}");
+
+      s1.set(2);
+
+      expect(sm.toString()).toEqual("{\"test\":2}");
+
+      s1.set(3);
+
+      expect(sm.toString()).toEqual("{\"test\":3}");
+
+      const s2 = sm.newState('glemm', [true,false]);
+
+      let serial = JSON.parse(sm.toString());   
+      expect(serial["test"]).toEqual(3);
+      expect(serial["glemm"]).toBeUndefined();
+
+      s2.set(false);
+
+      serial = JSON.parse(sm.toString());   
+      expect(serial["test"]).toEqual(3);
+      expect(serial["glemm"]).toEqual(false);
+    });
+  });
 });
diff --git a/dev/js/src/state/manager.js b/dev/js/src/state/manager.js
new file mode 100644
index 0000000..26cb482
--- /dev/null
+++ b/dev/js/src/state/manager.js
@@ -0,0 +1,79 @@
+/**
+ * Create a state manager object, that can deserialize and
+ * serialize states of associated states.
+ * At the moment this requires an element for serialization,
+ * but it may very well serialize in a cookie.
+ *
+ * @author Nils Diewald
+ */
+
+"use strict";
+
+define(['state'], function(stateClass) {
+
+  return {
+    // Create new state amanger.
+    // Expects an object with a value
+    // to contain the serialization of all states.
+    create : function (element) {
+      return Object.create(this)._init(element);
+    },
+
+
+    // Initialize state manager
+    _init : function (element) {
+      this._e = element;
+      this._states = {};
+      this._parse(element.value);
+
+      return this;
+    },
+
+
+    // Parse a value and populate states
+    _parse : function (value) {
+      if (value === undefined || value === '')
+        return;
+
+      
+      
+      this._states = JSON.parse(value);
+    },
+
+
+    // Return the string representation of all states
+    toString : function () {
+      return JSON.stringify(this._states);
+    },
+
+
+    // Update the query component for states
+    _update : function () {
+      this._e.value = this.toString();
+    },
+
+
+    // Create new state that is automatically associated
+    // with the state manager
+    newState : function (name, values) {
+
+      const t = this;
+      let s = stateClass.create(values);
+
+      // Load state
+      if (t._states[name] !== undefined) {
+        s.set(t._states[name]);
+      };
+      
+      // Associate with dummy object
+      s.associate({
+        setState : function (value) {
+          t._states[name] = value;
+          t._update();
+        }
+      });
+      
+      return s;
+    }
+  };
+});