creating alloy widgets
DESCRIPTION
Create Alloy widgets using Titanium Appcelerator.TRANSCRIPT
ALLOY WIDGETSImproving code re-use through a library of bespoke UI
components.
TiConf.euMartin Hudson, Jonti Hudson
February 2013
WHAT IS A WIDGET?
A self-contained bespoke UI component that holds all the logic associated with its use.
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
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)
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
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.
WHAT IS A WIDGET?
App Widget
Multiple widgets in the same window...
... or multiple instances of the same widget
WHAT IS A WIDGET?
App Widget
A CUSTOM TABLE VIEW WIDGET
An example of building a cross-platform widget that encapsulates common functionality but utilises platform specific behaviour.
CREATING A WIDGETTo create a new widget, right click the project name in the Titanium Studio project view... and select New → Alloy Widget.
CREATING A WIDGETTo 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.
WIDGET FILE STRUCTURE
Note the absence of Models – this is because the main app should handle data storage
A widgets folder is created with Controllers, Styles and Views sub-directories.
Widget – widget.xml
USING A WIDGET
App – index.xml
<Alloy><Window class="container"> <Require type="widget" src="co.mobiledatasystems.customEditableTable" id="table1"> </Require> <Button id="btnEdit" title="Allow Editing" onClick="btnEdit_click_Event"> </Button></Window></Alloy>
<Alloy> <TableView id="table"></TableView></Alloy>
In the main app, we will call the newly created widget using the “Require” tag.
Widget – widget.xml
USING A WIDGET
App – index.xml
<Alloy><Window class="container"> <Require type="widget" src="co.mobiledatasystems.customEditableTable" id="table1"> </Require> <Button id="btnEdit" title="Allow Editing" onClick="btnEdit_click_Event"> </Button></Window></Alloy>
<Alloy> <TableView id="table"></TableView></Alloy>
Note we specify that we want a widget and reference the name of our new widget. Alloy knows where to find it.
In the main app, we will call the newly created widget using the “Require” tag.
Widget – widget.xml
USING A WIDGET
App – index.xml
<Alloy><Window class="container"> <Require type="widget" src="co.mobiledatasystems.customEditableTable" id="table1"> </Require> <Button id="btnEdit" title="Allow Editing" onClick="btnEdit_click_Event"> </Button></Window></Alloy>
<Alloy> <TableView id="table"></TableView></Alloy>
In the main app, we will call the newly created widget using the “Require” tag.
In the widget, we specify the UI components we want to expose. In our case it is only a TableView.
STYLINGWidget – widget.tssApp – index.tss
We have referenced our instance of the widget “table1” in index.xml
".container": { backgroundColor:"white"},"#table1": { left: '10dp', right: '10dp', top: '20dp', 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.
STYLINGWidget – widget.tssApp – index.tss
".container": { backgroundColor:"white"},"#table1": { left: '10dp', right: '10dp', top: '40dp', bottom:'80dp'},"#btnEdit": { bottom:'10dp', left:'20dp', right:'20dp', height:'45dp'}
".container": { backgroundColor:"white"},"#table1": { left: '10dp', right: '10dp', top: '40dp', bottom:'80dp'},"#btnEdit": { bottom:'10dp', left:'20dp', right:'20dp', height:'45dp'}
CONTROLLERS - INITIALISEWidget – widget.jsApp – index.tss
In the widget's controller we can access all the parameters passed in using the arguments[] array.
//copy the arguments passed in to the widget via. the xml and tss parameters
var _args = arguments[0] || {};
var editable = null;
if(OS_ANDROID){editable = false;};
//get each element set in the widget's xml or tss parameters
Ti.API.info(JSON.stringify(_args));
//iterate round all the parameters we have passed in
for (var key in _args) { if (_args.hasOwnProperty(key)) {
//checks key is a direct property of _args, not somewhere down the object tree
if(OS_ANDROID){ switch (key){
".container": { backgroundColor:"white"},"#table1": { left: '10dp', right: '10dp', top: '40dp', bottom:'80dp'},"#btnEdit": { bottom:'10dp', left:'20dp', right:'20dp', height:'45dp'}
CONTROLLERS - INITIALISEWidget – widget.jsApp – index.tss
In the widget's controller we can access all the parameters passed in using the arguments[] array.
//copy the arguments passed in to the widget via. the xml and tss parameters
var _args = arguments[0] || {};
var editable = null;
if(OS_ANDROID){editable = false;};
//get each element set in the widget's xml or tss parameters
Ti.API.info(JSON.stringify(_args));
//iterate round all the parameters we have passed in
for (var key in _args) { if (_args.hasOwnProperty(key)) {
//checks key is a direct property of _args, not somewhere down the object tree
if(OS_ANDROID){ switch (key){
".container": { backgroundColor:"white"},"#table1": { left: '10dp', right: '10dp', top: '40dp', bottom:'80dp'},"#btnEdit": { bottom:'10dp', left:'20dp', right:'20dp', height:'45dp'}
CONTROLLERS - INITIALISEWidget – widget.jsApp – index.tss
We can read each parameter passed in and process them appropriately. In our example “editable” and “moving” are iOS specific. We will set a local variable in the case of Android.
//iterate round all the parameters we have passed in
for (var key in _args) { if (_args.hasOwnProperty(key)) {
//checks key is a direct property of _args, not somewhere down the object tree
if(OS_ANDROID){ switch (key){ case 'editing': editable = _args[key]; break; case 'moving': break; //android doesn't recognise this property default: $.table[key] = _args[key]; } } else { $.table[key] = _args[key]; }; };};
".container": { backgroundColor:"white"},"#table1": { left: '10dp', right: '10dp', top: '40dp', bottom:'80dp'},"#btnEdit": { bottom:'10dp', left:'20dp', right:'20dp', height:'45dp'}
CONTROLLERS - INITIALISEWidget – widget.jsApp – index.tss
We can read each parameter passed in and process them appropriately. In our example “editable” and “moving” are iOS specific. We will set a local variable in the case of Android.
//iterate round all the parameters we have passed in
for (var key in _args) { if (_args.hasOwnProperty(key)) {
//checks key is a direct property of _args, not somewhere down the object tree
if(OS_ANDROID){ switch (key){ case 'editing': editable = _args[key]; break; case 'moving': break; //android doesn't recognise this property default: $.table[key] = _args[key]; } } else { $.table[key] = _args[key]; }; };};
$.index.open();
var editMode = false;
//create some data to put in the tablevar rows = [];for(var i=0;i<10;i++){rows.push(Ti.UI.createTableViewRow({title:'Row number ' + i}));};$.table1.setData(rows); //call the "setData" method we created in the widget
//toggle the "editable" mode of the tablefunction btnEdit_click_Event(){ if(editMode){ $.btnEdit.title = "Allow Editing"; editMode = false; } else { $.btnEdit.title = "Cancel Editing"; editMode = true; }; $.table1.editing(editMode); //call the "editing" method we created in the widget};
CONTROLLERS – CALLING FUNCTIONSWidget – widget.jsApp – index.js
Using the commonJS framework, we can expose functions in the widget. In our example we expose “setData” so we can populate the table after the widget has created it.
//custom method we expose to set the table's dataexports.setData = function(rows /*Ti.UI.Row*/){ $.table.setData(rows);};
//custom method we expose to allow the table to be editableexports.editing = function(edit /*bool*/){ if(OS_IOS){ $.table.editing = edit; //allow row editing on iPhone & iPad } else { editable = edit; };};
$.index.open();
var editMode = false;
//create some data to put in the tablevar rows = [];for(var i=0;i<10;i++){rows.push(Ti.UI.createTableViewRow({title:'Row number ' + i}));};$.table1.setData(rows); //call the "setData" method we created in the widget
//toggle the "editable" mode of the tablefunction btnEdit_click_Event(){ if(editMode){ $.btnEdit.title = "Allow Editing"; editMode = false; } else { $.btnEdit.title = "Cancel Editing"; editMode = true; }; $.table1.editing(editMode); //call the "editing" method we created in the widget};
CONTROLLERS – CALLING FUNCTIONSWidget – widget.jsApp – index.js
Using the commonJS framework, we can expose functions in the widget. In our example we expose “setData” so we can populate the table after the widget has created it.
//custom method we expose to set the table's dataexports.setData = function(rows /*Ti.UI.Row*/){ $.table.setData(rows);};
//custom method we expose to allow the table to be editableexports.editing = function(edit /*bool*/){ if(OS_IOS){ $.table.editing = edit; //allow row editing on iPhone & iPad } else { editable = edit; };};
$.index.open();
var editMode = false;
//create some data to put in the tablevar rows = [];for(var i=0;i<10;i++){rows.push(Ti.UI.createTableViewRow({title:'Row number ' + i}));};$.table1.setData(rows); //call the "setData" method we created in the widget
//toggle the "editable" mode of the tablefunction btnEdit_click_Event(){ if(editMode){ $.btnEdit.title = "Allow Editing"; editMode = false; } else { $.btnEdit.title = "Cancel Editing"; editMode = true; }; $.table1.editing(editMode); //call the "editing" method we created in the widget};
CONTROLLERS – CALLING FUNCTIONSWidget – widget.jsApp – index.js
Here, the main app is responding to a button click event and changing the editable state in the widget.
//custom method we expose to set the table's dataexports.setData = function(rows /*Ti.UI.Row*/){ $.table.setData(rows);};
//custom method we expose to allow the table to be editableexports.editing = function(edit /*bool*/){ if(OS_IOS){ $.table.editing = edit; //allow row editing on iPhone & iPad } else { editable = edit; };};
$.index.open();
var editMode = false;
//create some data to put in the tablevar rows = [];for(var i=0;i<10;i++){rows.push(Ti.UI.createTableViewRow({title:'Row number ' + i}));};$.table1.setData(rows); //call the "setData" method we created in the widget
//toggle the "editable" mode of the tablefunction btnEdit_click_Event(){ if(editMode){ $.btnEdit.title = "Allow Editing"; editMode = false; } else { $.btnEdit.title = "Cancel Editing"; editMode = true; }; $.table1.editing(editMode); //call the "editing" method we created in the widget};
CONTROLLERS – CALLING FUNCTIONSWidget – widget.jsApp – index.js
Here, the main app is responding to a button click event and changing the editable state in the widget.
//custom method we expose to set the table's dataexports.setData = function(rows /*Ti.UI.Row*/){ $.table.setData(rows);};
//custom method we expose to allow the table to be editableexports.editing = function(edit /*bool*/){ if(OS_IOS){ $.table.editing = edit; //allow row editing on iPhone & iPad } else { editable = edit; };};
//create a handlers object that will contain references to functionsvar handlers = {};
//assign some functions that do nothing.handlers.click = function(r){};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){ case 'click' : handlers.click = listenerFunction; break; case 'delete' : handlers.deleteRow = listenerFunction; break; };};
if(OS_IOS){ $.table.addEventListener('delete', function(r){
$.table1.addEventListener('delete', function(r){Ti.API.info('row deleted: ' + JSON.stringify(r));var dialog = Ti.UI.createAlertDialog({ message: r.row.title, title: 'Deleted' }); dialog.show();});
CONTROLLERS – HANDLING EVENTSWidget – widget.jsApp – index.js
In the widget we create an “addEventListener” function that has two parameters, the name of the event and a reference to the function it will call in the main app.
We assign the reference to a “handlers” object we created in the widget.
//create a handlers object that will contain references to functionsvar handlers = {};
//assign some functions that do nothing.handlers.click = function(r){};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){ case 'click' : handlers.click = listenerFunction; break; case 'delete' : handlers.deleteRow = listenerFunction; break; };};
if(OS_IOS){ $.table.addEventListener('delete', function(r){
$.table1.addEventListener('delete', function(r){Ti.API.info('row deleted: ' + JSON.stringify(r));var dialog = Ti.UI.createAlertDialog({ message: r.row.title, title: 'Deleted' }); dialog.show();});
CONTROLLERS – HANDLING EVENTSWidget – widget.jsApp – index.js
In the main app we call the “addEventListener” function, passing in the name of the listener and defining the function we want to execute when the even is fired.
//assign some functions that do nothing.handlers.click = function(r){};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){ case 'click' : handlers.click = listenerFunction; break; case 'delete' : handlers.deleteRow = listenerFunction; break; };};
if(OS_IOS){ $.table.addEventListener('delete', function(r){ handlers.deleteRow(r); });};
$.table1.addEventListener('delete', function(r){Ti.API.info('row deleted: ' + JSON.stringify(r));var dialog = Ti.UI.createAlertDialog({ message: r.row.title, title: 'Deleted' }); dialog.show();});
CONTROLLERS – HANDLING EVENTSWidget – widget.jsApp – index.js
In the widget we listen for the table's “delete” event and call the appropriate handler object.
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