After spending a lot of time developing mobile apps, doing the things I used to do with jQuery when developing websites, but using a lot more code, I decided to scrap the framework idea I’d been iterating and use an existing framework to leverage a jQuery like syntax and loosely couple components. Two weeks ago, I trashed all the code I had written and started fresh.
The framework I speak of is TiFramework by Rick Blalock, a great piece of code found on github and what inspired the fundamentals of what Adamantium.js turned out to bee. The core of Adamantium.js is based on the codebase of TiFramework, but reworked and extended extensively. A big shout out also to Dawson Toth, creator of the Redux library, another slice of awesome code that inspired me along the way.
The roadmap for Adamantium.js is not written in stone and apart from its fundamentals (jQuery-like syntax, event-driven components, simplicity by convention), much is left to decide before a stable 1.0 release and I hope that early adopters and brave testers will help decide on how this piece of code best can serve the Titanium Mobile developer community.
Adamantium.js is based on the idea to let webdevelopers use existing skills and familiarity with jQuery to build mobile apps, using the Titanium Mobile platform. The architecture of apps in this framework will be based on module components and event driven interaction between these. The framework will also make extending component and element functionality and prototyping UI element constructors as straightforward as possible.
Apps in the eyes of Adamantium.js are based on components, of which there are four kinds with different intentions, purpose and use. To simplify the concept of Model-View-Controller, the component type Logic (short for Logic controller) has merged the traditional Model and Controller into a central hub of interaction with data layers and business logic.
The traditional View has been renamed to Screen, to avoid confusion with Titanium Mobiles definition and use of Views. Library is the third kind of component and is a format for plugins, sort of, to prototype new methods, bind callbacks to process events and extend the UI. Which is the fourth and last kind of component, which basically allows for prototyping custom element constructors.
I have prepared a simple example application, in which I show how to use most common methods and build an app with Adamantium.js. My intention for this app is to keep it updated as regularly as the framework evolves and provide examples for all the ways too leverage Adamantium.js. I hope for this to grow by user contribution, so if you try the framework and develop a cool example or proof of concept that you’d like to share, please let me know. Download the example application.
First, include the framework.
Ti.include("Adamantium.js");Create the main screen with child elements and event handlers.
SCR.MyScreen = $('Screen')
.attr('title', 'My first screen')
.add('UI.View', {
id: 'MyScreen.View',
height: 100,
width: 100,
backgroundColor: '#fc3',
top: 100
})
.bind('click', function (e) {
alert(e.source.id + ' was clicked!');
})
;Use object reference as selector for components and ID as string when selecting elements. The type selector lets you select multiple components or elements with one query.
$(SCR.MyScreen) // object
$('#MyScreen.View') // ID
$(':View') // typeCreate any element and append it to any other element or component. Any event callbacks previously bound by type will be inherited by all new elements of that type.
$('UI.View').attr({
id: 'MyScreen.View',
height: 100,
width: 100,
backgroundColor: '#cf3',
bottom: 100
})
.appendTo(SCR.MyScreen);Select elements by ID and create event-driven interaction.
$('#MyCustomLabel').click(function () {
$('#MyCustomLabel').attr('text', 'Changed');
});Select elements by type and create event-driven interaction.
$(':Label').click(function (e) {
$(e.source).attr('text', 'Changed');
});Easily bind event callbacks to attributes. Any event callbacks bound by type will be inherited by all new elements of that type.
$(':Label').attr('text', function (e) {
alert(e.attribute + ' was changed from ' + e.previous + ' to ' + e.value);
});Append an element to matched set context. Any event callbacks bound by type will be inherited by all new elements of that type
$(SCR.MyScreen).append('UI.OptionDialog', {
id: 'MyScreen.Confirm',
options: ['Save changes', 'Discard changes', 'Cancel'],
destructive: 1,
cancel: 2,
title: 'The text has been changed.\nDo you want to save the changes?'
});Add an element to matched set context. Any event callbacks bound by type will be inherited by all new elements of that type.
$(SCR.MyScreen).add('UI.TextArea', {
id: 'MyScreen.TextArea',
top: 15,
left: 15,
right: 15,
height: 170,
borderStyle: Ti.UI.INPUT_BORDERSTYLE_NONE,
textAlign: 'left',
font: {
fontSize: 18
}
});Bind event callback to matched set context.
$(':Button').bind('click', function (e) {
alert(e.source.id + ' was clicked!');
if (e.foo) {
alert(e.foo);
}
});Trigger event on matched set context.
(':Button').trigger();
$(':Button').trigger('click', {
foo: 'I shot the sheriff...'
});Change an attribute on matched set context.
(SCR.MyScreen).attr('title', 'I was changed!');Change multiple attributes on matched set context.
(SCR.MyScreen).attr({
title: 'I was changed',
backgroundColor: '#ccc'
});Bind callback to attribute changes on matched set context.
$(':Screen').attr('title', function (e) {
alert(e.attribute + ' was changed from ' + e.previous + ' to ' + e.value);
});Append matched set context to element.
$('UI.View').attr({
id: 'MyScreen.View',
height: 100,
width: 100,
backgroundColor: '#cf3',
bottom: 100
})
.appendTo(SCR.MyScreen);Create and add tab to matched set context. opts.screen is required. This method only applies to UI.TabGroup.
$('UI.TabGroup')
.tab({ screen: SCR.MyScreen, title: 'Tab title' })
.open({ transition: Titanium.UI.iPhone.AnimationStyle.FLIP_FROM_LEFT })
;Change attribute enabled to true on matched set context.
$(':Button').enable();Change attribute enabled to false on matched set context.
$(':Button').disable();Empty attribute on matched set context.
$(':Screen').empty('title');Navigate matched set context to defined screen. This method only applies to UI.TabGroup.
$(':TabGroup').navigate(SCR.MyScreen);Faux method for implying package of module. This is necessary for each element module used in each component.
SCR.MyScreen = $('Screen')
.module([
Ti.UI.TextArea,
Ti.UI.Button,
Ti.UI.OptionDialog
])
;Populate matched set context with rows. This method only applies to UI.TableView.
var data = [
{ title: 'First' },
{ title: 'Second' },
{ title: 'Third' }
];
$('#MyScreen.TableView').rows(data);
$('#MyScreen.TableView').rows(data, function (result) {
return {
id: 'row-' + result.title
};
}, 'UI.TableViewRow');Wrapper for element method scrollTo, takes x and y as arguments. This method only applies to UI.ScrollView.
$(':ScrollView').scrollTo(0, 50);Scroll element to top position. This method only applies to UI.TableView and UI.ScrollView.
$(':ScrollView').scrollToTop();Return attribute from matched set context.
var screenTitle = $(SCR.MyScreen).attr('title');Shorthand methods for the most common events are available and works much like you may be use too, in jQuery. Depending on the type of arguments you provide and the inherent methods of the element or component you are interacting with, the shorthand method has different actions.
Bind callback function to “click” events attached to matched set context.
$(':Button').click(function (e) {
alert('Source element/component: ' + e.source);
alert('Source element ID: ' + e.target);
alert('Event type is: ' + e.type);
});Trigger execution of “click” event attached to matched set context.
$(":Button").click({ foo: 'bar' });These examples show the shorthand method for the event click and methods to use just like this one are also available for these events: animate, beforeload, blur, change, close, complete, count, dblclick, deselectRow, doubletap, error, exit, focus, index, init, insert, load, move, open, refresh, scroll, scrollEnd, select, selected, show, singletap, swipe, touchcancel, touchend, touchmove, touchstart, twofingertap, update.
Some components have methods to trigger interaction, leading up to a natural trigger of the event in question. An example for this is a Screen, which have a close() method, that in turn sets off the “close” event. All these methods are available to use as easily as you would have imagined.
Execute inherent component method, if exists, on matched elements.
$("#myScreen").close();Any arguments you provide, regardless of type, will be forwarded to the components inherent method. The only exception is if the first argument is a function, in which case it will be regarded as a callback function to bind to the event.
These events will be documented properly once the project has moved to Google Code, but until then it’s trial and error that is your best friend, along with the documentation above.
When creating a new element, using .add() or .append(), you can add a post constructor hook to the element, which is called once the element is created and tracked. This is useful when creating complex interfaces or logic or in a simple example as the one below, where we use the post constructor hook to bind a callback to this elements click event.
$(SCR.MyScreen)
.append('UI.Button, {
id: 'MyScreen.SomeButton',
title: 'I am a button',
hook: function (o) {
$(o).click('I was clicked!');
})
.attr('rightNavButton', '#MyScreen.SomeButton')
;When you stage a screen, you can use a hook to define what actions to be taken once the screen has task its duty and closes itself. This is a bit more complex than post constructor hooks and to be called, the hook needs be referenced in the closing statement of the screen. To learn more about this, check out the interaction between SCR.Dashboard, SCR.Idea and SCR.EditTextArea in the example app. It shows how a single screen can be used multiple times, thanks to staging and staging hooks.
It’s really simple to extend the framework with a custom method to process the matched set context. We get the component/element as an argument, process it and return it – this process is then applied to each component or element in a matched set, when calling this method.
$.extend('pop', function (component, message) {
/*
Custom method, pop a message
*/
if (component && component.type == 'Label' && typeof message == 'string') {
alert(component.id + ' got popped: ' + message);
}
return component;
});
$(':Label').pop('Learn OOP or die repeating yourself');You can also use the component or elements context methods, including the one we just prototyped.
$.extend('snap', function (component) {
/*
Custom method, pop a message
*/
if (component && component.type == 'Label') {
$(component)
.pop('Johnny Appleseed is Kayzer Soze')
.attr('gotSnapped', true);
}
return component;
});
$(':Label').snap();If there are any shorthand event methods that you feel are missing, you can easily extend the framework with your own. It is as simple as letting Adamantium.js know which event name (or shorthand) you want to prototype.
$.shorthand('makeWaves');After this, we can use this method the same way we use .click() and .scroll(). If the matched set context has a function with this name, this shorthand method will allow us to call this in a simple way, like .open() or .focus()
// Bind callback to event
$(':Label').makeWaves(function (e) {
alert(component.id + ' has made waves!');
});
// Trigger event, with or without arguments
$(':Label').makeWaves({ foo: 'bar' });
$(':Label').makeWaves();One of the issues, at least for me, when working with events in Titanium Mobile, has been the duplication of events when adding elements to a stack and binding an observer to a parent element in this stack. Adamantium.js has it’s own system for handling events, where no event duplication is ever supposed to take place. However, I’ve had some issues with event duplication in the simulator, which I haven’t been able to reproduce on device. This will have to be solved before a stable release and when event propagation is introduced, this will mean the end of the event duplication we’ve all been building solutions around.
This project has so far only been developed for and tested on IOS devices. I do however plan on supporting Android, and Blackberry once that’s out of beta, and this is on the general roadmap for 1.0. Could be done earlier, it all depends on the will of the users – the community will decide on how important Android support is.
I have a few guides planned for the next few weeks and I hope to get discussions going in the comments, first up is an extensive walkthrough on how to extend the framework with a custom method. I’ll also post something about how to easily create custom UI elements and use them in your application. Please share in the comments if there are any ideas or concepts you’d like explored on this blog.
I will be moving this project to a Google code repository soon, where wiki and issue tracker will be available. Until then, download the example app for the source code and use the comments for issues and questions. I’m also available at my e-mail, adamrenklint@me.com.
Update: since this post, I have learned the awesomeness of Git and as of this moved this project to a Github repository for everyone to download, fork and watch. The issue tracker there will also serve as the main roadmap checkpoint along the way of development, so any issues you find should be reported there. And the link: http://github.com/adamrenklint/Adamantium.js
Pingback: The indestructible unicorn, in development – Adam Renklint, mobile app developer