Android activity, service, and broadcast recievers
Creating Alloy Widgets
1. ALLOY WIDGETS
Improving code re-use through a library of bespoke UI
components.
TiConf.eu
Martin Hudson, Jonti Hudson
February 2013
2. WHAT IS A WIDGET?
A self-contained bespoke UI component that
holds all the logic associated with its use.
3. WHAT IS A WIDGET?
A self-contained bespoke UI component that
holds all the logic associated with its use.
• Create a re-usable library across multiple
projects
4. WHAT IS A WIDGET?
A self-contained bespoke UI component that
holds all the logic associated with its use.
• Create a re-usable library across multiple
projects
• Create components that manage cross-
platform differences (e.g. a table edit / delete
component)
5. WHAT IS A WIDGET?
A self-contained bespoke UI component that
holds all the logic associated with its use.
• Create a re-usable library across multiple
projects
• Create components that manage cross-
platform differences (e.g. a table edit / delete
component)
• Improve readability of code
6. WHAT IS A WIDGET?
A self-contained bespoke UI component that
holds all the logic associated with its use.
• Create a re-usable library across multiple
projects
• Create components that manage cross-
platform differences (e.g. a table edit / delete
component)
• Improve readability of code
• Improve reliability due to re-use of tested
components.
7. WHAT IS A WIDGET?
App Widget
Multiple widgets in the same window...
8. WHAT IS A WIDGET?
App Widget
... or multiple instances of the same
widget
9. A CUSTOM TABLE VIEW WIDGET
An example of building a cross-platform widget that encapsulates common
functionality but utilises platform specific behaviour.
10. CREATING A WIDGET
To create a new widget, right click the project name in the Titanium Studio
project view... and select New → Alloy Widget.
11. CREATING A WIDGET
To create a new widget, right click the project name in the Titanium Studio
project view... and select New → Alloy Widget.
Use a “Reverse domain” naming convention to ensure widget names are not
replicated when you share widgets with others.
12. WIDGET FILE STRUCTURE
A widgets folder is created with Controllers, Styles and Views sub-directories.
Note the absence of Models – this is because the main app should handle
data storage
13. USING A WIDGET
In the main app, we will call the newly created widget using the “Require” tag.
App – index.xml Widget – widget.xml
<Alloy> <Alloy>
<Window class="container"> <TableView id="table"></TableView>
<Require type="widget" </Alloy>
src="co.mobiledatasystems.customEditableTable"
id="table1">
</Require>
<Button id="btnEdit" title="Allow Editing"
onClick="btnEdit_click_Event">
</Button>
</Window>
</Alloy>
14. USING A WIDGET
In the main app, we will call the newly created widget using the “Require” tag.
App – index.xml Widget – widget.xml
<Alloy> <Alloy>
<Window class="container"> <TableView id="table"></TableView>
<Require type="widget" </Alloy>
src="co.mobiledatasystems.customEditableTable"
id="table1">
</Require>
<Button id="btnEdit" title="Allow Editing"
onClick="btnEdit_click_Event">
</Button>
</Window>
</Alloy>
Note we specify that we want a widget and reference the name of our new
widget. Alloy knows where to find it.
15. USING A WIDGET
In the main app, we will call the newly created widget using the “Require” tag.
App – index.xml Widget – widget.xml
<Alloy> <Alloy>
<Window class="container"> <TableView id="table"></TableView>
<Require type="widget" </Alloy>
src="co.mobiledatasystems.customEditableTable"
id="table1">
</Require>
<Button id="btnEdit" title="Allow Editing"
onClick="btnEdit_click_Event">
</Button>
</Window>
</Alloy>
In the widget, we specify the UI components we want to expose. In our
case it is only a TableView.
16. STYLING
App – index.tss Widget – widget.tss
".container": {
backgroundColor:"white"
},
"#table1": {
left: '10dp',
right: '10dp',
top: '20dp',
bottom:'80dp'
},
"#btnEdit": {
bottom:'10dp',
left:'20dp',
right:'20dp',
height:'45dp'
}
We have referenced our instance of the
widget “table1” in index.xml
17. STYLING
App – index.tss Widget – widget.tss
".container": {
backgroundColor:"white"
},
"#table1": {
left: '10dp',
right: '10dp',
top: '40dp',
bottom:'80dp'
},
"#btnEdit": {
bottom:'10dp',
left:'20dp',
right:'20dp',
height:'45dp'
}
Note, where possible, do all the styling of
widget components in the main app. This
ensures maximum re-usability across projects.
18. CONTROLLERS - INITIALISE
App – index.tss Widget – widget.js
//copy the arguments passed in to the widget via.
".container": { the xml and tss parameters
backgroundColor:"white"
}, var _args = arguments[0] || {};
"#table1": { var editable = null;
left: '10dp',
right: '10dp', if(OS_ANDROID){
top: '40dp', editable = false;
bottom:'80dp' };
},
//get each element set in the widget's xml or tss
"#btnEdit": {
parameters
bottom:'10dp',
left:'20dp',
Ti.API.info(JSON.stringify(_args));
right:'20dp',
height:'45dp' //iterate round all the parameters we have passed
} in
for (var key in _args) {
if (_args.hasOwnProperty(key)) {
In the widget's controller we can //checks key is a direct property of _args, not
access all the parameters passed in somewhere down the object tree
using the arguments[] array. if(OS_ANDROID){
switch (key){
19. CONTROLLERS - INITIALISE
App – index.tss Widget – widget.js
//copy the arguments passed in to the widget via.
".container": { the xml and tss parameters
backgroundColor:"white"
}, var _args = arguments[0] || {};
"#table1": { var editable = null;
left: '10dp',
right: '10dp', if(OS_ANDROID){
top: '40dp', editable = false;
bottom:'80dp' };
},
//get each element set in the widget's xml or tss
"#btnEdit": {
parameters
bottom:'10dp',
left:'20dp',
Ti.API.info(JSON.stringify(_args));
right:'20dp',
height:'45dp' //iterate round all the parameters we have passed
} in
for (var key in _args) {
if (_args.hasOwnProperty(key)) {
In the widget's controller we can //checks key is a direct property of _args, not
access all the parameters passed in somewhere down the object tree
using the arguments[] array. if(OS_ANDROID){
switch (key){
20. CONTROLLERS - INITIALISE
App – index.tss Widget – widget.js
//iterate round all the parameters we have passed
".container": {
in
backgroundColor:"white"
},
for (var key in _args) {
"#table1": {
if (_args.hasOwnProperty(key)) {
left: '10dp',
right: '10dp',
//checks key is a direct property of _args, not
top: '40dp',
somewhere down the object tree
bottom:'80dp'
},
if(OS_ANDROID){
"#btnEdit": {
switch (key){
bottom:'10dp',
case 'editing':
left:'20dp',
editable = _args[key];
right:'20dp',
break;
height:'45dp'
case 'moving': break; //android
}
doesn't recognise this property
default:
We can read each parameter passed in
}
$.table[key] = _args[key];
and process them appropriately. In our
} else {
$.table[key] = _args[key];
example “editable” and “moving” are };
};
iOS specific. We will set a local };
variable in the case of Android.
21. CONTROLLERS - INITIALISE
App – index.tss Widget – widget.js
//iterate round all the parameters we have passed
".container": {
in
backgroundColor:"white"
},
for (var key in _args) {
"#table1": {
if (_args.hasOwnProperty(key)) {
left: '10dp',
right: '10dp',
//checks key is a direct property of _args, not
top: '40dp',
somewhere down the object tree
bottom:'80dp'
},
if(OS_ANDROID){
"#btnEdit": {
switch (key){
bottom:'10dp',
case 'editing':
left:'20dp',
editable = _args[key];
right:'20dp',
break;
height:'45dp'
case 'moving': break; //android
}
doesn't recognise this property
default:
We can read each parameter passed in
}
$.table[key] = _args[key];
and process them appropriately. In our
} else {
$.table[key] = _args[key];
example “editable” and “moving” are };
};
iOS specific. We will set a local };
variable in the case of Android.
22. CONTROLLERS – CALLING FUNCTIONS
App – index.js Widget – widget.js
$.index.open(); //custom method we expose to set the table's
data
var editMode = false; exports.setData = function(rows /*Ti.UI.Row*/){
$.table.setData(rows);
//create some data to put in the table };
var rows = [];
for(var i=0;i<10;i++){ //custom method we expose to allow the table to
rows.push(Ti.UI.createTableViewRow({title:'Row be editable
number ' + i})); exports.editing = function(edit /*bool*/){
}; if(OS_IOS){
$.table1.setData(rows); //call the "setData" $.table.editing = edit; //allow row
method we created in the widget editing on iPhone & iPad
} else {
//toggle the "editable" mode of the table editable = edit;
function btnEdit_click_Event(){ };
if(editMode){ };
$.btnEdit.title = "Allow Editing";
editMode = false;
} else { Using the commonJS framework, we
$.btnEdit.title = "Cancel Editing";
editMode = true; can expose functions in the widget. In
};
$.table1.editing(editMode); //call the our example we expose “setData” so
"editing" method we created in the widget
};
we can populate the table after the
widget has created it.
23. CONTROLLERS – CALLING FUNCTIONS
App – index.js Widget – widget.js
$.index.open(); //custom method we expose to set the table's
data
var editMode = false; exports.setData = function(rows /*Ti.UI.Row*/){
$.table.setData(rows);
//create some data to put in the table };
var rows = [];
for(var i=0;i<10;i++){ //custom method we expose to allow the table to
rows.push(Ti.UI.createTableViewRow({title:'Row be editable
number ' + i})); exports.editing = function(edit /*bool*/){
}; if(OS_IOS){
$.table1.setData(rows); //call the "setData" $.table.editing = edit; //allow row
method we created in the widget editing on iPhone & iPad
} else {
//toggle the "editable" mode of the table editable = edit;
function btnEdit_click_Event(){ };
if(editMode){ };
$.btnEdit.title = "Allow Editing";
editMode = false;
} else { Using the commonJS framework, we
$.btnEdit.title = "Cancel Editing";
editMode = true; can expose functions in the widget. In
};
$.table1.editing(editMode); //call the our example we expose “setData” so
"editing" method we created in the widget
};
we can populate the table after the
widget has created it.
24. CONTROLLERS – CALLING FUNCTIONS
App – index.js Widget – widget.js
$.index.open(); //custom method we expose to set the table's
data
var editMode = false; exports.setData = function(rows /*Ti.UI.Row*/){
$.table.setData(rows);
//create some data to put in the table };
var rows = [];
for(var i=0;i<10;i++){ //custom method we expose to allow the table to
rows.push(Ti.UI.createTableViewRow({title:'Row be editable
number ' + i})); exports.editing = function(edit /*bool*/){
}; if(OS_IOS){
$.table1.setData(rows); //call the "setData" $.table.editing = edit; //allow row
method we created in the widget editing on iPhone & iPad
} else {
//toggle the "editable" mode of the table editable = edit;
function btnEdit_click_Event(){ };
if(editMode){ };
$.btnEdit.title = "Allow Editing";
editMode = false;
} else {
$.btnEdit.title = "Cancel Editing";
editMode = true; Here, the main app is responding to a
};
$.table1.editing(editMode); //call the button click event and changing the
"editing" method we created in the widget
};
editable state in the widget.
25. CONTROLLERS – CALLING FUNCTIONS
App – index.js Widget – widget.js
$.index.open(); //custom method we expose to set the table's
data
var editMode = false; exports.setData = function(rows /*Ti.UI.Row*/){
$.table.setData(rows);
//create some data to put in the table };
var rows = [];
for(var i=0;i<10;i++){ //custom method we expose to allow the table to
rows.push(Ti.UI.createTableViewRow({title:'Row be editable
number ' + i})); exports.editing = function(edit /*bool*/){
}; if(OS_IOS){
$.table1.setData(rows); //call the "setData" $.table.editing = edit; //allow row
method we created in the widget editing on iPhone & iPad
} else {
//toggle the "editable" mode of the table editable = edit;
function btnEdit_click_Event(){ };
if(editMode){ };
$.btnEdit.title = "Allow Editing";
editMode = false;
} else {
$.btnEdit.title = "Cancel Editing";
editMode = true; Here, the main app is responding to a
};
$.table1.editing(editMode); //call the button click event and changing the
"editing" method we created in the widget
};
editable state in the widget.
26. CONTROLLERS – HANDLING EVENTS
App – index.js Widget – widget.js
$.table1.addEventListener('delete', function(r){ //create a handlers object that will contain
Ti.API.info('row deleted: ' + JSON.stringify(r)); references to functions
var dialog = Ti.UI.createAlertDialog({ var handlers = {};
message: r.row.title,
title: 'Deleted' //assign some functions that do nothing.
}); handlers.click = function(r){};
dialog.show(); handlers.deleteRow = function(r){};
});
//expose a function that can pass in a reference
to an external function and assign the reference
In the widget we create an to the appropriate handler.
“addEventListener” function that has exports.addEventListener = function(listenerName,
listenerFunction){
two parameters, the name of the event switch (listenerName){
case 'click' :
and a reference to the function it will handlers.click = listenerFunction;
break;
call in the main app. case 'delete' :
handlers.deleteRow = listenerFunction;
break;
We assign the reference to a };
};
“handlers” object we created in the if(OS_IOS){
widget. $.table.addEventListener('delete',
function(r){
27. CONTROLLERS – HANDLING EVENTS
App – index.js Widget – widget.js
$.table1.addEventListener('delete', function(r){ //create a handlers object that will contain
Ti.API.info('row deleted: ' + JSON.stringify(r)); references to functions
var dialog = Ti.UI.createAlertDialog({ var handlers = {};
message: r.row.title,
title: 'Deleted' //assign some functions that do nothing.
}); handlers.click = function(r){};
dialog.show(); handlers.deleteRow = function(r){};
});
//expose a function that can pass in a reference
to an external function and assign the reference
to the appropriate handler.
exports.addEventListener = function(listenerName,
listenerFunction){
switch (listenerName){
In the main app we call the case 'click' :
“addEventListener” function, passing in handlers.click = listenerFunction;
break;
the name of the listener and defining case 'delete' :
handlers.deleteRow = listenerFunction;
the function we want to execute when break;
};
the even is fired. };
if(OS_IOS){
$.table.addEventListener('delete',
function(r){
28. CONTROLLERS – HANDLING EVENTS
App – index.js Widget – widget.js
$.table1.addEventListener('delete', function(r){ //assign some functions that do nothing.
Ti.API.info('row deleted: ' + JSON.stringify(r)); handlers.click = function(r){};
var dialog = Ti.UI.createAlertDialog({ handlers.deleteRow = function(r){};
message: r.row.title,
title: 'Deleted' //expose a function that can pass in a reference
}); to an external function and assign the reference
dialog.show(); to the appropriate handler.
}); exports.addEventListener = function(listenerName,
listenerFunction){
switch (listenerName){
case 'click' :
handlers.click = listenerFunction;
break;
case 'delete' :
handlers.deleteRow = listenerFunction;
In the widget we listen for the table's break;
};
“delete” event and call the appropriate };
handler object. if(OS_IOS){
$.table.addEventListener('delete',
function(r){
handlers.deleteRow(r);
});
};
29. THANK YOU
Source code: http://bit.ly/alloy-customTableView
Slideshare: http://bit.ly/alloy-customTableViewSlides
Mobile Data Systems Ltd.
Turnkey mobile consultancy
www.mobiledatasystems.co
martin.hudson@mobiledatasystems.co