본문 바로가기

웹 개념

[web] spa 만들기 저장용

728x90

index.html

 

<!DOCTYPE html>
<html lang="ko">
<link rel="stylesheet" href="/web/style.css">

<head>
    <meta charset="UTF-8">
    <title>웹 SPA 개발</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&display=swap" rel="stylesheet">
    <script type="module" src="/web/src/index.js"></script>
</head>

<body>
    <div class="app"></div>
    <script type="module" src="/web/src/App.js"></script>
</body>

</html>

 

index.js

 

import App from './App.js';

new App({$target: document.querySelector('.App')});

 

App.js

 

import Header from './components/Header.js';
import HomePage from './page/HomePage.js';
import SignupPage from './page/SignupPage.js';
import {setPersonalInfo} from './components/Storage.js'

class App {
    constructor() {
        this.$body = document.body;
        this.render();
      }

    async render() {
        const header = new Header(this.$body);
        header.render();	

        const main = document.createElement("main");
        main.setAttribute("id", "page_content");
        this.$body.appendChild(main);

        const homePage = new HomePage(main);
        const signupPage = new SignupPage(main);

        homePage.render();
        console.log('prev setPersonalInfo')
        await setPersonalInfo();
        console.log('after setPersonalInfo')
        document.addEventListener("urlchange", (e) => {
            let pathname = e.detail.href;

            switch(pathname) {
                case "/web/":
                    homePage.render();
                    break;
                case "/web/signup":
                    signupPage.render();
                    break;
                default:
            }
        });

       
    }
}
export default App;

 

header.js

class Header {
    constructor($body) {
        this.$body = $body;
    }

    createMenuElem(divClass, spanClass, spanId, menuText) {
        const div = document.createElement("div");
        div.setAttribute("class", divClass);

        const span = document.createElement("span");
        span.setAttribute("class", spanClass);
        span.setAttribute("id", spanId);
        span.appendChild(document.createTextNode(menuText));

        div.appendChild(span);
        return div;
    }

    render() {
        const header = document.createElement("header");
        const home_menu = 
            this.createMenuElem("header header_left", "menu_name", "menu_home", "HOME");
        const signup_menu = 
            this.createMenuElem("header header_right", "menu_name", "menu_signup", "SIGNUP");

        header.appendChild(home_menu);
        header.appendChild(signup_menu);
        this.$body.appendChild(header);

        // HOME 메뉴 클릭 이벤트
        home_menu.addEventListener("click", () => {
            window.history.pushState("", "", "/web/");
            const urlChange = new CustomEvent("urlchange", {
                detail: { href: "/web/" }
            });
            document.dispatchEvent(urlChange);
        });

        // SIGNUP 메뉴 클릭 이벤트
        signup_menu.addEventListener("click", () => {
            window.history.pushState("", "", "/web/signup");
            const urlChange = new CustomEvent("urlchange", {
                detail: { href: "/web/signup" }
            });
            document.dispatchEvent(urlChange);
        });
    }
}
export default Header;

 

contentTitle.js

class ContentTitle {
    constructor($main, $title) {
        this.$main = $main;
        this.$title = $title;
    }

    render() {
        const div = document.createElement("div");
        div.setAttribute("class", "content_title");

        const h1 = document.createElement("h1");
        h1.appendChild(document.createTextNode(this.$title));

        div.appendChild(h1);
        this.$main.appendChild(div);
    }
}
export default ContentTitle;

 

style.css

 

body {
    margin: 0;
    padding: 0;
}

/* GNB */
header {
    display: flex;
    height: 60px;
    align-items: center;
    font-size: 1.5em;
    font-family: 'Bebas Neue', cursive;
    background: #29323C;
    background: linear-gradient(198deg, rgba(2,0,36,1) 0%, rgba(99,116,136,1) 0%, rgba(41,50,60,1) 100%);
    color: white;
}

.header {
    padding: 0px 20px;
}

.header_left {
}

.header_right {
}

#page_content {
    padding: 20px;
}

.content_title {
    text-align: center;
}

/* 인사 정보 페이지 */
#cards_container {
    width: 80%;
    margin: 10px auto;
    padding: 10px 10px;
}

.card {
    margin: 20px 10px;
    width: 100px;       
    height: 100px;
    border-radius : 25px;
    transform-style: preserve-3d;
    transform-origin: center right;
    transition: transform 1s;
    box-shadow: 5px 5px 10px rgba(0, 0, 0, .2);
}

.card.is-flipped {
    transform: translateX(-100%) rotateY(-180deg);
}

.card_plane {
    width: 100%;
    height: 100%;
    border-radius : 25px;
    text-align: center;
    font-weight: 500;   
    font-size: 20px;    
    color: black;     
    backface-visibility: hidden;
}

.card_plane--front {
    background: #F2F2F2;    
}

.card_plane--back {
    transform: rotateY(180deg);
}

/* 인사 정보 등록 페이지 */
#form_container {
    align-items: center;
    text-align: center;
    margin: 30px 0px;
}

.form_elem {
    display: block;
    width: 250px;
    margin: 0.75em auto;
}

.form_elem input {
    width: 100%;
    padding: 1em;
    border: solid 1.5px #9E9E9E;
    border-radius: 1rem;
    box-sizing: border-box;
    background: none;
}

.form_elem select {
    width: 100%;
    padding: 1em;
    border: solid 1.5px #9E9E9E;
    border-radius: 1rem;
    box-sizing: border-box;
    background: none;
}

.form_elem button {
    width: 100%;
    padding: 1em;
    border: 0;
    border-radius: 1rem;
    box-sizing: border-box;
    background: #dedede;
}

.header {
    position: absolute;
    padding: 0px 20px;
}

.header_left {
    left: 15px;  
}

.header_right {
    right: 15px;
}

.menu_name:hover {
    cursor: pointer;
}

#cards_container {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: center;
    align-content: space-around;
}

.card {
    width: 200px;
    height: 260px;
    cursor: pointer;
}

.card_plane {
    position: absolute;
    display: flex;
    justify-content: center;
    align-items: center; 
    font-weight: 700;  
    font-size: 40px;  
    color: white;    
}

.card_plane--front {
    background: #0D0D0D;
}

.card_plane--back {
    background: #F2F2F2;
    color: #3098F2;
}

 

storage.js

 

export const setPersonalInfo = async () => {
    // /web/src/data/new_data.json
    console.log('hi!!')
    const response = await fetch('/web/src/data/new_data.json');
    const data = await response.json();
    console.log('data >>> ', data)

    if (!localStorage.getItem("personalInfo")) {
        localStorage.setItem("personalInfo", JSON.stringify(data));
    }

}

export const setCardStatus = () => {
    if(!localStorage.getItem("cardStatus")) {
        localStorage.setItem("cardStatus", JSON.stringify([]));
    }
}

 

Homepage.js

 

import ContentTitle from '../components/ContentTitle.js';
import CardView from '../components/CardView.js';
import {setPersonalInfo, setCardStatus} from '../components/Storage.js'

class HomePage {
    constructor($main) {
        this.$main = $main;
    }

    async render() {
        const title = new ContentTitle(this.$main, "Great PeoPle");
        title.render();

        await setPersonalInfo();

        const cardView = new CardView(this.$main);
        cardView.render();

    }
}
export default HomePage;

 

CardView.js

 

import {cardDiv, cardPlane} from './Card.js'
import {setCardStatus} from './Storage.js'

class CardView {
    constructor($main) {
        this.$main = $main;
    }

    render() {
        const containerDiv = document.createElement("div");
        containerDiv.setAttribute("id", "cards_container");
        this.$main.appendChild(containerDiv);

        const personalInfo = JSON.parse(localStorage.getItem("personalInfo"))
        for(let i = 0; personalInfo.length; i++) {
            const card = cardDiv(i);    // 카드의 레이아웃 요소
            card.appendChild(cardPlane("front", personalInfo[i].nickname))
            card.appendChild(cardPlane("back", personalInfo[i].nickname))     // 카드 뒷면의 요소
            containerDiv.appendChild(card);
        }
    }
}
export default CardView;

 

Card.js

 

export const cardDiv = (index) => {
    const card_div = document.createElement("div");
    card_div.setAttribute("idx", index);
    card_div.setAttribute("class", "card");

    card_div.addEventListener("click", (e) => {
        card_div.classList.toggle("is-flipped");
    })

    return card_div;
}

export const cardPlane = (side, data) => {
    const cardPlane_div = document.createElement("div");
    cardPlane_div.setAttribute("class", "card_plane card_plane--" + side);
    cardPlane_div.appendChild(document.createTextNode(data));

    return cardPlane_div;
}
728x90