December 28th, 2007 · Posted by Tejus Parikh · No Comments

Building Appcelerator Widgets

Extending Appcelerator to support new widgets is actually pretty straight-forward. The first, and quite possibly most important, step is to create a name. The name must be unique to its namespace and, unless you want all your hard work to be ignored, be somewhat descriptive of what it does. If it’s a standard, general use widget, like a button, panel, or script block, it makes sense to put it in the app namespace. Specialized widgets, such as the widget to drop in google analytics, go in their own namespace. Staying within these conventions makes it simpler to include new widgets into the Appcelerator core. Other than convention, neither the name nor namespace will effect the functionality of the widget. For the stopwatch widget, we picked app:stopwatch. The rest of this article focuses on how we built it.

Once you’ve got a name, it’s time to create the directory structure for the widget. Navigate to the modules directory in the appcelerator source in your file-browser of choice, and create the following directory structure:
Directory Structure
The directory should be called %namespace%_%widget% and needs to contain a file called %namespace%_%widget%.js. The directories css, images, and js aren’t required, you can omit them if your module does need any css, images nor depend on any javascript not contained within the widget. The stopwatch does not need any third party javascript or images, so we’re going to remove those directories.

You can write out all the boiler plate on your own, or you can do what we do and copy from a pre-existing widget. app:script is a good choice, since its implementation is relatively simple.
Lets take a look at the structure of the template:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
Appcelerator.Module.Stopwatch =
{
	getName: function()
	{
		return 'appcelerator stopwatch';
	},
	getDescription: function()
	{
		return 'stopwatch widget';
	},
	getVersion: function()
	{
		return 1.0;
	},
	getSpecVersion: function()
	{
		return 1.0;
	},
	getAuthor: function()
	{
		return 'My Name';
	},
	getModuleURL: function ()
	{
		return 'http://www.appcelerator.org';
	},
	isWidget: function ()
	{
		return true;
	},
	getWidgetName: function()
	{
		return 'app:stopwatch';
	},
	getAttributes: function()
	{
		//these attributes become part of the parameter map.
	},
	getActions: function()
	{
		//custom actions that this widget responds to go here
	},
	compileWidget: function(params)
	{
		//put code here if you need to add run-time code
	},
	buildWidget: function(element,parameters)
	{
		//this is where you build your widget’s html code.
 
		return {
			'position' : Appcelerator.Compiler.POSITION_REMOVE
		};
	}
};
 
Appcelerator.Core.registerModule('stopwatch',Appcelerator.Module.Panel);

Boring Functions
getName, getDescription, getVersion, getAuthor, and getModuleURL, are all for documentation. They do not effect the functionality of your widget.

getSpecVersion is the version for the module framework. For now, and the foreseeable future, it’s 1.0. This will be used in future versions of Appcelerator to provide backwards compatibility.

Again, in the future modules might be more than just widgets. The function isWidget should return true for widgets, so that the Appcelerator compiler does the right thing.

All of these functions are already correctly filled out in the stub above.

getAttributes: function()
this method returns the list of supported element attributes. The attributes defined in here will become the parameter keys in the parameter map passed into buildWidget and compileWidget. Each item in the list should be an object with the following properties:

name description required
name the name of the attribute true
optional designates wether this is a required attribute true
description a short description of the attribute true
defaultValue the default value if the user does not suppy a value true

The stopwatch has one optional parameter to control wether or not the buttons should be displayed. The complete code for this function in the stopwatch widget is:

1
2
3
4
getAttributes: function()
{
    return [{name: 'show_button', optional: true, description: "Set to false to hide button"}];
},

buildWidget: function(element, parameters)
This is where you define the presentation for your widget. Only presentation logic should reside in this function. Things like state and event listeners go in the compileWidget function described later.

The stopwatch widget replaces <app:stopwatch></app:stopwatch> with the text for the current time and, optionally, some buttons to control the widget. Therefore, we need to construct an html string.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var html = [];
html.push('<div id="' + element.id + '" class="stopwatch">');
html.push('<table><tr>');
html.push('<td><h3 id="' + element.id + '_clock">00:00 <span>00</span></h3></td>');
if(parameters["show_button"] != "false") 
{
    html.push('<td class="stopwatch_start">')
    html.push('<app:button width="100" on="click then script[Appcelerator.Module.Stopwatch.start_stop(\'' 
            + element.id + '\')]">Start</app:button>');
    html.push('</td>');
    html.push('<td class="stopwatch_stop">');
    html.push('<app:button width="100" on="click then script[Appcelerator.Module.Stopwatch.start_stop(\'' 
            + element.id + '\')] and l:' + element.id + '_reset">Stop</app:button>');
    html.push('</td>');
    html.push('<td class="stopwatch_reset">');
    html.push('<app:button disabled="true" width="100" on="click then script[Appcelerator.Module.Stopwatch.clear_time(\''
            + element.id + '\')] and disable or l:' + element.id + '_reset then enable">Reset</app:button>');
    html.push('</td>');
}
html.push('</tr></table>');
html.push('</div>');

As you can see, this is pretty basic stuff. It’s just html. You’ll notice that we’ve used another widget app:button in this widget. This is completely valid and highly recommended. After all, it does not make sense to reinvent the wheel, if you don’t have to.

Also, notice that the buttons are controlled using on expressions. One drawback of using widgets inside other widgets is that they are not guaranteed to be loaded when compileWidget is called. Therefore, you cannot reliably add event listeners to widgets in compileWidget and you should use on expressions instead. In order to call functions within your module, you need to give the fully-qualified class name and surround the call in a script tag.

From this function, you need to return an object that looks like this:

1
2
3
4
5
6
return {
	'presentation' : html.join(' '),
	'position' : Appcelerator.Compiler.POSITION_REPLACE,
	'compile' : true,
	'wire' : true
};

What all this means:

name description required
presentation the html that will be inserted into the DOM false
position where whatever gets inserted goes true
compile set to true to have your compileWidget function be called false
wire set to true if you use Appcelerator (widgets, on attribute, etc) inside the widget false

Widgets aren’t required to have any presentation and could be completely non-visual. Therefore, the two most common values for position are Appcelerator.Compiler.POSITION_REMOVE, which removes this element from the DOM entirely (for non-visual widgets) and Appcelerator.Compiler.POSITION_REPLACE which removes the Appcelerator markup and substitutes what was defined in presentation.

compileWidget: function()
The stopwatch needs to store the state of its clocks and timer somewhere and the logical place is the class-level hash-map called stopwatches. In compileWidget, we create the entry for this particular stopwatch in the map:

1
2
3
4
5
compileWidget: function(params)
{
    var id = params['id'];
    Appcelerator.Module.Stopwatch.stopwatches.set(id, {'date': new Date(0), 'timer_id': null});
},

You could also assign event listeners to any html elements here as well, if you choose to use that approach instead of on events.

getActions: function()
This returns a list of functions that can serve as actions when the widget receives a message. The stopwatch allows you to start_stop, and clear_time. You might pause here and think that “toggle” and “reset” might be more logical names, but these are global actions available on all elements. Global action declarations take precedence over custom actions.

The code for this function is just:

1
2
3
4
getActions: function()
{
    return ['start_stop', 'clear_time'];  
},

Custom Actions
The get actions method allows you to define the custom actions that can be performed on the widget. To implement a custom action you need to implement a function with a particular signature:

start_stop: function(id,parameters,data,scope,version)
name description
id the id of your widget
parameters included parameters
data the data payload of the message
scope the scope of the message, usually ‘appcelerator’
version not used

The full implementation of the start_stop function is:

1
2
3
4
5
6
7
8
9
10
11
12
start_stop: function(id,parameters,data,scope,version)
{
    var stopwatch = Appcelerator.Module.Stopwatch.stopwatches.get(id);
    if(stopwatch.timer_id != null)
    {
        Appcelerator.Module.Stopwatch.stop(id);
    } 
    else
    {
        Appcelerator.Module.Stopwatch.start(id);
    }
},

Responding to messages and events
There are a few ways you can do this, which you choose depends on your needs. The standard is to not do anything. Your widget will automatically respect the on attributed specified on it. For example:

<app:script on="l:toggle_me then start_stop"></app:script>

will call the function start_stop on the message l:toggle_me. No additional code was required.

However, this doesn’t always work, such as when an element internal to the widget needs to respect the on and not the widget itself. In this case, you need to over-ride the function dontParseOnAttributes like so:

1
2
3
4
dontParseOnAttributes: function()
{
    return true;
},

then manually add the contents of on to the required element in your buildWidget function. Also, remember to set wire to true.

Registering Module Resources
Every module has at least one resource that it will need to register (itself). To do this, you need to call

Appcelerator.Compiler.registerModule(moduleName, moduleClass)

outside of the class declaration for your module.
If you have third party javascript, you need to call:

Appcelerator.Core.registerModuleWithJS(moduleName,moduleClass,[list_of_files]);

instead. The javascript must be in the javascript directory under the module root.

If your module has any module-specific css, you will also need to call

Appcelerator.Compiler.loadModuleCSS(moduleName,cssFileName).

For this call to work, the file must be in the module’s css directory.

Since the stopwatch has css, but no javascript, you’ll find the following at the very bottom of app_stopwatch.js

1
2
Appcelerator.Core.registerModule('app:stopwatch', Appcelerator.Module.Stopwatch);
Appcelerator.Core.loadModuleCSS('app:stopwatch', 'stopwatch.css');

The Final Product
The stopwatch is now complete. You can see the full source code at https://svn.appcelerator.org/appcelerator_sdk/trunk/src/web/modules/app_stopwatch/.

The stopwatch widget is slated to be included in the Appcelerator 2.1 release. Once that release goes live, so to will the Web Units.

Any questions about this guide or building widgets should be directed towards our google group, appcelerator-platform-sdk@googlegroups.com.

Popularity: 6% [?]

Tags: Documentation · Example

December 23rd, 2007 · Posted by Jeff Haynie · No Comments

Try Appcelerator in 5 minutes

We just released a new website at http://try.appcelerator.org

You can visit this website to give Appcelerator a try — right in the comfort of your browser. No downloads necessary.

This little utility is built with Appcelerator and gives you the ability to see the simplicity, elegance and power of what we call the “Web Expression Language”. This utility will help you construct simple web expressions and test them on-the-fly.

Enjoy!

Tech Tags:

Popularity: 8% [?]

Tags: Announcements

December 20th, 2007 · Posted by Nolan Wright · 3 Comments

2.0.2 is now available

The 2.0.2 maintenance release is now available at: http://www.appcelerator.org

You can find the full release notes for changes at:
http://www.appcelerator.org/release_notes_2.0.2.html

Summary of new features:

1. Panel Widget

Allows you to easily create pre-styled panels. This widget has
several options:
- display a header with a title
- display a close button
- display a shading button (hide/show the panel content)
- use rounded corners
- display as a quote box
- display with no header (just a plain panel)

2. Http Widget

This is a powerful widget that allows you to access server resources
by URI. You can access third-party APIs like Google and Yahoo or
access your existing services. This widget effectively allows you to
use the Appcelerator RIA SDK without using Appcelerator Services. It
also allows you to easily create composite applications (i.e., an app
that pulls data from multiple sources).

3. Box Widget

This is a layout widget that allows you to layout elements
horizontally or vertically.

4. Button Widget

Allows you to easily create pre-styled buttons. The Button widget
also supports a hover mode and a disabled mode.

5. Search Widget

Allows you to have a “search-as-you-type” style combo box.

6. if Widget

Allows you to add conditional logic to your applications. This is
particularly useful when used in conjunction with the iterator widget
when you need to conditionally display row data.

7. Progress Bar Widget

This widget was contributed by Martin Robinson (thanks Martin!). I
think the name of this widget adequately describes its function.

8. Resizable Attribute

Add resizable=”true” to images and DIVs to make them resizable.

9. Ruby Improvements

Method signatures are no longer required for service methods. Added the
ability to add logic that gets executed before and after each service
call. See the Ruby Getting Started docs for more information.

10. .NET Logging Improvements

Added support for multiple logging levels and a auto-flush buffer.
These can be configured via the appcelerator-config.xml file. See
the .NET Getting Started docs for more information.

We will now begin work on Release 2.1 with a target release date of
1/25/2008. If you would like to see any specific items addressed or
features considered, please let us know. You can also directly open
issues or feature requests by visiting http://jira.appcelerator.org.

Popularity: 6% [?]

Tags: Product Updates