Università Degli Studi di Parma Distributed Systems Group Cross-platform Programming Titanium Alloy Tutorial Alessandro Grazioli http://dsg.ce.unipr.it/?q=node/37 http://dsg.ce.unipr.it/ Alessandro Grazioli alessandro.grazioli81@gmail.com 2015 - Parma Università Degli Studi di Parma Distributed Systems Group Create a new project - Create a new project by selecting File → New → Project Select Alloy on the left section of the wizard and choose Mobile App Project → Default Alloy Project Alessandro Grazioli 2015 - Parma Università Degli Studi di Parma Distributed Systems Group Create a new project - We’ll add 2 tabs to our app ‣ Create files tabOneView.xml and tabTwoView.xml for the tab views in app/views <Alloy> <Tab id='first_tab' title='Tab 1' icon="KS_nav_views.png"> <Window title='Tab view one' class='container'> <Label>I am Window 1</Label> <Button id='open_button'>Open Child Window</Button> </Window> </Tab> </Alloy> tabOneView.xml code <Alloy> <Tab id='second_tab' title='Tab 2' icon="KS_nav_ui.png"> <Window title='Tab view two' class='container'> <TableView id='contactsTable'></TableView> </Window> </Tab> </Alloy> tabTwoView.xml code Alessandro Grazioli 2015 - Parma Università Degli Studi di Parma Distributed Systems Group Defining a Tab - Each element of the view requires an id so that the controller can access it Images are taken from app/images directory The view includes a Tab with a nested Window including a button We’ll now define the first tab’s child window ‣ Create file tabOneViewChild.xml in app/views <Alloy> <Window id="first_tab_child_window" title='Tab view one child' class='container'> </Window> </Alloy> Alessandro Grazioli 2015 - Parma Università Degli Studi di Parma Distributed Systems Group Setting the style - The style properties for a view have to be specified in a file having the same name of the view file and .tss extension (e.g., the style for index.xml view must be specified in index.tss file) - Style files have to be placed in app/styles directory You can also define a file named app.tss to include all the styles that have to be applied to every element in the app ‣ Create such a file and add the following code ".container": { backgroundColor:"white" } - Now, every element having class container will have a white background, regardless of the file where it has been defined If you want to specify the position of tabOneView’s button, you can create tabOneView.tss file and set its layout as: "#open_button": { position: 'absolute', top: '20px' } Alessandro Grazioli 2015 - Parma Università Degli Studi di Parma Distributed Systems Group Defining a TabGroup - Now that the tab views are ready, we’ll build the tab group to contain them in index.xml To specify the views to be included, use the Require element <Alloy> <TabGroup> <Require src="tabOneView" /> <Require src="tabTwoView" /> </TabGroup> </Alloy> - The tabs code can of course be included directly in index.xml file, but the use of Require elements makes the code more modular since the functionality for each component is separated into a specific controller file Alessandro Grazioli 2015 - Parma Università Degli Studi di Parma Distributed Systems Group Defining controllers - Now you can implement the controllers in app/controllers index.js is the controller for the view defined in index.html You can create the controller for tabOneView view in file tabOneView.js and add a function executed when the user presses the button $.open_button.addEventListener('click', function(e) { var tabViewOneChildController = Alloy.createController('tabOneViewChild'); tabViewOneChildController.openMainWindow($.first_tab); }); The code refers to a function defined in tabOneViewChild.js which takes a Tab as param and opens a window in it. Such a function is exported with openMainWindow name - The event listener $.open_button is called when the user presses the button having id open_button (the $. notation allows you to access elements by id) - Alloy.createController(‘ID’) returns the controller for the view whose id is passed as a parameter Alessandro Grazioli 2015 - Parma Università Degli Studi di Parma Distributed Systems Group Defining controllers - We’ll now define openMainWindow method ‣ Create app/controllers/tabOneViewChild.js file and add the following content function openMainWindow(tab){ tab.open($.first_tab_child_window); } exports.openMainWindow = openMainWindow; ‣ The code defines a function which takes a Tab as parameter and opens a window in it (the type Tab is required to be able to call method open for the parameter) ‣ The function is exported with name openMainWindow Alessandro Grazioli 2015 - Parma Università Degli Studi di Parma Distributed Systems Group Adding a map - We’ll now add a map and a text field to look for locations to tabOneViewChild by editing tabOneView.xml as follows <Alloy> <Window id="first_tab_child_window" title='Tab view one child' class='container'> <Require src="addressField" id="addressField" /> <Require src="map" id="map" /> </Window> </Alloy> - - The code requires two additional views to: ‣ display a map (map.xml) ‣ search for an address and center the map on it (addressField.xml) In the following we define the required views Alessandro Grazioli 2015 - Parma Università Degli Studi di Parma Distributed Systems Group Specifying the map module - The map is not part of Titanium, so we need to use a proper module named Ti.Map (we will add it to the project in 4 slides) Create file app/views/map.xml and add the following code <Alloy> <View id="map" ns="Ti.Map" > <Require src="annotation" title="Annotation" /> </View> </Alloy> - All UI components specified in the views are prefixed with Titanium.UI for convenience. However, to use a component not part of the Titanium.UI namespace, you need to use the ns attribute – Ti.Map will be used to interact with the map The map view requires a view, named annotation, which represents a labeled point of interest (POI) the user can click ‣ Create file app/views/annotation.xml and add the following code <Alloy> <Annotation id="annotation" /> </Alloy> - Create app/views/addressField.xml file and add the following code <Alloy> <View class="addressField"> <TextField id="textField" hintText="Enter an address" /> <Button id="searchButton" title="Search" /> </View> </Alloy> Alessandro Grazioli 2015 - Parma Università Degli Studi di Parma Distributed Systems Group Setting map’s views style - We’ll now define the style for the required views Create file app/styles/map.tss and add the following code "#map" : { mapType : 'Ti.Map.STANDARD_TYPE', top : '50dp', animate : true, regionFit : true, userLocation : true, region : { latitude : Alloy.Globals.LATITUDE_BASE, longitude : Alloy.Globals.LONGITUDE_BASE, latitudeDelta : 0.1, longitudeDelta : 0.1 } } - Map’s properties are described in 2 slides Create file app/styles/annotation.tss and add the following code "Annotation" : { animate : true, pincolor : Titanium.Map.ANNOTATION_RED } Alessandro Grazioli 2015 - Parma Università Degli Studi di Parma Distributed Systems Group Setting map’s views style - Create file app/styles/addressField.tss and add the following code "TextField" : { height : '40dp', top : '5dp', left : '5dp', right : '150dp', style : Ti.UI.INPUT_BORDERSTYLE_ROUNDED, backgroundColor : '#fff', paddingLeft : '5dp' } "Button" : { font : { fontSize : '20dp', fontWeight : 'bold' }, top : '5dp', height : '40dp', width : '150dp', right : '5dp' } ".addressField" : { backgroundColor : '#E0E0E0', height : '50dp', top : 0 } Alessandro Grazioli 2015 - Parma Università Degli Studi di Parma Distributed Systems Group Map attributes Map attributes - mapType indicates what type of map should be displayed (possible values are: Ti.Map.STANDARD_TYPE, Ti.Map.SATELLITE_TYPE and Ti.Map.HYBRID_TYPE) - animate is a boolean that indicates whether or not map actions, like opening and adding annotations, should be animated - regionFit is a boolean that indicates if the map should attempt to fit the region (MapView) in the visible view userLocation is a boolean that indicates if the map should show the user's current device location as a pin on the map region is an object that contains the 4 properties defining the visible area of the MapView ‣ latitude and longitude represent the center of the map and are set based on the two variables defined in alloy.js file ‣ The same latitude and longitude of a region can be represented with a different level of zoom via the latitudeDelta and longitudeDelta properties (they respectively represent the latitude north and south, and the longitude east and west, from the center of the map that will be visible) - the smaller the delta values, the closer the zoom on the map Alessandro Grazioli 2015 - Parma Università Degli Studi di Parma Distributed Systems Group Adding the map module to the project - To display a map, it is necessary to add the ti.map module - Open tiapp.xml and click on + on the modules side Choose ti.map module and add it To use the module, you need to specify it by adding to alloy.js file the following code Alloy.Globals.LATITUDE_BASE = 44.765; Alloy.Globals.LONGITUDE_BASE = 10.3; if (OS_IOS || OS_ANDROID) { Ti.Map = require('ti.map'); } - The first ad second line define two variables that will represent the center of the map The if block specifies that Ti.Map calls refer to ti.map module (the if condition is due to the fact that the map module is just available for iOS and Android platforms) – the ns attribute of map.xml file refers to this Alessandro Grazioli 2015 - Parma Università Degli Studi di Parma Distributed Systems Group Geocoding - To geocode the addresses provided by the user, you can use a script provided by Appcelerator in the Alloy Samples page (https://github.com/appcelerator/alloy/tree/master/samples/mapping/lib), namely geo.js, and store it in app/lib - Define the controllers for the new views ‣ Create file app/controllers/addressField.js and add the following code var geo = require('geo'); $.searchButton.addEventListener('click', function(e) { $.textField.blur(); geo.forwardGeocode($.textField.value, function(geodata) { $.trigger('addAnnotation', {geodata: geodata}); }); }); searchButton click event listener executes a function called forwardGeocode from geo.js which computes the latitude and longitude corresponding to the address the user provided; its second parameter is a callback to be executed upon correct coordinates retrieval. Alessandro Grazioli 2015 - Parma Università Degli Studi di Parma Distributed Systems Group Adding map controller ‣ Create file app/controllers/map.js and add the following code var annotations = new Array(); exports.addAnnotation = function(geodata) { var annotation = Alloy.createController('annotation', { title : geodata.title, latitude : geodata.coords.latitude, longitude : geodata.coords.longitude }); annotations.push(annotation); $.map.addAnnotation(annotation.getView()); }; $.map.setLocation({ latitude : geodata.coords.latitude, longitude : geodata.coords.longitude, latitudeDelta : 1, longitudeDelta : 1 }); Alessandro Grazioli 2015 - Parma Università Degli Studi di Parma Distributed Systems Group Editing controllers - Add a function to tabOneViewChild controller so that when the user presses searchButton, and its callback executes, function addAnnotation defined in map controller is called ($.map.addAnnotation) $.addressField.on('addAnnotation', function(e) { $.map.addAnnotation(e.geodata); }); - searchButton callback calls forwardGeocode function defined in geo.js which takes, as second parameter, a callback triggering function addAnnotation - since such a function is not defined in tabOneViewChild controller, the code we add uses the on method to call addAnnotation function defined in map.js - addAnnotation function adds a pin in the location searched by the user – to do so, you need to define the Annotation controller The OR means that each variable can assume the value passed as a parameter when the controller is created, or a default value var args = arguments[0] || {}; $.annotation.title = args.title || ''; $.annotation.latitude = args.latitude || Alloy.Globals.LATITUDE_BASE; $.annotation.longitude = args.longitude || Alloy.Globals.LONGITUDE_BASE; - The controller for a new annotation is created by addAnnotation method in map.js (see previous slide) Alessandro Grazioli 2015 - Parma Università Degli Studi di Parma Distributed Systems Group Events management - Events can be used to provide interactions between different controllers - Ti.App.addEventListener("app:clickedAnnotation", function(evt) { var indexOfClickedElement; for(var i = 0; i < annotations.length; i++) { var currentAnnotationTitle = annotations[i].getView().title; if(currentAnnotationTitle == evt.title) { // The index of the clicked annotation in the array indexOfClickedElement = i; var removePinAlert = Titanium.UI.createAlertDialog({ message: 'Remove pin?', buttonNames: ['Confirm', 'Cancel'] }); removePinAlert.addEventListener('click', function(e) { // Clicked cancel, first check is for iphone, second for android if (e.cancel === e.index || e.cancel === true) { return; } switch (e.index) { case 0: { annotations.splice(indexOfClickedElement, 1); $.map.removeAnnotation(evt.title); } break; We can modify map.js so that, when a user clicks on a pin on the map, such a pin is removed upon confirm ‣ Add an array for the annotations in map.js ‣ Each time an annotation is added to the map from function addAnnotation in map.js, also add it to the array ‣ Add an event listener for the clicked pin and ask the user if he/she wants to remove the pin ‣ case 1: { removePinAlert.hide(); } break; We also have to modify annotation controller to fire an event when the user clicks the annotation itself $.annotation.addEventListener('click', function(e) { Ti.App.fireEvent("app:clickedAnnotation", { title : e.source.title }); }); default: break; } }); removePinAlert.show(); break; // exit from for cycle } } }); Alessandro Grazioli 2015 - Parma Università Degli Studi di Parma Contacts management - Alloy can access native APIs such as the phone contacts We can display in tabTwoView a table listing all contacts Also, we can add a listener to the table so that, when the user clicks a row, an alert displaying the information of the contacts is presented $.contactsTable.addEventListener("click", function(e) { var contactDetails = e.source.title + "\n"; for(var temp in e.source.phone) { var temp_numbers = e.source.phone[temp]; for(var k=0;k<temp_numbers.length; k++) { var temp_num = temp_numbers[k]; temp_num = temp_num.replace(/[^\d.]/g, ""); contactDetails += temp_num + "\n"; } } alert("Clicked " + contactDetails); }); Distributed Systems Group var addressBookDisallowed = function() { alert("Cannot access address book"); }; if (Ti.Contacts.contactsAuthorization == Ti.Contacts.AUTHORIZATION_AUTHORIZED) { renderContacts(); } else if (Ti.Contacts.contactsAuthorization == Ti.Contacts.AUTHORIZATION_UNKNOWN) { Ti.Contacts.requestAuthorization(function(e) { if (e.success) { renderContacts(); } else { addressBookDisallowed(); } }); } else { addressBookDisallowed(); } var data = []; function renderContacts() { var contacts = Ti.Contacts.getAllPeople(); data = []; for (var i = 0; i < contacts.length; i++) { var title = contacts[i].fullName; var phone = contacts[i].phone; if (!title || title.length === 0) { title = "(no name)"; } data.push({ title : title, phone : phone }); } Alessandro Grazioli } $.contactsTable.setData(data); 2015 - Parma Università Degli Studi di Parma Distributed Systems Group Final result Alessandro Grazioli 2015 - Parma
© Copyright 2024