Доступ к базам данных – очень востребованный функционал приложений. И в этот раз мы рассмотрим способ для WebOS приложений получить данные из базы данных MySQL, находящейся на удаленном сервере.

Для начала создадим новое приложение и в нем новую сцену с названием Main. Как создать проект и сцену можно узнать из этой статьи.

Затем добавим в созданную сцену список:

views/main/main-scene.html

<div class="palm-group">
  <div class="palm-group-title" id="sample-toggle">
  	<span x-mojo-loc="">Database Content</span></div>
    <div class="palm-list">
       <div id="dbList" x-mojo-element='List'></div>
    </div>
  </div>
</div>

Для нашего списка нам необходимо определить HTML-шаблон элементов списка:
views/main/dbItemTemplate.html

<div class="palm-row" x-mojo-tap-highlight="momentary">
  <div id="nameField">#{Name} (#{Year})</div>
</div>

В шаблоне указано, что если в элементе списка есть свойства Name и Year, то они должны быть отображены.

Теперь в assistants/main-assistant.js внесем необходимые изменения для того, чтобы виджет-список создался при запуске:

function MainAssistant() {
}

MainAssistant.prototype.setup = function() {
this.count = 0;
this.dbListModel = {};
this.controller.setupWidget("dbList",
        {
            itemTemplate: "main/dbItemTemplate"
        },
        this.dbListModel);
}

Как видно из кода, в атрибутах виджета мы указываем шаблон элементов main/dbItemTemplate. Название должно совпадать с названием ранее созданного HTML-файла с описанием шаблона элементов.

После всех манипуляций, описанных выше, у нас должно получиться что-то подобное:

WebOS Empty List

Отлично, с GUI мы закончили, теперь займемся получением данных с сервера.

Для начала создадим базу данных и в ней таблицу, из которой мы будем получать записи и отображать их на форме:

CREATE TABLE remote_data(
 ID INTEGER NOT NULL AUTO_INCREMENT ,
 Name VARCHAR( 32 ) NOT NULL ,
 Year SMALLINT NOT NULL ,
 PRIMARY KEY ( ID )
);

Затем создадим PHP-скрипт, который будет получать данные из таблицы и выдавать их в формате JSON:

<?php
header('Content-type: application/json');
$dbhost = "localhost";
$dbuser = "root";
$dbpass = "";
$dbname = "webos_test";
$link = mysql_connect($dbhost, $dbuser, $dbpass)
    or die('Could not connect: ' . mysql_error());
mysql_select_db($dbname) or die('Could not select database');

switch($_POST['operation'])
{
 case 'getResults':
 {
  $table = $_POST['table'];
  $query = sprintf("SELECT * FROM %s", mysql_real_escape_string($table));
  $result = mysql_query($query) or die('Query failed: ' . mysql_error());
  $all_recs = array();
  while ($line = mysql_fetch_array($result, MYSQL_ASSOC))
  {
   $all_recs[] = $line;
  }
  break;
 }
}
echo json_encode($all_recs);
mysql_free_result($result);
mysql_close($link);
?>

Что делает этот скрипт:

  • Получает из POST-параметров название операции, непример getResults.
  • Если операция поддерживается, то получает из POST-параметров имя таблицы
  • Получает записи из таблицы в массив
  • Конвертирует в JSON и отдает клиенту

В результате у нас в выдаче должно получиться что-то подобное:

[
 {"ID":"1","Name":"Test 1","Year":"2008"},
 {"ID":"2","Name":"Test 2","Year":"2009"},
 {"ID":"3","Name":"Test 3","Year":"2010"}
]

Теперь нам надо научить наше WebOS приложение получать эти данные с сервера.

Для получения данных в приложении нам необходимо выполнить AJAX-запрос на сервер. Для этого используется метода Ajax.Request():

MainAssistant.prototype.getRemoteData = function(table) {
    var url = 'http://192.168.0.1/remote-data.php';
    try
    {
        if(!table)
        {
            throw('getRemoteData(): You should specify database table name');
        }
        var request = new Ajax.Request(url,
        	{
            	method: 'post',
            	parameters: {'operation': 'getResults', 'table': table},
            	evalJSON: 'true',
            	onSuccess: this.getRemoteDataSuccess.bind(this),
            	onFailure: function()
            	{
                	Mojo.Log.error('Failed to get Ajax response');
            	}
        	});
    }
    catch(e)
    {
        Mojo.log.error(e);
    }
}

В исходном коде указан IP адрес машины в локальной сети. Если вы тестируете приложение на эмуляторе, а сервер со скриптом и базой данных находится в Internet, то, скорее всего, доступа к серверу у вас по умолчанию не будет. Для того, чтобы разрешить эмулятору ходить в сеть, надо в настройках LAN-соединения разрешить доступ из подсети VirtualBox:
Share Internet with WebOS Emulator

Итак, в приведенном выше коде указано, что при успешном завершении запроса необходимо вызвать метод getRemoteDataSuccess(). В этом методе мы должны преобразовать полученный ответ от сервера в массив и обновить список. Делается это следующим образом:

MainAssistant.prototype.getRemoteDataSuccess = function(response) {

 	try
 	{
 		this.dbListModel.items = response.responseText.evalJSON();
 		this.controller.modelChanged(this.dbListModel);
 	}
 	catch(e)
 	{
 		Mojo.log.error(e);
 	}
}

Для преобразования JSON-строки в массив мы использовали метод evalJSON(), а для того чтобы виджет обновил список элементов мы вызвали метод modelChanged() объекта-контроллера.

Все это чудесно, научили приложение делать запрос к серверу и обрабатывать результаты, но если мы запустим приложение, то ничего не произойдет. А все потому что мы не указали, когда должен вызываться метод getRemoteData().

Вызвать этот метод можно при запуске приложения. У класса сцены есть метод, который отрабатывает, когда сцена загружена. Это метод ready(). Вот в нем мы и запустим выполнение AJAX-запроса:

MainAssistant.prototype.ready = function() {
this.getRemoteData('remote_data');
}

И вот, после запуска увидим такое:

WebOS List Loaded from MySQL Database

На этом все.

Скачать исходный код демонстрационного приложения.

Previous ArticleNext Article
Технический директор IT-Dimension, компании-разработчика кросс-платформенного программного обеспечения

Leave a Reply

Your email address will not be published. Required fields are marked *

П.

Пишем для Palm WebOS “на коленке”, создаем проект руками

В этот раз рассмотрим более детально структуру приложения для WebOS. В SDK есть достаточное количество дополнительных утилит, кторые могут помочь сгенерировать приложение или сцену. Но, при использовании автоматических кодогенераторов, понимания того, как же всетаки устроен проект, не прибавляется. Если кодогенератор отработает некорректно по какой-либо причине, без знания структуры проекта вернуть все в рабочее состояние будет непросто.

Для начала сделаем вот что: создадим пустой проект. В Eclipse идем в File -> New -> Other, в диалоговом окне выбираем General -> Project. Далее в мастере указываем имя проекта и путь, где он будет создан.

Создаем файл описания приложения

Создаем пустой файл в проекте, называем его appinfo.json

{
	"id": "com.itdimension.manuallycreatedwebosproject",
	"version": "1.0.0",
	"vendor": "Mobile-Developer.ru",
	"type": "web",
	"main": "index.html",
	"title": "Manually Created Project",
	"icon": "icon.png"
}

В этом файле указаны:

  • id – уникальный идентификатор приложения
  • version – версия приложения
  • vendor – название производителя
  • type – тип проекта
  • main – начальная страница приложения
  • title – название проекта (оно будет использовано для отображения в списке установленных  приложений WebOS)
  • icon – иконка приложения (также будет использоваться при отображении в Launcher’е)

Как видно, файл настроек приложения содержит упоминание двух файлов – файла главной страницы, а также иконки приложения. Узнать больше о параметрах, которые можно указать в appinfo.js можно здесь. Интересным также является тот факт, что кроме web-приложений можно создавать и нативные приложения. Для таких приложений свойство type будет иметь значение native.

Создаем файл главной страницы

В проекте Eclipse создаем новый html-файл, называем его index.html.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
	"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
	<title>CameraTest</title>
	<script 
		src="/usr/palm/frameworks/mojo/mojo.js" 
		type="text/javascript" x-mojo-version="1" />
	<link
		href="stylesheets/cameratest.css" 
		media="screen" rel="stylesheet" type="text/css" />
</head>
<body>
	<h2>Manually Created Project</h2>
</body>
</html>

Если в приложении будут использоваться нестандартные CSS-стили элементов управления, то они должны быть указаны обязательно ПОСЛЕ ссылки на mojo.js.

И вот, в данный момент у нас есть каркас минимального приложения, которое можно запустить на эмуляторе. Но прежде чем его запустить, нужно создать пакет, который будет установлен на эмулятор (или на устройство).

Для создания пакета в Palm Mojo SDK есть скрипт palm-package. Нам достаточно скормить этому скрипту путь к папке с проектом и на выходе получим .ipk файл с приложением.

palm-package ManuallyCreatedWebOSProject

В результате выполнения этой команды у нас должен получиться файл com.itdimension.manuallycreatedwebosproject_1.0.0_all.ipk
Для того чтобы загрузить пакет на эмулятор необходимо вызвать скрипт palm-install и скормить ему .ipk файл

D:\Projects\Eclipse Projects>palm-install com.itdimension.manuallycreatedwebosproject_1.0.0_all.ipk

После этого в эмуляторе запускаем Launcher и видим в нем наше приложение.
WebOS Manually Created Application in Launcher
После запуска приложения получим вот такой экран:
WebOS Manually Created App
Теперь надо бы сделать так, чтобы в нашем приложении кроме главной страницы было еще что-нибудь.

Создаем сцену

Для начала надо создать структуру каталогов:

  • app
    • assistants – в ней юудут находиться js-файлы, содержащие логику управления сценами
    • views – в ней будут содержаться html-файлы сцен

В WebOS-приложении есть понятие “stage” – окно и “scene” – панель в окне. Для каждой сущности нужен свой assistant-класс с обработчиками событий.

stage-assistant.js

function StageAssistant() {
}

StageAssistant.prototype.setup = function() {
    this.controller.pushScene("main");
}

Как видим, в конструкторе assistant-класса мы отображаем сцену с именем main. Теперь создадим саму сцену:

В папке views создаем папку main и в ней файл main-scene.html

<div id="main" class="palm-hasheader">
    <div class="palm-header">Camera Test</div>
    <div id="MyButton" x-mojo-element="Button"></div>
</div>

В папке assistants создаем main-assistant.js

function MainAssistant() {
}

MainAssistant.prototype.setup = function() {
	this.controller.setupWidget("MyButton", {}, {label:"My Button"});
}

Теперь создадим файл sources.json, в котором будут регистрироваться файлы с assistant-классами.

[
    {"source": "app\/assistants\/stage-assistant.js"},
    {
        "source": "app\/assistants\/main-assistant.js",
        "scenes": "main"
    }
]

В результате после запуска приложения получим такой пользовательский интерфейс:
WebOS Manually Created Scene

Скачать исходный код примера.

У.

Учимся работать с GPS в Palm WebOS

GPS нынче – одна из часто востребованных функций в мобильных устройствах и поэтому в этот раз будем рассматривать вопросы использования GPS в приложениях для Palm WebOS.

Итак… Работа с GPS в WebOS осуществляется посредством вызова системного сервиса palm://com.palm.location. Вызов сревиса происходит с помощью метода serviceRequest() контроллера приложения. Тоесть в коде это выглядит как-то так:

this.controller.serviceRequest('palm://com.palm.location', ...);

В WebOS сервис, осуществляющий взаимодействие с GPS-приемником имеет три публичных метода:

  • getCurrentPosition – Синхронный метод получения актуальных GPS-координат
  • startTracking – Подписка на изменение GPS-координат
  • getReverseLocation – Получение информации по указанным координатам (геокодер)

Рассмотрим каждый метод по очереди.

getCurrentPosition

В документации в качестве примера использования этого метода значится вот такой кусок кода:

this.controller.serviceRequest('palm://com.palm.location', {
   method:"getCurrentPosition",
   parameters:{},
   onSuccess:{},
   onFailure:{}
   }
});

На самом же деле этот кусок кода описывает вызов метода только формально и вводит в заблуждение. Первое, с чем приходится столкнутьс, это доступ к полученным координатам. Если писать что-то внутри операторных скобок, которые указаны в качестве значения параметра onSuccess, то получим ошибку. Правильным же способом является указание метода-обработчика для onSuccess и onFailure.

  • onSuccess – метод, который будет вызван в случае если запрос отработал успешно
  • onFailure – метод, который будет вызван в случае если запрос завершился с ошибкой

Тоесть минимально рабочим примером, который позволит нам получить координаты будет следующий код:

FirstAssistant.prototype.handleCoordButton = function(event)
{
	this.controller.serviceRequest(
		'palm://com.palm.location', 
		{
   			method:"getCurrentPosition",
   			parameters: {},
   			onSuccess: this.getCooSuccessResponseHandler.bind(this),
   			onFailure: {}
   		});
}

FirstAssistant.prototype.getCooSuccessResponseHandler = function(event){
	this.controller.showAlertDialog({
    title: $L("Location"),
    message: 'Latitude: '+event.latitude+'\nLongitude: '+event.longitude,
    choices:
    	[
			{label:$L("OK"), value:"ok"}    
    	]
    });
}

Как видно в примере, мы указываем обработчик только для onSuccess. Для onFailure это делается аналогичным образом. В обработчике onFailure модно также узнать код ошибки. Код ошибки содержится в свойстве errorCode объекта, передаваемого в качестве параметра обработчика. errorCode – это целочисленное свойство, которое может принимать одно из следующих значений:

  • 0: Нет ошибки
  • 1: Таймаут
  • 2: Координаты недоступны
  • 3: Неизвестная ошибка
  • 5: Выключен сервис GPS
  • 6: Доступ запрещен – Пользователь не принял условия лицензионного соглашения на использование сервиса GPS.
  • 7: У приложения уже есть сообщение в очереди
  • 8: Приложение временно в черном списке.

Если вы тестируете свое приложение на эмуляторе, то скорее всего сразу получите ошибку с кодом 6. Для того чтобы это исправить, надо в Launcher’е выбрать пункт Location Services и там согласиться с условиями лицензионного соглашения на использование геолокационных сервисов.
Кроме, собственно, широты и долготы, в обработчике получения координат можно узнать еще кучу полезной информации. Более детально о списке доступны параметров можно узнать здесь.

startTracking

Подписка на получение координат от сервиса. Очень полезная штука. Позволяет в асинхронном режиме получать новые значения координат. Пример из документации опять говорит нам вот что:

this.controller.serviceRequest('palm://com.palm.location', {
   method:"startTracking",
   parameters:{},
   onSuccess:{},
   onFailure:{}
   }
});

И здесь, опять же, снова нехватает нормальной демонстрации того, как надо указывать обработчики для onSuccess и onFailure.
А на самом деле все должно быть как-то так:

first-scene.html

<div id="main" class="palm-hasheader">
     <div class="palm-header">GPS Test</div>
     <div id="coordinates" class="palm-body-text">0</div>
</div>

first-assistant.js

...
FirstAssistant.prototype.setup = function() {
     this.controller.get("coordinates").update('Unknown');
     
     this.points = 0;
     this.trackingHandle = this.controller.serviceRequest(
     	'palm://com.palm.location', 
     	{
			method:"startTracking",
			parameters: {"subscribe":true},
			onSuccess:  this.trackingSuccessResponseHandler.bind(this),
			onFailure:  this.trackingFailedResponseHandler.bind(this)
		});
}

FirstAssistant.prototype.cleanup = function(event) {
	this.trackingHandle.cancel();
}
...
FirstAssistant.prototype.trackingSuccessResponseHandler = function(event){
	this.points++;
	this.controller.get("coordinates").update(
		'Coordinates:<ul>'+ 
		'<li>Count: ' + this.points + '</li>'+
		'<li>Latitude: ' + event.latitude + '</li>'+
		'<li>Longitude: ' + event.longitude+'</li>'+
		'</ul>');
}

FirstAssistant.prototype.trackingFailedResponseHandler = function(event){
	this.controller.get("coordinates").update(
		'Failed to retrieve coordinates: ' + event.errorCode);
}
...

Что мы тут сделали:

  • Создали текстовое поле с идентификатором coordinates
  • В конструкторе подписались на событие получения GPS-координат
  • Когда координаты приходят, показываем их в текстовом поле вместе со счетчиком получений
  • Если происходит ошибка, то показываем сообщение об ошибке и код ошибки
  • В деструкторе отписываемся от события вызвав метод this.trackingHandle.cancel()

Обо всех остальных параметрах, получаемых в onSuccess можно прочитать здесь.

getReverseLocation

Пример кода в документации следующий:

this.controller.serviceRequest('palm://com.palm.location', {
   method:"getReverseLocation",
   parameters:{},
   onSuccess:{},
   onFailure:{}
   }
});

В качестве параметров метода могут фигурировать latitude и longitude – широта и долгота.

В onSuccess в объекте-параметре метода свойство address будет содержать адрес, полученный по указанным координатам.

В onFailure коды ошибки могут быть такими:

  • 0: Нет ошибок
  • 6: Доступ запрещен
  • 7: У приложения есть сообщения в очереди
  • 8: Приложение временно в черном списке

Более подробно о методе getReverseLocation можно узнать здесь.

Скачать исходный код примера.