问题
The Goal
Make a dynamic list of products.
The Scenario
I have a shopping application with products. When I click on the add button
of a product, I want to display in the sidebar, the product what I have added.
Summarized Problem (You just need to read this)
I have the following code in my ProductsSummary/Index.cshtml
(using Razor Engine):
<ul class="summary">
@if (Session["ProductsSummary"] == null)
{
<li class="is-empty">
<p>Your summary is empty.</p>
</li>
<li data-bind="attr: { 'data-product-id': id }">
<div class="product-summary-actions float-right">
<button class="btn btn-danger btn-mini remove-item">
<i class="icon-remove"></i>
</button>
</div>
<div class="product-summary-quantity">
<h6 data-bind="text: infoComposition"></h6>
</div>
<div class="product-summary-description">
<p data-bind="text: name"></p>
</div>
</li>
}
else
{
foreach
(var product in
(List<MyApp.Models.Data.getSpecificProductToShoppingList_Result>)
Session["ProductsSummary"])
{
<!-- ko foreach: products -->
<li data-product-id="@product.id">
<div class="product-summary-actions float-right">
<button class="btn btn-danger btn-mini remove-item">
<i class="icon-remove"></i>
</button>
</div>
<div class="product-summary-quantity">
<h6>
@product.quantity
@product.measure@(@product.quantity == 1 ? "" : "s")
</h6>
</div>
<div class="product-summary-description">
<p>@product.name</p>
</div>
</li>
<!-- /ko -->
}
}
</ul>
As you can see, there is three <li>
on the code. The first is to display a message with "The summary is empty." case the session is null (and of course, the Session is null case any product has been added); the second li
serves as model for Knockout when I add something when the session is null; and the last one is to display the products what are in the session.
I'm feeling a little "DRY" right here. How can I reuse the same template regardless of whether session exists or not?
Detailed Problem
I have the following code in my ProductsSummary/Index.cshtml
(using Razor Engine):
<ul class="summary">
@if (Session["ProductsSummary"] == null)
{
<li class="is-empty">
<p>Your summary is empty.</p>
</li>
<li data-bind="attr: { 'data-product-id': id }">
<div class="product-summary-actions float-right">
<button class="btn btn-danger btn-mini">
<i class="icon-remove"></i>
</button>
</div>
<div class="product-summary-quantity">
<h6 data-bind="text: infoComposition"></h6>
</div>
<div class="product-summary-description">
<p data-bind="text: name"></p>
</div>
</li>
}
else
{
foreach
(var product in
(List<MyApp.Models.Data.getSpecificProductToShoppingList_Result>)
Session["ProductsSummary"])
{
<!-- ko foreach: products -->
<li data-product-id="@product.id">
<div class="product-summary-actions float-right">
<button class="btn btn-danger btn-mini remove-item">
<i class="icon-remove"></i>
</button>
</div>
<div class="product-summary-quantity">
<h6>
@product.quantity
@product.measure@(@product.quantity == 1 ? "" : "s")
</h6>
</div>
<div class="product-summary-description">
<p>@product.name</p>
</div>
</li>
<!-- ko -->
}
}
</ul>
As you can see, there is an if
that checks if ProductsSummary
session exists. If yes, then the application displays on the screen a list of products that I added on my summary.
Case the session is null, as you can see, the application displays the message within the li
with is-empty
class.
The point is: I really need of the "template" after <li class="is-empty">[...]</li>
to display an item that was added to the summary?
I mean, I know that Knockout needs something to display when I click the "Add Product" button regardless of whether or not there is a session, but I'm repeating the same template for similar purposes.
Look to this fragment:
<li data-product-id="@product.id">
<div class="product-summary-actions float-right">
<button class="btn btn-danger btn-mini remove-item">
<i class="icon-remove"></i>
</button>
</div>
<div class="product-summary-quantity">
<h6>
@product.quantity
@product.measure@(@product.quantity == 1 ? "" : "s")
</h6>
</div>
<div class="product-summary-description">
<p>@product.name</p>
</div>
</li>
In this case, I'm using it within foreach
because I must to display the items fetched from the database.
On the other hand, the following fragment exists if there isn't a session:
<li data-bind="attr: { 'data-product-id': id }">
<div class="product-summary-actions float-right">
<button class="btn btn-danger btn-mini">
<i class="icon-remove"></i>
</button>
</div>
<div class="product-summary-quantity">
<h6 data-bind="text: infoComposition"></h6>
</div>
<div class="product-summary-description">
<p data-bind="text: name"></p>
</div>
</li>
As you can see, the both fragments are similar — one represents the data from database, and the other represents a model to work with Knockout when there is no session, respectively — and I need a way to "templatize" this.
What I Really Need
- Someone enters in my site/application;
- At the right side of my layout there is a sidebar with a message: "The summary is empty.";
- "Oh, what a nice product! I will add it to my summary!", then the user clicks on
Add
button, the "The summary is empty." message disappears and the product added by user appears in the format of an item from a list (the same template that I have passed before [first/second fragment]).- "Ok, I will to another category of products now." — *The user clicks on "TVs" category* — "OH MY GOD! Look at this TV! I will add to my summary right now!" — *The user clicks on "Add Button" of a random TV.* — Already had a product in the list, but another (the TV) appears.
- "Oh, nevermind. I do not have money. I will remove these items from my summary." — *The user clicks on "remove button" of each product on the summary* — And without products, the summary displays: "The summary is empty." just like a magic, without any refresh or something like this.
(Funny, huh?)
The KnockoutJS Script
$(document).ready(function () {
function Product(id, name, measure, quantity) {
this.id = ko.observable(id);
this.name = ko.observable(name);
this.measure = ko.computed(function () {
return quantity > 1 ? measure + "s" : measure;
}, this);
this.quantity = ko.observable(quantity);
this.infoComposition = ko.computed(function () {
return this.quantity() + " " + this.measure()
}, this);
}
function SummaryViewModel() {
this.products = ko.observableArray([]);
this.addToSummary = function (formElement) {
var $productId = $(formElement).children("[name=productId]").val();
var match = $(".summary")
.find("li[data-product-id=" + $productId + "]").length;
if (!match) {
var $productName =
$(formElement).children("[name=productName]").val(),
$productMeasure =
$(formElement).children("[name=productMeasure]").val(),
$productQuantity =
$(formElement).children("[name=productQuantity]").val();
this.products.push
(new Product
($productId,
$productName,
$productMeasure,
$productQuantity));
$.ajax({
type: "POST",
url: "/ProductsSummary/Add",
data:
{
productId: $productId,
productQuantity: $productQuantity
}
});
}
}
};
var summaryViewModel = new SummaryViewModel();
ko.applyBindings(summaryViewModel);
$("body").on("click", ".remove-item", function () {
summaryViewModel.products.remove(ko.dataFor(this));
$.ajax({
type: "POST",
url: "/ProductsSummary/Remove",
data: { productId: $(this).closest("li").data("product-id") }
});
});
});
What is happening, eventually?
What I'm doing works and does not work. Technically, my code works, but I wouldn't to repeat it. Is it possible?
Technical Details
The server-side team is with C#.NET with MVC 4 and Razor Engine and the client-side team is KnockoutJS and jQuery.
回答1:
For the empty cart message, you can do this:
<li class="is-empty" data-bind="visible: products().length < 1">
<p>Your summary is empty.</p>
</li>
For the rest, you should be able to do this (no MVC loops):
<!-- ko foreach: products -->
<li data-bind="attr: { 'data-product-id': id }">
<div class="product-summary-actions float-right">
<button class="btn btn-danger btn-mini remove-item">
<i class="icon-remove"></i>
</button>
</div>
<div class="product-summary-quantity">
<h6 data-bind="text: infoComposition"></h6>
</div>
<div class="product-summary-description">
<p data-bind="text: name"></p>
</div>
</li>
<!-- /ko -->
And populate the list on the client side, even if you have items saved to the session. In your view, serialize the existing data to a JSON object and save it to a javascript variable on the page which can be read on document ready and pushed into the products observable.
var existingItems = @Html.Raw(Json.Encode((List<MyApp.Models.Data.getSpecificProductToShoppingList_Result>)Session["ProductsSummary"]));
$(document).ready(function() {
// push existingItems into products observable.
});
Note my syntax may not be quite right on the JSON encoding.
来源:https://stackoverflow.com/questions/17304720/shopping-cart-logic-trouble-with-knockoutjs