FE - 메뉴 추가 화면

손대중·2022년 4월 8일
0

이제 메뉴 추가 화면을 구현해보자. 대략 아래와 같은 틀을 가진다.
(참고로 아직 서버에 API 개발은 안했으므로, 테스트 데이터를 만들어서 개발 진행했음)

각각의 영역은 svelte-material-ui 의 Card 컴포넌트를 사용했다.

https://sveltematerialui.com/demo/card

뭐 사실 입력할게 별로 없기 때문에 하나로 묶어도 되긴 하는데, 그냥 나누고 싶었다.

메뉴 데이터 자체가 트리 구조이다 보니, 부모 메뉴 선택이 좀 까다로워 보여서 별도로 분리한 측면이 크다.

부모 메뉴 선택 영역

전체 메뉴 데이터 중에서 부모 메뉴를 선택할 수도 있고, 안 할 수도 있다. (선택 안하는 경우는 자연스럽게 최상위 뎁스에 위치)

메뉴 데이터는 트리 구조이며, 테스트 데이터를 보면 어떤 구조인지 명확하게 알 수 있다.

// temp data
	const menus = [
		{name: 'menu 1', _id: 'menu 1'},
		{name: 'menu 2', _id: 'menu 2', children: [
			{name: 'menu 2 1', _id: 'menu 2 1', parent: 'menu 2'},
			{name: 'menu 2 2', _id: 'menu 2 2', parent: 'menu 2'},
			{name: 'menu 2 3', _id: 'menu 2 3', parent: 'menu 2', children: [
				{name: 'menu 2 3 1', _id: 'menu 2 3 1', parent: 'menu 2 3'},
				{name: 'menu 2 3 2', _id: 'menu 2 3 2', parent: 'menu 2 3'},
				{name: 'menu 2 3 3', _id: 'menu 2 3 3', parent: 'menu 2 3', children: [
					{name: 'menu 2 3 3 1', _id: 'menu 2 3 3 1', parent: 'menu 2 3 3'},
					{name: 'menu 2 3 3 2', _id: 'menu 2 3 3 2', parent: 'menu 2 3 3'},
					{name: 'menu 2 3 3 3', _id: 'menu 2 3 3 3', parent: 'menu 2 3 3'},
				]},
			]},
		]},
		{name: 'menu 3', _id: 'menu 3'},
	];

부모 메뉴는 어느 depth 의 메뉴든지 선택이 가능하며, 메뉴마다 children 들이 있을 수도 있고 없을 수도 있기 때문에,

select box 에서 메뉴를 선택했을때 하위 depth 의 select box 를 모두 리셋하고, 추가로 선택 메뉴의 children 이 있을 때 그 다음 depth 의 select box 를 생성 & 노출한다.

뭐 말이 어려운데, 아래 캡쳐를 보면 금방 알 수 있다.

사용자가 선택한 메뉴에 따라 select box 를 생성 & 노출해야 하기 때문에, svelte-material-ui 의 Select 컴포넌트를 wrapping 한 컴포넌트를 별도로 만들어서 사용했다.

https://sveltematerialui.com/demo/select

  • MenuSelect.svelte

    <Select key={(menu) => `${(menu && menu._id) || ''}`} bind:value={value} label={`${depth + 1}뎁스`} >
        <Option value='선택 안함' />
        {#each menus as menu}
        <Option value={menu}>{menu.name}</Option>
        {/each}
    </Select>
    
    <script>
        import Select, { Option } from '@smui/select';
    
        export let menus;
    
        export let depth;
    
        export let change;
    
        let value;
    
        $: change(value, depth);
    </script>

코드는 직관적이라 참고할만한 문법은 아래 정도인 것 같다.

  • key={(menu)..... 부분
    • Select 컴포넌트 가이드 문서를 확인
  • export 의 의미
    • svelte 에서 export 는 부모로부터 값을 전달 받을때 사용
    • 정확히는 props 내 변수를 뽑아옴 (부모가 전달한 값이 props 에 위치함)
  • $: ... 의 의미
    • Vue.jscomputedwatch 와 비슷한 녀석
    • ... 내 참조하고 있는 변수의 값이 변경되면 해당 구문이 실행
    • 위 코드에서는 value 의 값이 변경될때마다 - select box 내에서 메뉴를 선택할때마다 실행

이제 MenuSelect 컴포넌트를 쓰도록 AdminMenu.svelte 를 수정하자.

AdminMenu.svelte 에서는 아래 임무를 수행하는 코드를 추가한다.

  • store 로부터 메뉴 데이터 가져오기
    • 지금은 일단 그냥 테스트 데이터만 사용, 추후에 개발해야함
  • select box 를 렌더링하기 용이한 형태로 데이터 컨버팅
  • select box 렌더링 로직

일단 코드 부터 보자. (중요한 부분만 언급하며, 전체 코드는 GitHub 에서 확인)

  • AdminMenu.svelte

    <div class="card-container">
        <h3>부모 메뉴 선택</h3>
        <Card variant="outlined">
            <Content>
                <div class="card-content-container">
                    {#each parents as parent, i}
                    <div>
                        <MenuSelect menus={parent.list} depth={i} change={changeParent}></MenuSelect>
                    </div>
                    {/each}
                </div>
                <pre class="status"><h2>선택된 부모 메뉴 : { selectedParents() }</h2></pre>
            </Content>
            <Actions>
                <Button on:click={ () => init("parent") } variant="raised"><Label>초기화</Label></Button>
            </Actions>
        </Card>
    </div>
    
    <script>
        import { push } from 'svelte-spa-router';
        import Button, { Label } from '@smui/button';
        import Card, { Content, Actions } from '@smui/card';
        import Textfield from '@smui/textfield';
        import HelperText from '@smui/textfield/helper-text';
    
        import MenuSelect from './MenuSelect.svelte';
    
        // temp data
        const menus = [......];
    
        let parents = [{list: menus, select: null}];
    
        let name = '';
    
        const changeParent = (value, depth) => {
            if (!value) {
                return;
            }
    
            parents = parents.slice(0, depth + 1);
    
            parents[depth].select = value;
    
            if (value.children && value.children.length > 0) {
                parents.push({list: [...value.children], select: null});
            }
    
        };
    </script>

위에서 언급한대로 svelte-material-ui 의 Card, Content, Actions 등의 컴포넌트를 사용했으며, 자세한 내용은 가이드 문서 참고하면 된다.

중요한 점은 parents 에 따라 MenuSelect 가 생성 & 노출된다는 것과 MenuSelectchangeParent 함수를 전달하고 통해 메뉴가 선택될때 MenuSelect 에서 changeParent 를 호출함으로서 부모에서 관리중인 parents 를 조작한다는 점이다.

그 외에 문법적으로 특이한 부분은 없어 보인다.

기타 정보 입력 화면

사실 현재 입력할만한 데이터가 name 밖에 없기 때문에, 크게 복잡할 건 없다.

부모 선택 영역과 마찬가지로 svelte-material-ui 의 Card 를 쓰며, 그 외 input 을 위해 Textfield, HelperText 등의 컴포넌트를 사용했다. (자세한 내용은 아래 링크 참조)

https://sveltematerialui.com/demo/textfield

코드가 길지 않아 AdminMenu.svelte 에 그냥 추가했다. (전체 코드는 GitHub 에서 확인)

  • AdminMenu.svelte

    <div class="card-container">
        <h3>메뉴 정보 입력</h3>
        <Card variant="outlined">
            <Content>
                <div class="card-content-container">
                    <div>
                        <Textfield bind:value={name} label="메뉴명" required>
                            <HelperText slot="helper">메뉴명을 입력해주세용.</HelperText>
                        </Textfield>
                    </div>
                </div>
            </Content>
            <Actions>
                <Button on:click={ () => init("info") } variant="raised"><Label>초기화</Label></Button>
            </Actions>
        </Card>
    </div>
    
    <script>
        import { push } from 'svelte-spa-router';
        import Button, { Label } from '@smui/button';
        import Card, { Content, Actions } from '@smui/card';
        import Textfield from '@smui/textfield';
        import HelperText from '@smui/textfield/helper-text';
    
        let name = '';
    </script>

대충 화면 틀은 완성했다. (짜잘한 부분은 일단 넘어가자.)

이제는 실제 API 부분을 개발 진행해보자.

업로드중..

0개의 댓글