Creating Alloy Widgets

Preview:

DESCRIPTION

Create Alloy widgets using Titanium Appcelerator.

Citation preview

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

martin.hudson@mobiledatasystems.co