Monday

postMessage API >> send messages & call parent's js function: between jsp and react app (in iframe)

I tried to:
- open a react app in an iframe from jsp
- send message by postMessage API>> from jsp to react, from react to jsp
- call parent's function by postMessage API inside iframe(react app)

http://localhost:8080 >> jsp 
http://localhost:3000 >> react

jsp example:
 ....
<tiles:put name="body" type="string">
   
 <div id="reactIframeLoading" style="height:50px; color:red; font-size:12px; 
      margin-left:2px;">
      React Iframe Loading...<br> 
      <a href="javascript:loadReactIframe()">Retry</a>
 </div> 

 <iframe 
      id="reactIframe" 
      style="display:none;"
      onload="reactIframePostMessage(); reactIframeLoaded();"
      src="http://localhost:3000/"
      width="100%" height="200" border="0" marginwidth="0"
      marginheight="0" scrolling="no">
 </iframe> 
  
 <script type="text/javascript">    
     const dataForReactPostMessage = {
           isReadOnly: false,
           someData: {
               someDataChild: 'eda'
          }
      }

     function callMeFromReact(msg) {
         console.log('callMeFromReact: params: ' + msg);
     }
  
     function reactIframeLoaded() {
        document.getElementById("reactIframe").style.display='';
        document.getElementById("reactIframeLoading").style.display='none';
     }  
  
     function loadReactIframe(){
         document.getElementById("reactIframe").src="http://localhost:3000/";
     }
  
     function reactIframePostMessage(){
         document.getElementById("reactIframe").contentWindow.postMessage(
              dataForReactPostMessage, 'http://localhost:3000/'
         );
     }
  
    window.addEventListener('message', handleFrameTasks);

    //get messages from iframe or run function given by iframe  
     function handleFrameTasks(event) {
           if(event.origin !== "http://localhost:3000") {
              return;
           }

           //if function is given
           var data = event.data;
           if(typeof(window[data.func]) == "function") {
                 window[data.func].call(null, data.params[0]);

          //if data is given
          } else if(data.event_id === 'my_first_message') {
                console.log('post message from react to jsp // event_id: my_first_message= ' + JSON.stringify(event.data));
          }
     }
  </script>
</tiles:put>

react example:
import React, { Component } from 'react'

class Example extends Component {

    constructor(props) {
        super(props);
        this.handleFrameTasks = this.handleFrameTasks.bind(this);
        this.sendParentPostMessage = this.sendParentPostMessage.bind();
    }

    componentWillMount() {
        window.addEventListener('message', this.handleFrameTasks);
    }

    componentWillUnmount() {
        window.removeEventListener("message", this.handleFrameTasks);
    }

    handleFrameTasks(event) {
        if(event.origin !== "http://localhost:8080") {
           return;
        }
        console.log('postmessage from jsp to react // event.data= ' + JSON.stringify(event.data));
    }

    sendParentPostMessage() {
        const dataForJspPostMessage = {
            event_id: 'my_first_message',
            someData: {
                someDataChild1: 'eda 1', 
                someDataChild2: 'eda 2'
            }
        } 
        //send data to parent
        window.parent.postMessage(dataForJspPostMessage, 'http://localhost:8080'); 

        //call js function named as 'callMeFromReact' in parent
        window.parent.postMessage(
            {'func':'callMeFromReact','params':['SomeMessages']}, 
            'http://localhost:8080'
        ); 

        window.parent.postMessage(
            {'func':'nonExistingMethod','params':['SomeMessages']}, 
            'http://localhost:8080'
        );
    }

    render() {
        return (
            <div>
                <h1>Hello, world!</h1>
                <button onClick={this.sendParentPostMessage}>sendParentPostMessage</button>
            </div>
        );
    }

}

export default Example

UPDATE: last version for post message listener:
window.addEventListener('message',  function (event) {
      if(event.origin != null && event.origin.includes(".int.teb.com.tr")) {
            var data = event.data;
            if(data != null && typeof(window[data.func]) == "function"){
                if(data.params != null && data.params.length > 1) {
                  var args = new Array();
                  for(var i = 0; i < data.params.length; i++){
                       args.push(data.params[i]);
                  }

                  window[data.func].apply(this, args);
                  
                } else {
                  window[data.func].call(null, data.params);
                }
            }
      }
}); 

Javascript tips

Some important concepts:

1.  "hoisting" : Javascript moves all declarations to the top before executing the code. It other words, a variable can be used before it is declared.

Advice: If you want to avoid confusion, declare variables at the beginning of the function.
Note: Javascipt does this only for declarations. Initializations aren't hoisting to the top.
<script>
    var x = 3;  
    elem = document.getElementById("demo");      
    elem.innerHTML = "x is " + x + " and y is " + y; 
    var y = 4;  
    //result: x is 3 and y is undefined
</script>



<script>
    var x = 3; 
    var y;
    elem = document.getElementById("demo");      
    elem.innerHTML = "x is " + x + " and y is " + y; 
    y = 4;  
    //result: x is 3 and y is undefined
</script>



<script>
    var x = 3; 
    var y = 4; 
    elem = document.getElementById("demo");      
    elem.innerHTML = "x is " + x + " and y is " + y; 
    //result: x is 3 and y is 4
</script>


<script>
    x = 3; 
    y = 4; 
    elem = document.getElementById("demo");      
    elem.innerHTML = "x is " + x + " and y is " + y; 
    var x, y;
    //result: x is 3 and y is 4
</script>

2.  
var a = 1 >> declares the variable in the current scope which can be local or global.
       a = 1 >> if the variable couldn't be found anywhere in the scope chain, it becomes global.This usage is very bad practice!!!

3.  Arrow functions aren't same with regular functions. 'this' keyword not bound to the element when it is used in arrow functions.
Try not to use arrow functions with constructors, click handlers, class/object methods!



react-booststrap-table example


npm install react-bootstrap-table --save
npm install bootstrap --save

import React from 'react';
import ReactDOM from 'react-dom';
import BootstrapTableTest from './bootstrap/BootstrapTableTest';
import './index.css';
import * as serviceWorker from './serviceWorker';

ReactDOM.render(<BootstrapTableTest />, document.getElementById('root'));

serviceWorker.unregister();

getSprintList rest service json example:

[{"sprintNo":"2","sprintBeginDate":"2019-04-15","sprintEndDate":"2019-04-15","active":"Y"},
{"sprintNo":"4","sprintBeginDate":"2019-04-15","sprintEndDate":"2019-04-15","active":"N"}]

 I used fetchings in the same class without separating to middleware layer, it is a simple example.
import React, { Component } from 'react'
import {BootstrapTable, TableHeaderColumn} from 'react-bootstrap-table';
import '../../node_modules/bootstrap/dist/css/bootstrap.min.css';
import '../../node_modules/react-bootstrap-table/dist/react-bootstrap-table.min.css';

class BootstrapTableTest extends Component {
    constructor(props) {
        super(props)
        this.state = {
            sprints: [],
            error: null
        }
    }   

    componentDidMount() {
        fetch(`http://localhost:8080/agile/getSprintList`, {
            method: "GET"
        })
        .then(response => {
            console.log('sprint componentDidMount', response)
            if(response.ok) {
                console.log('Successfully fetch sprintList')
                return response.json();
            } else {
                throw new Error('Cant fetch sprint list. Something went wrong.')
            }
        })
        .then(data => 
            this.setState({sprints: data})
        )
        .catch(err => this.setState({error: err}))
    }

    updateRow(row, cellName, cellValue) {
        const rowJson = JSON.stringify({
            sprintNo: row.sprintNo,
            sprintBeginDate: row.sprintBeginDate,
            sprintEndDate: row.sprintEndDate,
            active: row.active
        })
        console.log('sprint updateRow', rowJson)

        fetch(`http://localhost:8080/agile/updateSprint`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: rowJson
        })
       .then(response => {
            console.log('sprint updateRow', response)
            if (response.ok) {
               console.log('Successfully updated sprint')
            } else {
               throw new Error('Somthing happened wrong while updating sprint')
           }
       })
       .catch(err => this.setState({error: err}))
    }

    addRow(row) {
        const rowJson = JSON.stringify({
            sprintNo: row.sprintNo,
            sprintBeginDate: row.sprintBeginDate,
            sprintEndDate: row.sprintEndDate,
            active: row.active
        })
        console.log('sprint addRow', rowJson)

        fetch(`http://localhost:8080/agile/addSprint`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: rowJson
        })
        .then(response => {
            console.log('sprint addRow', response)
            if (response.ok) {
               console.log('Successfully added sprint')
            } else {
               throw new Error('Somthing happened wrong while adding sprint')
            }
        })
        .catch(err => this.setState({error: err}))
    }

    deleteRow(rowKeys) {
        fetch(`http://localhost:8080/agile/deleteSprint?sprintNo=${rowKeys}`, {
            method: 'POST',
            headers: {
               'Content-Type': 'application/json'
            },
        })
        .then(response => {
            console.log('sprint deleteRow', response)
            if (response.ok) {
                console.log('Successfully removed sprint', rowKeys)
            } else {
               throw new Error('Somthing happened wrong while deleting sprint')
            }
        })
        .catch(err => this.setState({error: err}))
    }

    numericValidator(value, row) {
        const nan = isNaN(parseInt(value, 10))
        if(nan) {
            return 'Enter numeric values.'
        }
        return true;
    }

    createCustomModalHeader(onClose, onSave) {
        const headerStyle = {
            fontWeight: 'bold',
            padding: '2px 2px',
            margin: '5px',
            fontSize: 'medium',
            textAlign: 'center',
            display: 'initial',
            backgroundColor: '#eeeeee'
        };
        return (
            <div className='modal-header' style={ headerStyle }>
                <h3>Add New Sprint</h3>
            </div>
        );
    }

    render() {
        if(this.state.error) {
            return <p>{this.state.error.message}</p>
        }

        const jobTypes = [ 'Y', 'N' ];

        const selectRow = {
            mode: 'checkbox',
            clickToSelect: true
        };

        const cellEditProp = {
            mode: 'click',
            blurToSave: true,
            afterSaveCell: this.updateRow
        };

        const options = {
            afterDeleteRow: this.deleteRow,
            afterInsertRow: this.addRow,
            deleteText: 'Delete',
            insertText: 'Insert',
            paginationSize: 1,
            prePage: 'Previous',
            nextPage: 'Next',
            firstPage: 'First Page',
            lastPage: 'Last Page',
            defaultSortName: 'sprintNo',
            defaultSortOrder: 'desc',
            insertModalHeader: this.createCustomModalHeader
        };

        return(
            <BootstrapTable data={this.state.sprints} 
                striped hover pagination search multiColumnSearch ignoreSinglePage 
                selectRow={ selectRow }
                cellEdit={ cellEditProp }
                options={ options }
                height='750' scrollTop={ 'Bottom' }
                deleteRow insertRow 
                exportCSV csvFileName='sprint.csv'
            >
                <TableHeaderColumn 
                    isKey dataField='sprintNo' dataAlign='right' dataSort={ true }
                    editable={ {validator: this.numericValidator} } >
                    Sprint Number
                </TableHeaderColumn>
                <TableHeaderColumn  
                    dataField='sprintBeginDate' 
                    editable={ { type: 'date' } }>
                    Sprint Begin Date
                </TableHeaderColumn>
                <TableHeaderColumn  
                    dataField='sprintEndDate' 
                    editable={ { type: 'date' } }>
                    Sprint End Date
                </TableHeaderColumn>
                <TableHeaderColumn  
                    dataField='active' 
                    editable={ { type: 'select', options: { values: jobTypes } } }>
                    Is Active
                </TableHeaderColumn>
            </BootstrapTable >
        )
    }

}
export default BootstrapTableTest