在你眼前






4.56/5 (9投票s)
使用 Knockout.js、Bootstrap 和 Google Charts 让您的页面生动起来。
引言
使用 Knockout.js、Bootstrap 和 Google Charts 可以创建一个丰富且响应式的客户端界面。
这是一个每周工资计算器,它将费用分类并计算每小时、每周和每年的工资估算。它具有响应性,使用 Knockout.js 立即重新计算,同时使用 Google Charts 以图表形式显示结果。它是一个 SPA,包含一个使用 Bootstrap 格式化的 HTML 页面。

背景
一位客户想要一种方法来向港口卡车司机展示他们的费用正在侵蚀他们的薪水。司机们花很多时间在驾驶室里和手机上等待。客户希望有一种引人注目且引人入胜的方式让司机自己发现事实。Knockout.js、Bootstrap 和 JQuery 提供了工具,而 Google Chart 则有助于传达信息。
Using the Code
SPA 页面在逻辑上被组织成几个部分,每个部分服务于一个单独的目的。这些部分可以定义为
- 资源
- 变量定义
- 图表定义
- HTML
- ViewModel
- 实用程序
页面的第一部分使用内容分发网络 (CDN) 提供 JQuery、Bootstrap、Knockout.js 和 Google Charts。这些脚本引用包含在 HTML 文档的 Head
部分。
在那些 a
样式标签集设置了页面的 CSS。这是可选的。它也可以引用一个 CSS 文件。
Knockout.js 是使页面具有响应性的关键。当用户更改工作时间、支付金额或任何费用时,页面都会重新计算。它是响应式的。这些项目的数值存储在变量中。Knockout 会跟踪这些变量。被 Knockout 跟踪的变量称为可观察对象。它们被 Knockout.js 观察。
应用程序的费用类别已定义并存储在数组中。每个费用类别都有一个名称、一个代码和一个子类别名称数组。我们有类别代码,以便更容易按类别合计。我们没有子类别代码,因为我们不按子类别进行任何计算。我们将类别代码从 2 开始,因为我们将类别 1 保留给税后收入。
var expenseCategories = [
{
"expenseSubCategories": [
{
"name": "Truck"
},
{
"name": "Yard Space"
},
{
"name": "Parking"
},
{
"name": "Radio"
},
{
"name": "Other"
}
], "name": "Lease and Rent",
"categoryCode": 2
},
费用可以是每周、每月或每年。我们需要能够将费用转换为每周和每年的金额。我们创建一个数组来存储时间段及其系数。
然后我们定义图表。我们在此处执行此操作,因为它们在我们的 HTML 中有引用,并且必须首先定义。在此应用程序中,我们有两个仪表图和一个饼图。饼图记录按类别的费用,仪表测量每小时和每年的工资。
Google Chart 会根据图表类型下载图表构建信息。饼图使用“corechart”包。加载后,它会运行一个回调函数。我们将回调函数绘制我们的图表。图表使用数据表来存储数据。我们将使用数组来填充图表数据表。我们在绘制图表之前定义我们的图表数据。我们的图表数据数组命名为“expenseCategoryChartData
”。请注意,数组中的第一个项目存储了我们数据的列标题。
var expenseCategoryChartData = [['Category', 'Count'],
['Take Home',takeHome],['Lease and Rent', leaseExpense],
['Maintenance', maintenanceExpense],['Fuel', fuelExpense],
['Insurance', insuranceExpense],['Fees and Fines', feeExpense]];
google.load("visualization", "1", { packages: ["corechart"] });
google.setOnLoadCallback(drawCategoryChart);
function drawCategoryChart() {
var pieData = google.visualization.arrayToDataTable(expenseCategoryChartData);
var pieOptions = {
backgroundColor: "#BE6527", //Note: set to match page css
chartArea: {left:"10%",top:"5%",
width:"70%",height:"25%"},
legend: {position: 'left'},
slices: [ {color: '#168E10', offset: 0.2 },{color: '#C5161F'},{color: '#FFA500'},
{color: '#499DDA'},{color: '#88108E'},
{color: '#10558E'}] //Note: set to match page color scheme
};
var pieChart = new google.visualization.PieChart
(document.getElementById('chartCategories'));
pieChart.draw(pieData, pieOptions);
}
我们通过将数组转换为名为“pieData
”的数据表变量来开始我们的图表绘制函数。我们还为饼图定义了各种选项。我们将矩形图表背景颜色设置为与页面背景相同的颜色,使其与页面融为一体。我们为图表颜色设置了颜色方案,以补充背景颜色。
我们定义饼图并提供一个参数,该参数是 HTML 页面上图表的项目 ID,在本例中为“chartCategories
”。接下来,我们提供数据和选项并绘制图表。
我们对我们的两个仪表图也遵循相同的过程。
接下来是我们的 HTML。在这里,我们开始利用 Knockout 并添加将有助于我们实现响应式的标记。
我们将从 2 个 Div
标记开始。第一个只是一个名称。第二个定义一个容器。
<div class='liveExample'>
<div class="container">
<div class="row">
<table width='100%'>
<td width='60%' >
<form role="form">
<fieldset>
<table width='100%'>
<thead>
<tr><div class="col-md-6"><th >
<h3>Weekly Pay</h3></th></div></tr>
<tr>
<div class="col-md-1">
<th class='quantity' >Hours Worked</th></div>
<div class="col-md-1">
<th class='price' >Paid</th></div>
<div class="col-md-1">
<th class='price text-right' >Per Hour</th></div>
</tr>
</thead>
<tbody>
<tr>
<div class="col-md-1"><td class='quantity' >
<input data-bind="value: hoursWorked"
class="form-control" /></td></div>
<div class="col-md-1"><td class='price' >
<input data-bind="value: amountPaid" class="
form-control" /></td></div>
<div class="col-md-1"><td class='price text-right' >
<label data-bind="text: formatCurrency(grossPerHour())" >
</label></td></div>
</tr>
</tbody>
</table>
“row
”类是 Bootstrap,描述了 Bootstrap 在多种设备和方向上处理行的标准方式。带有 role=form 的 form 标签和 fieldset 标签由 Knockout 使用。在这里,我们使用表格将页面的显示分为 4 个部分。左上角是输入小时数和薪水的地方。右边是显示计算出的总计的地方。下面是添加和显示个人费用的地方。最下面是显示图表的地方。
HTML 中的“col-
”类使用 Bootstrap CSS 定义了宽度。但是,“price
”和“quantity
”类被 Knockout 使用。这些类有助于告诉 Knockout 这些项目需要什么样的数字和格式。最好也将这些类应用于标题。另外请注意,数据输入字段的类为“form-control
”。
Knockout 喜欢表格并使用“thead
”和“tbody
”标签。我们使用 Bootstrap 的“text-right
”来右对齐我们显示的内容。“data-bind
”元素在标签内将这些项目标识为 Knockout 的可观察对象。例如,如果我们的工时或薪水发生变化,“grossThisPeriod
”变量将重新计算。然后该值由一个函数格式化。
文档的 Expenses
部分涉及更多内容。在这里,我们有一个用于类别的 select
和一个用于子类别的依赖项 select
。
<tbody data-bind='foreach: expenseItemLines' >
<tr>
<td ><select data-bind="options: expenseCategories,
optionsText: 'name', optionsCaption: 'Select...',
value: expenseCategory" > </select></td>
<td data-bind="with: expenseCategory">
<select data-bind="options: expenseSubCategories,
optionsText: 'name', optionsCaption: 'Select...',
value: $parent.expenseSubCategory "> </select></td>
<td class='price' ><input data-bind="value:
expenseAmount" class="form-control" /></td>
<td ><select data-bind="options: expensePeriods,
optionsText: 'expensePeriodName',
value: expensePeriod" ></select></td>
<div class="col-md-1"><td class='price text-right'
data-bind="text:
formatCurrency(expenseSubtotal())" ></td></div>
<td class='text-right' ><a href='#'
data-bind='click: removeExpenseLine'>Remove</a></td>
</tr>
</tbody>
最重要的是,我们只使用一行表格,并让 Knockout 负责复制这些行以允许我们添加费用。我们也可以删除这些费用。Knockout 会为我们重新计算一切。
我们表格中的每一行都将显示一个行项目费用。我们将它们的集合命名为“expenseItemLines
”,并使用 tbody
标签进行数据绑定。foreach
表示我们将能够迭代该集合。
第一个 select
非常简单。它利用我们在文档顶部定义的数组并返回类别代码。依赖项 select
非常相似。“with:
“关键字建立了依赖关系。value
部分中的“$parent.
”提供了导航。
我们有一个 form-control 输入框,另一个 select
来获取费用周期系数,计算出的每周费用金额,最后是一个用于删除行项目费用的链接。
在行定义下方,有一个按钮用于向 expenseItemLines
添加空白行,还有一个字段用于显示本周的总费用。
最后在 HTML 的末尾,我们为图表设置了布局和 ID。
到目前为止,我们已经定义了内容并完成了布局。现在是功能部分。
我们的应用程序功能来自一个我们称之为“appViewModel
”的 Knockout 视图模型。视图模型定义了可观察对象及其行为。在我们的应用程序中,我们还有行项目费用。我们在视图模型中为它们创建了一个模型,称为“expenseItemLine
”。
function AppViewModel(data) {
this.hoursWorked = ko.observable();
this.amountPaid = ko.observable();
this.grossPerHour = ko.computed(function() {
if (this.hoursWorked() > 0 && this.amountPaid() > 0) {
var amount = (this.grossPerPeriod() / this.hoursWorked());
return amount;
}
else {
return 0;
}
}, this
);
我们通过将我们的输入定义为 Knockout 可观察对象来开始我们的视图模型。接下来我们定义依赖于我们输入的变量。我们想确保有数据供我们计算,因此我们添加了验证检查,以确保我们不会执行诸如除以零之类的操作。如果没有数据,我们返回零。这对于生成错误数据时会出错的 Google Chart 很有帮助。
接下来,我们选择定义我们的费用行项目数组。我们使用可观察对象 grandTotal
和一个计算我们费用的回调函数。我们首先重置我们的总计,否则它们会不断增长。我们使用 JQuery $.each
来迭代我们的费用数组。我们进行检查以确保有一个类别分配给费用,并且有一个金额。然后我们调用一个函数将费用添加到类别总计中,这些总计会填充我们的饼图使用的数组。在迭代完费用后,我们重新绘制图表并显示总计。
现在我们有了总工时和薪水以及该期间的总费用金额,我们可以执行净计算。同样,我们将进行验证检查以确保有数据,否则返回 0。我们根据需要重置总计并重新绘制图表。
我们需要为我们的视图模型提供方法。我们需要能够添加费用和删除费用,因此我们为它们创建了函数。我们还定义了一个变量来模拟我们的 expenseItemLine
。它类似于数据记录,我们为每个字段定义了可观察对象。subtotal
字段是计算出来的,因此我们执行验证检查并执行计算,然后返回。
self.expenseItemLines = ko.observableArray
([new expenseItemLine()]); // Put one line in by default
self.grandTotal = ko.computed(function() {
resetExpenseCategoryTotals();
var total = 0;
$.each(self.expenseItemLines(), function() {
if (this.expenseCategory() != null)
{
var selectedCategory = this.expenseCategory().categoryCode;
var expenseAmountThisPeriod = this.expenseSubtotal();
if (expenseAmountThisPeriod > 0) {
updateExpenseCategoryTotal
(selectedCategory, expenseAmountThisPeriod);
}
}
total += this.expenseSubtotal()
})
redrawCharts();
return total;
});
this.netPerPeriod = ko.computed(function() {
if (this.amountPaid() > 0)
{
var netPeriod = (this.amountPaid() - self.grandTotal());
resetTakeHomeCategoryTotal();
updateExpenseCategoryTotal(1, netPeriod);
return netPeriod;
}
else
{
return 0;
}
}, this);
我们希望费用子类别 select
依赖于类别选择。为了实现这一点,我们将子类别订阅到类别。现在,类别更改会更改子类别选项。
Knockout 的 applyBindings
实例化我们的视图模型。
在 Knockout 代码之后,我们有一些用于重置我们用于图表数据和重绘图表的总计的实用程序。这些特定于我们的应用程序。
最后一个函数非常有帮助,因为 Google Chart 有时会通过页面刷新来减小图表的大小。没有这个函数,如果你不断刷新页面,图表会缩小直到无法读取。该函数将其大小重置为正常。
$(window).resize(function(){
redrawCharts();
});
关注点
这是一个独立的 SPA,但 Knockout 和其他技术也可以用来发出 AJAX 调用,并更新数据库或由数据库更新。希望这个项目展示了使用 Knockout.js、Bootstrap、Jquery 和 Google Charts 等产品的响应式的简便性和优势。