在移动浏览器中添加图表

使用 PHP、XML、jQuery、jQuery Mobile 和 jQuery charting 创建一个精巧的互动式移动民意测验应用程序

使用 PHP、XML、jQuery、jQuery Mobile 和 jQuery charting 为移动环境开发缩略型和用户友好的在线应用程序。在本文中,我们将创建民意测验应用程序的前端和后端,该应用程序使用图表来显示每个调查的结果。

Jack D. Herrington, 高级软件工程师, Fortify Software, Inc.

Jack Herrington 的照片Jack Herrington 是一位生活和工作在海湾地区的工程师、作家和主持人。您可以通过 http://jackherrington.com 来关注他的工作和作品。



2011 年 10 月 31 日

在移动平台上的图表

移动技术引起了人们浓厚的兴趣,很容易就能明白个中原因。从前些年起,现代手机的性能就开始能与台式电脑的性能相抗衡了。我口袋里揣着的手机是双核的!实际上,电话机的 “电话” 用途已经开始被淡化了。Web 浏览和应用才是手机的关键所在,拥有诸如 jQuery Mobile 这样的工具,就可以轻易地为您的应用程序创建一个移动友好界面。

常用缩略词

  • Ajax:异步 JavaScript + XML
  • DOM:文档对象模型
  • HTTP:超文本标记语言
  • SQL:结构化查询语言
  • XML:可扩展标记语言

在本文中,我在后端结合使用 PHP 和 XML 技术并且在前端结合使用 jQuery、jQuery Mobile 和 jqPlot 技术,以构建一个可高度交互的民意测验应用程序。在完成的示例应用程序中,站点管理员可以创建具有关联答案的新调查问题。在前端,用户可以浏览调查清单,对于他们最喜欢的答案进行投票,并查看结果图表。您会对于今日移动设备的强大功能而感到惊讶,您还可以使用现成的工具轻易地为这些设备编写应用程序。

先编写后端,然后再来创建应用程序的前端。


创建调查表

要构建后端,首先要为 MySQL 数据库定义数据模型。清单 1 显示了用于创建驱动应用程序的两个表格的代码。

清单 1. db.sql
DROP TABLE IF EXISTS polls;
CREATE TABLE polls(
  id INT NOT NULL AUTO_INCREMENT,
  question TEXT NOT NULL,
  primary key ( id ) );

DROP TABLE IF EXISTS answers;
CREATE TABLE answers(
  id INT NOT NULL AUTO_INCREMENT,
  poll INT NOT NULL,
  answer TEXT NOT NULL,
  count INT,
  primary key ( id ) );

第一个表格 polls 包含各种不同的调查,每个调查都包含一个问题。第二个表格 answers 包含每个调查问题的答案,使用 poll 字段管理关系和当前答案投票数的计数值。

这并不是一个适于生产的民意测验应用程序模式。一个适于生产的模式必须包含一个用于追踪用户投票的投票表格,以确保每个人只投票一次。因为民意测验的准确性并不是本文要关心的重点,所以如果您真的想构建一个民意测验应用程序的话,关于这方面的修改就留给大家来完成。

要构建数据库,首先要使用 mysqladmin 来进行创建,再使用 mysql 命令来运行 db.sql 脚本,如下所示:

% mysqladmin --user=root --password=foo create polls
% mysql --user=root --password=foo polls < db.sql

创建数据库后,现在就可以构建 PHP 页面,以便您可以向数据库添加新调查。清单 2 显示了create.php 网页的代码。

清单 2. create.php
<?php
function add_answer( $db, $qid, $answer ) {
  $sql = 'INSERT INTO answers VALUES ( 0, ?, ?, 0 )';
  $sth = $db->prepare($sql);
  $sth->execute( array( $qid, $answer ) );
}

$dd = new PDO('mysql:host=localhost;dbname=polls', 'root', '');
if ( isset( $_POST['question'] ) ) {
  $sql = 'INSERT INTO polls VALUES ( 0, ? )';
  $sth = $dd->prepare($sql);
  $sth->execute( array( $_POST['question'] ) );
  $qid = $dd->lastInsertId();
  if ( isset( $_POST['a1'] ) && strlen( $_POST['a1'] ) > 0 )
    add_answer( $dd, $qid, $_POST['a1'] );
  if ( isset( $_POST['a2'] ) && strlen( $_POST['a2'] ) > 0 )
    add_answer( $dd, $qid, $_POST['a2'] );
  if ( isset( $_POST['a3'] ) && strlen( $_POST['a3'] ) > 0 )
    add_answer( $dd, $qid, $_POST['a3'] );
  if ( isset( $_POST['a4'] ) && strlen( $_POST['a4'] ) > 0 )
    add_answer( $dd, $qid, $_POST['a4'] );
  if ( isset( $_POST['a5'] ) && strlen( $_POST['a5'] ) > 0 )
    add_answer( $dd, $qid, $_POST['a5'] );
}
?>
<html>
<body>
<form method="post">
<table>
<tr><th>Question</td><th><input type="text" name="question" /></td></tr>
<tr><th>Answer 1</th><td><input type="text" name="a1" /></td></tr>
<tr><th>Answer 2</th><td><input type="text" name="a2" /></td></tr>
<tr><th>Answer 3</th><td><input type="text" name="a3" /></td></tr>
<tr><th>Answer 4</th><td><input type="text" name="a4" /></td></tr>
<tr><th>Answer 5</th><td><input type="text" name="a5" /></td></tr>
</table>
<input type="submit" value="Add Question" />
</form>
</body>
</html>

位于脚本顶端的 PHP 代码是用于在表单发布后将问题及其答案添加至数据库的代码。PHP 代码所要做的第一件事情是连接数据库。然后再查看是否有问题发布。如果有问题发布,它会首先在调查表格中插入一条新的记录并返回它的惟一 ID。随后,它会使用脚本最顶端所定义的 add_answer 函数来添加每个答案。这个 add_answer 函数仅将新记录添加至带有相关调查的答案和惟一 ID 的答案表格。

在 PHP 文件的底部是表单的 HTML 代码。当用户选择提交按钮时表单标记会将表单中的值提交回脚本。

图 1 显示了在浏览器中的效果。

图 1. 添加一个调查问题
带有六个字段(问题和答案) 和 Add Question 按钮的 Web 表单的屏幕截图

图 1 显示了一个非常简单的 Web 表单,只包含 6 个输入字段。第一字段为问题,其后的五个字段均为答案输入。在底部有一个 Add Question 按钮,用于将问题添加至数据库。

当您单击 Add Question 时,程序会将记录添加至数据库并且再次返回空白表单,以便您添加另一个问题。要实际查看可用的调查,您需要一个新的脚本。清单 3 中的 polls.php 脚本返回一个 XML 块,列出了当前可用的调查。

清单 3. polls.php
<?php
header( 'Content-Type:text/xml' );

$dbh = new PDO('mysql:host=localhost;dbname=polls', 'root', '');

$sql = 'SELECT * FROM polls';

$q = $dbh->prepare( $sql );
$q->execute( array() );

$doc = new DOMDocument();
$r = $doc->createElement( "polls" );
$doc->appendChild( $r );

foreach ( $q->fetchAll() as $row) {
  $e = $doc->createElement( "poll" );
  $e->setAttribute( 'id', $row['id'] );
  $e->setAttribute( 'question', $row['question'] );
  $r->appendChild( $e );
}

print $doc->saveXML();
?>

该脚本会先与数据库连接,并对调查表运行一个简单的 SELECT 查询。之后,脚本会创建一个新的 XML DOM 文档,在根部添加一个 “polls” 标记,然后为每个调查添加各个 “poll” 标记。每个 poll 标记都拥有调查的惟一 ID 以及问题文本。

要测试页面,只需在浏览器中浏览并选择 View Source 来查看返回的内容。另一个选项是使用 curl 来获取返回的 XML,如下所示:

$ curl "http://localhost/poll/polls.php"
<?xml version="1.0"?>
<polls><poll id="1" question="Is jQuery great?"/></polls>
$

前端使用 Ajax 请求来访问此脚本。

接下来的三个脚本处理调查的答案部分。为了简单起见,首先创建一个名为 build_answers 的 helper 函数,当给定一个数据库句柄或调查 ID 后,该函数会输出该调查所有答案的 XML(参见 清单 4)。

清单 4. build_answers.php
<?php
function build_answers( $dbh, $poll ) {
  $sql = 'SELECT * FROM answers WHERE poll=?';

  $q = $dbh->prepare( $sql );
  $q->execute( array( $poll) );

  $doc = new DOMDocument();
  $r = $doc->createElement( "answers" );
  $doc->appendChild( $r );

  foreach ( $q->fetchAll() as $row) {
    $e = $doc->createElement( "answer" );
    $e->setAttribute( 'id', $row['id'] );
    $e->setAttribute( 'answer', $row['answer'] );
    $e->setAttribute( 'count', $row['count'] );
    $r->appendChild( $e );
  }

  print $doc->saveXML();
}
?>

build_answers 函数首先连接至数据库执行 SQL 查询来获取指定调查的答案。之后,再使用 XML DOM 代码来创建一个新的 XML 输出,该输出在根部带有一个 “answer” 标记,以及该调查所有答案的 “answer” 标记。每个答案都拥有其惟一的 ID、答案文本和该答案的投票计数。

要将此挂钩至一个页面,您需要使用 清单 5 中所示的 answers.php 脚本。

清单 5. answers.php
<?php
require_once( 'build_answers.php' );

header( 'Content-Type:text/xml' );

$dbh = new PDO('mysql:host=localhost;dbname=polls', 'root', '');
build_answers( $dbh, $_REQUEST['id']  );
?>

这个简单的脚本连接至数据库,并将来自查询的 ID 发送至 build_answers 函数中。

下面显示的是从请求到该脚本的一个示例:

$ curl "http://localhost/poll/answers.php?id=1"
<?xml version="1.0"?>
<answers>
       <answer id="1" answer="Yep, awesome!" count="7"/>
       <answer id="2" answer="It's pretty good." count="2"/>
       <answer id="3" answer="It's ok." count="1"/>
       <answer id="4" answer="Nah, it's not so hot." count="1"/>
</answers>
$

构建服务的最后一步就是创建一个 Ajax 页面,用于投票。清单 6 显示了该脚本。

清单 6. vote.php
<?php
require_once( 'build_answers.php' );

header('Content-Type: text/xml');

$poll = 0;

$dd = new PDO('mysql:host=localhost;dbname=polls', 'root', '');
if ( isset( $_REQUEST['id'] ) ) {
  $sth = $dd->prepare("SELECT count, poll FROM answers WHERE id=?");
  $sth->execute( array( $_REQUEST['id'] ) );
  $count = 0;
  foreach ( $sth->fetchAll() as $row) {
    $count = $row['count'];
	$poll = $row['poll'];
  }
  $count++;
  $sql = 'UPDATE answers SET count=? WHERE id=?';
  $sth = $dd->prepare($sql);
  $sth->execute( array( $count, $_REQUEST['id'] ) );
}

build_answers( $dd, $poll );
?>

该脚本与 answers.php 页面的区别在于您得先查找指定答案的计数,然后再增大它,并更新数据库中的答案记录。此外,当脚本找到答案,会在 $poll 变量中存储相关调查的惟一 ID。接着该调查 ID 会被发送至 build_answers 以输出当前的答案集。

导出答案当前状态的重要之处在于,可以一步完成投票和获取当前调查结果的操作。由于可选方案是先执行投票操作,然后再发出答案请求以获取当前的投票计数,所以能够通过投票脚本返回当前答案计数列表来一步完成以上所有操作,绝对是最理想的方案。

后端脚本完成后,就开始进入前端脚本的编写。


构建前端

移动界面与传统的 Web 页面有很大的差别。选项的数量减少,可用的选项和按钮越做越大,以便您能够轻松用姆指进行浏览。现在您要花费大量的时间来构建这一大堆的东西,但是有了 jQuery Mobile,为什么还要那么麻烦呢?jQuery Mobile 是一款用户界面工具包,构建于非常受欢迎的 jQuery JavaScript 库之上。

界面构建在一个 Web 页面的三个 “页面” 中。第一页包含问题列表。当用户选择一个问题,页面就会滑动至左边显示第二页,第二页显示了所选择问题的答案列表。当用户选择一个答案,页面就会滑动至左边显示第三页,第三页显示了所选择调查的结果图表。要构建该图表,使用非常酷的 jqPlot 库,它也构建于 jQuery 之上。

清单 7 显示该代码。

清单 7. index.html
<html><head>
<link rel="stylesheet" href="css/jquery.mobile-1.0a4.1.css" />
<link rel="stylesheet" type="text/css" href="css/jquery.jqplot.css" />
<script src="js/jquery-1.6.1.min.js"></script>
<script src="js/jquery.mobile-1.0a4.1.js"></script>
<script language="javascript" type="text/javascript" 
        src="js/jquery.jqplot.js"></script>
<script language="javascript" type="text/javascript" 
        src="js/plugins/jqplot.donutRenderer.js"></script>

<script type="text/javascript">
function plotData( data ) {
  ds = [];
  $(data).find('answer').each( function() {
    ds.push( [ $(this).attr('answer'), parseInt( $(this).attr('count') ) ] );
  } );
  $.jqplot('chart1', [ds], {
    seriesDefaults:{
       renderer:$.jqplot.DonutRenderer
    },
    legend: {show:true}
  });
}
function vote( poll, answer ) {
  $.ajax( { url: 'vote.php',
    data:{id:answer},
    success:function( data ) {
     plotData( data );
    }
  });
}
function openPoll( poll ) {
  $.ajax( { url: 'answers.php',
    data:{id:poll},
    success:function( data ) {
      $(data).find('answer').each( function() {
        var name = $(this).attr('answer');
        var id = $(this).attr('id');
        $('#answer-list').append(
          '<li><a href="#results" poll="'+poll+'" answer="'+id+'" 
                  class="answer">'+name+'</a></li>'
        );
      } );
      $('.answer').click(function(e) {
      vote( $(this).attr('poll'),
            $(this).attr('answer') );
      })
      $('ul').listview('refresh');
    }
  });
}
$(document).ready(function(){
   $.jqplot.config.enablePlugins = true;
   $.ajax( { url: 'polls.php',
     success:function( data ) {
       $(data).find('poll').each( function() {
         var name = $(this).attr('question');
         var id = $(this).attr('id');
         $('#poll-list').append(
           '<li><a href="#answers" pollid="'+id+'" class="poll">'+name+'</a></li>'
         );
       });
       $('.poll').click(function(e) {
         openPoll( $(this).attr('pollid') );
       })
       $('ul').listview('refresh');
     }
   });
});
</script>

</head><body>
  <div data-role="page" id="home"> 
    <div  data-role="header"><h1>Poll Central</h1></div> 
    <div  data-role="content">
  <ul data-role="listview" data-inset="true" data-theme="c" data-dividertheme="b" 
       id="poll-list">
  </ul>
      <div id="chart0" class="plot" style="width:400px;height:280px;"></div>
      </div>
  </div> 

  <div data-role="page" id="answers">
    <div data-role="header">
      <h1 id="poll-question">Question</h1>
    </div>
    <div data-role="content">  
    <ul data-role="listview" data-inset="true" data-theme="c" data-dividertheme="b" 
        id="answer-list">
    </ul>
    </div>
  </div>

  <div data-role="page" id="results">
    <div data-role="header">
      <h1>Results</h1>
    </div>
    <div data-role="content"> 
    <div data-role="collapsible">
    <h3>Results Graph</h3>
    <p>
      <div class="jqPlot" id="chart1" style="height:320px; width:480px;"></div>
    </p>
    </div>
    
    <a href="#home" data-role="button">Start Again</a>
    </div>
  </div>
</body></html>

这段代码较多,但是如果您把它理解成是将三个不同页面集合在一个页面,就会相对比较好理解。它从主页开始,主页在正文标记的开始处定义。主页中的 ul 列表标签填充有 Ajax 请求,当文档准备就绪即可运行。该 Ajax 请求会调用 polls.php 脚本来返回一些 XML,再将其解析并转换成 li 标签,将其添加至 ul 标签。

当用户单击主页的调查列表项,会显示出答案页面。接着调用 openPoll 函数,它通过使用与主页相同的技术填充答案页面的 li 标签。

当选中其中一个答案,会在 vote 方法中调用 vote.php 脚本。当 Ajax 方法返回,plotData 函数被调用,该函数使用 jqPlot 库来构建附带结果的 "环状图表"。

看一下此过程。图 2 显示了主页。

图 2. 主屏幕
包含问题的主屏幕的屏幕截图

图 2 显示了一个移动友好页面,其中包含了每个可用调查的列表按钮。然而,在本例中,由于只有一个调查,所以也只有一个按钮。当您单击此按钮,会看到如 图 3 所示的答案页面。

图 3. 答案页面
含有四个调查答案的主屏幕的屏幕截图

图 3 显示了该调查的答案列表。该页面还包含了一个后退按钮,让用户返回到主页。jQuery Mobile 一切都为您考虑好了,您可以将精力集中于应用程序上。

单击一个答案来注册投票,并且查看结果图表,如 图 4 所示。

图 4. 第一个结果
一个投票的环状结果图表的屏幕截图

图 4 显示了一个酷似环状的图表。由于只有一个投票,所以您在数据系列中只能看到一个元素。要添加更多的投票,单击后退按钮进行多次投票。您将看到如 图 5 所示的结果。

图 5. 添加多个投票后
多投票的环状结果图形的屏幕截图

看起来好多了! 每个问题占了图形相对于总投票数的部分比例。

结束语

移动设备是否是未来趋势?如果不是,它们也会占据未来的一大部分。iPhones、iPads、Android 手机和平板电脑越来越畅销。人们想要拥有便利的设备,可以让他们访问一些缩略型和用户友好的在线应用程序。从应用程序开发的角度来看,问题在于尽力为这些设备开发多版本的用户界面。像 jQuery 和 jQuery Mobile 这样的库能让您轻松地构建出可在移动环境下运行的用户界面。

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML, Open source, Web development
ArticleID=768623
ArticleTitle=在移动浏览器中添加图表
publish-date=10312011