Introduced state class for simple reactive state propagation

Change-Id: I3c98751ff124b289499d179bf3fff4bee9350e7e
diff --git a/Changes b/Changes
index 0027b2e..b43f219 100755
--- a/Changes
+++ b/Changes
@@ -1,4 +1,4 @@
-0.37 2019-11-19
+0.37 2019-12-05
         - Removed deprecated 'kalamar_test_port' helper.
         - Separated KalamarHelpers and KalamarPages.
         - Renamed 'doc_link_to' to 'embedded_link_to'
@@ -16,6 +16,7 @@
         - Added 'navi->set' and 'navi->add' helper.
         - Added settings skeleton.
         - Added query panel for query views.
+        - Added state object.
 
 0.36 2019-09-19
         - Rename all cookies to be independent
diff --git a/dev/js/runner/all.html b/dev/js/runner/all.html
index f0a71ad..1d3349c 100644
--- a/dev/js/runner/all.html
+++ b/dev/js/runner/all.html
@@ -48,7 +48,8 @@
         'spec/statSpec',
         'spec/vcSpec',
         'spec/tourSpec',
-        'spec/utilSpec'
+        'spec/utilSpec',
+        'spec/stateSpec'
       ],
       function () {
         window.onload();
diff --git a/dev/js/spec/stateSpec.js b/dev/js/spec/stateSpec.js
new file mode 100644
index 0000000..865691e
--- /dev/null
+++ b/dev/js/spec/stateSpec.js
@@ -0,0 +1,57 @@
+define(['state'], function (stateClass) {
+
+  describe('KorAP.State', function () {
+    it('should be initializable', function () {
+      let s = stateClass.create();
+      expect(s.get()).toBeFalsy();
+
+      s = stateClass.create(true);
+      expect(s.get()).toBeTruthy();
+    });
+
+    it('should be settable and gettable', function () {
+      let s = stateClass.create();
+      expect(s.get()).toBeFalsy();
+      s.set(true);
+      expect(s.get()).toBeTruthy();
+    });
+
+    it('should be associatable', function () {
+      let s = stateClass.create();
+
+      // Create
+      let obj1 = {
+        x : false,
+        setState : function (value) {
+          this.x = value;
+        }
+      };
+
+      // Create
+      let obj2 = {
+        x : true,
+        setState : function (value) {
+          this.x = value;
+        }
+      };
+      
+      expect(s.get()).toBeFalsy();
+      expect(obj1.x).toBeFalsy();
+      expect(obj2.x).toBeTruthy();
+
+      // Associate object with state
+      s.associate(obj1);
+      s.associate(obj2);
+
+      expect(s.get()).toBeFalsy();
+      expect(obj1.x).toBeFalsy();
+      expect(obj2.x).toBeFalsy();
+
+      s.set(true);
+
+      expect(s.get()).toBeTruthy();
+      expect(obj1.x).toBeTruthy();
+      expect(obj2.x).toBeTruthy();
+    });
+  });
+});
diff --git a/dev/js/src/state.js b/dev/js/src/state.js
new file mode 100644
index 0000000..013ae10
--- /dev/null
+++ b/dev/js/src/state.js
@@ -0,0 +1,64 @@
+/**
+ * Create a state object, that can have a single value
+ * (mostly boolean) and multiple objects associated to it.
+ * Whenever the state changes, all objects are informed
+ * by their setState() method of the value change.
+ *
+ * @author Nils Diewald
+ */
+define(function () {
+
+  "use strict";
+
+  return {
+
+    /**
+     * Constructor
+     */
+    create : function (value) {
+      return Object.create(this)._init(value);
+    },
+
+    // Initialize
+    _init : function (value) {
+      this._assoc = [];
+      this.value = value;
+      return this;
+    },
+
+
+    /**
+     * Associate the state with some objects.
+     */
+    associate : function (obj) {
+
+      // Check if the object has a setState() method
+      if (obj.hasOwnProperty("setState")) {
+        this._assoc.push(obj);
+        obj.setState(this.value);
+      } else {
+        console.log("Object " + obj + " has no setState() method");
+      }
+    },
+
+    /**
+     * Set the state to a certain value.
+     * This will set the state to all associated objects as well.
+     */
+    set : function (value) {
+      if (value != this.value) {
+        this.value = value;
+        for (let i in this._assoc) {
+          this._assoc[i].setState(value);
+        }
+      };
+    },
+
+    /**
+     * Get the state value
+     */
+    get : function () {
+      return this.value;
+    }
+  }
+});