[Blazor] ToDoList 만들기

개요

  • Blazor 학습 목적으로 컴포넌트 단위로 나눠서 ToDoList 만들기 진행하였습니다.

참고

  • 저 같은 경우에는 ToDoList 를 어떻게 만들까 고민하다가, 지인을 통해 https://codesandbox.io/s/mashup-todolist-fwv17?fontsize=14 해당 사이트에서 보여주는 ToDoList 를 똑같이 만들어 보는게 좋다고 하여, Blazor 를 이용하여 최대한 동일하게 만들어 보려고 노력 하였습니다.


Blazor 코드

  • 저는 우선 개발을 컴포넌트 단위로 나눠서 개발 진행하였습니다.
  • 하나의 페이지에, C# 코드를 한번에 넣을 수도 있지만 그렇게 되면 코드의 복잡성이 매우 올라가고, 가독성에도 좋지 않아서 최대한 Component 로 만들 수 있는 것들은 Component 로 빼서 만들었습니다.
  • ToDoList 에서 만든 컴포넌트 목록은 다음과 같습니다.
    • CircleButtonComponent.razor
    • DateComponent.razor
    • DoingWorkCount.razor
    • InputComponent.razor
    • TodoListComponent.razor
  • 위와 같이 총 5가지의 컴포넌트들을 만들었습니다.
  • 위 컴포넌트들의 코드는 다음과 같습니다.

CircleButtonComponent.razor

<div class="btn-container">
    <button class="@(IsClicked == true ? "circle-add-button" : "circle-not-add-button")"
            type="button"
            @onclick="(() => CircleChange.InvokeAsync())">
        @ButtonContent
    </button>
</div>

@code {
    [Parameter]
    public bool IsClicked { get; set; }
    [Parameter]
    public string? ButtonContent { get; set; }
    [Parameter]
    public EventCallback CircleChange { get; set; }
}
.btn-container {
    display: flex;
    justify-content: center;
}

.circle-add-button {
    display: block;
    height: 80px;
    width: 80px;
    border-radius: 50%;
    border: none;
    background: #38d9a9;
    font-size: 100px;
    line-height: 80px;
    color: white;
    text-align: center;
    transition: all ease 0.5s;
    -webkit-transform: rotate(0deg);
}

.circle-add-button:hover {
    background: #63e6be;
}

.circle-not-add-button {
    height: 80px;
    width: 80px;
    border-radius: 50%;
    border: none;
    background: #ff8787;
    font-size: 100px;
    line-height: 80px;
    color: white;
    transition: all ease 0.5s;
    -webkit-transform: rotate(45deg);
}

DateComponent.razor

<h1>@CurrentDate</h1>
<h5>@CurrentDayOfWeek</h5>

@code {
    [Parameter]
    public string? CurrentDate { get; set; }
    [Parameter]
    public string? CurrentDayOfWeek { get; set; }
}

DoingWorkCount.razor

@using ToDoList.Data

<div class="doing-work">
    할 일 
        @todos!.Where(x => x.IsDone == false).Count()
    개 남음
</div>


@code {
    [Parameter]
    public List<TodoItem>? todos { get; set; }
}
.doing-work {
    margin-top: 40px;
    color: #20c997;
    font-size: 20px;
    font-weight: bold;
}

InputComponent.razor

<form class="container" @onsubmit=Enter hidden="@IsClicked">
    <div>
        <input class="item1" placeholder="할 일을 입력 후, Enter 를 누르세요" 
                @bind-value="newTodo" />
        <button type="submit" hidden class="item2" @onclick="(() => OnClick.InvokeAsync(newTodo))">추가</button>
    </div>
</form>


@code {
    [Parameter]
    public string? newTodo { get; set; }
    [Parameter]
    public bool IsClicked { get; set; }
    [Parameter]
    public EventCallback<string> OnClick { get; set; }
    [Parameter]
    public EventCallback Enter{ get; set; }
}
.container {
    display: flex;
    justify-content: center;
    align-content: center;
    align-items: center;
    text-align: center;

    padding: 10px;
    width: 100%;
}

.item1 {
    flex-direction: row;
    width: 850px;
    height: 50px;
    border-radius: 10px;
}

.item2 {
    flex-direction: row;
    width: 150px;
    height: 50px;
    border-radius: 10px;
}

TodoListComponent.razor

@using ToDoList.Data

<div>
    <ul>
        @foreach (var todo in todos!)
        {
            <li>
                <div class="continer" @onmouseover="(() => MouseOver.InvokeAsync(todo.Id))" 
                                      @onmouseout="(() => MouseOut.InvokeAsync(todo.Id))">
                    <input class="check-button rounded-checkbox" 
                    type="checkbox" 
                    checked="@todo.IsDone" 
                    onchange="@(()=>{ CheckedClick.InvokeAsync((todo.Id, true));})" />
                    <div class="@(todo.IsDone == true ? "content-check-item" : "content-not-check-item")">@todo.Title</div>
                    <input
                        type="button" 
                        hidden="@todo.IsDisabled"
                        class="img-button" 
                        @onclick="(() => DeleteOnClick.InvokeAsync(todo.Id))"></input>
                </div>
            </li>
        }
    </ul>
</div>

@code {
    [Parameter]
    public List<TodoItem>? todos { get; set; }
    [Parameter]
    public EventCallback<int> DeleteOnClick { get; set; }
    [Parameter]
    public EventCallback<(int, bool)> CheckedClick { set;get; }
    [Parameter]
    public EventCallback<int> MouseOver { get; set; }
    [Parameter]
    public EventCallback<int> MouseOut { get; set; }
}
li {
    list-style: none;
}

.continer {
    display: flex;
}

.rounded-checkbox {
    width: 35px;
    height: 35px;
    font-size: 30px;
    margin-top: 13px;
    margin-right: 25px;
    border-radius: 50%;
    vertical-align: middle;
    border: 1px solid gray;
    appearance: none;
    -webkit-appearance: none;
    outline: none;
    cursor: pointer;
}

.rounded-checkbox:checked {
    appearance: auto;
    clip-path: circle(50% at 50% 50%);
    accent-color: #9b59b6;
}

.content-check-item {
    flex-direction: column;
    font-size: 30px;
    margin-top: 6px;
    color: #ced4da;
}

.content-not-check-item {
    flex-direction: column;
    font-size: 30px;
    margin-top: 6px;
    color: #495057;
}

.img-button {
    background: url("../images/trash.png") no-repeat center center;
    border: none;
    width: 15px;
    height: 20px;
    cursor: pointer;
    background-size: cover;
    flex-direction: column;
    font-size: 20px;
    margin-top: 8px;
    margin-left: auto;
    justify-content: center;
    text-align: center;
}

  • 위와 같이 각각의 컴포넌트 코드들을 보았습니다.
  • 이제는 해당 컴포넌트들을 종합적으로 사용하여 사용자에게 보여주는 TodoList Page 를 생성하여 코드를 작성해 보도록 하겠습니다.

Todo.razor

@page "/todo"

@using ToDoList.Component
@using ToDoList.Data

<PageTitle>Todo</PageTitle>

<DateComponent CurrentDate="@CurrentDate" CurrentDayOfWeek="@CurrentDayOfWeek"></DateComponent>
<DoingWorkCount todos="@todos"></DoingWorkCount>

<hr />

<CircleButtonComponent
        IsClicked="@IsClicked"
        ButtonContent="@ButtonContent"
        CircleChange="CircleChange">
</CircleButtonComponent>

<InputComponent newTodo="@newTodo"
                Enter="Enter"
                OnClick="AddTodo"
                IsClicked="IsClicked">
</InputComponent>

<TodoListComponent todos="@todos"
                   CheckedClick="((int id, bool state) args) => StateChanged(args.id, args.state)"
                   MouseOver="MouseOver"
                   MouseOut="MouseOut"
                   DeleteOnClick="DeleteData">
</TodoListComponent>


@code {
    private List<TodoItem> todos = new();
    private string? newTodo { get; set; }
    private string? CurrentDate { get; set; }
    private string? CurrentDayOfWeek { get; set; }
    private bool IsClicked { get; set; } = true;
    private int ClickCount { get; set; } = 1;
    private string? ButtonContent { get; set; } = "+";

    protected override void OnInitialized()
    {
        base.OnInitialized();
        CurrentDate = GetDate();
        CurrentDayOfWeek = GetCurrentDayOfWeek();
    }

    private void AddTodo(string keyword)
    {
        if (!string.IsNullOrWhiteSpace(keyword))
        {
            var newId = todos.Count > 0 ? todos.Max(t => t.Id) + 1 : 1;
            todos.Add(new TodoItem { Id = newId, Title = keyword });
            newTodo = string.Empty;
        }
    }

    protected void DeleteData(int id)
    {
        todos.Remove(todos.Find(t => t.Id == id)!);
    }

    protected void StateChanged(int id, bool state)
    {
        var todo = todos.First(t => t.Id == id);
        todo.IsDone = !todo.IsDone;
    }

    protected void MouseOver(int id)
    {
        var todo = todos.Find(t => t.Id == id);
        todo.IsDisabled = false;
    }

    protected void MouseOut(int id)
    {
        var todo = todos.Find(t => t.Id == id);
        todo.IsDisabled = true;
    }

    private void Enter()
    {
        AddTodo(newTodo!);
    }

    private void CircleChange()
    {
        ClickCount++;
        IsClicked = ClickCount % 2 == 0 ? false : true;
        ButtonContent = IsClicked == true ? "+" : "+";
    }

    private string GetDate()
    {
        DateTime today = DateTime.Today;
        string title = $"{today.Year.ToString()}년 {today.Month.ToString()}월 {today.Day.ToString()}일";
        return title;
    }

    private string GetCurrentDayOfWeek()
    {
        DateTime nowDt = DateTime.Now;
        string[] days = { "일요일", "월요일", "화요일", "수요일", "목요일", "금요일", "토요일" };
        string day = string.Empty;

        for (int index = 0; index < days.Length; index++)
        {
            if (Convert.ToInt32(nowDt.DayOfWeek) == index)
            {
                day = days[index];
                break;
            }
        }

        return day;
    }
}
body {
    background: #e9ecef;
}
  • 위와 같이 앞서 만든 5개의 컴포넌트들을 Todo.razor 페이지에서 사용하고 있는 것을 확인할 수 있습니다.

실행 결과

  • 실행 결과, 원래 만들려고 했던 TodoList 와 완전히 동일하지는 않지만, 비슷하게 개발은 되었습니다...ㅎㅎ


첨부파일

  • 전체 코드는 아래 올려 놓도록 하겠습니다~!

ToDoList.zip
1.2 MB

728x90

이 글을 공유하기

댓글

Designed by JB FACTORY