1

i want to create a component that get json data from php server, the data then i put in ResultGrid class property, named jsonProps i defined the property and typescript didn't complain, however, when i ran the code in the browser, the property that i set isn't recognize by the browser, here's the code

i also tried using setter in typescript, it didn't work(the typescript code that i post didn't use it, but the previous version )

The HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>File</title>
</head>
<body>
</body>
<script src="ract.js"></script>
</html>

The Typescript code (ract.ts)

let SHOW: boolean = false;

class InputText extends HTMLElement {
    constructor() {
        super();
        let template = document.createElement("template");
        template.innerHTML = this.render();
        this.attachShadow({mode: "open"});
        this.shadowRoot.appendChild(template.content.cloneNode(true));
    }

    render(): string {
        return(`
            <div>
                1. Nama: 
                <input type="text" name="nama">
            </div>
            <div>
                2. NRP:
                <input type="text" name="nrp">
            </div>
            <div>
                3. Kelas:
                <input type="text" name="kelas">
            </div>
            <div>
                4. Jenis kelamin:
                <input type="radio" name="kelamin" value="pria">
                Pria
                <input type="radio" name="kelamin" value="wanita">
                Wanita
            </div>
            <div>
                5. Agama:
                <select name="agama">
                    <option value="islam">Islam</option>
                    <option value="kristen">Kristen</option>
                    <option value="katolik">Katolik</option>
                    <option value="hindu">Hindu</option>
                    <option value="budha">Budha</option>
                    <option value="konghucu">Konghucu</option>
                </select>
            </div>
            <div>
                6. Tempat / Tanggal lahir:
                <input type="text" name="tempat">
                <input type="text" name="tgl">
            </div>
            <div>
                7. Alamat
            </div>
                <textarea name="alamat" cols="30" rows="10"></textarea>
            <div>
                8. Riwayat pendidikan:
                <ul type="a">
                    <li>
                        SD:
                        <input type="text" name="sd">
                    </li>
                    <li>
                        SMP:
                        <input type="text" name="smp">
                    </li>
                    <li>
                        SMA:
                        <input type="text" name="sma">
                    </li>
                </ul>
            </div>
            <div>
                9. Email
                <input type="email" name="email">
            </div>
            <div>
                10. Homepage
                <input type="url" name="homepage">
            </div>
            <div>
                11. Hobby
                </div>
                <textarea name="hobby" cols="30" rows="10"></textarea>
            <div>
                12. interest:
                <input type="checkbox" name="interest1" value="komputer">
                komputer
                <input type="checkbox" name="interest2" value="sport">
                sport
                <input type="checkbox" name="interest3" value="travelling">
                travelling
                <input type="checkbox" name="interest4" value="writing">
                writing
                <input type="checkbox" name="interest5" value="reading">
                reading
                </div>
            <div>
                <button onclick="router()">Simpan</button>
                <button type="reset">Reset</button>
            </div>
        `);
    }
}

class ResultGrid extends HTMLElement {
    jsonProps: Record<string, string | string[]>;

    constructor() {
        super();
        this.jsonProps; // this property is undefined in the browser
    }

    connectedCallback() {
        this.getData();
        let template = document.createElement("template");
        template.innerHTML = this.render();
        this.attachShadow({mode: "open"});
        this.shadowRoot.appendChild(template.content.cloneNode(true));
    }

    async getData() {
        let url = // read.php url;
        try {
            let res = await fetch(url, {
                headers: {
                    'Accept': 'application/json;text/plain'
                }
            });

            if(res.status !== 200) {
                throw new Error(await res.text());
            } else {
                this.jsonProps = await res.json(); // setting property here
            }
        } catch(e) {
            console.error(e.message);
        }
    }

    render() {
        return (`
            <style>
                #grid {
                    display: grid;
                    width: 40vw;
                    grid-template-columns: 1fr 1fr;
                }
            </style>

            <div id="grid">
                <div>Nama</div>
                <div>${this.jsonProps.nama}</div>
                <div>Nrp</div>
                <div>${this.jsonProps.nrp}</div>
                <div>Kelas</div>
                <div>${this.jsonProps.kelas}</div>
                <div>Jenis Kelamin</div>
                <div>${this.jsonProps.kelamin}</div>
                <div>Agama</div>
                <div>${this.jsonProps.agama}</div>
                <div>Tempat</div>
                <div>${this.jsonProps.tempat}</div>
                <div>Tanggal lahir</div>
                <div>${this.jsonProps.tgl}</div>
                <div>Alamat</div>
                <div>${this.jsonProps.alamat}</div>
                <div>SD</div>
                <div>${this.jsonProps.sd}</div>
                <div>SMP</div>
                <div>${this.jsonProps.smp}</div>
                <div>SMA</div>
                <div>${this.jsonProps.sma}</div>
                <div>Email</div>
                <div>${this.jsonProps.email}</div>
                <div>Homepage</div>
                <div>${this.jsonProps.homepage}</div>
                <div>Hobby</div>
                <div>${this.jsonProps.hobby}</div>
                <div>Interest</div>
                <div>${(this.jsonProps.interests as string[]).map((itr: string) => itr)}</div>
                <button onclick="router()">return</button>
            </div>
        `);
    }
}

(function init() {
    customElements.define("input-text", InputText);
    customElements.define("result-grid", ResultGrid);
    let input = document.createElement("input-text");
    document.body.appendChild(input);
})()

function router() {
   if(SHOW === false) {
       (async () => {
            let inputShadow = document.querySelector("input-text").shadowRoot;    
            const url: string = // savetofile.php url;

            let intagr: string[] = [];
        
            (function() {
                (inputShadow.querySelector('input[name="interest1"]') as HTMLInputElement).checked &&
                    intagr.push((inputShadow.querySelector('input[name="interest1"]') as HTMLInputElement).value);
                (inputShadow.querySelector('input[name="interest2"]') as HTMLInputElement).checked &&
                    intagr.push((inputShadow.querySelector('input[name="interest2"]') as HTMLInputElement).value);
                (inputShadow.querySelector('input[name="interest3"]') as HTMLInputElement).checked &&
                    intagr.push((inputShadow.querySelector('input[name="interest3"]') as HTMLInputElement).value);
                (inputShadow.querySelector('input[name="interest4"]') as HTMLInputElement).checked &&
                    intagr.push((inputShadow.querySelector('input[name="interest4"]') as HTMLInputElement).value);
                (inputShadow.querySelector('input[name="interest5"]') as HTMLInputElement).checked &&
                    intagr.push((inputShadow.querySelector('input[name="interest5"]') as HTMLInputElement).value);
            })();       
        
            let send: Record<string, string | string[]> = {
                nama: (inputShadow.querySelector('input[name="nama"]') as HTMLInputElement).value,
                nrp: (inputShadow.querySelector('input[name="nrp"]') as HTMLInputElement).value,
                kelas:  (inputShadow.querySelector('input[name="kelas"]') as HTMLInputElement).value,
                kelamin: (inputShadow.querySelector('input[name="kelamin"]') as HTMLInputElement).value,
                agama: (inputShadow.querySelector("select") as HTMLSelectElement).value,
                tempat: (inputShadow.querySelector('input[name="tempat"]') as HTMLInputElement).value,
                tgl: (inputShadow.querySelector('input[name="tgl"]') as HTMLInputElement).value,
                alamat: (inputShadow.querySelector('textarea[name="alamat"]') as HTMLTextAreaElement).value,
                sd: (inputShadow.querySelector('input[name="sd"]') as HTMLInputElement).value,
                smp: (inputShadow.querySelector('input[name="smp"]') as HTMLInputElement).value,
                sma: (inputShadow.querySelector('input[name="sma"]') as HTMLInputElement).value,
                email: (inputShadow.querySelector('input[name="email"]') as HTMLInputElement).value,
                homepage: (inputShadow.querySelector('input[name="homepage"]') as HTMLInputElement).value,
                hobby: (inputShadow.querySelector('textarea[name="hobby"]') as HTMLTextAreaElement).value,
                interests: intagr
            }
        
            try {
                let res = await fetch(url, {
                    method: 'POST',
                    body: JSON.stringify(send),
                    headers: {
                        'Content-type': "application/json",
                        'Accept': "text/plain"
                    }
                });
                if(res.status !== 200) {
                    let text = await res.text();
                    throw new Error(text);
                }
            } catch (error) {
                alert(error.message);
            }
       })();
        document.querySelector("input-text").remove();
        let resultPage = document.createElement("result-grid");
        document.body.appendChild(resultPage);
        SHOW = true;
    } else {
        document.querySelector("result-grid").remove();
        let inputPage = document.createElement("input-text");
        document.body.appendChild(inputPage);
        SHOW = false;
    }
}

savetofile.php code

<?php
    $request = json_decode(file_get_contents("php://input"), true);
    header('Accept: application/json');
    header('Content-type: text/plain');
    $fp = fopen("data.json", "w");
    if(!$fp) {
        http_response_code(500);
        echo "failed to create file";
    } else {
        fwrite($fp, json_encode($request));
        http_response_code(200);
        fclose($fp);
    }
?>

read.php code

<?php
        try {
        $filename = './data.json';
        $json_file = fopen($filename, 'r');
        if(!$json_file) {
            throw new Exception("error opening file", 1);
        } else {
            header("Content-type: application/json");
            $read_file = fread($json_file, filesize($filename));
            echo json_encode(json_decode($read_file));
        }
        fclose();
    } catch (Exception $th) {
        header("Content-type: text/plain");
        http_response_code(500);
        echo $th->getMessage();
    }
?>

here's also my tsconfig

tsconfig.json

{
    "compilerOptions": {
        "target": "esnext",
        "module": "commonjs",
        "lib": ["ESNext", "DOM"],
        "noImplicitAny": true,
        "preserveConstEnums": true,
        "outDir": "./",
        "sourceMap": true
    },
    "include": ["ract.ts"],
    "exclude": ["node_modules", "**/*.spec.ts"]
}

the error message is:

Uncaught TypeError: this.jsonProps is undefined

2 Answers2

1

You are getting data asynchronous

  • So this.jsonProps is undefined until the getData() function received it:

  • Move the render() call

  • (and no need for a costly <template> to create HTML)

class ResultGrid extends HTMLElement {
    constructor() {
        super();
    }

    connectedCallback() {
        this.getData();
    }

    async getData() {
        let url = // read.php url;
        try {
            let res = await fetch(url, {headers:'Accept': 'application/json;text/plain'}});
            if(res.status !== 200) {
                throw new Error(await res.text());
            } else {
                this.jsonProps = await res.json(); // setting property here

                // !! NOW .render() can access this.jsonProps
                this.attachShadow({mode:"open"}).innerHTML = this.render();

            }
        } catch(e) {
            console.error(e.message);
        }
    }
Danny '365CSI' Engelman
  • 16,526
  • 2
  • 32
  • 49
  • @danny-365csi-engelman i moved the render inside asynchronous part, suffice to say it work now. –  May 22 '21 at 10:01
0

as suggested by @danny-365csi-engelman and after reading

How do I return the response from an asynchronous call?

Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference

i moved the entire async function inside connectedCallback()

class ResultGrid extends HTMLElement {
    constructor() {
        super();
    }

    connectedCallback() {
        (async () => {
            let url = // read.php url
            try {
                let res = await fetch(url, {
                    headers: {
                        'Accept': 'application/json;text/plain'
                    }
                });
    
                if(res.status !== 200) {
                    throw new Error(await res.text());
                } else {
                    let template = document.createElement("template");
                    template.innerHTML = this.render(await res.json());
                    this.attachShadow({mode: "open"});
                    this.shadowRoot.appendChild(template.content.cloneNode(true));
                }
            } catch(e) {
                console.error(e.message);
            }
        })();
    }

and update the render method

render(jsonProps: Record<string, string | string[]>) {
        return (`
            <style>
                #grid {
                    display: grid;
                    width: 40vw;
                    grid-template-columns: 1fr 1fr;
                }
            </style>

            <div id="grid">
                <div>Nama</div>
                <div>${jsonProps.nama}</div>
                <div>Nrp</div>
                <div>${jsonProps.nrp}</div>
                <div>Kelas</div>
                <div>${jsonProps.kelas}</div>
                <div>Jenis Kelamin</div>
                <div>${jsonProps.kelamin}</div>
                <div>Agama</div>
                <div>${jsonProps.agama}</div>
                <div>Tempat</div>
                <div>${jsonProps.tempat}</div>
                <div>Tanggal lahir</div>
                <div>${jsonProps.tgl}</div>
                <div>Alamat</div>
                <div>${jsonProps.alamat}</div>
                <div>SD</div>
                <div>${jsonProps.sd}</div>
                <div>SMP</div>
                <div>${jsonProps.smp}</div>
                <div>SMA</div>
                <div>${jsonProps.sma}</div>
                <div>Email</div>
                <div>${jsonProps.email}</div>
                <div>Homepage</div>
                <div>${jsonProps.homepage}</div>
                <div>Hobby</div>
                <div>${jsonProps.hobby}</div>
                <div>Interest</div>
                <div>${(jsonProps.interests as string[]).map((itr: string) => itr)}</div>
                <button onclick="router()">return</button>
            </div>
        `);
    }
  • You are still using a bloated ``template`` only for inserting HTML. This will do: ``this.attachShadow({mode:"open"}).innerHTML = this.render(await rs.json()));`` – Danny '365CSI' Engelman May 22 '21 at 11:18