65.9K
CodeProject 正在变化。 阅读更多。
Home

一次编写,随处运行:漫画收藏混合应用

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (6投票s)

2015 年 3 月 10 日

CPOL

9分钟阅读

viewsIcon

21683

downloadIcon

177

演示了 SplitView、关系型 ComboBoxes、DatePicker、ListView、表格和切换列、导出到 Excel,以及使用 PhoneGap Build 编译为混合应用。

引言

这款漫画 CRUD JQuery Mobile 应用的目的是演示如何构建一个数据库应用程序来存储漫画收藏的详细信息,然后使用 PhoneGap Build 将其编译成一个混合应用程序。此应用的方法将采用 SplitView 方法,通过加载包含其他“文件”内容的可选列表来显示表之间的关系,展示 ListView 的使用方法,使用表格添加报表功能,以及如何使用 JavaScript 将此报表导出到 Excel。最后,应用将使用 PhoneGap 进行编译,使其成为一个混合应用。

我假设您对 JQuery Mobile 和 PhoneGap 有一些了解,以便进行本次练习。

下载 My_Comic_Books.zip

下载 PhoneGap Build Apk

总而言之,本文的内容代码将探讨以下 JQuery Mobile 的部分。

1. 标题和标题按钮

2. 页脚

3. ListViews - 标题、描述和计数气泡

4. 文本输入

5. 选择输入(组合框/下拉列表)

6. 表格和导出到 Excel

7. Datepicker 小部件

8. 按钮和链接

9. 标签

10. 使用 CSS 进行样式设置

背景

使用 JQuery Mobile,可以创建可以在任何设备上运行的 HTML5 移动应用程序。当这些应用程序与 PhoneGap Build 编译时,它们可以像原生应用程序一样安装在移动设备上。通过此应用程序,我们有两个存储评分和图书类型的“文件”。这些评分和图书类型会动态加载到漫画屏幕中,显示上面提到的关系型组合框。以下 8 张图展示了漫画收藏应用程序的外观。

图 1:启动板(在黑莓设备上)

启动板为用户提供 4 个选项,用于访问漫画屏幕、图书类型、图书评分和报表。

图 2:漫画(在 iPad 上运行)

漫画是添加漫画书详细信息的主要屏幕。此屏幕被定义为一个拆分视图屏幕,用户可以从中选择并搜索他们想要的漫画书。在捕获漫画书之前,应定义评分和图书类型。

图 3:图书类型

图书类型定义了您的图书所属的漫画书类型。可以是图形小说、连载、合订本等您可以定义的类型。添加漫画书时,图书类型将从该文件读取并加载到下拉列表中。

图 5:评分

评分定义了您可以分配给漫画书的评分。可以是良好、一般、好、全新等状态。

图 6:报表(在另一台黑莓手机上)

当用户选择报表时,应用程序可提供各种类型的报表。这些报表可以导出为 Excel 格式。点击 **要显示的列** 提供了切换报表显示列的选项。即使这些列在表格中隐藏,它们在点击导出到 Excel 时仍会被导出到 Excel。

图 7:漫画报表

图 7.1:漫画 Excel 报表

一旦点击导出到 Excel,就会生成一个名为 ComicBook.xls 的 Excel 报表,其中包含表格内容。

图 8:图书类型报表

图 8.1:图书类型 Excel 报表,通过从该屏幕导出到 Excel 生成

图 9:评分报表

图 9.1:评分 Excel 报表

使用代码

设计我的漫画收藏

开始设计此应用,您需要遵循正常的样板原型来创建 HTML5 应用程序。您可以参考附件文件中的源 HTML 和 JavaScript。

设计启动板:HTML 定义 - 图 1

<div id="pgMenu" data-role="page" data-theme="b" class="my-page">
<header id="pgMenuheader" data-role="header" data-position="fixed">
<h1>My Comic Books</h1>
</header>
<div id="pgMenucontent" data-role="content">
<ul data-role="listview" data-inset="true" id="sbItems">
<li data-icon="false"><a data-transition="slide" id="sbComicBook" href="#pgAddComicBook"><h2>Comic Books</h2><p>Maintain Comic Books</p><img height="200" width="100%" src="apps80.png" alt="Comic Books" class="ui-li-thumb"></img></a></li>
<li data-icon="false"><a data-transition="slide" id="sbBookType" href="#pgAddBookType"><h2>Book Types</h2><p>Maintain Book Types</p><img height="200" width="100%" src="apps80.png" alt="Book Types" class="ui-li-thumb"></img></a></li>
<li data-icon="false"><a data-transition="slide" id="sbGrade" href="#pgAddGrade"><h2>Grades</h2><p>Maintain Grades</p><img height="200" width="100%" src="apps80.png" alt="Grades" class="ui-li-thumb"></img></a></li>
<li data-icon="false"><a data-transition="slide" id="sbReports" href="#pgReports"><h2>Reports</h2><p>Access Reports</p><img height="200" width="100%" src="apps80.png" alt="Reports" class="ui-li-thumb"></img></a></li>
</ul>
</div>

<footer id="pgMenufooter" data-role="footer" data-position="fixed">
<h1>Powered by JQM.Show © Anele Mbanga 2015</h1>
</footer></div>

启动板实际上是一个 ListView,它是从 jquery mobile 网站的这个示例克隆的。这在上面的图 1 中有描绘。

设计漫画录入屏幕:图 2

此屏幕已被创建为拆分屏幕,以显示每个漫画书的 ListView 和详细信息。在加载此屏幕之前,可用的评分和图书类型将从任何已捕获到此屏幕的内容中加载。下面的方法是从 app.ComicBookBindings 调用

case 'pgAddComicBook':
// clear the add page form fields
pgAddComicBookClear();
app.pgAddComicBookcheckForComicBookStorage();
//load related select menus before the page shows
app.pgAddComicBookLoadGrade();
app.pgAddComicBookLoadBookType();
break;

这确保我们在显示屏幕之前加载最新的图书类型和评分类型。

<div id="pgAddComicBook" data-role="page">
<header id="pgAddComicBookheader" data-role="header" data-position="fixed">
<h1>My Comic Books > Add Comic Book</h1>
</header>
<div id="pgAddComicBookcontent" data-role="content">
<div style="width:30%;float:left;">
<ul data-role="listview" data-inset="true" id="pgAddComicBookList" data-autodividers="true" data-filter="true" data-filter-placeholder="Search Comic Books" data-filter-reveal="false">
<li data-role="list-divider">Your Comic Books</li>
<li id="noComicBook">You have no comic books</li>
</ul>
</div>
<div style="width:65%;float:left;margin-left:35px;">
<form action="#" method="post" id="pgAddComicBookForm" name="pgAddComicBookForm">
<div data-role="fieldcontain">
<label for="pgAddComicBookTitle" id="lblpgAddComicBookTitle">Title<span style='color:red;'>*</span></label>
<input required title="Enter title here." type="text" name="pgAddComicBookTitle" id="pgAddComicBookTitle" placeholder="Enter title here." autocomplete="off" data-clear-btn="true"></input>
</div>
<div data-role="fieldcontain">
<label for="pgAddComicBookIssueNumber" id="lblpgAddComicBookIssueNumber">Issue Number<span style='color:red;'>*</span></label>
<input required title="Enter issue number here." type="number" name="pgAddComicBookIssueNumber" id="pgAddComicBookIssueNumber" placeholder="Enter issue number here." autocomplete="off" data-clear-btn="true"></input>
</div>
<div dir="ltr" data-role="fieldcontain">
<label for="pgAddComicBookGrade" id="lblpgAddComicBookGrade">Grade<span style='color:red;'>*</span></label>
<select name="pgAddComicBookGrade" id="pgAddComicBookGrade" data-native-menu="false" data-mini="true" data-inline="true" dir="ltr" class="required">
<option value="null" data-placeholder="true">Select Grade</option>
<option ></option>
</select>
</div>
<div dir="ltr" data-role="fieldcontain">
<label for="pgAddComicBookBookType" id="lblpgAddComicBookBookType">Book Type<span style='color:red;'>*</span></label>
<select name="pgAddComicBookBookType" id="pgAddComicBookBookType" data-native-menu="false" data-mini="true" data-inline="true" dir="ltr" class="required">
<option value="null" data-placeholder="true">Select Book Type</option>
<option ></option>
</select>
</div>
<div data-role="fieldcontain">
<label for="pgAddComicBookPublicationDate" id="lblpgAddComicBookPublicationDate">Publication Date<span style='color:red;'>*</span></label>
<input required data-options='{"mode":"flipbox","dateFormat":"%Y-%m-%d","overrideDateFormat":"%Y-%m-%d"}' title="Enter publication date here." type="text" name="pgAddComicBookPublicationDate" id="pgAddComicBookPublicationDate" placeholder="Enter publication date here." autocomplete="off" data-role="datebox"></input>
</div>
<div data-role="fieldcontain">
<label for="pgAddComicBookBookValue" id="lblpgAddComicBookBookValue">Book Value<span style='color:red;'>*</span></label>
<input required title="Enter book value here." type="number" name="pgAddComicBookBookValue" id="pgAddComicBookBookValue" placeholder="Enter book value here." autocomplete="off" data-clear-btn="true"></input>
</div>
<div data-role="fieldcontain">
<label for="pgAddComicBookPrice" id="lblpgAddComicBookPrice">Price<span style='color:red;'>*</span></label>
<input required title="Enter price here." type="number" name="pgAddComicBookPrice" id="pgAddComicBookPrice" placeholder="Enter price here." autocomplete="off" data-clear-btn="true"></input>
</div>
<div data-role="fieldcontain">
<input required title="" type="checkbox" name="pgAddComicBookSigned" id="pgAddComicBookSigned" autocomplete="off" value="Signed"></input>
<label for="pgAddComicBookSigned" id="lblpgAddComicBookSigned">Signed<span style='color:red;'>*</span></label>
</div>
<div data-role="fieldcontain">
<input required title="" type="checkbox" name="pgAddComicBookBagged" id="pgAddComicBookBagged" autocomplete="off" value="Bagged"></input>
<label for="pgAddComicBookBagged" id="lblpgAddComicBookBagged">Bagged<span style='color:red;'>*</span></label>
</div>
<div><button type="submit" id="pgAddComicBookSave" class="ui-btn ui-corner-all ui-shadow ui-btn-b">Save Comic Book</button>
</div>
<div><button id="pgAddComicBookDelete" class="ui-btn ui-corner-all ui-shadow">Delete Comic Book</button>
</div>
<div><button id="pgAddComicBookBack" class="ui-btn ui-corner-all ui-shadow">Cancel</button>
</div>
</form>
</div>
</div>
</div>

此屏幕没有标题按钮,但由于它定义为拆分视图,因此可以从同一屏幕创建、更新和删除漫画书。我决定在这里改进导航,并将这些按钮放在屏幕底部,因为这样对用户来说更方便。返回上一屏幕,只需点击取消即可。

您会注意到,当您运行应用时,会显示一个吐司消息通知用户记录已保存。

我们在左侧 div 中定义了

<div style="width:30%;float:left;">

右侧 div 中有

<div style="width:65%;float:left;margin-left:35px;">

左侧 div 包含 ListView,右侧 div 包含漫画书详细信息。每次保存漫画书时,ListView 都会使用添加的记录进行更新。

// code to run when the Save button is clicked on Add page.
// Save click event on Add page
$('#pgAddComicBookSave').on('click', function(e){
e.preventDefault();
e.stopImmediatePropagation();
// save the Comic Book
var ComicBookRec;
//get form contents into an object
ComicBookRec = pgAddComicBookGetRec();
//save object to localstorage
app.addComicBook(ComicBookRec);
});

漫画书详细信息由 pgAddComicBookGetRec 读取并存储在 ComicBookRec 中。您会注意到此应用程序中的命名约定。为了便于维护和可读性,控件会通过它们所属的页面和相应的方法来引用。pgAdd 表示我定义的用于添加的页面。

// save the defined Add page object to local storage
// add a new record to localstorage.
app.addComicBook = function(ComicBookRec){
// get Comic Book records.
var ComicBookObj = app.getComicBook();
// define a record object to store the current details
var Title = ComicBookRec.Title;
// cleanse the record key of spaces.
Title = Title.replace(/ /g,'-');
// update local storage object with new record.
ComicBookObj[Title] = ComicBookRec;
//sort the objects.
var keys = Object.keys(ComicBookObj);
keys.sort();
var sortedObject = Object();
var i;
for (i in keys) {
key = keys[i];
sortedObject[key] = ComicBookObj[key];
}
ComicBookObj = sortedObject;
// save all existing records to localstorage.
localStorage['mycomicbooks-comicbook'] = JSON.stringify(ComicBookObj);
toastr.success('Comic Book record saved.', 'My Comic Books');
//find which page are we coming from, if from sign in go back to it
var pgFrom = $('#pgAddComicBook').data('from');
switch (pgFrom) {
case "pgSignIn":
$.mobile.changePage('#pgSignIn', {transition: 'slide'});
break;
default:
// clear the edit page form fields
pgAddComicBookClear();
//stay in the same page to add more records
app.pgAddComicBookcheckForComicBookStorage();}
};

我还确保尽可能多地注释代码。点击底部的保存按钮会调用一个名为

app.addComicBook

这基本上会读取所有可用的 LocalStorage 漫画书,添加/更新已添加的漫画书,按标题对所有漫画书进行排序,然后将它们保存回 LocalStorage,使用以下命令清除屏幕内容

pgAddComicBookClear();

并通过执行以下操作重新加载左侧 div 的 ListView

app.pgAddComicBookcheckForComicBookStorage();}

我在这里关于 使用 JQuery Mobile 创建 CRUD Web 应用程序 的文章更详细地介绍了这些类型的应用程序。

设计报表启动板:HTML 定义 - 图 6

<div id="pgReports" data-role="page" data-theme="b" class="my-page">
<header id="pgReportsheader" data-role="header" data-position="fixed">
<h1>My Comic Books > Reports</h1>
</header>
<div id="pgReportscontent" data-role="content">
<ul data-role="listview" data-inset="true" id="sbRptItems">
<li data-icon="false"><a data-transition="slide" id="sbRptBack" href="#pgMenu"><h2>Main Menu</h2><p>Go to Main Menu</p><img height="200" width="100%" src="apps80.png" alt="Main Menu" class="ui-li-thumb"></img></a></li>
<li data-icon="false"><a data-transition="slide" id="sbRptComicBook" href="#pgRptComicBook"><h2>Comic Books Report</h2><p>View & Export Comic Books</p><img height="200" width="100%" src="apps80.png" alt="Comic Books" class="ui-li-thumb"></img></a></li>
<li data-icon="false"><a data-transition="slide" id="sbRptBookType" href="#pgRptBookType"><h2>Book Types Report</h2><p>View & Export Book Types</p><img height="200" width="100%" src="apps80.png" alt="Book Types" class="ui-li-thumb"></img></a></li>
<li data-icon="false"><a data-transition="slide" id="sbRptGrade" href="#pgRptGrade"><h2>Grades Report</h2><p>View & Export Grades</p><img height="200" width="100%" src="apps80.png" alt="Grades" class="ui-li-thumb"></img></a></li>
</ul>
</div>
<footer id="pgReportsfooter" data-role="footer" data-position="fixed">
<h1>Powered by JQM.Show © Anele Mbanga 2015</h1>
</footer></div>

图 6 中显示的定义由这段代码生成。图书类型和评分遵循相同的设计方法。

设计漫画书表格:HTML 定义 - 图 7

<div id="pgRptComicBook" data-role="page">
<header id="pgRptComicBookheader" data-role="header" data-position="fixed">
<h1>My Comic Books > Comic Books</h1>
<a data-role="button" id="pgRptComicBookBack" data-icon="arrow-l" class="ui-btn-left">Back</a>
<a data-role="button" id="pgRptComicBookNew" data-icon="plus" data-theme="b" class="ui-btn-right">New</a>
</header>
<div id="pgRptComicBookcontent" data-role="content">
<table id="RptComicBook" data-column-btn-text="Columns To Display" data-column-btn-theme="b" data-role="table" data-mode="columntoggle" data-column-popup-theme="a" class="ui-responsive table-stroke table-stripe ui-shadow ui-body-d">
<caption>Comic Books Report</caption>
<thead>
<tr class="ui-bar-d">
<th >Title</th>
<th data-priority="2">Issue Number</th>
<th data-priority="3">Grade</th>
<th data-priority="4">Book Type</th>
<th data-priority="5">Publication Date</th>
<th data-priority="6">Book Value</th>
<th data-priority="7">Price</th>
<th data-priority="8">Signed</th>
<th data-priority="9">Bagged</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr><td></td></tr>
<tr><td colspan="9">Powered by JQM.Show - https://play.google.com/store/apps/details?id=com.b4a.JQMShow</td></tr></tfoot>
</table>
<div data-role="fieldcontain">
<a download="ComicBook.xls" onclick="return ExcellentExport.excel(this, 'RptComicBook', 'ComicBook');" id="pgRptComicBookExport" data-corners="true" href="#" class="ui-btn ui-shadow">Export to Excel</a></div>
</div>
</div>

此页面有一个返回按钮,还有一个显示用于创建新漫画书的新按钮。表格的标题列是强制性的,因此不会显示在切换列选择器中。这是因为它不是用 data-priority 定义的,而是作为

<th >Title</th>

您可以更改在切换按钮选择器中显示的文本,方法是更改表格中此属性的文本。

data-column-btn-text="Columns To Display"

也可以在这里更改按钮主题,在可用的 a-e 主题之间进行选择。

data-column-btn-theme="b"

点击切换按钮时显示的弹出窗口的主题在这里定义

data-column-popup-theme="a"

我们希望表格易于用户阅读,因此通过将此项作为类的一部分来条纹化行

table-stripe

添加页面上的 ListView 是可点击的,当用户点击每个漫画时,会调用此方法

//listview item click eventt.
$(document).on('click', '#pgAddComicBookList a', function(e){
e.preventDefault();
e.stopImmediatePropagation();
//get href of selected listview item and cleanse it
var href = $(this)[0].href.match(/\?.*$/)[0];
var Title = href.replace(/^\?Title=/,'');
//read record from local storage and update screen.
app.pgAddComicBookeditComicBook(Title);
});

这会读取每个已添加列表项的 href,并调用 app.pgAddComicBookeditComicBook 方法,该方法从 LocalStorage 读取漫画书详细信息并将其显示在屏幕上。由于此 ListView 是动态的,每次添加漫画时都需要重新加载。此动态加载是通过此方法完成的。

//display records in listview during runtime.
app.pgAddComicBookdisplayComicBook = function(){
// get Comic Book records.
var ComicBookObj = app.getComicBook();
// create an empty string to contain html
var html = '';
// make sure your iterators are properly scoped
var n;
// loop over records and create a new list item for each
//append the html to store the listitems.
for (n in ComicBookObj){
//get the record details
var ComicBookRec = ComicBookObj[n];
//define a new line from what we have defined
var nItem = ComicBookLi;
//update the href to the key
nItem = nItem.replace(/Z2/g,n);
//update the title to display, this might be multi fields
var nTitle = '';
nTitle += ComicBookRec.Title;
nTitle += ', ';
nTitle += ComicBookRec.IssueNumber;
//replace the title;
nItem = nItem.replace(/Z1/g,nTitle);
//there is a count bubble, update list item
var nCountBubble = '';
nCountBubble += ComicBookRec.Price;
//replace the countbubble
nItem = nItem.replace(/COUNTBUBBLE/g,nCountBubble);
//there is a description, update the list item
var nDescription = '';
nDescription += ComicBookRec.Grade;
nDescription += ', ';
nDescription += ComicBookRec.BookType;
nDescription += ', ';
nDescription += ComicBookRec.PublicationDate;
//replace the description;
nItem = nItem.replace(/DESCRIPTION/g,nDescription);
html += nItem;
}
//update the listview with the newly defined html structure.
$('#pgAddComicBookList').html(ComicBookHdr + html).listview('refresh');
};

以上是上面图 7 的基本介绍。

导出到 Excel

通过此 js 文件可以实现此功能,该文件在 HTML 5 应用程序中进行了定义。

<script src="excellentexport.min.js" type="text/javascript"></script>

例如,在这样定义按钮时调用它。

<div data-role="fieldcontain">
<a download="ComicBook.xls" onclick="return ExcellentExport.excel(this, 'RptComicBook', 'ComicBook');" id="pgRptComicBookExport" data-corners="true" href="#" class="ui-btn ui-shadow">Export to Excel</a></div>
</div>

我必须说,这是一个非常有趣的发现。但这不会导出 CSS 定义。我还必须添加以下 CSS 以使表格更用户友好。

tr { border-bottom: 1px solid #d6d6d6;}
th { border-bottom: 1px solid #d6d6d6;}
caption {font-weight: bold; font-size: 1.1em;}

标题

我了解到,在某些情况下,标题末尾会显示一个 ...,其中标题文本很长。可以通过添加此样式来消除这种情况。

.ui-header .ui-title, .ui-footer .ui-title {margin-right: 0 !important; margin-left: 0 !important;}

和这个

$('#msgboxheader h1').text('Confirm Delete');

is a very easy method to change a header title for any element with <h1></h1> definition.

我们已经定义了我们的漫画收藏应用程序并创建了所有相关的代码,现在是时候将此应用程序编译为 phonegap 了。

制作我的漫画收藏为混合应用 - 这是商业的,但是,如果您免费编译,您的项目将是开源的,因为它们将在 GitHub 上。

1. 获取 GitHub for Windows,它可以让您轻松地将文件夹拖放到 GitHub,并进行同步和发布。正如您将注意到的,随附的 **config.xml** 文件包含了轻松将您的应用编译到 phonegap 的所有必要内容。注册 github.com 并将您的桌面 GitHub 与 Web 版关联。

一旦您的桌面已发布,您的 Web 版本应该会显示如下:请看下面的 My-Comic-Books

2. 在 https://build.phonegap.com 上注册 PhoneGap Build 并关联您的 GitHub 帐户。您可以在那里找到如何执行此操作的说明,或者只需将您的 GitHub URL 复制到 PhoneGap Build。

点击“准备构建”并构建您的应用程序,您的应用程序将由相应平台上的编译器构建。对于 iPhone/iPad,您需要一个 Apple 开发者帐户,他们会在此处注册您的个人资料详细信息,以便您可以编译 ipa。目前,我们只需编译并获取 Android 的 apk。还有 Windows Phone 的 xap。

您可以使用 MoboRobo 将您的新应用程序安装到您的移动设备上。

将我的漫画收藏安装到实际设备(使用 MoboRobo 截屏)

在实际设备上运行我的漫画收藏

由于此应用程序是为大屏幕设计的,因此在此设备上的输入屏幕显示不正常。我在此处包含了一个指向 apk 的 Dropbox 文件的链接,供您下载并在您的设备上进行探索。就这样。

关注点

虽然使用 JQuery Mobile 开发具有出色功能的应用程序可能相对容易,但在将所有内容编译为 PhoneGap Build 的混合应用程序的过程中,起初有些困难。有 GitHub for Desktop 和 GitHub Web 版可以检查一切是否正常工作,然后是 PhoneGap Build。然而,任何想要发布应用程序的人都必须经历这些步骤才能得到最终产品。

最令人欣慰的是在实际移动设备上运行输出。我认为 PhoneGap Build 团队在这方面做得非常出色,提供了这样的工具。MoboRobo 应用的存在也使得轻松截取设备屏幕截图以及为 iPhone/iPad/Android 设备安装应用程序变得容易。

在 JQ Mobile 中创建表格对我来说是一次愉快的练习,然后找到导出到 Excel 的功能是一个绝佳的补充。我正在开发一个全 RAD 工具来实际构建 JQuery Mobile 应用程序,而这些补充功能让我大开眼界。这样的 RAD 工具将能够构建所有这些移动应用程序,包括相应的脚本,而无需任何编程经验,也不需要编写任何代码。

我还想在我的 JQuery Mobile 应用程序中使用列表视图、开关和可折叠视图来添加权限。我似乎还没有看到类似的东西。

© . All rights reserved.