用户操作
[即时聊天] [发私信] [加为好友]
付江ID:java060515
260868次访问,排名237好友48人,关注者256
关注软件开发和互联网业界
java060515的文章
原创 29 篇
翻译 37 篇
转载 42 篇
评论 494 篇
付江的公告
最近评论
sunhao_v:的确好东西,收藏再说!
sunhao_v:的确好东西,收藏再说!
sunhao_v:的确好东西,收藏再说!
sunhao_v:的确好东西,收藏再说!
sunhao_v:的确好东西,收藏再说!
文章分类
    收藏
      相册
      存档
      软件项目交易
      订阅我的博客
      XML聚合  FeedSky
      订阅到鲜果
      订阅到Google
      订阅到抓虾
      订阅到BlogLines
      订阅到Yahoo
      订阅到GouGou
      订阅到飞鸽
      订阅到Rojo
      订阅到newsgator
      订阅到netvibes

      转载 利用Adobe AIR创建桌面对话应用程序收藏

      新一篇: 构建执行插入、更新和删除操作的Web应用程序(上) | 旧一篇: Flex中的MySQL管理

      桌面程序是Adobe® Integrated RuntimeAIR)的杀手级应用(killer app)。在与其他工程师谈论Adobe AIR时,他们经常会提出同一个问题:什么是Adobe AIR的杀手级应用?对这个问题考虑的越深入,我就越觉得,除了工程师或架构师以外,其他人只是想使用某些应用程序中的桌面功能。

      以对话应用程序为例。当然,可以通过web进行对话。AOL就有基于webAOL Instant MessengerAIM),还有其他不计其数的web对话小部件。 但人们仍然喜欢使用AIMiChatTrillian客户机,因为他们觉得用着方便。用户无须打开浏览器即可启动对话客户机。这些客户机的特点与桌面应用程序一样:拥有本地菜单;在系统托盘中显示;拥有弹出式本地窗口;可与其他本地应用程序合作;用户双击即可打开等等。

      用户能够通过Adobe AIR访问跨平台技术中的所有桌面功能,同时还能使用开发web应用程序所用的那些开发工具。这真是太完美了!

      本文将演示如何创建基于Adobe AIR的简单对话应用程序。它将与利用JSON传输数据的Ruby on Rails web应用程序进行通讯。为执行与Ajax有关的任务,它将利用广受欢迎的Prototype.js库与Rails应用程序进行通讯。

      Adobe AIR对话应用程序还符合Adobe AIR新安全模型,这可确保从服务器上下载且在客户机上执行的JavaScript代码不会直接访问Adobe AIR应用程序编程接口(API)。通过这种方法,就不会通过强大的Adobe AIR接口下载恶意代码,以至于危害用户的计算机。

      从图1可以看到,这个Adobe AIR对话应用程序实际上是由不同的HTML页面构成的:root.htm页面,它可访问Adobe AIR API并可利用这些API维护对话消息的本地数据库缓存;ui.htm页面,它可利用Prototype.js库与Rails服务器进行通讯。

      本地缓存数据库

      Rails服务器

      AJAX代码

      数据库

      1. 处理与Ajax有关的任务以及本地数据库缓存的两个AIR页面

      Rails新安全模型中,root.html页面禁止运行解释JSON代码所必需的eval命令。这可防止JSON代码诱骗客户机运行Adobe AIR方法,以至对客户机器造成损害。

      ui.html页面可运行eval命令,以解释来自服务器的JSON响应。但它不能直接访问Adobe AIR接口。在root.htmlui.html这两个页面之间有一个特殊“桥梁”,当它下载了新消息时,ui.html页面就能通知root.html页面。这种更小型、更安全的“桥梁”就是使客户机免受恶意代码侵害的机制。

      对整个对话系统有了基本理解后,接下来将着手创建Rails服务器后台应用程序。

      创建Rails服务器

      要创建Rails应用程序,首先选择Macintosh中很好用的Locomotive应用程序。在Windows平台中,可使用Instant Rails。这些套装应用程序都拥有创建Rails应用程序所需的一切功能。它们的界面很简单,如图2所示,其中将显示机器中的所有应用程序,并可创建新应用程序。

      2. Locomotive界面

       

      我要求Locomotive创建名为chat的新应用程序。然后利用chat应用程序中的上下文菜单在主页面中加载浏览器,如图3所示。

      3. Rails加载页面

      好了,一切就绪。现在要创建数据库和对话模型了。首先创建三个MySQL数据库:chat_developmentchat_testchat_production。然后利用Rails生成器创建名为messages的新模型。

      然后编辑生成的001_create_messages.rb文件,并添加要在表格中显示的数据列,如清单1所示。

      清单 1. 001_create_messages.rb

      class CreateMessages < ActiveRecord::Migration

        def self.up

          create_table :messages do |t|

            t.column :user, :string

            t.column :posted, :datetime

            t.column :message, :string

          end

        end

       

        def self.down

          drop_table :messages

        end

      end

      我添加了三列:用户名字段、消息提交日期和时间字段以及消息正文字段。如果想使用其他对话主题扩展本例,只需在此处添加其他表格并在控制器中增加新方法。

      我还对models目录下的message.rb文件进行了必要的修改,如清单2所示。

      清单 2. message.rb

      class Message < ActiveRecord::Base

      end

      瞧瞧,我没进行任何修改。Rails的功能难道不齐全吗?

      接着要创建对话控制器,Adobe AIR对话应用程序将利用该控制器发送新消息并选择新消息。清单3就是该控制器的代码。

      清单 3. chat_controller.rb

      class ChatController < ApplicationController

        scaffold :message

       

        def post

          msg = Message.new

          msg.user = params[:user]

          msg.message = params[:message]

          msg.posted = DateTime.now

          msg.save

         

          render( :text => { :id => msg.id }.to_json )

        end

       

        def getall

          render( :text => Message.find(:all).to_json )

        end

       

        def getsince

          msgs = Message.find( :all, :conditions => [ "id > ?", params[:id ] ] )

          render( :text => msgs.to_json )

        end

      end

      在代码开始部分,调用了经典的Rails scaffolding方法,以在浏览器中显示这些消息。Adobe AIR应用程序开始运行后,就可删除该方法。我还添加了新的post方法,它使用“user”和“message”两个参数,然后将新消息的提交日期设为当日。

      getall方法返回一个JSON数组,数组中包含消息表中的所有数据。getsince方法也返回消息的JSON数组,其中只包含在指定id之后创建的消息。这样可提高Adobe AIR应用程序中的选择效率。

      为测试该控制器,我在Rails应用程序中打开对话控制器。结果如图4所示。

      4. scaffolding列表界面

      由于此时的数据库中没有任何消息,因此列表为空。单击New message链接添加消息,这将打开数据输入表单,如图5所示。

      5.使用scaffolding创建新消息

      在其中输入示例消息并单击Create,这将返回如图6所示的列表。

      5.创建消息后的列表

      该列表显示我有了一条新记录。很好,这表示数据库连接运行正常。但Adobe AIR应用程序不会使用该接口,因此要测试JSON接口。首先是getall方法,我在浏览器中修改URL,使其指向getall动作,就是这样!JSON返回了该条记录,如图7所示。

      1-7.JSON getall动作

      效果真不错。在导出JSON数据时,to_json方法的确非常方便。

      接下来测试post方法,Adobe AIR应用程序将利用该方法提交对话消息。只要在URL中输入post动作,并指定“user”和“message”作为URL参数,就可测试该方法。

      post动作返回以JSON编码的新建记录的id值,如图8所示。

      8. 消息提交动作产生的JSON响应

      最后测试getsince动作。它与getall类似,但使用id参数,并且只返回id值大于指定值的消息。这里我将id指定为0,这将返回所有消息。可以看到返回了两条记录,一条是利用scaffolding添加的消息,另一条是刚刚提交的消息。

      9就是getsince动作的结果。

      9. getsince动作的JSON响应

      以上就是对话示例的服务器部分。认真地讲,Ruby实现这种功能需要多少行代码?也许15行以上。真疯狂!而Rails绝不会让我失望。

      接着创建在用户桌面计算机中运行的AIR对话应用程序。

      创建Adobe AIR对话应用程序

      我设想的对话应用程序其实很简单:在窗口顶端显示对话消息,首先显示用户名,然后显示消息。在窗口底部,我想有个地方可以输入用户名和消息,然后可以单击一个按钮将其添加到对话窗口中。我的要求如图10所示。

      10. AIR对话窗口

      我将其设计得很简单,以方便读者可以根据需要对其进行修改,并创建自己的对话应用程序。

      代码开始部分是XML文件,它告诉应用程序有关Adobe AIR的信息。清单4就是application.xml文件的代码。

      清单 4. application.xml

      <?xml version="1.0" encoding="utf-8" ?>

      <application xmlns="http://ns.adobe.com/air/application/1.0.M5"

        appId="com.example.SimpleChat" version="1.0">

      <name>Simple Chat</name>

      <description></description>

      <copyright></copyright>

       

      <initialWindow>

        <title>Chat Window</title>

        <content>root.html</content>

        <systemChrome>standard</systemChrome>

        <transparent>false</transparent>

        <visible>true</visible>

        <minimizable>true</minimizable>

        <maximizable>true</maximizable>

        <resizable>true</resizable>

        <width>250</width>

        <height>450</height>

        <x>150</x>

        <y>150</y>

      </initialWindow>

      <icon />

      <fileTypes />

      </application>

      其中只有两个关键元素:在<title>标签中指定的对话应用程序的标题;在<width><height>标签中指定的窗口初始大小。其中设置了该页面的用户界面大小,以使其适应250x450的面积。接着是大小调整代码,它用来调整窗口的大小,并修改窗口中所有元素的形状,使其保持清晰。

      清单5就是root.html页面的代码,用以处理数据库。

      清单 5. root.html

      <html>

      <head>

      <title>Chat Window</title>

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

      <script type="text/javascript">

      var db;

       

      var Exposed = {};

       

      Exposed.trace = function(str) { air.trace(str); }

       

      Exposed.alertNewMessage = function() {

        if ( air.Shell.supportsDockIcon ) {

          air.Shell.shell.icon.bounce();

        }

      }

       

      Exposed.addMessage = function( id, user, posted, message ) {

        var del = new air.SQLStatement();

        del.text = "DELETE FROM messages WHERE id = :id;";           

        del.sqlConnection = db;

        del.parameters[':id'] = id;

        del.execute( );

       

        var add = new air.SQLStatement();

        add.text = "INSERT INTO messages (id, user, posted, message) VALUES ( :id, :user, :posted, :message );";           

        add.sqlConnection = db;

        add.parameters[':id'] = id;

        add.parameters[':user'] = user;

        add.parameters[':posted'] = posted;

        add.parameters[':message'] = message;

        add.execute( );

      }

       

      Exposed.getMessages = function() {

        var get = new air.SQLStatement();

        get.text = "SELECT * FROM messages;";

        get.sqlConnection = db;

        get.execute();

        var data = get.getResult().data;

        return ( data == null ) ? [] : data;

      }

       

      function doLoad() {

        document.getElementById('UI').contentWindow.parentSandboxBridge = Exposed;

        window.resize = document.getElementById('UI').contentWindow.childSandboxBridge.onResizeEvent;

       

        db = new air.SQLConnection( true );

        db.open( air.File.applicationResourceDirectory.resolvePath( "chat.db" ), true );

       

        var cr_exp = new air.SQLStatement();

        cr_exp.text = "CREATE TABLE IF NOT EXISTS messages ( id, user, posted, message );";

        cr_exp.sqlConnection = db;

        cr_exp.execute();

       

        if ( window.addEventListener != null )

          window.addEventListener( 'resize', function() { window.resize( ); }, false );

        if ( document.addEventListener != null )

          document.addEventListener( 'resize', function() { window.resize( ); }, false );  

       

        document.getElementById('UI').contentWindow.childSandboxBridge.startMessages();

      }

      </script>

      </head>

      <body onload="doLoad();" style="margin:2px;padding;0px;">

       <iframe id="UI"

           src="http://muttmansion.com/ui.html"

           sandboxRoot="http://muttmansion.com/"

           documentRoot="app-resource:/"

           width="95%"

           height="95%"

           style="border: 0px; margin: 0px; padding: 0px; width: 95%; height:95%">

       </iframe>

      </body>

      </html>

      您也许想知道为何要在对话应用程序中使用本地数据库。道理很简单:为了快速启动。如果旧消息存储在本地,则当每次加载应用程序时就不必回头查找它们。所要做的就是首先显示本地存储的消息,然后要求用户提供排在最后一个id值之后的新消息,这样即可得到所有的新消息。

      doLoad()函数创建了该数据库。然后,为提供给ui.html页面的Exposed变量增加两个方法,分别用于添加消息以及获取消息列表。这就是本文开头提到的root.html沙盒和ui.html沙盒之间的“桥梁”。这可确保在ui.html中运行的JSON评估代码不会运行任何Adobe AIR方法。它只可访问消息添加和消息获取方法。

      alertNewMessageroot.html的另一个函数,它既有趣又有用。当一条需要用户关注的新消息到达时,alertNewMessage就会使Dock栏中的应用程序图标(适用于Mac OS X机器)上下跳动。这是AIR的一项强大功能。它可以方便地访问诸如此类的操作系统特性,使用户感觉是在使用一种全面集成的应用程序。

      root.html的最后一项重要功能是,它可将大小调整消息转发给ui.html,以确保将窗口调整到适当大小。

      清单6就是ui.html页面的代码。

      清单 6. ui.html

      <html>

      <head>

      <head><title>Chat Window</title>

      <script src="prototype.js"></script>

      <style>

      body { font-family: arial, verdana, sans-serif; font-size:small; margin:0px; padding:0px; }

      #chat td { font-size: small; }

      </style>

      </head>

      <body onload="doLoad()">

      <div id="chatcontainer" style="height:315px;overflow:auto;">

      <table id="chat">

      </table>

      </div>

       

      <form id="chatmessage" style="margin:0px;">

      Username: <input type="text" name="user" value="Jack" size="7" /><br/>

      <textarea name="message" id="messagetext" style="width:100%;">

      </textarea>

      </form>

      <button onclick="addmessage()">Add Message</button>

       

      <script>

      var lastid = 0;

       

      function addmessage()

      {

        new Ajax.Request( 'http://192.168.1.192:3005/chat/post', {

           method: 'post',

           parameters: $('chatmessage').serialize(),

           onSuccess: function( transport ) {

             $('messagetext').value = '';

           }

        } );

      }

       

      function startMessages()

      {

          var msgs = parentSandboxBridge.getMessages();

          for( var i = 0; i < msgs.length; i++ )

          {

              var elTR = $('chat').insertRow( -1 );

              var elTD1 = elTR.insertCell( -1 );

              elTD1.appendChild( document.createTextNode( msgs[i].user ) );

              var elTD2 = elTR.insertCell( -1 );

              elTD2.appendChild( document.createTextNode( msgs[i].message ) );

              lastid = parseInt( msgs[i].id );

          }

          window.setTimeout( getMessages, 1000 );

      }

       

      function getMessages()

      {

        new Ajax.Request( 'http://192.168.1.192:3005/chat/getsince?id='+lastid, {

          onSuccess: function( transport ) {

            var msgs = eval( transport.responseText );

            for( var i = 0; i < msgs.length; i++ )

            {

              var message = msgs[i].attributes.message;

              var user = msgs[i].attributes.user;

              var posted = msgs[i].attributes.posted;

              var id = parseInt( msgs[i].attributes.id );

       

              parentSandboxBridge.addMessage( id, user, posted, message );

       

              if ( id > lastid )

              {

                var elTR = $('chat').insertRow( -1 );

                var elTD1 = elTR.insertCell( -1 );

                elTD1.appendChild( document.createTextNode( user ) );

                var elTD2 = elTR.insertCell( -1 );

                elTD2.appendChild( document.createTextNode( message ) );

       

                lastid = id;

       

                   parentSandboxBridge.alertNewMessage();

              }

            }

            window.setTimeout( getMessages, 1000 );

          }

        } );

      }

       

      function onResizeEvent() {

          $('chatcontainer').style.width = (document.width-5)+'px';

          $('chatcontainer').style.height = (document.height-100)+'px';

      }

       

      childSandboxBridge = { onResizeEvent: onResizeEvent, startMessages: startMessages };

       

      function doLoad()

      {

        onResizeEvent();

      }

      </script>

       

      </body>

      </html>

      该页面首先设置onResizeEvent 回调函数。然后利用startMessages()函数从数据库中获取缓存消息的当前列表,并预加载消息表格。startMessages()