或:让我们的简单应用为其他人工作
在 本系列的第一部分(于去年年底开始)中,我们介绍了开发学校计划应用的过程。到目前为止(查看第一部分的最终代码),我们已经实现了同时显示多个学校计划,并且通过 Cordova 支持了 Web、iOS 和 Android 平台。
让我们想象一下,其他人也看到了它的益处,想要使用我们的应用。通过简单地替换一个文件(我们将其命名为 www/app_data/plans.json
)并进行一些其他调整,就可以创建适合他们的应用。这是我们将在本部分开头关注的内容。
我们最终的目标是创建一个应用来显示存储在服务器上的数据(学校计划),这样每个用户都可以从任何计算机查看自己的数据。在本教程中,为了避免分散对主要目标的注意力,服务器部分将被简化,计划数据库将写入 JSON 文件。如果您想将其扩展到 SaaS,请随意进行,并在下面的评论部分告诉我们。
将要构建的内容
一个移动应用程序,将
- 显示学校计划。
- 离线工作。
- 在多个平台上运行。
- 使用存储在服务器/JSON 文件中的学校计划(我们本系列接下来的部分的目标)。
先决条件
如果您还没有完成 第一篇文章,我们建议您现在就完成它。您至少应该完成 第一部分的先决条件,并确保您熟悉它们。
然后按照以下步骤操作,更新您从上一篇文章中获得的代码示例。如果您没有这个代码示例(即,如果您没有完成上一篇文章),请按照 说明加载本教程中的任何阶段。使用 stage4 作为起点。
您还应该确保您已经安装了 NodeJS。
添加图标
在第一部分中,我省略了图标,因此 Cordova 添加了一个默认图标。让我们使用自定义图标使应用看起来更专业。我已经从 findicon.com 下载了一个背包图标,调整了大小并复制到了 www/img
文件夹。在应用目录(school-plan/
- 在运行 cordova create
后创建)中 - 编辑 config.xml
文件并添加以下内容
<icon src="www/img/backpack-128.png" />
如果您愿意,您可以添加更多特定信息 - 例如,为每个所需的平台添加更多图像大小。您可以在 Cordova 文档 中找到更多信息。以下是在 Firefox OS 中的特殊定义
<platform name="firefoxos">
<icon width="128" height="128" src="www/img/backpack-128.png" />
<icon width="64" height="64" src="www/img/backpack-64.png" />
<icon width="32" height="32" src="www/img/backpack-32.png" />
</platform>
由于 Cordova 中的 Firefox OS 部分存在错误,因此请使用以下命令创建 www/icon 目录。
mkdir www/icon
修改数据代码
在本阶段,我们将修改学校计划应用,使其使用数据而不是纯 HTML(查看 GitHub 上完成的 stage 5 代码)。
之前我们将所有学校计划数据硬编码到 index.html
中。现在我们将数据与其可视化表示分离。为此,我们将从 www/index.html
中删除旧数据,只保留最小的结构。
清空 index.html
文件中的 <brick-tabbar>
和 <brick-deck>
元素,使它们看起来像这样
<brick-tabbar id="plan-group-menu" selected-index="0">
</brick-tabbar>
<brick-deck id="plan-group" selected-index="0">
</brick-deck>
JavaScript 项目中最常用的数据结构是 JSON;因此我们将以这种格式添加我们的数据。目前,将学校计划与应用一起分发是可以的(稍后我们将从服务器获取它,以获得更大的灵活性)。我们的 JSON 包含一个计划的 Array
。Plan
是一个包含 title
、id
、可选的 active
字段和 week
的 Object
。后者本身是一个课程计划的 Array
。
注意:使用 jsHint 检查代码质量。
[
{
"title": "Name of the plan",
"id": "id-of-the-plan",
"active": 1, // should the plan be active now?
"week": [
[
"first hour of Monday",
"second hour",
"third",
// ... and so on
[], // no activities on Tuesday
], [
"",
"",
"Wednesday is starting on third hour",
"fourth"
]
}
]
您可以在 Github 上找到一个 示例文件。将其复制到您的项目中的 www/app_data/plans.json
文件夹。
现在我们需要从应用目录中读取此 JSON 文件。
由于卡片和标签在加载 JavaScript 文件时还不存在,因此我们现在应该从 www/js/index.js
中删除将它们链接在一起的部分。转到该文件,找到 assignTabs()
函数,并将其完全删除,以及其下面的调用。
没有数据,应用程序将不会显示任何内容。数据需要在应用准备就绪后立即加载 - 找到 onDeviceReady
方法,并在其内部顶部输入以下代码行
var request = new XMLHttpRequest();
request.onload = app.renderData;
request.open("get", "app_data/plans.json", true);
request.send();
注意:之前我计划使用 Cordova 的 FileSystem 插件,但它只会使代码更复杂。
请求成功返回我们的 JSON 后,它将传递给 app.renderData
(如下所示)。它将 JSON 格式的文本解析为 JavaScript,并将数据发送到 app.createUI
,以便创建必要的 DOM 元素来形成 UI。
在 onDeviceReady
方法下方添加以下代码块
renderData: function() {
var plans = JSON.parse(this.responseText);
app.createUI(plans);
},
为了创建 UI,我们需要工作日名称。最佳选择是使用 Cordova 的 Globalization 插件。要将此插件添加到应用程序,只需在您的终端中运行以下命令,确保您位于根 school-plan
目录中
cordova plugin add org.apache.cordova.globalization
接下来,在 renderData()
方法下方添加我们之前提到的 createUI()
方法。它看起来像这样
createUI: function(plans) {
var deck = document.getElementById('plan-group');
var tabbar = document.getElementById('plan-group-menu');
navigator.globalization.getDateNames(function(dayOfWeek) {
// render UI in the callback
}, function() {}, {type: 'narrow', item: 'days'});
},
工作日使用 navigator.globalization.getDateNames
方法检索。dayOfWeek
将保存一个工作日名称的 Array
,例如(在我的例子中是波兰语) - ['Pn', 'Wt', 'Śr', 'Cz', 'Pt', 'So', 'Nd']
。如果您想使用完整的日期名称,只需将 type: 'narrow'
更改为 type: 'wide'
。
现在我们需要为单个计划创建 DOM 元素。这一次,brick-tabbar-tab
和 brick-card
元素使用 JavaScript 创建。标签使用其 target
参数引用相应的卡片。它在每种情况下都与卡片的 id
相同。Brick 将解析此值并创建一个 tab.targetElement
,它将链接到卡片元素。在 getDateNames
的回调中,输入以下代码(替换上面的“// 在回调中渲染 UI”注释)
for (var i = 0; i < plans.length; i++) {
var plan = plans[i];
// create card
var card = document.createElement('brick-card');
card.setAttribute('id', plan.id);
deck.appendChild(card);
//create tab
var tab = document.createElement('brick-tabbar-tab');
tab.setAttribute('target', plan.id);
tab.appendChild(document.createTextNode(plan.title));
tabbar.appendChild(tab);
// link card to tab
card.tabElement = tab;
card.addEventListener('show', function() {
this.tabElement.select();
});
// create plan table
var table = document.createElement('table');
}
与编写纯 HTML 不同,我们现在将创建 table
的主体,然后是标题。这是因为 table.insertRow()
或者在内部创建一个新的 tbody
和 tr
,或者将一行添加到任何现有的 HTMLTableSectionElement
(如果已经创建,则为 thead
)。我们也可以调用 table.tBodies(0)
,但这会使代码更复杂。
我们不想显示没有课程的日子。让我们将只有非空日子的计划复制到新的 cleanPlan
数组中。将此代码放在创建 <table>
元素的行之后(参见上面的列表)
var numberOfDays = plan.week.length;
var cleanPlan = [];
for (j = 0; j < numberOfDays; j++) {
if (plan.week[j].length > 0) {
cleanPlan.push(plan.week[j]);
}
}
在我们能够创建其他 DOM 元素(<tr>
和 <td>
)之前,有一个问题需要解决 - 我们在 JSON 文件中以人类理解的方式表示计划 - 小时在日期内。不幸的是,HTML 中的表格是按行创建的(日期在小时内),这意味着表示计划的数组需要翻转。
例如,存储在 JSON 中的数组将如下所示:(dXhY
代表 X 天 Y 小时)
d1h1 d1h2 d1h3 ...
d2h1 d2h2 d2h3 ...
d3h1 d3h2 d3h3 ...
...
但我们的 <table>
结构将如下所示
d1h1 d2h1 d3h1 ...
d1h2 d2h2 d3h2 ...
d1h3 d2h3 d3h3 ...
...
在最后一个代码块之后添加以下代码块,开始执行此数据转换
var daysInHours = [];
for (j = 0; j < cleanPlan.length; j++) {
for (var k = 0; k < cleanPlan[j].length; k++) {
if (!daysInHours[k]) {
daysInHours[k] = [];
}
daysInHours[k][j] = cleanPlan[j][k];
}
}
上面最重要的行是 daysInHours[k][j] = cleanPlan[j][k];
,其中索引反转 - 一个数组的 kj
元素成为另一个数组的 jk
元素。d3h2
取代了 d2h3
,反之亦然。
daysInHours
数组现在应该保存为 UI 准备好的计划。现在我们可以遍历它,将计划渲染到 HTML 表格中。这里需要注意一个重要的事情 - table.insertRow
需要使用(否则为可选的)设置为 -1
的索引,因为默认情况下,Android 会将行插入到表格的顶部。
在最后一个代码块下方添加以下代码块
for (var j = 0; j < daysInHours.length; j++) {
var tr = table.insertRow(-1);
var td = tr.insertCell(-1);
td.appendChild(document.createTextNode(j + 1));
for (var k = 0; k < cleanPlan.length; k++) {
var td = tr.insertCell(-1);
if (daysInHours[j][k]) {
td.appendChild(document.createTextNode(daysInHours[j][k]));
}
}
}
我们遍历所有的小时(索引 j
)。第一个 <tr>
在数组的底部创建,然后是包含该行号的 textNode
的 <td>
。之后,我们遍历小时内的日期(索引 k
)并创建更多单元格 - 如果该小时和日期有计划,则创建一个 textNode
。
你可能会惊讶地看到这段代码使用 cleanPlan.length
而不是 daysInHours[j].length
。这是因为我们需要在每一天创建一个单元格,即使没有安排课程,否则我们将最终得到像这样的损坏的表格结构。
现在我们准备创建一个带有日期的标题。将以下代码块添加到上一个代码块的正下方。
var thead = table.createTHead();
var tr = thead.insertRow();
var th_empty = document.createElement('th');
tr.appendChild(th_empty);
var weekDayNumber;
for (var j = 0; j < numberOfDays; j++) {
var weekDayNumber = (j + 1) % 7;
if (plan.week[j].length > 0) {
var th = document.createElement('th');
th.appendChild(document.createTextNode(dayOfWeek.value[weekDayNumber]));
tr.appendChild(th);
}
}
首先,为该列创建一个空标题单元格,包含对应的一天的小时。然后,我们遍历日期并仅在当前日期有计划时(与之前 cleanPlan
的方式相同),创建一个带有日期名称的新 <th>
。
接下来,我们需要一行将创建的 <table>
放入 brick-card
元素中 - 将以下行添加到上一个代码块下方。
card.appendChild(table);
当所有选项卡都准备好后,我们希望应用程序加载时第一个选项卡被选中。为此,我们将使用以下代码块 - 将其添加到上一行的正下方。
if (plan.active) {
selectTab(deck, tab);
}
selectTab
是在 app
对象之外创建的辅助函数。它使用 轮询 在 activeTab.targetElement
上检测 Brick 是否已将选项卡与卡片链接,如下所示。将此代码块添加到 index.js 文件的底部。
function selectTab(deck, activeTab) {
function selectActiveTab() {
if (!activeTab.targetElement) {
return window.setTimeout(selectActiveTab, 100);
}
deck.showCard(activeTab.targetElement);
}
selectActiveTab();
}
测试时间
此时,应用程序应该与第 1 部分第 4 阶段的应用程序完全相同。唯一的区别可能是计划标题中本地化日期名称的不同。
当你想测试你的应用程序时,在终端中输入以下命令(从你的 school-plan 应用程序的根目录)。
cordova prepare
cordova serve
这将使应用程序及其不同的平台在 localhost:8000
上对你可用。
如果你还想在 Firefox OS 上测试应用程序,你需要在 Firefox 中打开 WebIDE,转到打开应用程序 > 打开打包应用程序,然后从文件选择器中选择打开 school-plan/platforms/firefoxos/www
目录作为打包应用程序。从这里,你可以选择在模拟器或真实的 Firefox OS 设备上加载应用程序。有关更多详细信息,请参阅 MDN WebIDE 页面。
注意:如果你想为希望使用该应用程序的不同用户定制应用程序,你可以在此阶段通过用每个用户的不同信息替换 www/app_data/plans.json
文件来完成。
2 条评论