В этот раз мы узнаем, как работать Download Manager API, которое появилось в новой версии Mojo SDK v1.2. В этой статье рассматривается возможность загрузки файлов с устройства на сервер с помощью Download Manager API.

Допустим, у нас есть Web-сервис, позволяющий загружать файлы с описанием (напримепр, фотографии). И вот, наше приложение должно обеспечить работу с этим сервисом для устройств, работающих под управлением WebOS.

Вот у нас есть простенькая HTML-форма для загрузки файлов:

upload.html

<form enctype="multipart/form-data" action="uploadtest.php" method="POST">
  <table border="0" cellspacing="5">
    <tr>
      <td>Info:</td>
      <td><input type="text" name="info" /></td>
    </tr>
    <tr>
      <td>File:</td>
      <td><input type="file" name="filename" /></td>
    </tr>
    <tr>
      <td colspan="2" align="center"><input type="submit" /></td>
    </tr>
  </table>
</form>

Также есть простенький Web-сервис, который принимает файлы и выполняет проверку по размеру, а также наличие описания. Выдачу результатов загрузки пользователь получает в HTML-виде:

uploadtest.php

<?php
do
{
    $max_file_size = 1024 * 100;
    $info = $_POST['info'];
    if(!$info) break;
    echo "Info - OK<br>";
    $filename = $_FILES['filename']['tmp_name'];
    $uploaded_size = filesize($filename);
    echo "File size: $uploaded_size<br>";
    if($uploaded_size == 0) break;
    echo "File size > 0<br>";
    if($uploaded_size > $max_file_size) break;
    echo "File size < MAX<br>";
    echo "PROFIT !!! : )";
    exit();
}
while(false);
echo "Error";
?>

В случае успешной загрузки файла на сервер мы получим в выдаче что-то подобное:

Info – OK
File size: 5092
File size > 0
File size < MAX
PROFIT !!! : )

И вот, мы сейчас узнаем как аналогичный функционал получить в приложении для Palm WebOS.

Создадим новое приложение и в нем сцену main.

app/views/main/main-scene.html

<div class="palm-group">
  <div class="palm-group-title">File info</div>
  <div class="palm-row first">
    <div class="palm-row-wrapper textfield-group" x-mojo-focus-highlight="true">
      <div class="title">
      	<div class="label">File</div>
        <div id="FileTextBox" x-mojo-element="TextField"></div>
      </div>
    </div>
  </div>
  <div class="palm-row last">
    <div class="palm-row-wrapper textfield-group" x-mojo-focus-highlight="true">
      <div class="title">
        <div class="label">Info</div>
        <div id="InfoTextBox" x-mojo-element="TextField"></div>
      </div>
    </div>
  </div>
</div>
<div id="SelectFileButton" x-mojo-element="Button"></div>
<div id="UploadButton" x-mojo-element="Button"></div>
<div id="ExitButton" x-mojo-element="Button"></div>
<div id="info" class="palm-body-text"></div>

Сцена содержит группу с двумя текстовыми полями. В первом поле можно ввести имя файла, который будет загружен на сервер, а во втором – текстовое описание. Также на сцене находятся три кнопки: выбор файла, загрузка, выход.

app/assistants/main-assistant.js

function MainAssistant() {
}

MainAssistant.prototype.setup = function() {
    this.controller.setupWidget("FileTextBox",
        {
            modelProperty: 'value'
        },
        this.fileModel =
        {
            value: ''
        });
    this.controller.setupWidget("InfoTextBox",
        {
            modelProperty: 'value',
            autoFocus: true
        },
        this.infoModel =
        {
            value: ''
        });
    this.controller.setupWidget("SelectFileButton",
        {}, this.selectFileModel = {label: "Select File"});
    this.controller.setupWidget("UploadButton",
        {
            type: Mojo.Widget.activityButton
        },
        this.uploadButtonModel =
        {
            label: "Upload",
            disabled: true
        });
    this.controller.setupWidget("ExitButton", {}, {label: "Exit"});

    Mojo.Event.listen($(SelectFileButton), Mojo.Event.tap, this.onSelectFile.bind(this));
    Mojo.Event.listen($(UploadButton), Mojo.Event.tap, this.onUpload.bind(this));
    Mojo.Event.listen($(ExitButton), Mojo.Event.tap, this.onExit.bind(this));
    Mojo.Event.listen($(FileTextBox), Mojo.Event.propertyChange,
        this.onPropertyChanged.bind(this));
    Mojo.Event.listen($(InfoTextBox), Mojo.Event.propertyChange,
        this.onPropertyChanged.bind(this));
}

MainAssistant.prototype.onPropertyChanged = function(value) {
}

MainAssistant.prototype.onSelectFile = function() {
}

MainAssistant.prototype.onUpload = function() {
}

MainAssistant.prototype.onExit = function() {
    this.controller.stageController.getAppController().closeAllStages();
    window.close();
}

MainAssistant.prototype.cleanup = function(event) {
    Mojo.Event.stopListening($(SelectFileButton), Mojo.Event.tap, this.onSelectFile);
    Mojo.Event.stopListening($(UploadButton), Mojo.Event.tap, this.onUpload);
    Mojo.Event.stopListening($(ExitButton), Mojo.Event.tap, this.onExit);
}

Кнопка Upload имеет тип Activity Button – при нажатии на такой кнопке появляется индикатор активности.

К текстовым полям мы подключили обработчик onPropertyChanged(), который будет вызываться при вводе текста с клавиатуры.

В методе cleanup() мы отключаем все обработчики событий от виджетов. Это необходимо делать всегда для того чтобы не ьыло утечек памяти.

В результате у нас должно получиться такое:

WebOS Uploader GUI

Теперь можно переходить к реализации логики работы приложения.

Во-первых, надо обеспечить проверку ввода данных пользователем. Мы не можем запускать загрузку до того, как пользователь введет имя файла и описание в текстовые поля. Для проверки введенных значений у нас есть метод onPropertyChanged().

MainAssistant.prototype.onPropertyChanged = function(value) {
    do
    {
        if(this.fileModel.value.length == 0) break;
        if(this.infoModel.value.length == 0) break;
        if(this.uploadButtonModel.disabled)
        {
            this.uploadButtonModel.disabled = false;
            this.controller.modelChanged(this.uploadButtonModel);
        }
        return;
    }
    while(false);
    if(!this.uploadButtonModel.disabled)
    {
        this.uploadButtonModel.disabled = true;
        this.controller.modelChanged(this.uploadButtonModel);
    }
}

Теперь надо дать возможность пользователю выбрать файл для загрузки. Сделать это можно с помощью виджета FilePicker.

MainAssistant.prototype.onSelectFile = function() {
    Mojo.FilePicker.pickFile(
        {
            onSelect: this.onFileSelected.bind(this)
        },
        this.controller.stageController);
}

MainAssistant.prototype.onFileSelected = function(result) {
this.fileModel.value = result.fullPath;
this.controller.modelChanged(this.fileModel);
this.onPropertyChanged();
}

Webos FilePicker

К сожалению, после того, как мы изменили значение в модели и вызвали modelChanged(), метод onPropertyChanged() автоматически не вызывается, поэтому нам приходится вызывать его руками.

Теперь надо реализовать логику отправки файла:

MainAssistant.prototype.onUpload = function() {
    try
    {
        var posturl = "http://192.168.0.1/uploadtest.php";
        this.controller.serviceRequest('palm://com.palm.downloadmanager/',
            {
                method: 'upload',
                parameters:
                {
                    fileName: this.fileModel.value,
                    fileLabel: 'filename',
                    url: posturl,
                    contentType: 'application/x-www-form-urlencoded',
                    subscribe: true,
                    postParameters:
                    [
                        {key: 'info' , data: this.infoModel.value}
                    ]
                },
                onSuccess : this.onUploadStatus.bind(this),
                onFailure: function(e)
                {
                    this.uploadTicket = 0;
                    this.enableControls(true);
                    this.uploadButtonModel.disabled = false;
                    this.controller.modelChanged(this.uploadButtonModel);
                    $(UploadButton).mojo.deactivate();
                    Mojo.Controller.errorDialog("Upload failed, " + e.errorCode + " : " + e.errorText);
                }
            });
    }
    catch(e)
    {
        Mojo.Controller.errorDialog(e);
    }
}

Для отправки файла на сервер мы испотльзуем метод upload сервиса Download Manager. Параметры у этого метода такие:

  • fileName – имя файла на устройстве
  • fileLabel – это значение соответствует атрибута name тега input в HTML-форме.
  • url – адрес скрипта, которому будет отправлен запрос
  • subscribe – если указать значение этого параметра равным true, то мы выполним подписку на уведомления о состоянии загрузки. Это мы и сделали
  • onSuccess – функция, которая будет вызываться при получении уведомления о состоянии загрузки
  • onFailure – функция, которая будет вызвана в случае ошибки загрузки
  • postParameters – массив, содержащий POST-параметры запроса. Каждый элемент этого массива должен содержать поле key с именем параметра и поле data со значением параметра. В этом массиве мы передаем параметр info (аналогичный input с типом text есть в HTML-форме).
MainAssistant.prototype.enableControls = function(value) {
this.fileModel.disabled = !value;
this.infoModel.disabled = !value;
this.selectFileModel.disabled = !value;
this.controller.modelChanged(this.fileModel);
this.controller.modelChanged(this.infoModel);
this.controller.modelChanged(this.selectFileModel);
}

MainAssistant.prototype.onUploadStatus = function(response) {
    if(response.subscribed)
    {
        this.enableControls(false);
        this.uploadButtonModel.disabled = true;
        this.controller.modelChanged(this.uploadButtonModel);
        $(UploadButton).mojo.activate();
        this.uploadTicket = response.ticket;
    }
    else if(response.completed)
    {
        this.uploadTicket = 0;
        this.enableControls(true);
        this.uploadButtonModel.disabled = false;
        this.controller.modelChanged(this.uploadButtonModel);
        $(UploadButton).mojo.deactivate();

        $(info).update(response.responseString);
    }
}

Как видно из кода, в функции, которая отрабатывает при получении состояния загрузки, мы проверяем значение поля subscribe объекта-параметра, который содержит всю информацию о загрузке. Поле subscribe содержит значение true только когда загрузка успечно началась. Если мы получаем true в поле subscribe, то деактивируем все текстовые поля, а также кнопку Select File и кнопку Upload. Также  мы отображаем индикатор на кнопке Upload с помощью метода activate().

Когда мы получаем значение true в поле completed, это значит что загрузка завершилась. Мы активируем виджеты и убираем индикатор с кнопки Upload с помощью метода deactivate(). Затем мы отображаем ответ от сервера на сцене.

Кроме всего прочего нам необходимо обработать ситуацию, когда приложение завершается до того, как файл загружен на сервер. После начала загрузки мы можем получить уникальный идентификатор загрузки с помощью поля ticket объекта, возвращаемого сервисом Download Manager. В методе cleanup() сцены нам необходимо проверить это значение, если оно ненулевое, то завершить загрузку принудительно.

MainAssistant.prototype.cleanup = function(event) {
        Mojo.Event.stopListening($(SelectFileButton), Mojo.Event.tap, this.onSelectFile);
        Mojo.Event.stopListening($(UploadButton), Mojo.Event.tap, this.onUpload);
        Mojo.Event.stopListening($(ExitButton), Mojo.Event.tap, this.onExit);
        if(this.uploadTicket != 0)
        {
                var handler = function(res) { this.uploadTicket = 0 }
                this.controller.serviceRequest('palm://com.palm.downloadmanager/',
                {
                    method: 'cancelDownload',
                    parameters:
                    {
                        ticket : this.uploadTicket
                    },
                    onSuccess : handler.bind(this),
                    onFailure : handler.bind(this)
                });
    }
}

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

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

Leave a Reply

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

Л.

Локализация приложений для Palm WebOS

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

В Palm WebOS локализация происходит достаточно прозрачно. Никаких сверхъестественных знаний не требуется.

Локализируем имя приложения, отображаемое в Launcher

Для того, чтобы в Launcher’е ваше приложение меняло название при смене системных настроек локализации, необходимо скопировать файл appinfo.json в папку с локализированными ресурсами.

Путь для размещения локализированных ресурсов формируется таким образом:

  • В каталоге с проектом создается папка resources
  • В папке resources для каждого языка создается отдельная папка и в ней необходимо размещать файлы локализации, соблюдая структуру каталогов.

Например мы создали приложение следующим образом:

palm-generate -p "id=com.mobiledeveloper.localizableapp" -p "title=Localizable Application" LocalizableApplication
palm-generate -t new_scene -p "name=Main" LocalizaleApplication

Получаем appinfo.json такого вида:

{
 "id": "com.mobiledeveloper.localizableapplication",
 "version": "1.0.0",
 "vendor": "Mobile-Developer.ru",
 "type": "web",
 "main": "index.html",
 "title": "LocalizableApplication",
 "icon": "icon.png"
}

Теперь нам необходимо сделать локализацию. Для этого файл appinfo.json мы копируем в /resources/es_us/appinfo.json и текст файла заменяем на следующий:

{
 "id": "com.mobiledeveloper.localizableapplication",
 "version": "1.0.0",
 "vendor": "Mobile-Developer.ru",
 "type": "web",
 "main": "../../index.html",
 "title": "Translated Application",
 "icon": "../../icon.png"
}

В результате, при смене языка на испанский, получим такое в Launcher’е:
WebOS Localized Application in Launcher

Локализируем HTML-текст сцен

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

Допустим, у нас был файл /views/main/main-scene.html

<div class="row" style="padding-left: 5px;">
    My<br />text <b>here</b>!
</div>

Для создания испанской локализации нам надо скопировать его в /resources/es_us/views/main/main-scene.html и заменить текст:

<div class="row" style="padding-left: 5px;">
    Your<br />image <b>there</b>!
</div>

После смены языковых настроек в системе, мы увидим измененный текст.

Локализация строк в JavaScript

Все строки, для которых необходима локализация, должны быть помещены в функцию $L():

$L("My text here");

Если по каким-то причинам вы не можете использовать исходную строку в качестве ключа, то значение ключа в словаре с переводом можно указать вручную.

$L("value":"Done", "key": "done_key");

Файл со словарем должен находиться непосредственно в каталоге с локализированніми ресурсами и называться strings.json

{
"My text here": "Mi texto aquí",
"done_key": "Listo",
}

Файл со словарем должен иметь кодировку UTF-8 (без BOM). Если работаете в Eclipse, то, если не указать кодировку для файла и попытаться ввести неанглийские символы и сохранить файл, должно появиться сообщение об ошибке.

Если в строку необходимо подставлять значения переменных, то не надо делать конкатенацию строк, лучше переменные указать в тексте. Это делается потому что для каждого языка положение этих переменных в тексте и последовательность следования переменных может отличаться.

Например английская версия строки может выглядеть так:

"Not enough memory to #{action} the file #{fname}."

…а финская так:

"Liian vähän muistia tiedoston #{fname} #{action}."

Чтобы правильно сделать локализацию подобных строк, нужно использовать метод interpolate():

var data={num:10};
var localizedText = $L("You have #{num} messages").interpolate(data);

или класс Template

var template = new Template($L("You have #{num} messages"));
var localizedText = template.evaluate({num: 10});

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

С.

Скринкаст – Пишем “Hello World” для Palm webOS

Что-то такой длительный перерыв получился со скринкастами.

В этот раз видео будет о разработке для Palm webOS.

Что из видео можно узнать

  • Как установить все необходимое ПО для разработки
  • Настроить Eclipse для разработки webOS приложений
  • Создать минимальное приложение
  • Добавить новую форму в приложение
  • Написать обработчик нажатия кнопки

Описание последовательности действий:

  • Устанавливаем Apple Safari
  • Устанавливаем VirtualBox
  • Устанавливаем Java Runtime Environment
  • Устанавливаем webOS SDK
  • Распаковываем Eclipse
  • Запускаем Eclipse
  • В Help -> Install New Software добавляем URL для загрузки плагина, необходимого для разработки webOS приложений ( https://cdn.downloads.palm.com/sdkdownloads/1.1/eclipse-plugin/eclipse-3.4/site.xml )
  • Устанавливаем плагин
  • Создаем минимальное приложение
  • Запускаем webOS эмулятор
  • С помощью меню Run As… запускаем приложение в эмуляторе
  • Командой “palm-generate -t new_scene -p "name:First" HelloWorld” создаем новый view в приложении
  • Меняем шаблон нового view’а
  • Делаем так, чтобы новый view запускался при старте приложения
  • Дописываем обработчик нажатия кнопки
  • Дописываем метод инициализации view’а и привязываем в нем обработчик кнопки

Загрузки:

Внимание!!! Видео в разрешении 1280×800 – Смотреть лучше в полноэкранном режиме.