Index ¦ Archives  ¦ Atom  ¦ RSS

Creating an Enyo app + service from scratch

This post was private until Enyo is outside of HP's EAP for WebOS, but no longer!

 

Just a quick note: I use a lot of fake names here that you should replace with your own names. Here's just a few examples:

approot: the root directory of wherever you put your app

appname: the name of your app

companyname: the name of your company, though it can just be a domain name you own

If there's any confusion about naming, post a comment and I'll clarify.

Folder Layout


A package can handle one application, and zero or more services, so you need one folder for the package, one for the application, and one folder for each service:

approot/:

appname.package/:

packageinfo.json

appname.application/:

appinfo.json

appname.servicename/:

services.json

sources.json

All files in appname.package and appname.application must exist to have an initially working app. Having a service is not required.

Inital App Creation

index.html

<!doctype html>
<html>
<head>
<title>Enyo FeedReader</title>
<script src="../../enyo/enyo.js" type="text/javascript"></script>
</head>
<body>
<script type="text/javascript">
  new CompanyName.AppName().renderInto(document.body);
</script>
</body>
</html>

AppName.js

Create your main JS file, call it AppName.js:

enyo.kind({
    name:"CompanyName.AppName",
    kind: "layout-type",
    components: [
    ],
other-attribs
});

Then create a depends.js with enyo.depends("AppName.js",file2,...) in it.

That's all. Open up index.html in Chrome/Safari and it'll work. Add components to have them show up.

Initial push to device

Now let's push it to the device/emulator. To do so, we'll have to define our packageinfo.json and appinfo.json files.

appname.package/packageinfo.json

{
    "id":"com.companyname.appname",
    "package_format_version": 2,
    "loc_name": "AppName",
    "version":"0.0.1",
    "icon":"",
    "miniicon":"",
    "vendor":"CompanyName",
    "vendorurl":"companyname.com",
    "app":"com.companyname.appname.app",
}

appname.application/appinfo.json

{
  "id": "com.companyname.appname.app",
  "version": "0.0.1",
  "vendor": "CompanyName",
  "type": "web",
  "main": "index.html",
  "title": "AppName",
  "icon": "",
  "uiRevision": "2"
}

Packaging and pushing

With those files, you can now get it to run on the device

approot/\$ palm-package appname.package appname.application

approot/\$ palm-install com.companyname.appname_0.0.1_all.ipk

approot/\$ palm-launch com.companyname.appname

Service Creation

We've got it running as a basic app, now let's add a node.js service. This gives us an extra thread to run our work on, as well as a way to share code between our applications. This doesn't make much sense in my head, but maybe HP will allow some sort of dependency-tracking later that let's apps depend on some free app that provides a service only.

appname.package/packageinfo.json

in appname.package/packageinfo.json, we need to add a new "services" key:

{
...snip...,
  "services":[
    "com.companyname.appname.servicename",
  ]
}

appname.servicename/services.json

And now create appname.servicename/services.json. This is a tricky one, so I'm going to explain it a lot.

{
  "services":[
    {
      "name":"com.companyname.appname.servicename",
      "commands":[
        {
          "name":"commandname",
          "assistant":"assistantname",
        }
      ]
    }
  ]
}

That is the bare minimum to have a service callable at palm://com.companyname.appname.servicename/commandname.

At the very top level, you can provide extra keys like "id", "description","commandTimeout", and "activityTimeout".

The "services" key is a list of services provided by the package, it should have one entry for each entry in packageinfo.json.

Inside a "services" entry, you can provide extra keys: "description","assistant","commandTimeout".

The "assistant" key here refers to a long-lasting assistant that will have it's setup() called at service startup, and is available within commands as "this.controller.service.assistant".

The "commands" key should be a list of commands that the service provides. If these are missing "name" or "assistant", the entire service is considered invalid.

Each "commands' entry can provide, extra, "description", "watch", "subscribe", "commandTimeout", and "schema".

All references to "assistant" are to class names, not to files. The file may be anything, as long as it is included in sources.json as in the next section. There is a significant difference between the assistant for a service and for a command, so look for that later on as well.

appname.servicename/sources.json

In appname.servicename/sources.json, just include a list of sources. There's something about libraries, but I don't know what they refer to...

[
  {"source":"filename.js"},
]

appname.servicename/filename.js

This is where the actual service stuff goes. I've written two small helpers to distinguish service and command assistants, as well as setup the necessary bits I need for them:

var createService = function(name, p) {
    this[name] = function(){};
    this[name].prototype = p;
    this[name].prototype.setup = this[name].prototype.setup || function(){};
};
var createFunction = function(name, p) {
    this[name] = function() {};
    this[name].prototype= p;
};

These are simply called with the name of the assistant to create, and it's prototype:

createService("servicename",{
  setup: function(){
    console.log("servicename has been setup");
  }
});

createFunction("commandname",{
  run: function(future){
    console.log("servicename's commandname has been called with these arguments:");
    for(var arg in this.controller.args){
      console.log(arg + ": " + this.controller.args[arg]);
    }
    future.result = {reply:"commandname returning"};
  };
});

Testing the service

If you've included all of that above in the appropriate files, you can now re-package and push it to the device to test the service.

approot/\$ palm-package appname.package appname.application appname.servicename

approot/\$ palm-install com.companyname.appname_0.0.1_all.ipk

approot/\$ luna-send -n 1 palm://com.companyname.appname.servicename/commandname "{}"

That last command should return within a few seconds with: {"reply":"commandname returning","returnValue":true}. If not, please re-check the code. If you have entered it all correctly, comment below with the error.

Using the service

Add a PalmService component:

{name:"serviceAccess",kind:"PalmService",service:"palm://com.companyname.appname.servicename/"}

Then, when you need to call it:

this.$.serviceAccess.call({arg:"value"},{method:"commandname",onSuccess:"successFunctionName"});

And all you need now is a function called successFunctionName to handle the results:

successFunctionName: function(inSender, inResponse){
this.$.someDisplayType.action(inResponse.reply);
}
{ "id":"com.newcompanyname.devour", "package\_format\_version": 2, "loc\_name": "Devour", "version":"0.0.1", "icon":"", "miniicon":"", "vendor":"NewCompanyName", "vendorurl":"newcompanyname.com", "app":"com.newcompanyname.devour.app", "services":[ "com.newcompanyname.devour.convorebackend", ] }

© Fahrzin Hemmati. Built using Pelican. Theme by Giulio Fidente on github.