Những cuộc chiến trình duyệt giữa iPhone và Android, Phần 2: Xây dựng một ứng dụng dựa vào trình duyệt cho iPhone và Android

Sử dụng HTML 5, CSS, JavaScript, Ajax, và jQuery

Đây là bài viết thứ hai trong loạt bài hai phần "Những cuộc chiến trình duyệt giữa Android và iPhone" về phát triển các ứng dụng dựa trên trình duyệt cho iPhone và Android. Trong Phần 1, chúng tôi giới thiệu WebKit, máy trình duyệt ở trung tâm của trình duyệt trong iPhone và Android. Trong bài này, chúng ta đi sâu hơn bằng cách xây dựng một ứng dụng quản lý mạng chạy trên cả hai trình duyệt của iPhone và Android. Ứng dụng này trình diễn lưu trữ SQL cục bộ của trình duyệt và Ajax, các công nghệ chủ yếu cho phép trải nghiệm ứng dụng phong phú từ bên trong của trình duyệt di động. Ngoài ra, ứng dụng còn sử dụng thư viện JavaScript phổ biến của jQuery.

Frank Ableson, Thiết kế phần mềm

Sau khi sự nghiệp trong đội bóng rổ của trường cao đẳng kết thúc mà không có một hợp đồng dài hạn nhiều năm chơi cho đội LA Lakers, Frank Ableson đã chuyển chí hướng của mình sang thiết kế phần mềm máy tính. Ông thích giải quyết các vấn đề phức tạp, nhất là trong các lĩnh vực truyền thông và lập giao diện phần cứng. Khi không làm việc, ông dành thời gian với người vợ Nikki và con cái. Bạn có thể gặp Frank tại địa chỉ frank@cfgsolutions.com.



15 04 2011

Mở đầu

Về mặt lịch sử, đã có một số trở ngại trong việc phát triển các ứng dụng Web di động có tính năng phong phú, nhưng bối cảnh đó đang thay đổi nhanh chóng. Thách thức về tốc độ mạng không đủ vẫn chưa được loại bỏ, nhưng tốc độ đã được cải thiện rất nhiều khi công nghệ 3G đã trở nên sẵn có rộng rãi cho điện thoại di động. Tương tự như vậy, giao diện người dùng đã đi một chặng đường dài, với iPhone dẫn đầu còn Android đẩy thêm đà bằng các thiết kế đổi mới và máy WebKit mạnh mẽ cung cấp cách biểu hiện HTML và CSS vào hạng nhất. Việc cung cấp cho người dùng một giao diện người dùng di động yếu kém không còn nhận được việc nữa.

Giống như trải nghiệm trình duyệt của máy tính để bàn, các ứng dụng Web di động đã trải qua một thời kỳ phục hưng do lưu trữ cơ sở dữ liệu cục bộ đã trở nên có sẵn cho trình duyệt. Một công nghệ khác mang lại sức mạnh cho các ứng dụng Web di động thế hệ kế tiếp là Ajax. Ajax mô tả thói quen sử dụng JavaScript để gọi ra một trang Web phía máy chủ để lấy các phần tử dữ liệu cụ thể qua HTTP mà không cần thiết phải lấy về và hiển thị lại toàn bộ trang Web. Vì Ajax làm việc theo cách không đồng bộ, nên các ứng dụng Web di động đã đạt được một bước tiến to lớn về các tính năng.

Trong bài viết này, chúng ta nhắm vào iPhone và Android. Để hỗ trợ phát triển, chúng ta cũng sẽ thử nghiệm ứng dụng với Safari trên máy tính để bàn. Vì Safari cũng dựa trên WebKit, đây là một nền tảng phát triển lý tưởng, đẩy nhanh việc phát triển và cung cấp một trợ giúp gỡ lỗi hoàn hảo, nhờ ứng dụng Thanh tra WebKit (WebKit Inspector) rất hữu ích.


Ứng dụng

Trước khi nhảy vào các chi tiết cụ thể của mã lệnh, chúng ta hãy xem xét các mục tiêu của ứng dụng. Chúng ta muốn làm gì với ứng dụng quản lý/giám sát mạng của mình?

Nếu bạn đã từng quản lý một trang Web cho khách hàng — nội bộ hay bên ngoài — bạn chắc là đã từng được thông báo rằng trang Web đã bị sập. Thông báo này có thể đến từ một công cụ giám sát chủ động tự động, hoặc đôi khi đến bằng cách ít mong muốn hơn, đó là một e-mail hoặc một cú điện thoại từ khách hàng của bạn cho biết, "Trang Web sập rồi. Hãy kiểm tra nó và phục hồi lại cho tôi càng sớm càng tốt".

Việc đầu tiên mà bạn thường cần làm là gì khi biết rằng trang Web không truy cập được? Bạn mở trình duyệt của mình và cố gắng tải trang chủ. Có lẽ việc ngừng chạy liên quan đến kết nối mạng từ một địa điểm. Đôi khi điều đó xảy ra, và đáng công kiểm tra điều này trước khi lao vào đống cỏ dại để tìm một vấn đề thực tế không xảy ra trong bản thân trang Web đó.

Nếu chúng ta loại trừ vấn đề cơ bản là kết nối máy khách, bước tiếp theo trong lựa chọn chữa chạy trang Web là cố gắng thu thập một số chi tiết then chốt, như tài nguyên hệ thống tệp, bộ nhớ có sẵn, nhiều kết nối khác nhau, thông báo lỗi cuối cùng, v.v. Để thực hiện việc này thường đòi hỏi truy cập đến chính máy chủ, thông qua một phiên làm việc trên máy tính từ xa (Remote Desktop) hoặc phiên SSH (Secure Shell) . Nhưng điều gì sẽ xảy ra nếu bạn không ở bên máy tính của mình?

Chẳng vui vẻ gì khi nhận được thông báo "trang Web sập rồi". Nó luôn luôn xảy ra ở những lúc không thích hợp nhất như khi bạn rời khỏi văn phòng của mình và không có kết nối Internet truyền thống. Có thể có một chút cảm giác bất lực. Theo thời gian, bạn biết rằng một trang Web cụ thể có xu hướng hỏng hóc bởi một nhúm những vấn đề đáng ngờ thông thường. Vấn đề đó có thể là một tài nguyên bên ngoài như cơ sở dữ liệu hoặc bộ xử lý thanh toán của bên thứ ba không sẵn sàng hoặc có lẽ việc truyền tệp đã thất bại, làm cho biểu hiện một phần của trang Web với thông tin quá hạn hay không đúng. Bất kể thách thức là gì, có một vài số liệu thống kê quan trọng có thể có ích trong lựa chọn chữa chạy ban đầu. Những số liệu thống kê đó hoặc các chỉ số hiệu năng sẽ khác nhau giữa trang Web này với trang Web khác. Việc xây dựng một công cụ trợ giúp giải quyết vấn đề chính là mục đích của ứng dụng mà chúng ta sẽ xem xét trong bài viết này.

Ứng dụng được thảo luận ở đây được thiết kế để hỗ trợ lựa chọn chữa chạy các vấn đề của trang Web khi bạn đang ở ngoài văn phòng. Nếu bạn có một thiết bị iPhone hay Android, bạn đã mang theo mình một chút quyền năng. Chúng ta có thể sử dụng các khả năng của các nền tảng mạnh mẽ này để giúp quản lý các trang Web của chúng ta.


Các cân nhắc khi thiết kế

Một động lực thiết kế của ứng dụng này là càng độc lập với cơ sở dữ liệu máy chủ càng tốt — nói cách khác, chúng ta muốn ứng dụng này chạy mà không cần phải tạo một tài khoản dịch vụ của bên thứ ba. Chúng ta sẽ xây dựng ứng dụng này theo cách mà người dùng chỉ cần tải trang Web một lần và chạy nó từ trình duyệt cục bộ của họ. Tất nhiên, người sử dụng có thể đánh dấu trang và làm mới nó khi muốn nhận được bất kỳ tính năng mới nào có khả năng đã được thêm vào ứng dụng theo thời gian. Tuy nhiên, tất cả dữ liệu được lưu trữ cục bộ trên thiết bị di động trong một cơ sở dữ liệu SQL.

Mặc dù chắc chắn là đáng lưu trữ những dữ liệu kiểu này trên máy chủ ở đâu đó, chức năng quản lý và lưu trữ dữ liệu cho nhiều người dùng nằm ngoài phạm vi của bài viết này. Mối quan tâm chính của chúng ta là sử dụng lưu trữ cục bộ và dùng Ajax để xây dựng một ứng dụng thực tế và có ích. Chúng ta sẽ kết thúc bằng một số các bước hợp lý tiếp theo để mở rộng ứng dụng này.

Mã nguồn cho ứng dụng này có thể được chia thành hai nhóm riêng biệt. Chúng ta có mã chạy trên thiết bị di động và bao gồm:

  • Một tệp index.html là vỏ ứng dụng của chúng ta.
  • Một tệp JavaScript có tên là netmon.js, chứa phần lớn các chức năng của ứng dụng.
  • Một tệp JavaScript có tên là json2.js, chứa các thường trình liên quan đến JSON.
  • Nhiều tệp CSS chứa thông tin định kiểu. Nhớ lại từ Phần 1 là nhiều thông tin định kiểu được chứa trong một tệp CSS chính. Tuy nhiên, chúng ta có các tệp CSS đặc thù của thiết bị để hỗ trợ tinh chỉnh dáng vẻ và cảm nhận của ứng dụng trên một nền tảng cụ thể.
  • Chúng ta bao gồm tệp thư viện jquery.js để hỗ trợ trong thao tác DOM (Mô hình đối tượng tài liệu) và các truy vấn Ajax.

Sau đó chúng ta có mã chạy trên máy chủ và mã này là duy nhất cho mỗi trang Web mà chúng ta muốn quản lý. Các chi tiết cụ thể về mã này sẽ thay đổi tùy theo việc triển khai thực hiện của nó, nhưng nội dung kết quả sinh ra luôn luôn giống nhau: một đối tượng JSON (JavaScript Object Notation - Ký pháp của đối tượng JavaScript) có chứa các đặc tính cụ thể, bao gồm các mục đặc tính có tên, là một mảng các cặp tên-giá trị. Dữ liệu bên trong đối tượng JSON này định đoạt cách nó được biểu hiện bên trong trình duyệt trong ứng dụng của chúng ta. Ngoài ra, các cặp tên-giá trị cung cấp dữ liệu vận hành then chốt đặc thù cho mỗi trang Web, (hy vọng rằng) cung cấp các thông tin cần thiết để lựa chọn chữa chạy một cách nhanh chóng và giúp các nhân viên hỗ trợ định vị và giải quyết vấn đề kịp thời.


Xây dựng ứng dụng

Chúng ta bắt đầu bằng cách xem xét mô hình dữ liệu của ứng dụng này. Khi chúng ta nắm chắc được mô hình dữ liệu, chúng ta sẽ xem xét các mã lệnh tương tác với dữ liệu, đưa ứng dụng vào cuộc sống.

Mô hình dữ liệu

Khi dữ liệu được lưu trữ trên thiết bị di động thông qua trình duyệt, nó được lưu trữ trong một cơ sở dữ liệu HTML 5 hoặc một kho cơ sở dữ liệu SQL có thể truy cập bằng trình duyệt. Đặc tả kỹ thuật của dữ liệu SQL dựa trên trình duyệt vẫn còn thay đổi liên tục, các chi tiết cụ thể của nó vẫn đang được rèn giũa. Tuy nhiên, theo ngôn ngữ thực tế, nó hiện đã sẵn dùng trong iPhone, Android và các môi trường khả WebKit khác. Chức năng cơ sở dữ liệu này về cơ bản là một giao diện JavaScript tới một triển khai SQLite nằm bên dưới. Cơ sở dữ liệu của chúng ta chỉ có một bảng: tbl_resources.

Bảng này bắt chước dữ liệu mà ứng dụng của chúng ta sử dụng trong thời gian chạy — về cơ bản là một mảng các đối tượng JSON. Mỗi đối tượng bao gồm:

  • Một tên trang Web.
  • URL của trang chủ của trang Web đó.
  • URL của các thống kê chủ yếu của trang Web; chúng ta gọi đây là ping URL.
  • Trạng thái hiện tại của trang Web: hoặc OK (Tốt) hoặc BAD (Xấu).
  • Bản tóm tắt hiện tại của trang Web, đây là một đoạn văn bản ngắn mô tả điều kiện hiện tại của trang Web đó. Ví dụ, bản tóm tắt này có thể trông giống như cơ sở dữ liệu dưới đây.
  • Một mảng các cặp tên-giá trị có chứa các chi tiết đặc thù của trang Web để trợ giúp trong việc mô tả các điều kiện vận hành hiện tại của trang Web. Lưu ý rằng các giá trị này có thể là các giá trị khi trang Web vẫn chạy, chưa sập. Một phần tử đặc biệt có thể chứa dữ liệu cung cấp cái nhìn bên trong của vấn đề đang treo đó trong một tương lai gần.

Liệt kê 1 là một ví dụ đối tượng JSON đại diện cho một trang Web.

Liệt kê 1. Đối tượng JSON biểu diễn một trang Web
    [
      {
         name : 'msi-wireless.com',
         homeurl : 'http://msiservices.com',
         pingurl : 'http://ibm.msi-wireless.com/mobile2/netmon.php',
         status : 'OK',
         summary : 'Everything is fine...',
         items :
         [
          {name : 'DiskSpace', value : '22.13 GB'},
          {name : 'Database Up?', value : 'Yes'}
         ]
      }
   ]

Vai trò của cơ sở dữ liệu là để lưu trữ bền vững dữ liệu theo thời gian, mặc dù thao tác được thực hiện thông qua một mảng chứa các đối tượng này, thay vì liên tục tham chiếu đến cơ sở dữ liệu. Chiến lược này được thực hiện để làm cho mọi việc đơn giản hơn một chút trong JavaScript và để giảm thiểu số lần dữ liệu đi vào và đi ra khỏi cơ sở dữ liệu. Tuy nhiên, đối với một ứng dụng có rất nhiều phần tử dữ liệu, làm việc trực tiếp với cơ sở dữ liệu có thể ưu thế hơn, hoặc khả dĩ hơn là dùng cách "phân trang", ở đó lấy ra một số phần tử từ cơ sở dữ liệu mỗi một lần, và một mảng JavaScript sẽ chứa một "cửa sổ" các phần tử, biểu diễn một tập con của toàn bộ số các mục.

Hình 1 là một ảnh chụp nhanh của cấu trúc cơ sở dữ liệu, cùng với một vài bản ghi. Việc hiển thị cơ sở dữ liệu có thể được thực hiện bằng Trình thanh tra Web (Web Inspector), là một phần của nền tảng trình duyệt Safari/WebKit. Đây là một trong những lý do tại sao việc phát triển bằng WebKit lại mạnh mẽ đến như vậy. Ở đây, chúng ta đang xem xét Trình thanh tra Web trên máy tính để bàn. Tất cả các mã lệnh này hoạt động tốt trên iPhone và Android.

Lưu ý: Android V2.0 là đích của mã này. Tính năng WebKit đặt trong Android đã được hoàn thiện với mỗi bản phát hành.

Hình 1. Ảnh chụp màn hình cấu trúc cơ sở dữ liệu và một vài bản ghi
Ảnh chụp màn hình cấu trúc cơ sở dữ liệu và một vài bản ghi

Bây giờ chúng ta nắm chắc được các phần tử dữ liệu trông như thế nào, làm thế nào để chúng ta quản lý chúng bằng ứng dụng của mình?


Phía máy khách — Khung nhìn mặc định

Giao diện người dùng mặc định cho ứng dụng là một danh sách các trang Web thuộc quyền quản lý, được sắp xếp theo giá trị trạng thái của chúng. Các trang Web không OK được hiển thị đầu tiên, như trong Hình 2.

Hình 2. Các trang Web không OK
Ảnh cho biết các trang Web không OK

Chúng ta thấy rằng có ba trang Web thuộc quyền quản lý. Hiện tại, chúng ta có hai trang Web cho biết có vấn đề. Nếu một trang Web đang ở trong điều kiện tốt, có nghĩa là đặc tính trạng thái của nó là OK, chúng ta không hiển thị trường tóm tắt và nó được hiển thị bằng văn bản màu đen. Nếu trạng thái này là BAD, chúng ta hiển thị bản tóm tắt tiếp theo tên của trang đó và một kiểu dáng có tên là BAD trong tệp CSS định đoạt các thuộc tính hiển thị — chữ màu đỏ, trong ví dụ này. Để biết thêm chi tiết, xem tệp netmon.css; mã nguồn đầy đủ cho ứng dụng này có sẵn trong phần Tải về.

Bằng cách nhấn vào một mục, các chi tiết của mục này sẽ chuyển đổi giữa nhìn thấy và ẩn đi. Như đã thấy trong Hình 2, mỗi mục có ba liên kết có sẵn, tiếp theo là các vị trí trang chủ và ping URL, rồi đến một phần liệt kê các chi tiết của trang Web, muốn nói rằng đó là danh sách các cặp tên-giá trị đại diện cho các điều kiện của trang Web đó.

Trong ví dụ này, chúng ta thấy rằng trang Web có tên ibm demo 2 có một bản tóm tắt là "No more coffee ?” (Hết cà phê à ?). Tất nhiên, đây không nhất thiết là tình trạng khẩn cấp về kỹ thuật, nhưng nó đưa ra một ví dụ vui để xem xét. Đi tiếp xuống phần Details (Các chi tiết) cho mục này, chúng ta thấy các số liệu thống kê quan trọng đằng sau các điều kiện của máy chủ này: Coffee Pot is empty (Bình cà phê đã hết).

Chúng ta thấy rằng chúng ta có thể nhấn vào liên kết trang chủ, để khởi chạy một cửa sổ trình duyệt mới. Thứ hai, chúng ta có thể làm mới dữ liệu bằng việc nhấn vào liên kết Refresh (Làm mới). Chúng ta sẽ kiểm tra bước Refresh ngay sau đây.

Cuối cùng, chúng ta có thể loại bỏ mục này bằng cách chọn liên kết Remove (Loại bỏ). Một truy vấn đơn giản window.confirm() yêu cầu chúng ta xác nhận có thật sự ta muốn thực hiện tác vụ không thể vãn hồi lại được này hay không.


Phía máy khách -- HTML

Để tạo danh sách này, chúng ta cần phải xem xét hai tệp một cách chi tiết. Đầu tiên là tệp index.html, trong Liệt kê 2 và thứ hai là tệp netmon.js, trong Liệt kê 3. Trong bài viết này, chúng ta sẽ xem xét một số đoạn trích ngắn, mặc dù bạn sẽ muốn xem phần Tải về để lấy mã nguồn đầy đủ.

Chúng ta vẫn đang xác định siêu khung nhìn WebKit di động để giúp hướng dẫn trình duyệt rằng ta muốn trang Web được biểu hiện như thế nào. Ta cũng sử dụng một số lệnh JavaScript chọn lựa để xác định một tệp CSS cụ thể nhắm vào một thiết bị cụ thể.

Điểm mới là việc có bao gồm thêm một số tệp JS cùng với một số các tệp JavaScript "cục bộ". Tệp được bọc ngoài bằng một thẻ div HTML mới, với mã định danh (ID) là entryform, trong đó có các phần tử cần thiết để thêm trực tiếp một mục mới từ bên trong ứng dụng. Không cần phải tải một trang HTML khác, theo như cách tiếp cận truyền thống để hoàn thành nhiệm vụ này. Hãy nhớ lại rằng một trong các mục tiêu thiết kế của chúng ta là ứng dụng không dựa vào các công cụ bổ sung dựa trên máy chủ để thao tác hoặc lưu trữ dữ liệu bên ngoài trang Web mà chúng ta đang giám sát. Liệt kê 2 chứa các câu lệnh include (bao gồm) từ tệp index.html.

Liệt kê 2. index.html
<link rel="stylesheet" href="netmon.css" type="text/css" />
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="netmon.js"></script>
<script type="text/javascript" src="json2.js"></script>

<script type="text/javascript">
   if (navigator.userAgent.indexOf('iPhone') != -1) {
      document.write('<link rel="stylesheet" href="iphone.css" type="text/css" />');
   } else if (navigator.userAgent.indexOf('Android') != -1) {
      document.write('<link rel="stylesheet" href="android.css" type="text/css" />');
   } else {
      document.write('<link rel="stylesheet" href="desktop.css" type="text/css" />');
   }

Phía máy khách — Điền danh sách

Dữ liệu được lấy ra từ cơ sở dữ liệu cục bộ của chúng ta và được hiển thị trong danh sách các mục máy chủ. Để có được danh sách này, chúng ta mở cơ sở dữ liệu và đưa ra một truy vấn SQL để lấy tất cả các mục có trong bảng cơ sở dữ liệu của chúng ta.

Một kỹ thuật phổ biến trong các ứng dụng dạng này là tạo ra các bảng trên đường đi. Hãy nhớ: Ta không có Quản trị viên cơ sở dữ liệu ở cạnh để giúp chúng ta. Ứng dụng của chúng ta phải làm tất cả công việc bằng sức lực của riêng nó. Liệt kê 3 chứa mã nguồn của netmon.js: tệp này thực hiện một số mục, bao gồm:

  • Xác định một hàm tạo kiểu-dữ liệu cho đối tượng netmonResource.
  • Một nhóm các hàm cần thiết để hỗ trợ ứng dụng trong việc tương tác với cơ sở dữ liệu và các các trang Web được theo dõi. Nhóm này được triển khai thực hiện trong vùng tên Netmon và bao gồm:
    • dbHandle— Một hàm được sử dụng để quản lý kết nối của chúng ta với cơ sở dữ liệu.
    • initialize— Một hàm được sử dụng để tạo/mở cơ sở dữ liệu và lấy ra danh sách các máy chủ mà chúng ta đang quản lý. Liệt kê 3 có hàm này. Hàm này cố gắng mở cơ sở dữ liệu (nếu chưa mở) và truy vấn cơ sở dữ liệu. Nếu bảng cơ sở dữ liệu không có, hàm này khởi tạo quá trình tạo ra bảng đó.
    • createResourcesTable— Một hàm được dùng để tạo ra bảng cơ sở dữ liệu cần thiết và tạo ra một mục mặc định duy nhất với mục đích trình diễn.
    • addEntry— Một hàm được dùng để chèn một dòng mới vào bảng cơ sở dữ liệu.
    • deleteEntry— Một hàm được dùng để loại bỏ một mục nhập từ bảng cơ sở dữ liệu.
    • refreshEntry— Một hàm được dùng để làm mới mục nhập bằng cách lấy ping URL của mục nhập qua một cuộc gọi Ajax.
    • Resources— Một mảng chứa một "bộ nhớ nhanh" của các mục của cơ sở dữ liệu của chúng ta. Một số hàm khác làm việc trên mảng này, chứ không làm việc trực tiếp với cơ sở dữ liệu.
    • Render— Hàm này trả về một chuỗi HTML, có định dạng theo một số quy tắc mà chúng ta đưa ra về cách chúng ta muốn mỗi mục nhập máy chủ sẽ được hiển thị. Nếu chúng ta có những ý tưởng định dạng mới, các quy tắc này sẽ được thực hiện trong hàm này, cùng với bất kỳ các kiểu dáng CSS cần thiết.
Liệt kê 3. Tệp netmon.js
  initialize : function () {
	try {
      if (netmon.dbHandle == null) {
       netmon.dbHandle = openDatabase("netmondb","1.0","netmon",1000);
      }
		   
      $("#mainContent").html("");
      netmon.dbHandle.transaction(function(tx) {
	   tx.executeSql("SELECT * FROM tbl_resources order by status,nme",
       // parameters
		 [],
  	 // good result
		 function(tx,result){
	 	try {
	 	   var items = new Array();
			for (var i = 0; i < result.rows.length; i++) {
				var row = result.rows.item(i);
				items[i] = new netmonResource();
				items[i].name = row['nme'];
				items[i].homeurl = row['homeurl'];
				items[i].pingurl = row['pingurl'];
				items[i].status = row['status'];
				items[i].summary = row['summary'];
				items[i].items = eval(row['items']);
				if (items[i].items == undefined) {
				   items[i].items = [];
				}
			}
			netmon.resources = items.slice(0);
	   // setup gui with our data				
          if (netmon.resources.length > 0) {
             jQuery.each(netmon.resources,function (index, value) {
             $("#mainContent").append(netmon.render(index,value));
          });
          $(".serverentry").click (function() {$(this).find(".serveritems").toggle();});
          $(".serveritems").hide();
       }
	} catch (e) {
	  alert("Error fetching rows from database..." + e);
	}
 },
 function(tx,error){
 	//alert("bad result on query: " + error.message + " 
                         let's try to create the db table");
	netmon.createResourcesTable();
    }
    ); 
 });
}catch (e) {
   alert("error opening database.  " + e);
}
     
},
...
   render : function(index,itm) {
      try {
         var ret = "";
         ret += "<div class='serverentry " + itm.status + " " + 
                 (index % 2 == 0 ? 'even' : 'odd') + "'>";
         //ret += "<span class='name'>" + itm.name +
         "</span>&nbsp;&nbsp;<a target='_blank' href='" +
         itm.homeurl + "'>Show</a><br /><a target='_blank'
         href='javascript:netmon.deleteEntry
                       (\"" + itm.name + "\");'>Remove</a><br />";
         ret += "<span class='name'>" + itm.name + "</span>";
         if (itm.status != "OK") {
            ret += "<span class='summary'>-" + itm.summary + "</span><br />";
         }
         
         ret += "<div class='serveritems'>"; 
         ret += "<a target='_blank' href='" + itm.homeurl + "'>Home</a>&nbsp;&nbsp;
         <a target='_blank' href='javascript:netmon.refreshEntry
                     (" + index + ",false); '>Refresh</a>&nbsp;&nbsp;
         <a target='_blank' href='javascript:netmon.deleteEntry
                     (\"" + itm.name + "\");'>Remove</a><br />";
         ret += "Home URL:&nbsp;" + itm.homeurl + "<br />";
         ret += "PING URL:&nbsp;" + itm.pingurl + "<br />";
	 ret += "<hr />Details<br />";
         jQuery.each(itm.items,function (j,itemdetail) {
            ret += ">>" + itemdetail.name + "=" + itemdetail.value + "<br />";
         });
         ret += "</div>";      
         ret += "</div>";
         return ret;
      } catch (e) {
            return "<div class='error'>Error rendering item 
                     [" + itm.name + "] " + e + "</div>";
      }
   }
};

Vùng tên netmon có chứa nhiều logic mà chúng ta cần phải triển khai thực hiện trang Web này, được tăng cường thêm một số hàm trợ giúp trong tệp index.html. Chúng ta hãy xem xét tác vụ thêm một mục mới vào cơ sở dữ liệu.


Phía máy khách — Thêm một trang Web

Ứng dụng này quản lý một danh sách có một hoặc nhiều trang Web được người dùng quan tâm. Đối với mỗi trang Web, chúng ta bước đầu thu thập và lưu trữ:

  • The site name (Tên trang Web).
  • The home URL (URL của trang chủ).
  • The ping URL.

Để thực hiện việc này, chúng ta tạo ra một biểu mẫu đơn giản, có ba trường, các nhãn của chúng và một vài nút bấm, như trong Hình 3.

Hình 3. Biểu mẫu đơn giản
Ảnh chụp màn hình cho thấy một biểu mẫu đơn giản

Nội dung của biểu mẫu này thực sự có trong cùng trang index.html như trong Liệt kê 2. Chúng ta chuyển đổi giữa việc xem danh sách mặc định và biểu mẫu thêm máy chủ mới này bằng một vài thủ thuật jQuery. Danh sách các mục ứng với trang Web được gói trong một thẻ div HTML có mã định danh là mainContent. Biểu mẫu được chứa trong một thẻ div có mã định danh là entryform, ban đầu được ẩn dấu đi. Vào bất kỳ thời điểm nào, tính nhìn thấy được của hai phần tử div này là loại trừ lẫn nhau. Để tráo đổi việc hiển thị của chúng, ta gọi phương thức jQuery có tên là toggle trên mỗi một phần tử này như thấy trong hàm addNewEntry, trong Liệt kê 4.

Liệt kê 4. Hàm addNewEntry
function addNewEntry() {
   $("#entry_name").val("");
   $("#entry_homeurl").val("");
   $("#entry_pingurl").val("");

   $("#mainContent").toggle();
   $("#entryform").toggle();
}

Khi một mục máy chủ mới đã được tạo ra, chúng ta cần phải lưu nó vào cơ sở dữ liệu và làm mới danh sách các máy chủ của chúng ta để phản ánh cả mục mới trong danh sách. Người sử dụng khởi tạo điều này bằng cách chọn nút Save, để gọi hàm JavaScript saveEntry được triển khai trong tệp index.html. Hàm này tạo ra một đối tượng JavaScript mới gọi là netmonResource. Chúng ta kiểm tra hợp lệ tất cả các trường đã được điền đúng, sau đó bắt đầu lưu vào cơ sở dữ liệu. Chúng ta lưu mục mới bằng cách gọi netmon.addEntry. Sau đó, chúng ta nhảy trở lại danh sách các mục bằng cách một lần nữa gọi phương thức toggle trên hai phần tử div div chính. Lưu ý: Chúng ta đã không thực hiện toàn bộ biểu thức chính quy (regex) để kiểm tra hợp lệ các URL, đó sẽ là một ý tưởng tốt.

Phần thảo luận sau đây sẽ tham khảo Liệt kê 3: netmon.js.

Việc sử dụng giao diện cơ sở dữ liệu SQL trong JavaScript đòi hỏi hai bước cơ bản. Trước tiên, chúng ta cần phải mở cơ sở dữ liệu nếu nó chưa được mở trước đó. Trong ứng dụng này, chúng ta mở cơ sở dữ liệu trong hàm khởi tạo. Để tạo một bản ghi mới trong cơ sở dữ liệu, chúng ta hãy xem xét mã trong hàm addEntry, trong Liệt kê 5. Một giao dịch SQL được bắt đầu bằng cách gọi <databasehandle>.transaction(function (tx) {tx.executeSql()});. Trong trường hợp của chúng ta, netmon.dbHandle điều khiển cơ sở dữ liệu.

Hàm executeSql có bốn đối số:

  1. Một câu lệnh SQL với mọi tham số, chẳng hạn như các giá trị trường, được biểu diễn bằng một ký tự giữ chỗ là ?. Mỗi ? được thay thế bằng một giá trị, dựa vào vị trí thứ tự xuất hiện trong đối số thứ hai của hàm này.
  2. Một mảng các giá trị được sử dụng như là các tham số, thay thế các ký tự giữ chỗ ? đã chỉ ra trong câu lệnh SQL trong đối số đầu tiên.
  3. Một hàm gọi lại được gọi ra nếu câu lệnh được thực hiện thành công. Các đối số cho hàm gọi lại này bao gồm phần xử lý giao dịch và một bộ kết quả.
  4. Một hàm gọi lại được gọi ra nếu câu lệnh có lỗi. Các đối số cho hàm gọi lại này bao gồm một phần xử lý giao dịch và một đối tượng lỗi.

Liệt kê 5 chỉ ra hàm netmon.addentry.

Liệt kê 5. Hàm netmon.addentry
   addEntry : function (entry) {
      try {
         netmon.dbHandle.transaction(function(tx) {
            tx.executeSql("insert into tbl_resources 
                 (nme,homeurl,pingurl,status,summary,items) values (?,?,?,?,?,?)",
               [
                  entry.name,
                  entry.homeurl,
                  entry.pingurl,
                  entry.status,
                  entry.summary,
                  JSON.stringify(entry.items)
               ],
               function (tx,results) {
                //  alert(entry.name + " added.");
                netmon.initialize();
               },
               function (tx,error) {
                  alert("Error in addEntry [" + error.message + "]");
               });
            });
      }catch (e) {
         alert("Error in netmon.addEntry " + e);
      }
   }

Bây giờ chúng ta đã có thể thêm vào các mục. Việc xóa các mục khỏi danh sách cũng giống như vậy. Hãy tham khảo hàm netmon.deleteEntry để xem cách xóa một mục khỏi cơ sở dữ liệu.

Đã đến lúc xem cách chúng ta làm mới các mục bằng Ajax.


Phía máy khách — Làm mới bằng Ajax

Như mô tả, Ajax là một kỹ thuật để lấy ra nội dung phía máy chủ mà không đòi hỏi làm mới toàn bộ trang Web. Điều này được thực hiện bằng các công nghệ bên dưới khác nhau tùy thuộc vào trình duyệt. Tuy nhiên, chúng ta đã có cách tiếp cận dựa vào thư viện Javascript là jQuery ẩn dấu đi điều này. Đúng, đây là bài viết về phát triển cho trình duyệt iPhone và Android, nhưng hiện nay, rất phổ biến và được chấp nhận — thậm chí được khuyến khích — việc sử dụng một thư viện JavaScript. Khi bạn nhận thấy việc này dễ dàng như thế nào trong jQuery thì bạn chắc sẽ đồng ý thôi.

Hàm netmon.refreshEntry chứa mã Ajax của chúng ta, như trong Liệt kê 6, cũng như một số mã để cập nhật cơ sở dữ liệu với dữ liệu kết quả.

Liệt kê 6. Hàm netmon.refreshEntry
   refreshEntry : function (entryidx,bfollow) {
      try {
         //alert("refresEntry [" + netmon.resources[entryidx].name + "]
                             [" + netmon.resources[entryidx].pingurl + "]");
         $.get(netmon.resources[entryidx].pingurl,
            function (data,textstatus) {
              // alert("response is here : [" + data + "]");
               try {
                  var response = eval(data);

                  netmon.dbHandle.transaction(function(tx) {
                     tx.executeSql("update tbl_resources set status = ?,
                             summary = ?,items = ? where nme = ?",
               [
                  response[0].status,
                  response[0].summary,
                  JSON.stringify(response[0].items),
                  netmon.resources[entryidx].name
               ],
               function (tx,results) {
                  netmon.initialize();
                  if (bfollow) {
                     if (entryidx + 1 < netmon.resources.length) {
                        netmon.refreshEntry(entryidx + 1,true);
                     }
                  }
               },
               function (tx,error) {
                  //alert("Error in refreshEntry [" + error.message + "]");
                  if (bfollow) {
                     if (entryidx + 1 < netmon.resources.length) {
                        netmon.refreshEntry(entryidx + 1,true);
                     }
                  }
                  
               });
            });

               } catch (e) {
                  alert("error handling response [" + e + "]");
               }
            }
            );
         
      } catch (e) {
         alert("Error refreshEntry " + e);
      }
      
   }

Để nhận được dữ liệu từ máy chủ, chúng ta chỉ cần gọi $.get(). Đối số đầu tiên là URL và đối số thứ hai là một hàm để gọi khi cuộc gọi đã xong. Trong ví dụ của chúng ta, chúng ta cập nhật cơ sở dữ liệu với dữ liệu mới được lấy ra (xem Liệt kê 3: netmon.refreshEntry) bằng cách sử dụng một số kỹ năng cơ sở dữ liệu thảo luận ở trên.

jQuery cũng có phương tiện để cung cấp "dữ liệu mã hóa kiểu biểu mẫu" để truyền các tham số đến một trang Web, cũng như đưa ra một gợi ý về loại dữ liệu nào được dự kiến sẽ quay trở lại. Hãy xem Tài nguyên để tham khảo các hàm Ajax của jQuery. Có những chi tiết hơn cần làm, tùy thuộc vào các nhu cầu của ứng dụng của bạn.

Lưu ý rằng jQuery cũng đưa ra một phương pháp để lấy trực tiếp một đối tượng JSON. Tuy nhiên, thay vào đó, chúng ta chọn sử dụng hàm Ajax "chung", khả dụng hơn cho các ứng dụng khác mà bạn có thể muốn xây dựng.

Khi chúng ta nhận được dữ liệu quay về từ máy chủ, chúng ta muốn biến nó thành một đối tượng JavaScript để truy cập vào các đặc tính của nó. Chúng ta có thể làm điều này đơn giản với hàm JavaScript có tên eval: var object = eval(<json text>);.

Tại thời điểm này, chúng ta đã làm khá tốt với các chức năng phía máy khách. Chỉ còn nhiều mục nữa cần trình bày: các trang phía máy chủ, có trách nhiệm tạo ra JSON mà các ứng dụng của chúng ta dự kiến sẽ xử lý.


Phía máy chủ — Xây dựng trang Web giám sát của bạn

Chúng ta đã mô tả cấu trúc JSON mà ứng dụng của chúng ta đang chờ nó quay lại, thế nhưng nó được tạo như thế nào? Câu trả lời ngắn gọn là do bạn quyết định. Nếu bạn thích làm việc với các công nghệ .NET, thì bạn có thể tạo ra một trang .aspx để tạo dữ liệu động. Một cách khác là bạn có thể viết một kịch bản lệnh shell được lập lịch định kỳ để tạo ra một tệp văn bản mà bạn chỉ cần lấy ra để tìm trạng thái được cập nhật gần đây nhất. Yêu cầu thấp nhất là ứng dụng phải có khả năng tìm nạp một đối tượng JSON dựa trên một URL duy nhất, còn việc bạn hoàn thành nhiệm vụ tạo JSON là do bạn quyết định.

Tệp PHP ví dụ mẫu được hiển thị trong Liệt kê 7 tạo ra một danh sách các tệp và tìm kiếm có chọn lựa tệp có tên là error.txt. Nếu tệp này không rỗng, chúng ta giả định rằng đã có vấn đề gì đó và xây dựng đối tượng JSON của chúng ta để chỉ báo một giá trị trạng thái BAD và sử dụng các nội dung của tệp error.txt để cập nhật các đặc tính tóm tắt.

Liệt kê 7. Tệp PHP ví dụ
<?
$filecount = 0;
$statusValue = "OK";
$summaryValue = "No Error";
?>
[
{
items : [
{ "name" : "Date", "value" : "<? echo date("m/d/y H:m:s"); ?>"  },
<?
foreach (new DirectoryIterator("./data") as $fileInfo) {
   if ($fileInfo->isDot() || $fileInfo->isDir()) continue;
   $filecount++;
   echo "{\"name\" : \"".$fileInfo->getFilename()."\",\"value\" : 
\"".$fileInfo->getSize()."\"},";
   if ($fileInfo->getFilename() == "error.txt") {
     if ($fileInfo->getSize() > 0) {
        $statusValue = "BAD";
        $fsize = $fileInfo->getSize();
        $fh = fopen("./data/".$fileInfo->getFilename(),"r");
        $summaryValue = fread($fh,$fsize);
        fclose($fh);
     }
   }
}
?>
 {"name" : "FileCount","value" : "<? echo $filecount ?>"}
],
"status" : "<? echo $statusValue ?>",
"summary" : "<? print str_replace("\n","",$summaryValue) ?>"
}
]

Mã này tạo ra dữ liệu JSON sau đây như trong Liệt kê 8, giả sử có một điều kiện lỗi.

Liệt kê 8. Dữ liệu JSON do Liệt kê 4 tạo ra
[
    {
        "items" : [
            {
                "name" : "Date",
                "value" : "11/22/09 15:11:51" 
            },
            {
                "name" : "error.txt",
                "value" : "20" 
            },
            {
                "name" : "bad.html",
                "value" : "91" 
            },
            {
                "name" : "testfile.txt",
                "value" : "44" 
            },
            {
                "name" : "good.html",
                "value" : "87" 
            },
            {
                "name" : "FileCount",
                "value" : "4" 
            } 
        ],
        "status" : "BAD",
        "summary" : "the sky is falling." 
    } 
]

Trong ví dụ này, chúng ta đưa ra ngày dữ liệu được tạo ra, một số đếm các tệp hiện tại, cũng như một danh sách các tệp và kích thước của chúng. Hãy nhớ rằng múi giờ cho máy chủ của bạn có thể khác với vị trí địa phương của bạn.

Vì error.txt không phải có chiều dài không byte, nên chúng ta đọc trong nội dung của tệp đó và gán nó vào đặc tính tóm tắt. Đó chính là — một kịch bản giám sát rất cơ bản.

Khi tạo JSON của mình, bạn có thể thấy rằng bạn gặp rắc rối về phân tích cú pháp mã lệnh từ bên trong ứng dụng dựa trên trình duyệt của bạn. Nếu điều này xảy ra, có thể bạn muốn sử dụng một công cụ xác nhận hợp lệ JSON (xem Tài nguyên). Hình 4 cho thấy mục này được biểu hiện trong trình duyệt của máy tính để bàn.

Hình 4. Công cụ xác nhận hợp lệ JSON
Hình 4 chỉ ra công cụ xác nhận hợp lệ JSON

Hình 5 cho thấy ứng dụng đang chạy trong iPhone.

Hình 5. Ứng dụng đang chạy trong iPhone
Hình ảnh cho thấy ứng dụng đang chạy trên iPhone

Hình 6 cho thấy ứng dụng đang chạy trên Android với danh sách máy chủ hơi khác một chút.

Hình 6. Ứng dụng đang chạy trong Android
Hình 6 cho thấy ứng dụng đang chạy trên Android

Bây giờ ứng dụng hầu như đã hoàn thành — nhiều hơn hay ít hơn. Luôn có những điều mới có thể được thêm vào, vì vậy trong phần tiếp theo và cũng là phần cuối cùng, chúng ta sẽ xem xét một danh sách những thứ còn lại, được coi như là một bài tập cho bạn.


Các bước tiếp theo

Không thiếu các ý tưởng mới có thể được nghĩ ra để làm cho ứng dụng này tốt hơn. Dưới đây là một vài mục thú vị để thêm vào:

  • Chỉnh sửa các mục nhập của bạn — Hiện nay, bạn chỉ có thể Add New (Thêm mới) hoặc Delete (Xóa).
  • Thực hiện một số biểu thức chính quy để kiểm tra hợp lệ các trường nhập liệu trong biểu mẫu.
  • Gửi thông tin cho đồng nghiệp qua e-mail trực tiếp từ ứng dụng này.
  • Lưu trữ các cấu hình trên một máy chủ để bạn có thể chuyển từ thiết bị này đến thiết bị khác.
  • Kết hợp ứng dụng này vào một ứng dụng được thiết kế riêng bằng một công cụ như là PhoneGap hoặc Appcelerator. Việc này sẽ cho phép bạn bán các ứng dụng trong AppStore.

Tóm tắt

Bài viết này dựa vào kiến thức nền tảng từ Phần 1 để xây dựng một ứng dụng Web di động với chức năng hoạt động tốt bằng cách sử dụng lưu trữ cục bộ SQL và truy vấn Ajax. Bản thân ứng dụng có thể được sử dụng như một công cụ để hỗ trợ giám sát mạng hiện tại của bạn chỉ với rất ít công việc bên phía máy chủ.

Hy vọng rằng, bạn được truyền cảm hứng để xây dựng các ứng dụng di động riêng của mình dành cho iPhone và Android.


Tải về

Mô tảTênKích thước
Part 2 source codeos-androidiphone2-browserwars2.zip31KB

Tài nguyên

Học tập

Lấy sản phẩm và công nghệ

Thảo luận

  • Các nhóm tin nền tảng Eclipse là nơi dừng chân đầu tiên của bạn để thảo luận các câu hỏi về Eclipse. (Lựa chọn này sẽ khởi chạy ứng dụng trình đọc tin Usenet mặc định của bạn và mở eclipse.platform).
  • Các nhóm tin Eclipse có nhiều tài nguyên cho những người đang quan tâm đến việc sử dụng và mở rộng Eclipse.
  • Tham gia vào developerWorks blogs và và dành hết tâm trí cho cộng đồng My developerWork.

Bình luận

developerWorks: Đăng nhập

Các trường được đánh dấu hoa thị là bắt buộc (*).


Bạn cần một ID của IBM?
Bạn quên định danh?


Bạn quên mật khẩu?
Đổi mật khẩu

Bằng việc nhấn Gửi, bạn đã đồng ý với các điều khoản sử dụng developerWorks Điều khoản sử dụng.

 


Ở lần bạn đăng nhập đầu tiên vào trang developerWorks, một hồ sơ cá nhân của bạn được tạo ra. Thông tin trong bản hồ sơ này (tên bạn, nước/vùng lãnh thổ, và tên cơ quan) sẽ được trưng ra cho mọi người và sẽ đi cùng các nội dung mà bạn đăng, trừ khi bạn chọn việc ẩn tên cơ quan của bạn. Bạn có thể cập nhật tài khoản trên trang IBM bất cứ khi nào.

Thông tin gửi đi được đảm bảo an toàn.

Chọn tên hiển thị của bạn



Lần đầu tiên bạn đăng nhập vào trang developerWorks, một bản trích ngang được tạo ra cho bạn, bạn cần phải chọn một tên để hiển thị. Tên hiển thị của bạn sẽ đi kèm theo các nội dung mà bạn đăng tải trên developerWorks.

Tên hiển thị cần có từ 3 đến 30 ký tự. Tên xuất hiện của bạn phải là duy nhất trên trang Cộng đồng developerWorks và vì lí do an ninh nó không phải là địa chỉ email của bạn.

Các trường được đánh dấu hoa thị là bắt buộc (*).

(Tên hiển thị cần có từ 3 đến 30 ký tự)

Bằng việc nhấn Gửi, bạn đã đồng ý với các điều khoản sử dụng developerWorks Điều khoản sử dụng.

 


Thông tin gửi đi được đảm bảo an toàn.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=70
Zone=Nguồn mở
ArticleID=647102
ArticleTitle=Những cuộc chiến trình duyệt giữa iPhone và Android, Phần 2: Xây dựng một ứng dụng dựa vào trình duyệt cho iPhone và Android
publish-date=04152011