diff --git a/thehive_button/public/vis_controller.js b/thehive_button/public/vis_controller.js
index 2ddeb1fb70341ad31761e11a79e26ba170cdff01..3018390b86d22dc8f9fa0a62c2164d98963a1047 100644
--- a/thehive_button/public/vis_controller.js
+++ b/thehive_button/public/vis_controller.js
@@ -26,6 +26,11 @@ import {
   makeId,
 } from '@elastic/eui';
 
+// TODO:
+// - Explicit reset button
+// - Reset when data (query) changes
+// - Hide modal when clicked outside
+
 
 // ********** React components **********
 
@@ -63,12 +68,50 @@ class NewCaseButton extends Component {
 
   constructor(props) {
     super(props);
+    const n_obs = props.observables.length;
+    
+    // initial state of form fields - used to reset form
+    this.initial_case_state = {
+      // Case parameters
+      title: "",
+      description: "\n\n--\nCreated from Kibana",
+      severity: "2", // medium
+      tlp: "2", // amber
+      tags: [], // TODO (not implemented yet)
+      // An array of values for each editable part of observables, prefilled with default values
+      obsDescrs: new Array(n_obs).fill(""),
+      obsTLPs: new Array(n_obs).fill(2), // TODO (not implemented yet)
+      obsIOCs: new Array(n_obs).fill(false),
+      obsTags: new Array(n_obs).fill([]), // TODO (not implemented yet)
+      obsSelected: new Array(), // list of indices of selected observables
+    }
+    
+    // The complete state is here, so it's kept even when modal is closed
     this.state = {
       isModalVisible: false,
-    };
+      ...this.initial_case_state, // TODO isn't deep copy needed here (and in reset_form below)?
+    }
+
     // Each handler function in a class (method) must be "binded" this way
     this.closeModal = this.closeModal.bind(this);
     this.showModal = this.showModal.bind(this);
+
+    this.onTitleChange = this.onTitleChange.bind(this);
+    this.onSeverityChange = this.onSeverityChange.bind(this);
+    this.onTLPChange = this.onTLPChange.bind(this);
+    this.onDescriptionChange = this.onDescriptionChange.bind(this);
+    
+    this.onObsDescrChange = this.onObsDescrChange.bind(this);
+    this.onObsTLPChange = this.onObsTLPChange.bind(this);
+    this.onObsIOCChange = this.onObsIOCChange.bind(this);
+    this.onObsTagsChange = this.onObsTagsChange.bind(this);
+    this.onObsSelectionChange = this.onObsSelectionChange.bind(this);
+    
+    this.submitCase = this.submitCase.bind(this);
+  }
+
+  resetForm() {
+    this.setState(this.initial_case_state);
   }
 
   closeModal() {
@@ -79,10 +122,199 @@ class NewCaseButton extends Component {
     this.setState({ isModalVisible: true });
   }
 
+  // Event handlers for change of case parameter
+  onTitleChange(evt) {
+    this.setState({title: evt.target.value});
+  }
+  onSeverityChange(value) {
+    this.setState({severity: value});
+  }
+  onTLPChange(value) {
+    this.setState({tlp: value});
+  }
+  onDescriptionChange(evt) {
+    this.setState({description: evt.target.value});
+  }
+
+  // Event handlers for change of observable parameters
+  onObsDescrChange(evt, rowindex) {
+    const val = evt.target.value;
+    this.setState((state, props) => {
+      // Copy old obsDescrs, update the corresponding item and pass as new state
+      let descrs = [...state.obsDescrs];
+      descrs[rowindex] = val;
+      return {obsDescrs: descrs};
+    });
+  }
+  onObsTLPChange(evt, rowindex) { // TODO
+    const val = evt.target.value;
+    this.setState((state, props) => {
+      // Copy old obsTLPs, update the corresponding item and pass as new state
+      let tlps = [...state.obsTLPs];
+      tlps[rowindex] = val;
+      return {obsTLPs: tlps};
+    });
+  }
+  onObsIOCChange(evt, rowindex) {
+    const val = evt.target.checked;
+    this.setState((state, props) => {
+      // Copy old obsIOCs, update the corresponding item and pass as new state
+      let iocs = [...state.obsIOCs];
+      iocs[rowindex] = val;
+      return {obsIOCs: iocs};
+    });
+  }
+  onObsTagsChange(evt, rowindex) { // TODO
+    const val = evt.target.value;
+    this.setState((state, props) => {
+      // Copy old obsTags, update the corresponding item and pass as new state
+      let tags = [...state.obsTags];
+      tags[rowindex] = val;
+      return {obsTags: tags};
+    });
+  }
+  
+  // Event handler for row (de)selection
+  onObsSelectionChange(selectedItems) {
+    // Extract indices from the items and store them into state
+    const selectedIndices = selectedItems.map(item => item.i);
+    this.setState({obsSelected: selectedIndices});
+  }
+  
+  // Submit case button handler
+  submitCase(evt) {
+    const params = this.props.params;
+    
+    // Get case parameters
+    const title = this.state.title;
+    const descr = this.state.description;
+    const severity = parseInt(this.state.severity);
+    const start_date = null;
+    const owner = params.owner;
+    const flag = false;
+    const tlp = parseInt(this.state.tlp);
+    const tags = this.state.tags;
+    
+    if (!title) {
+      toastNotifications.addDanger("Title can't be empty");
+      return;
+    }
+    
+    // Get list of selected observables and their params
+    let observables = [];
+    let selectionIndices = [...this.state.obsSelected]; // make a copy
+    selectionIndices.sort();
+    for (let i = 0; i < selectionIndices.length; i++) {
+      const j = selectionIndices[i]; // index of a selected obs. in the list of all observables
+      // fill in observable definition according to model at
+      // https://github.com/TheHive-Project/TheHiveDocs/blob/master/api/artifact.md
+      const obs = {
+        dataType: 'ip',
+        data: this.props.observables[j],
+        message: this.state.obsDescrs[j],
+        tlp: this.state.obsTLPs[j],
+        ioc: this.state.obsIOCs[j],
+        tags: this.state.obsTags[j],
+      };
+      observables.push(obs);
+    }
+    //console.log("Selected observables:", observables);
+    
+    // All data cached in local variables - reset the form fields
+    this.resetForm();
+    
+    // Submit request to create the case, handle response
+    // TODO rewite to await
+    createHiveCase(params.url, params.apikey, title, descr, severity, start_date, owner, flag, tlp, tags)
+      .then((resp) => {
+        if ('error' in resp) {
+          // Error contacting The Hive
+          console.error("TheHiveButton: ERROR when trying to create new case:", resp.error);
+          toastNotifications.addDanger("ERROR: " + resp.error);
+          return;
+        }
+
+        console.log("TheHiveButton: Case created:", resp);
+        const case_id = resp.id;
+        const case_url = params.url + "index.html#/case/" + case_id + "/details";
+        
+        // Show notification
+        let obs_text;
+        if (observables.length > 0) {
+          obs_text = "Adding " + observables.length + " observables in background ...";
+        }
+        else {
+          obs_text = "(no observables added)";
+        }
+        toastNotifications.add({
+          title: "Case created",
+          color: "success",
+          iconType: "checkInCircleFilled",
+          text: (
+            <div>
+              <p><b><a href={case_url} target="_blank">Edit the new Case</a></b></p>
+              <p>{obs_text}</p>
+            </div>
+          ),
+        });
+         
+        // Open a new window with the case in The Hive
+        // (adding observables may take some time, so the case is opened first;
+        //  The Hive web is dynamic so the observables appear as they are added)
+        window.open(case_url, '_blank');
+        
+        if (observables.length == 0)
+          return;
+        
+        // Submit request to add observables
+        console.log("TheHiveButton: adding " + observables.length + " observables ...");
+        addCaseObservables(params.url, params.apikey, case_id, observables)
+          .then((resp) => {
+            if ('error' in resp) {
+              console.error("TheHiveButton: ERROR when trying to add observables: " + resp.error);
+              toastNotifications.addDanger("ERROR when trying to add observables: " + resp.error);
+            }
+            else {
+              console.log("TheHiveButton: Done, observables added.");
+              toastNotifications.add("Done, observables added.");
+            }
+          }); // addObservables().then() func
+      }); // createHiveCase.then() func
+    
+    // Close the popup window
+    this.closeModal();
+  }
+
+  // Render function
   render() {
     let modal;
     if (this.state.isModalVisible) {
-      modal = <ModalContent close={this.closeModal} params={this.props.params} observables={this.props.observables} />;
+      modal = <ModalContent
+        close={this.closeModal}
+        observables={this.props.observables}
+        // form state
+        title={this.state.title}
+        description={this.state.description}
+        severity={this.state.severity}
+        tlp={this.state.tlp}
+        tags={this.state.tags}
+        obsDescrs={this.state.obsDescrs}
+        obsTLPs={this.state.obsTLPs}
+        obsIOCs={this.state.obsIOCs}
+        obsTags={this.state.obsTags}
+        obsSelected={this.state.obsSelected}
+        // event handlers
+        onTitleChange={this.onTitleChange}
+        onSeverityChange={this.onSeverityChange}
+        onTLPChange={this.onTLPChange}
+        onDescriptionChange={this.onDescriptionChange}
+        onObsDescrChange={this.onObsDescrChange}
+        onObsTLPChange={this.onObsTLPChange}
+        onObsIOCChange={this.onObsIOCChange}
+        onObsTagsChange={this.onObsTagsChange}
+        onObsSelectionChange={this.onObsSelectionChange}
+        submitCase={this.submitCase}
+      />;
     }
     return (
       <div>
@@ -93,32 +325,12 @@ class NewCaseButton extends Component {
   }
 }
 
+
 // The popup window with a form
-// Props:
-//  .close - function to close the Modal
-//  .params - visualization parameters (from vis.params)
-//  .observables - list of IP addrsses or other observables to add to the Case
 class ModalContent extends Component {
   constructor(props) {
     super(props);
-    const observables = props.observables;
-    const n_obs = observables.length;
-    
-    // Initialize state (all changeable case parameters)
-    // TODO: move state up to the button, so it persists when modal is closed (and add explicit reset button + reset on submit)
-    this.state = {
-      title: "",
-      description: "\n\n--\nCreated from Kibana",
-      severity: "2", // medium
-      tlp: "2", // amber
-      tags: [],
-      // An array of values for each editable part of observables, prefilled with default values
-      obsDescrs: new Array(n_obs).fill(""),
-      obsTLPs: new Array(n_obs).fill(2),
-      obsIOCs: new Array(n_obs).fill(false),
-      obsTags: new Array(n_obs).fill([]),
-      obsSelected: new Array(), // list of indices of selected observables
-    };
+    // No state here, everything is in the parent class (NewCaseButton)
     
     // "Select" options
     this.severityOptions = [
@@ -133,7 +345,7 @@ class ModalContent extends Component {
       {value: "3", inputDisplay: "red"},
     ];
     
-    // Table column definition
+    // Table columns definition
     this.columns = [
       {
         field: "id",
@@ -145,7 +357,7 @@ class ModalContent extends Component {
         description: "Description of the observable in the context of the case",
         render: (value, item) => (<EuiFieldText
           value={item.descr}
-          onChange={(e) => this.onChangeDescr(e, item.i)}
+          onChange={(e) => this.props.onObsDescrChange(e, item.i)}
           disabled={!item.selected}
         />)
       },
@@ -163,7 +375,7 @@ class ModalContent extends Component {
         render: (value, item) => (<EuiCheckbox
           id={"ioc-checkbox-"+item.id}
           checked={item.ioc}
-          onChange={(e) => this.onChangeIOC(e, item.i)}
+          onChange={(e) => this.props.onObsIOCChange(e, item.i)}
           disabled={!item.selected}
         />)
       },
@@ -174,63 +386,11 @@ class ModalContent extends Component {
       },*/
     ]
     
-    // Each handler function in a class (method) must be "bind" this way
-    this.submitCase = this.submitCase.bind(this);
-    this.onChangeDescr = this.onChangeDescr.bind(this);
-    this.onChangeTLP = this.onChangeTLP.bind(this);
-    this.onChangeIOC = this.onChangeIOC.bind(this);
-    this.onChangeTags = this.onChangeTags.bind(this);
-    this.onSelectionChange = this.onSelectionChange.bind(this);
-  }
-  
-  // Event handlers - change observable parameters
-  onChangeDescr(evt, rowindex) {
-    const val = evt.target.value;
-    this.setState((state, props) => {
-      // Copy old obsDescrs, update the corresponding item and pass as new state
-      let descrs = [...state.obsDescrs];
-      descrs[rowindex] = val;
-      return {obsDescrs: descrs};
-    });
-  }
-  onChangeTLP(evt, rowindex) { // TODO
-    const val = evt.target.value;
-    this.setState((state, props) => {
-      // Copy old obsTLPs, update the corresponding item and pass as new state
-      let tlps = [...state.obsTLPs];
-      tlps[rowindex] = val;
-      return {obsTLPs: tlps};
-    });
-  }
-  onChangeIOC(evt, rowindex) {
-    const val = evt.target.checked;
-    this.setState((state, props) => {
-      // Copy old obsIOCs, update the corresponding item and pass as new state
-      let iocs = [...state.obsIOCs];
-      iocs[rowindex] = val;
-      return {obsIOCs: iocs};
-    });
-  }
-  onChangeTags(evt, rowindex) { // TODO
-    const val = evt.target.value;
-    this.setState((state, props) => {
-      // Copy old obsTags, update the corresponding item and pass as new state
-      let tags = [...state.obsTags];
-      tags[rowindex] = val;
-      return {obsTags: tags};
-    });
-  }
-  
-  // Event handler - a row is (de)selected
-  onSelectionChange(selectedItems) {
-    // Extract indices from the items and store them into state
-    const selectedIndices = selectedItems.map(item => item.i);
-    this.setState({obsSelected: selectedIndices});
+    // Create a reference to observables table, so it's node can be accessed in componentDidMount
+    this.obsTableRef = React.createRef();
   }
   
   // Main render function
-  // TODO should't it be easier to simply store whole table_data in State?
-  //   everything has to be re-rendered on any state change anyway
   render() {
     const obs = this.props.observables;
     // Create data for the observables table
@@ -238,16 +398,16 @@ class ModalContent extends Component {
     for (let i = 0; i < obs.length; i++) {
       table_data[i] = {
         id: obs[i],
-        descr: this.state.obsDescrs[i],
-        tlp: this.state.obsTLPs[i],
-        ioc: this.state.obsIOCs[i],
-        tags: this.state.obsTags[i],
+        descr: this.props.obsDescrs[i],
+        tlp: this.props.obsTLPs[i],
+        ioc: this.props.obsIOCs[i],
+        tags: this.props.obsTags[i],
         // auxiliary fields, not shown in table:
         i: i, // row index
-        selected: this.state.obsSelected.includes(i),
+        selected: this.props.obsSelected.includes(i),
       };
     }
-    //console.log("render(): Table data:", table_data);
+    this.table_data = table_data; // needed in componentDidMount
     
     return (
       <EuiOverlayMask>
@@ -261,15 +421,15 @@ class ModalContent extends Component {
               <EuiFlexGroup>
                 <EuiFlexItem grow={1}>
                   <EuiFormRow label="Title" fullWidth>
-                    <EuiFieldText name="title" value={this.state.title} onChange={(e) => this.setState({title: e.target.value})} required={true} fullWidth />
+                    <EuiFieldText name="title" value={this.props.title} onChange={this.props.onTitleChange} required={true} fullWidth />
                   </EuiFormRow>
                 </EuiFlexItem>
                 <EuiFlexItem grow={false}>
                   <EuiFormRow label="Severity">
                     <EuiSuperSelect
                       options={this.severityOptions}
-                      valueOfSelected={this.state.severity}
-                      onChange={(val) => this.setState({severity: val})}
+                      valueOfSelected={this.props.severity}
+                      onChange={this.props.onSeverityChange}
                     />
                   </EuiFormRow>
                 </EuiFlexItem>
@@ -278,16 +438,16 @@ class ModalContent extends Component {
                     <EuiSuperSelect
                       prepend="TLP"
                       options={this.tlpOptions}
-                      valueOfSelected={this.state.tlp}
-                      onChange={(val) => this.setState({tlp: val})}
+                      valueOfSelected={this.props.tlp}
+                      onChange={this.props.onTLPChange}
                     />
                   </EuiFormRow>
                 </EuiFlexItem>
               </EuiFlexGroup>
               <EuiFormRow label="Description" fullWidth>
                 <EuiTextArea
-                  defaultValue={this.state.description}
-                  onChange={(e) => this.setState({description: e.target.value})}
+                  defaultValue={this.props.description}
+                  onChange={this.props.onDescriptionChange}
                   rows={4}
                   fullWidth
                 />
@@ -295,10 +455,11 @@ class ModalContent extends Component {
               
               <EuiTitle size="s"><h3>Add observables from current query ...</h3></EuiTitle>
               <EuiBasicTable
+                ref={this.obsTableRef}
                 columns={this.columns}
                 items={table_data}
                 itemId={(item) => item.id}
-                selection={ {onSelectionChange: this.onSelectionChange} }
+                selection={ {onSelectionChange: this.props.onObsSelectionChange} }
                 noItemsMessage="No observables found"
                 rowProps={{
                   // Hack to allow selection by clicking anywhere in the table row
@@ -318,110 +479,29 @@ class ModalContent extends Component {
 
           <EuiModalFooter>
             <EuiButtonEmpty onClick={this.props.close}>Cancel</EuiButtonEmpty>
-            <EuiButton onClick={this.submitCase} fill>Create Case</EuiButton>
+            <EuiButton onClick={this.props.submitCase} fill>Create Case</EuiButton>
           </EuiModalFooter>
         </EuiModal>
       </EuiOverlayMask>
     );
   }
   
-  submitCase(evt) {
-    const params = this.props.params;
-    
-    // Get case parameters
-    const title = this.state.title;
-    const descr = this.state.description;
-    const severity = parseInt(this.state.severity);
-    const start_date = null;
-    const owner = params.owner;
-    const flag = false;
-    const tlp = parseInt(this.state.tlp);
-    const tags = this.state.tags;
-    
-    if (!title) {
-      toastNotifications.addDanger("Title can't be empty");
-      return;
-    }
+  componentDidMount() {
+    // There's no way to specify initially selected items in EuiBasicTable by 
+    // props, but we may need to select some (in case a user selects some obs.,
+    // closes the modal and opens it again).
+    // However, the selection is stored as a 'selection' field of table's state,
+    // so here we directly edit the state just after the table crated.
     
-    // Get list of selected observables and their params
-    let observables = [];
-    let selectionIndices = [...this.state.obsSelected]; // make a copy
-    selectionIndices.sort();
-    for (let i = 0; i < selectionIndices.length; i++) {
-      const j = selectionIndices[i]; // index of a selected obs. in the list of all observables
-      // fill in observable definition according to model at
-      // https://github.com/TheHive-Project/TheHiveDocs/blob/master/api/artifact.md
-      const obs = {
-        dataType: 'ip',
-        data: this.props.observables[j],
-        message: this.state.obsDescrs[j],
-        tlp: this.state.obsTLPs[j],
-        ioc: this.state.obsIOCs[j],
-        tags: this.state.obsTags[j],
-      };
-      observables.push(obs);
+    // Prepare the 'selection' array - it should contain a list of selected row specifications
+    let selection = [];
+    for (let i = 0; i < this.props.obsSelected.length; i++) {
+      selection.push(this.table_data[this.props.obsSelected[i]]);
     }
-    console.log("Selected observables:", observables);
-    
-    // Submit request to create the case, handle response
-    createHiveCase(params.url, params.apikey, title, descr, severity, start_date, owner, flag, tlp, tags)
-      .then((resp) => {
-        if ('error' in resp) {
-          // Error contacting The Hive
-          console.error("TheHiveButton: ERROR when trying to create new case:", resp.error);
-          toastNotifications.addDanger("ERROR: " + resp.error);
-          return;
-        }
-
-        console.log("TheHiveButton: Case created:", resp);
-        const case_id = resp.id;
-        const case_url = params.url + "index.html#/case/" + case_id + "/details";
-        
-        // Show notification
-        let obs_text;
-        if (observables.length > 0) {
-          obs_text = "Adding " + observables.length + " observables in background ...";
-        }
-        else {
-          obs_text = "(no observables added)";
-        }
-        toastNotifications.add({
-          title: "Case created",
-          color: "success",
-          iconType: "checkInCircleFilled",
-          text: (
-            <div>
-              <p><b><a href={case_url} target="_blank">Edit the new Case</a></b></p>
-              <p>{obs_text}</p>
-            </div>
-          ),
-        });
-         
-        // Open a new window with the case in The Hive
-        // (adding observables may take some time, so the case is opened first;
-        //  The Hive web is dynamic so the observables appear as they are added)
-        window.open(case_url, '_blank');
-        
-        if (observables.length == 0)
-          return;
-        
-        // Submit request to add observables
-        console.log("TheHiveButton: adding " + observables.length + " observables ...");
-        addCaseObservables(params.url, params.apikey, case_id, observables)
-          .then((resp) => {
-            if ('error' in resp) {
-              console.error("TheHiveButton: ERROR when trying to add observables: " + resp.error);
-              toastNotifications.addDanger("ERROR when trying to add observables: " + resp.error);
-            }
-            else {
-              console.log("TheHiveButton: Done, observables added.");
-              toastNotifications.add("Done, observables added.");
-            }
-          }); // addObservables().then() func
-      }); // createHiveCase.then() func
     
-    // Close the popup window
-    this.props.close();
+    // Get ref to EuiBasicTable element and update its state 
+    const table_node = this.obsTableRef.current;
+    table_node.setState({selection: selection});
   }
 }