7부: 기본 페이지 만들기

작성자: Rick Anderson

완료된 프로젝트 다운로드

기본 페이지 만들기

이 섹션에서는 기본 애플리케이션 페이지를 만듭니다. 이 페이지는 관리자 페이지보다 더 복잡하므로 몇 가지 단계로 살펴보겠습니다. 그 과정에서 좀 더 고급 Knockout.js 기법을 볼 수 있습니다. 페이지의 기본 레이아웃은 다음과 같습니다.

기본 페이지의 제품, 카트, 주문 및 주문 세부 정보 요소 간의 상호 작용 다이어그램

기본 페이지의 제품, 카트, 주문 및 주문 세부 정보 요소 간의 상호 작용 다이어그램 제품 요소에는 항목 요소를 가리키는 화살표가 있는 GET A P I/products 레이블이 지정됩니다. items 요소는 POST A P I/orders라는 화살표로 orders 요소에 연결됩니다. orders 요소는 GET A P I/orders라는 레이블이 지정된 화살표를 사용하여 세부 정보 요소에 연결됩니다. 세부 정보 요소는 GET A P I / orders / i d로 레이블이 지정됩니다.

  • "제품"에는 다양한 제품이 있습니다.
  • "카트"는 제품과 수량이 나열된 배열을 포함합니다. "카트에 추가"를 클릭하면 카트가 업데이트됩니다.
  • "Orders"는 주문 ID의 배열을 보유합니다.
  • "세부 정보"는 항목 배열(수량이 있는 제품)인 주문 세부 정보를 포함합니다.

먼저 데이터 바인딩이나 스크립트 없이 HTML에서 몇 가지 기본 레이아웃을 정의합니다. Views/Home/Index.cshtml 파일을 열고 모든 내용을 다음으로 바꿉니다.

<div class="content">
    <!-- List of products -->
    <div class="float-left">
    <h1>Products</h1>
    <ul id="products">
    </ul>
    </div>

    <!-- Cart -->
    <div id="cart" class="float-right">
    <h1>Your Cart</h1>
        <table class="details ui-widget-content">
    </table>
    <input type="button" value="Create Order"/>
    </div>
</div>

<div id="orders-area" class="content" >
    <!-- List of orders -->
    <div class="float-left">
    <h1>Your Orders</h1>
    <ul id="orders">
    </ul>
    </div>

   <!-- Order Details -->
    <div id="order-details" class="float-right">
    <h2>Order #<span></span></h2>
    <table class="details ui-widget-content">
    </table>
    <p>Total: <span></span></p>
    </div>
</div>

다음으로 스크립트 섹션을 추가하고 빈 뷰 모델을 만듭니다.

@section Scripts {
  <script type="text/javascript" src="@Url.Content("~/Scripts/knockout-2.1.0.js")"></script>
  <script type="text/javascript">

    function AppViewModel() {
        var self = this;
        self.loggedIn = @(Request.IsAuthenticated ? "true" : "false");
    }

    $(document).ready(function () {
        ko.applyBindings(new AppViewModel());
    });

  </script>
}

앞에서 스케치한 디자인을 기반으로 보기 모델에는 제품, 카트, 주문 및 세부 정보에 대한 관찰 가능한 정보가 필요합니다. 개체 AppViewModel에 다음 변수를 추가합니다.

self.products = ko.observableArray();
self.cart = ko.observableArray();
self.orders = ko.observableArray();
self.details = ko.observable();

사용자는 제품 목록의 항목을 카트에 추가하고 카트에서 항목을 제거할 수 있습니다. 이러한 함수를 캡슐화하기 위해 제품을 나타내는 다른 뷰 모델 클래스를 만듭니다. 다음 코드를 AppViewModel에 추가합니다.

function AppViewModel() {
    // ...

    // NEW CODE
    function ProductViewModel(root, product) {
        var self = this;
        self.ProductId = product.Id;
        self.Name = product.Name;
        self.Price = product.Price;
        self.Quantity = ko.observable(0);

        self.addItemToCart = function () {
            var qty = self.Quantity();
            if (qty == 0) {
                root.cart.push(self);
            }
            self.Quantity(qty + 1);
        };

        self.removeAllFromCart = function () {
            self.Quantity(0);
            root.cart.remove(self);
        };
    }
}

클래스에는 ProductViewModel 카트에서 제품을 이동하는 데 사용되는 두 가지 함수가 addItemToCart 포함되어 있습니다. 즉, removeAllFromCart 제품의 한 단위를 카트에 추가하고 제품의 모든 수량을 제거합니다.

사용자는 기존 주문을 선택하고 주문 세부 정보를 가져올 수 있습니다. 이 기능을 다른 뷰 모델로 캡슐화합니다.

function AppViewModel() {
    // ...

    // NEW CODE
    function OrderDetailsViewModel(order) {
        var self = this;
        self.items = ko.observableArray();
        self.Id = order.Id;

        self.total = ko.computed(function () {
            var sum = 0;
            $.each(self.items(), function (index, item) {
                sum += item.Price * item.Quantity;
            });
            return '$' + sum.toFixed(2);
        });

        $.getJSON("/api/orders/" + order.Id, function (order) {
            $.each(order.Details, function (index, item) {
                self.items.push(item);
            })
        });
    };
}

OrderDetailsViewModel 주문으로 초기화되고 AJAX 요청을 서버로 전송하여 주문 세부 정보를 가져옵니다.

또한 OrderDetailsViewModeltotal 속성을 주목하세요. 이 속성은 특수한 종류의 계산된 관찰 가능 속성입니다. 이름에서 알 수 있듯이, 계산된 관찰 가능 항목은 데이터 바인딩을 통해 계산된 값, 즉 이 경우에는 주문의 총 비용에 연결할 수 있도록 합니다.

다음으로 다음 함수를 AppViewModel추가합니다.

  • resetCart 는 카트에서 모든 항목을 제거합니다.
  • getDetails 는 새로운 OrderDetailsViewModel 을(를) details 목록에 추가하여 주문의 세부 정보를 가져옵니다.
  • createOrder 는 새 주문을 만들고 카트를 비웁니다.
function AppViewModel() {
    // ...

    // NEW CODE
    self.resetCart = function() {
        var items = self.cart.removeAll();
        $.each(items, function (index, product) {
            product.Quantity(0);
        });
    }

    self.getDetails = function (order) {
        self.details(new OrderDetailsViewModel(order));
    }

    self.createOrder = function () {
        var jqxhr = $.ajax({
            type: 'POST',
            url: "api/orders",
            contentType: 'application/json; charset=utf-8',
            data: ko.toJSON({ Details: self.cart }),
            dataType: "json",
            success: function (newOrder) {
                self.resetCart();
                self.orders.push(newOrder);
            },
            error: function (jqXHR, textStatus, errorThrown) {
                self.errorMessage(errorThrown);
            }  
        });
    };
};

마지막으로 제품 및 주문에 대한 AJAX 요청을 수행하여 뷰 모델을 초기화합니다.

function AppViewModel() {
    // ...

    // NEW CODE
    // Initialize the view-model.
    $.getJSON("/api/products", function (products) {
        $.each(products, function (index, product) {
            self.products.push(new ProductViewModel(self, product));
        })
    });

    $.getJSON("api/orders", self.orders);
};

좋아, 코드가 많지만 우리는 단계별로 만들었으므로 디자인이 분명하길 바랍니다. 이제 HTML에 몇 가지 Knockout.js 바인딩을 추가할 수 있습니다.

제품

제품 목록에 대한 바인딩은 다음과 같습니다.

<ul id="products" data-bind="foreach: products">
    <li>
        <div>
            <span data-bind="text: Name"></span> 
            <span class="price" data-bind="text: '$' + Price"></span>
        </div>
        <div data-bind="if: $parent.loggedIn">
            <button data-bind="click: addItemToCart">Add to Order</button>
        </div>
    </li>
</ul>

제품 배열을 반복하고 이름과 가격을 표시합니다. "주문에 추가" 단추는 사용자가 로그인한 경우에만 표시됩니다.

"주문에 추가" 단추는 제품의 ProductViewModel 인스턴스에서 addItemToCart를 호출합니다. 이는 Knockout.js좋은 기능을 보여 줍니다. 뷰 모델에 다른 뷰 모델이 포함된 경우 내부 모델에 바인딩을 적용할 수 있습니다. 이 예제에서는 foreach 내의 바인딩이 각 ProductViewModel 인스턴스에 적용됩니다. 이 방법은 모든 기능을 단일 뷰 모델에 배치하는 것보다 훨씬 더 클리너입니다.

카트

카트에 대한 바인딩은 다음과 같습니다.

<div id="cart" class="float-right" data-bind="visible: cart().length > 0">
<h1>Your Cart</h1>
    <table class="details ui-widget-content">
    <thead>
        <tr><td>Item</td><td>Price</td><td>Quantity</td><td></td></tr>
    </thead>    
    <tbody data-bind="foreach: cart">
        <tr>
            <td><span data-bind="text: $data.Name"></span></td>
            <td>$<span data-bind="text: $data.Price"></span></td>
            <td class="qty"><span data-bind="text: $data.Quantity()"></span></td>
            <td><a href="#" data-bind="click: removeAllFromCart">Remove</a></td>
        </tr>
    </tbody>
</table>
<input type="button" data-bind="click: createOrder" value="Create Order"/>

카트 배열을 반복하고 이름, 가격 및 수량을 표시합니다. "제거" 링크와 "순서 만들기" 단추는 뷰 모델 함수에 바인딩됩니다.

주문

주문 목록에 대한 바인딩은 다음과 같습니다.

<h1>Your Orders</h1>
<ul id="orders" data-bind="foreach: orders">
<li class="ui-widget-content">
    <a href="#" data-bind="click: $root.getDetails">
        Order # <span data-bind="text: $data.Id"></span></a>
</li>
</ul>

그러면 주문이 반복되고 주문 ID가 표시됩니다. 링크의 클릭 이벤트가 함수에 getDetails 바인딩됩니다.

주문 세부 정보

주문 세부 사항에 대한 바인딩은 다음과 같습니다.

<div id="order-details" class="float-right" data-bind="if: details()">
<h2>Order #<span data-bind="text: details().Id"></span></h2>
<table class="details ui-widget-content">
    <thead>
        <tr><td>Item</td><td>Price</td><td>Quantity</td><td>Subtotal</td></tr>
    </thead>    
    <tbody data-bind="foreach: details().items">
        <tr>
            <td><span data-bind="text: $data.Product"></span></td>
            <td><span data-bind="text: $data.Price"></span></td>
            <td><span data-bind="text: $data.Quantity"></span></td>
            <td>
                <span data-bind="text: ($data.Price * $data.Quantity).toFixed(2)"></span>
            </td>
        </tr>
    </tbody>
</table>
<p>Total: <span data-bind="text: details().total"></span></p>
</div>

주문 항목을 반복하여 제품, 가격, 그리고 수량을 표시합니다. 세부 정보 배열에 하나 이상의 항목이 포함된 경우에만 주변 div가 표시됩니다.

결론

이 자습서에서는 Entity Framework를 사용하여 데이터베이스와 통신하고 Web API를 ASP.NET 데이터 계층 위에 공용 인터페이스를 제공하는 애플리케이션을 만들었습니다. ASP.NET MVC 4를 사용하여 HTML 페이지를 렌더링하고, Knockout.js 및 jQuery를 사용하여 페이지 다시 로드 없이 동적 상호 작용을 제공합니다.

추가 리소스: