Creating Mobile Apps with Sencha Touch 2


Creating Mobile Apps with Sencha Touch 2 Learn to use the Sencha Touch programming language and expand your skills by building 10 unique applications John Earl Clark Bryan P. Johnson BIRMINGHAM - MUMBAI Creating Mobile Apps with Sencha Touch 2 Copyright © 2013 Packt Publishing All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews. Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the authors, nor Packt Publishing, and its dealers and distributors will be held liable for any damages caused or alleged to be caused directly or indirectly by this book. Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information. First published: April 2013 Production Reference: 1280313 Published by Packt Publishing Ltd. Livery Place 35 Livery Street Birmingham B3 2PB, UK. ISBN 978-1-84951-890-1 www.packtpub.com Cover Image by Avishek Roy (roy007avishek88@gmail.com) Credits Authors John Earl Clark Bryan P. Johnson Reviewer Kristian Kristensen Acquisition Editor Usha Iyer Lead Technical Editor Sweny Sukumaran Technical Editors Jalasha D'costa Saumya Kunder Project Coordinator Amey Sawant Proofreaders Maria Gould Kate Robinson Indexer Tejal Soni Graphics Ronak Dhruv Production Coordinators Manu Joseph Nilesh Mohite Nitesh Thakur Cover Work Nitesh Thakur About the Authors John Earl Clark holds a Master's degree in Human Computer Interaction from Georgia Tech and an undergraduate degree in Music Engineering from Georgia State University. John and his co-author, Bryan P. Johnson, worked together at MindSpring and later EarthLink, starting out in technical support and documentation, before moving into application development, and finally management of a small development team. After leaving EarthLink in 2002, John began working independently as a consultant and programmer, before starting Twelve Foot Guru, LLC with Bryan in 2005. John has been working with Sencha Touch since the first early beta releases. He has also worked with Sencha's ExtJS since the early days when it was still known as YUI-Ext. John has also written a previous book with Bryan Johnson called Sencha Touch Mobile JavaScript Framework by Packt Publishing. When he is not buried in code, John spends his time woodworking, playing the guitar, and brewing his own beer. I would like to thank my family for all of their love and support. I would also like to thank Bryan for his help, his patience, and his continued faith in our efforts. Bryan P. Johnson is a graduate of the University of Georgia. Bryan went on to work for MindSpring Enterprises in late 1995, where he met his co-author John Clark. At MindSpring, and later EarthLink, for over seven years, Bryan served in multiple positions, including Director of System Administration and Director of Internal Application Development. After leaving EarthLink, Bryan took some time off to travel before joining John in starting Twelve Foot Guru. Bryan has worked with Sencha's products since the early days of YUI-EXT, and has used Sencha Touch since its first betas. Last year, he and John wrote their first Sencha Touch book, Sencha Touch Mobile JavaScript Framework by Packt Publishing. I would like to thank my friends and family for their support and my co-author John for his patience during the creation of this book. About the Reviewer Kristian Kristensen is an independent software development consultant. Through his company Kristensen Inc., he takes on the role of teacher, coach, facilitator, and anything in between to help software shops improve their processes and skills. He is particularly interested in languages and how they shape our thoughts and problem solving abilities. He worked as a consultant for Microsoft before embarking on the journey of freelance consulting. He holds a Master's in Software Engineering from Aalborg University and currently lives in Brooklyn, NY with his wife. For Heather… www.PacktPub.com Support files, eBooks, discount offers and more You might want to visit www.PacktPub.com for support files and downloads related to your book. Did you know that Packt offers eBook versions of every book published, with PDF and ePub files available? You can upgrade to the eBook version at www.PacktPub. com and as a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at service@packtpub.com for more details. At www.PacktPub.com, you can also read a collection of free technical articles, sign up for a range of free newsletters and receive exclusive discounts and offers on Packt books and eBooks. TM http://PacktLib.PacktPub.com Do you need instant solutions to your IT questions? PacktLib is Packt's online digital book library. Here, you can access, read and search across Packt's entire library of books.  Why Subscribe? • Fully searchable across every book published by Packt • Copy and paste, print and bookmark content • On demand and accessible via web browser Free Access for Packt account holders If you have an account with Packt at www.PacktPub.com, you can use this to access PacktLib today and view nine entirely free books. Simply use your login credentials for immediate access. Table of Contents Preface 1 Chapter 1: A Simple Task List 7 A brief overview of Sencha Architect 7 The Toolbox 10 The help section 11 The design area 12 The Project Inspector area 13 The Config area 14 Getting started with the task list 14 Creating the data store 15 Adding a Model, Fields, and Field Types 16 Adding the model to the store 17 Making copies 18 Adding filters 19 Pay attention to the man behind the curtain 20 Architect versus coding by hand 25 Creating the views 25 Configuring the Tab Panel 27 Adding the lists and details 28 Setting up the templates 30 Testing with starter data 31 Adding the back button 34 Creating the forms 35 Add Task form 35 Editing and completing a task 39 Testing the application 40 Extra credit 41 Summary 41 Table of Contents [ ii ] Chapter 2: A Feed Reader 43 The basic application 44 An overview of NavigationView 44 Adding the form 46 Back to the navigation view 49 Adding the controller 50 Getting the data from a remote source 52 Enter the JSONP proxy 53 Yahoo Query Language (YQL) 54 The YQL console 55 Meanwhile, back at the controller 58 The details dataview 60 And now, the CSS 64 Homework 67 Summary 68 Chapter 3: Going Command Line 69 The basic application 70 Installing Sencha Cmd 70 Advantages of Sencha Cmd 72 Generating the application skeleton 72 Creating the TimeCop layout 76 Creating the theme 79 Creating the increment button 81 Creating the start button 82 Using native APIs with Ext.device 84 Testing and running native applications 85 Registering as a developer 85 Becoming an Apple developer 85 Provisioning an application 86 Meanwhile back in the code 87 Using the native notifications 87 Compiling the application 88 Setting up packager.json 89 Building native Android applications 91 Creating the Android signing certificate 91 Creating the Android configuration file 92 Compiling and launching the Android application 93 Summary 94 Table of Contents [ iii ] Chapter 4: Weight Weight 95 Sencha Charts overview 95 The basic application 97 Setting up the application and building the form 97 Creating the data entry form 101 Creating the AddTag view 103 Creating the config form 105 Creating the DataEntry controller 109 Defining the models and stores 112 Meanwhile, back in the controllers 114 Config.js 118 Getting started with Sencha Touch Charts 121 Creating the overview chart 121 Adding the axes 123 Creating the series 124 The interactions section 127 Creating the details view 130 Creating the goalChart view 130 Creating the word chart 134 Homework 140 Summary 141 Chapter 5: On Deck: Using Sencha.io 143 The basic application 144 Creating the models and stores 144 Creating the views 147 Getting started with Sencha.io 153 The sign-up process 154 Downloading and installing the Sencha.io SDK 154 Registering your application and Auth group 155 Updating the application for Sencha.io 160 Updating the stores 160 Creating the controller 161 Overriding the Carousel component 166 Back in the controller 169 Deploying the application 172 Homework 173 Summary 174 Table of Contents [ iv ] Chapter 6: Catalog Application and API 175 What is an API? 175 Using a remote API in Sencha Touch 177 Creating your own API 180 Receiving data 181 Communicating with the database 182 Sending data back to the application 182 More information on APIs 182 Building the basic application 182 Creating the item model 183 RewriteRule and .htaccess 185 The item store 186 Creating the category model and store 187 Testing the store and the model 188 Creating the XTemplates 192 The API and the database 193 The GET request 194 The POST request 197 The PUT request 198 The DELETE request 199 The rest of the API 199 Summary 200 Chapter 7: The Decider: External APIs 201 Using an external API 202 The API key 202 API functions 202 External APIs and Sencha Touch 205 The basic application 207 Creating the categories list 210 Creating the contact controller 214 Integrating with Google Maps and Foursquare 216 Starting the mainView.js controller 216 Creating the confirmLocation view 217 Creating the Friends Chooser view 221 Creating the restaurant list, store, and details 223 Creating the restaurant store and model 225 Creating the details view 227 Finishing the main view controller 228 Homework 232 Summary 232 Table of Contents [ v ] Chapter 8: Evolver: Using Profiles 233 An overview of profiles 234 Profile basics 234 Using profiles 236 Creating the Evolver application 238 About WordPress 239 Using the plugin 241 Setting up the profiles and app.js 241 Setting up the models and stores 244 Creating the views 248 Creating the phone details view 249 Creating the tablet details view 250 The main views 251 Creating the phone main view 251 Creating the tablet main view 253 Creating the controllers 255 Conditional styling 259 Media queries 260 Summary 261 Chapter 9: Workbook: Using the Camera 263 Designing the basic application 263 Creating the models and stores 265 The views 267 Creating the book views 268 Adding the book list to the main view 272 Starting the book controller 273 Creating the note views 276 Creating the controller 280 Getting started with images 284 Capturing an image 285 Storing the image 287 Displaying the image 287 Sending images 288 Summary 289 Chapter 10: Game On 291 Building the basic board 292 Creating the square model 292 Exploring itemTpl 295 Creating the game controller 299 Understanding basic controller functions 302 Table of Contents [ vi ] The game board logic 303 Starting a turn 303 Checking the turn 304 Checking if a move is legal 306 Decorating the move 314 Clearing the move 318 Going beyond the finished game 318 Summary 320 Index 321 Preface Welcome to Creating Mobile Apps with Sencha Touch 2. The goal of this book is to help you learn the Sencha Touch mobile development platform by guiding you through a series of complete applications. Each application will focus on a different aspect of the language and show off many of the capabilities of Sencha Touch. The Sencha Touch language is an HTML5 framework that uses JavaScript and CSS to create powerful and flexible mobile applications. These applications can be hosted like a regular website, or compiled into apps (applications) which can be sold on the Apple or Android app stores. What this book covers Chapter 1, A Simple Task List, walks you through the use of Sencha Architect, a graphical application development tool for the Sencha Touch framework. Chapter 2, A Feed Reader, continues our exploration of Sencha Architect and explores using external data in your application, as well as creating complex layouts with xTemplates. Chapter 3, Going Command Line, we step away from Sencha Architect and explore the power of the Sencha Cmd tool for creating applications. We also cover compiling a basic application so we can use additional features of your mobile device. Preface [ 2 ] Chapter 4, Weight Weight, is an exercise and weight tracking application that uses the powerful Sencha Charts package to create visual displays for our data. Chapter 5, On Deck: Using Sencha.io, explores the use of the Sencha.io framework to store data on a remote server. Chapter 6, Catalog Application and API, builds on our use of APIs in the previous chapter to show how we might design and build our own custom API. Chapter 7, The Decider: External APIs, covers the use of multiple external APIs (Google Maps and FourSquare) to create a single application. Chapter 8, Evolver: Using Profiles, uses the Sencha Touch profiles to create a unique interface based on the mobile device you are using. It also covers pulling in data from WordPress to create a mobile version of your traditional website. Chapter 9, Workbook: Using the Camera, shows you how to use the camera on your mobile device from inside the Sencha Touch Framework. Chapter 10, Game On, shows you how to create a simple turn-based game. This can be used as a basis for creating your own turn-based games. What you need for this book The tools required are as follows: • Sencha Touch 2.1 (Commercial or GPL). • Sencha Cmd 3. • Sencha Architect 2.1 (used in Chapter 1, A Simple Task List and Chapter 2, A Feed Reader). • Touch Charts (used in Chapter 4, Weight Weight. This is included in Sencha Touch 2.1 GPL or available as a separate commercial purchase). Who this book is for This book is for people who have a basic knowledge of Sencha Touch and want to see how the features of Sencha Touch can be used as part of a complete application. Preface [ 3 ] Conventions In this book, you will find a number of styles of text that distinguish between different kinds of information. Here are some examples of these styles, and an explanation of their meaning. Code words in text, database table names, folder names, filenames, file extensions, pathnames, dummy URLs, user input, and Twitter handles are shown as follows: "As with our previous iOS configuration file, we create a JSON file called packager_android.json." A block of code is set as follows: listeners: [ { fn: 'onStartButtonTap', event: 'tap', delegate: '#startButton' } ] Any command-line input or output is written as follows: sencha generate model Contact --fields=id:int,firstName, lastName,email,phone New terms and important words are shown in bold. Words that you see on the screen, in menus or dialog boxes for example, appear in the text like this: "The Toolbox section of Sencha Architect is where you will find all of the components offered by Sencha Touch." Warnings or important notes appear in a box like this. Tips and tricks appear like this. Preface [ 4 ] Reader feedback Feedback from our readers is always welcome. Let us know what you think about this book—what you liked or may have disliked. Reader feedback is important for us to develop titles that you really get the most out of. To send us general feedback, simply send an e-mail to feedback@packtpub.com, and mention the book title via the subject of your message. If there is a topic that you have expertise in and you are interested in either writing or contributing to a book, see our author guide on www.packtpub.com/authors. Customer support Now that you are the proud owner of a Packt book, we have a number of things to help you to get the most from your purchase. Downloading the example code You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you. Errata Although we have taken every care to ensure the accuracy of our content, mistakes do happen. If you find a mistake in one of our books—maybe a mistake in the text or the code—we would be grateful if you would report this to us. By doing so, you can save other readers from frustration and help us improve subsequent versions of this book. If you find any errata, please report them by visiting http://www.packtpub. com/submit-errata, selecting your book, clicking on the errata submission form link, and entering the details of your errata. Once your errata are verified, your submission will be accepted and the errata will be uploaded on our website, or added to any list of existing errata, under the Errata section of that title. Any existing errata can be viewed by selecting your title from http://www.packtpub.com/support. Preface [ 5 ] Piracy Piracy of copyright material on the Internet is an ongoing problem across all media. At Packt, we take the protection of our copyright and licenses very seriously. If you come across any illegal copies of our works, in any form, on the Internet, please provide us with the location address or website name immediately so that we can pursue a remedy. Please contact us at copyright@packtpub.com with a link to the suspected pirated material. We appreciate your help in protecting our authors, and our ability to bring you valuable content. Questions You can contact us at questions@packtpub.com if you are having a problem with any aspect of the book, and we will do our best to address it. A Simple Task List At its core, most programming tasks fall into three categories: data display, data entry, and data storage. We will start our first project with the goal of covering how Sencha Touch handles each of these three basic categories. To do this, we will create a common programming application, the to-do list, or task list. In this application, we will use the local storage available in HTML5 to store tasks including a name, description, creation date, completing date, and priority. We will then create a task list for displaying the current tasks as well as our completed tasks. We will discuss ways to test your display and to manage errors. We will then create the forms for entering in new tasks, editing existing tasks, and marking a task as complete. Finally, we will explore some of the possible additional features for this type of application in our Extra Credit section. A brief overview of Sencha Architect Sencha Architect is a cross-platform visual development tool for Sencha Touch and Ext JS. Sencha Architect is available for Mac, Windows, and Linux, and it can be downloaded at the following link: http://www.sencha.com/products/architect For most of the chapters in this book we will be using a combination of Sencha Architect and standard coding to create our projects. This will give you an idea of some of the powerful advantages of the designer, while not hiding any of the actual code. A Simple Task List [ 8 ] This is actually one of the key benefits of Sencha Architect; while it allows you to rapidly create interfaces and test them, behind the scenes the designer is generating standard JavaScript files, which you can edit with any text editor. This advantage allows you to quickly assemble the basic elements of your application, while maintaining the ability to tweak the code by hand as needed. We will cover this a bit more later on, but for now let's take a look at how Sencha Architect is set up. When you first launch Sencha Architect, you are presented with a dialog box where you can choose to work on a new Ext JS project or a new Sencha Touch project, or you can choose from a list of existing projects: Since we are concerned with Sencha Touch in this book, you should select a new Sencha Touch 2.1 project. Chapter 1 [ 9 ] The difference between Ext JS and Sencha Touch Both ExtJ S and Sencha Touch are products created by the company Sencha Inc. Where Sencha Touch is used to develop mobile applications for various devices, Ext JS is used to create web applications for desktop browsers such as Firefox, Chrome, or Internet Explorer. For this book, we'll stick with Sencha Touch. Once you have chosen your new project type, the Sencha Architect window will open. This window contains a visual display of the application and allows us to modify the application using drag-and-drop as well as directly entering code. A Simple Task List [ 10 ] The Toolbox The Toolbox section of Sencha Architect is where you will find all of the components offered by Sencha Touch. These components are listed in alphabetical order on the right side of the Toolbox section, while the basic types of components are listed on the left side. Clicking on one of these component types will limit the list to that particular type. The following types are provided by default: • Behaviors: It provides empty containers for functions and controllers • Charts: It is a collection of graphs and charts that can pull data directly from a store • Containers: It contains elements such as panels, tab panels, carousels, and field sets Chapter 1 [ 11 ] • Data: It contains data-driven pieces such as stores, proxies, readers, writers, and filters • Forms: It contains basic form elements such as text fields, radio buttons, select fields, and buttons • Models: It includes the basic data model, validations, fields, proxies, readers, and writers • Resources: It allows you to add external files for JavaScript and CSS, as well as packaging files for compiled applications • Trees: Trees are the store types needed for nested tree components • Views: It contains all of the basic viewable components in Sencha Touch such as containers, form fields, media, pickers, toolbars, lists, and buttons There is also the Everything option to show all the types in the list. You can also use the + button in the CUSTOM section to add your own custom types for limiting the list. This is very helpful for frequently used components, or simply for customizing the lists to fit your own personal habits. Once a custom type is created, you can just drag components over from the list on the right, and drop them into your custom type. Components can also be searched directly by name using the Filter... field at the top of the toolbox area. The help section When any component is selected from the toolbox the help section directly below it will display information for the component. There is also a blue link at the bottom of the help area that says See Class Documentation. Clicking on this link will take you to the Sencha website documentation for the specific component that you have selected. This documentation is an invaluable source of information and you should familiarize yourself with it as quickly as possible. A Simple Task List [ 12 ] The design area The design area is where we will begin creating our first application. By default, a Sencha Touch application starts out with an iPhone 320 x 480 layout. This layout can be changed to be displayed as an iPad, Nexus S, or Kindle Fire display size. This allows you to look at your design under multiple devices. You can also set the orientation of the device and zoom in and out of the design view area. Chapter 1 [ 13 ] The design area also offers an option to view and work with the code behind the design. This is a great learning tool if you're just getting into mobile programming. By switching between the Design and Code view, you can examine complex layouts and see exactly how the JavaScript code is used to create them. The Project Inspector area The Project Inspector area provides an alternative view to your project's code. As you drag components onto the design area they will also appear in Project Inspector. The Project Inspection area will display these components as a hierarchical list. This is often very helpful in seeing which components are nested inside of other components. Components can also be dragged from the Toolbox list into Project Inspector. It is often easier to manage certain components by dropping them into Project Inspector, rather than the design area. This can ensure that you correctly position the component within the required container. The Resources section is a new addition in Version 2.1, and it allows you to add external files to your project. If you have an older Version 2.0 Sencha Touch project, you can right-click on Library and select Upgrade to change the project to a newer Sencha Touch 2.1 project. A Simple Task List [ 14 ] The Config area The Config area will display all the configuration options for any component selected in the design area or in Project Inspector. All of the typical configuration options, such as height, width, layout, ID, padding, margin, events, and functions can be accessed from the Config area. The configuration name is listed on the left and the value is on the right. Clicking on the value will allow you to edit it. You can also click on the + next to certain sections such as Functions and Events to add new items to Config. Getting started with the task list To see how all of these pieces work together to create an application, let's start by creating our data store for the Task Manager application. Save the new file you have opened as TaskList and let's get to work on adding some components. Chapter 1 [ 15 ] Creating the data store To add a component to the project, we need to drag the component from the toolbox and drop it on the project or onto the proper section of the project inspector. For our first component, let's choose a plain data store. From Toolbox, select Data, and then click on Store and drag it onto our iPhone in the design area. You should now see a store called MyStore appear in the Property Inspector under the Stores list: You will also notice that there is a red warning icon next to our store. This tells us that our store is missing a few of its required components. In this case, the store needs a proxy to control how the data is sent and received and it needs a model to tell it what data to expect. From the Toolbox list, select a LocalStorage Proxy object and drag it over onto our store. You will probably notice that some components can only be placed within other components. For example, when you drag the proxy over, you can't just drop it into the iPhone diagram like we did before. A proxy component will only exist as part of a data store. This means that you have to drop a proxy component onto a data store in Property Inspector in order for it to correctly add itself to the store. Once you have dropped the proxy onto the store, we need to add a model, and link it to our store. A Simple Task List [ 16 ] Adding a Model, Fields, and Field Types In the Data section of our Toolbox, scroll up to find the listing for Model. Drag the Model object over to the Models section of our Project Inspector. This will create a new model called MyModel. Select MyModel in Project Inspector and look at the Config section. The first thing we probably want to change here is the name. Click on the value listed for userClassName, change MyModel to Task and press Enter on your keyboard. The model should now be listed as Task in both Config and Project Inspector. Next, we need to add some fields to our model. With the Task model selected, you should see a listing for Fields in the Config area. Click on the + button next to Fields and enter id into the text area that appears. Click on Finish on the screen or press Enter on your keyboard. Your Config area should now look something like this: Chapter 1 [ 17 ] Repeat the previous steps to add the following fields to the task model: • name • description • create • completed • priority • isCompleted Now that you have all of your fields, we need to define the data types for each field. In Project Inspector, click on the field called id. This will open the configuration for that field and you should see that there is currently no value listed for type. Click on the value next to type. A drop-down menu will appear and you can select int from the list. Now we need to do the same thing for our other fields. Select each field in turn and set them as follows: • name: Set it as string • description: Set it as string • create: Set it as date • completed: Set it as date • priority: Set it as int • isCompleted: Set it as boolean Now that you have all of the model fields and types defined, we need to add the model to the store. Adding the model to the store Click on MyStore in Project Inspector. As we did with our model, we probably want to change the name of the store to make it easier to refer to and keep track of in our code. Click on the values next to userClassName and storeID and change both to say taskStore. A Simple Task List [ 18 ] Next, you will need to click on and edit the model Config in the store to select our Task model. Once complete, your store configuration should look something like this: Making copies Now that we have our store and model, we need to make a copy of it to hold our completed tasks. Both stores will use the same model and most of the same setup information. We only need to duplicate the store and change the id and userClassName values. Once we finish that, we will create filters for the store so that it only grabs the data we need. To duplicate TaskStore, right-click on it in Project Inspector and select Duplicate. A new store will appear called MyStore2, with the same proxy and model information. Select MyStore2 in Project Inspector and change both the id and userClassName values in the Config section to CompletedStore. Chapter 1 [ 19 ] Adding filters Now that we have our two stores, we need to set some filters to make sure that TaskStore only loads current tasks and CompletedStore only loads completed tasks. You can add filters in the Config section for each of our stores. First, select TaskStore and then click on the + button next to Filters in the Config section. This will add a new filter called MyFilter. Click on the arrow next to MyFilter to reveal its Config options. We need to add a function to this filter in order to tell it what records to grab. Click on the + button next to filterFn to add a new filter function. Up in the Project Inspector area, filterFn should appear beneath MyFilter. Click on filterFn in Property Inspector to bring up the code editor for the function. The editor should appear with the following code: filterFn: function(item) { } This sets up the basic function for the filter and passes us each record in the store as item. If our function returns true, the record is included in the store and if it returns false, the record is ignored. Our model has a Boolean value called isComplete. We can check this value in our function like this: return !item.data.isComplete; This takes the record we were passed as item, and checks the record's data for isComplete. If a task record is complete this will be true, so we put the ! character in front to grab only the records where isComplete is false. The filter is now complete. Take the same steps to add a filter to CompletedStore: 1. Add a filter to CompletedStore. 2. Add a function to the filter using filterFn. 3. Add the code for the filter function. In this case, our function just needs to look for tasks where isComplete is true (just drop the ! character this time): return item.data.isComplete; A Simple Task List [ 20 ] Both stores will now correctly filter the tasks based on completion. While we have been moving these components around on screen, Sencha Architect has been doing a bit of heavy lifting for us on the backend. Let's take a peek behind the curtain and see what's going on with the actual code. Pay attention to the man behind the curtain The first thing to look at is on your hard drive where you saved the Task Manager project file. You will notice that the designer has created a number of files here: app. html, app.js, and TaskManager.xds. We also have folders for app and metadata. Sencha Architect uses both TaskManager.xds and the metadata folder. The TaskManager.xds file is the main project file you are currently working in and the metadata folder contains resources for that project file. We can ignore those files for now because the interesting stuff is in the other files. Let's start with our app.html file. If you open this file in your favorite code editor, you should see something like this: Chapter1 This should look pretty familiar to anyone who is used to dealing with HTML and JavaScript. The file creates a basic HTML page, includes the JavaScript and CSS files for Sencha Touch, and also includes our app.js file (which we will get to in a second). The file then sets up some browser detection so that if the user attempts to access the application with a non-WebKit browser, they will be told that their browser is incompatible and will be given a list of compatible browsers. Chrome and Safari are WebKit browsers and are available for Windows and Mac. Chrome is also available on Linux. In this book we will be using Safari for our testing, but the examples will work in Chrome as well. One other thing to note is the message in the comments at the top of app.html. This particular file is autogenerated each time you save the TaskManager project. If you make changes to it in your code editor, they will be overwritten the next time you save. A word about CacheFly CacheFly is a CDN (Content Delivery Network). They have computers all over the world, and can send files to your users from a server that's close to them, so that the files take less time to travel across the Internet, and therefore less time to load. That also means that you save on your own server bandwidth by not serving those files yourself. Next, let's take a look at our app.js file: /* * File: app.js * * This file was generated by Sencha Architect version 2.0.0. * http://www.sencha.com/products/designer/ * A Simple Task List [ 22 ] * This file requires use of the Sencha Touch 2.0.x library, under independent license. * License of Sencha Architect does not include license for Sencha Touch 2.1.x. For more * details see http://www.sencha.com/license or contact license@ sencha.com. * * This file will be auto-generated each and every time you save your project. * * Do NOT hand edit this file. */ Ext.Loader.setConfig({ enabled: true }); Ext.application({ models: [ 'Task' ], stores: [ 'TaskStore', 'CompletedStore' ], name: 'MyApp' }); Like our HTML file, we start out with a stern warning at the top about hand editing the file. A word about hand editing Sencha Architect does a lot of work for you, but that means that it can also accidentally overwrite code that you've written by hand. Your best bet is to wait to add any code yourself until you've fully laid out and configured all of the components for your application with the Architect first. If we skip down past that section, we have a setup function for the Ext.Loader followed by a definition of our application. This includes links to all of our models and stores, as well as the name of our application (which we should probably change once we finish snooping around in the code). Chapter 1 [ 23 ] Ext.Loader is a special part of Sencha Touch that will load JavaScript files as they're needed. Rather than include all of your JavaScript files in the HTML file, Ext.Loader will only load them as they're needed. This drastically cuts down on your application's startup time. You can learn more about Ext.Loader at http://www.sencha.com/blog/ using-ext-loader-for-your-application. Close the app.js file and open up the app folder. As you can see we have two folders called model and store. These, of course, contain our code for the model and the store. Open the store/TaskStore.js file first: Ext.define('MyApp.store.TaskStore', { extend: 'Ext.data.Store', requires: [ 'MyApp.model.Task' ], config: { autoLoad: true, model: 'MyApp.model.Task', storeId: 'TaskStore', proxy: { type: 'localstorage', id: 'Tasks' } }, filters: { filterFn: function(item) { return !item.data.isComplete; } } }); Beyond the ever-present warning about hand editing, you will see our store definition written out in plain JavaScript. Note that the store definition not only contains the code for our proxy, it also includes the filter function and it lists our task model as the model for the store. A Simple Task List [ 24 ] Why is it "MyApp.model.Task"? Ext.Loader turns the name of your components into a filename by turning the periods into slashes. This means that if your component is MyApp.model.Task then Ext.Loader will look in your application folder for a folder called MyApp. It will look inside that MyApp folder for a model folder that has a Task.js file in it. This is also a good way to keep your application folder organized. If you put all of your models in a model folder and all of your views in a view folder then you'll know where to find them when you need to find them later. Close the TaskStore.js file and let's look at the last file, model/Task.js. This is the file for our model: Ext.define('MyApp.model.Task', { extend: 'Ext.data.Model', config: { fields: [ { name: 'id', type: 'int' }, { name: 'name', type: 'string' }, { name: 'description', type: 'string' }, { name: 'created', type: 'date' }, { name: 'completed', type: 'date' }, { name: 'isCompleted', type: 'boolean' } ] } }); Chapter 1 [ 25 ] Notice that there is a fair amount of overlap between what's in the store and what's in the model. This duplication allows the model and store to act independently of each other while still maintaining consistency for the data itself. We will look closer at that when we deal with the forms for our application. Architect versus coding by hand As you can see, Sencha Architect generates code for us, but we could also choose to create this exact same code by hand. Sencha Architect offers benefits to the novice coder by allowing applications to be built visually and allowing the coder to explore the code as needed. The designer also generates code according to Sencha Touch best practices. This keeps the novice user from learning bad habits and encourages cleaner code when the coder needs to begin programming by hand. For the seasoned coder, Sencha Touch offers ways to rapidly prototype interfaces and create mockups for clients. The code behind these mockups can then be used outside the designer to create complex applications that might be problematic if not impossible for Sencha Architect to accomplish. By using a combination of Sencha Architect and traditional text-based coding, we hope that this book will offer additional benefits to the reader both in terms of speed and consistency. Creating the views So far, none of our code has actually created anything on the screen. Now we need to create some visual components for the user to interact with, starting with the main panel that will contain our application. Drag a Tab Panel object out of the Toolbox list on the left and drop it onto the iPhone screen in the designer. A Panel option will now appear in Project Inspector. Select Tab Panel and add the following information to the Config area: • Make sure the initialView checkbox is checked • Change userClassName from myTabPanel to mainView • Delete the third tab by right-clicking on it and choosing Delete Save the project and let's take another look at the code for your project. In the app.js file, you will now see the following code: Ext.Loader.setConfig({ enabled: true }); A Simple Task List [ 26 ] Ext.application({ models: [ 'Task' ], stores: [ 'TaskStore' ], views: [ 'MainView' ], name: 'MyApp', launch: function() { Ext.create('MyApp.view.MainView', {fullscreen: true}); } }); The designer has now added a launch function that creates an instance of our MainView panel and sets it to fullscreen. If we take a look in our app folder, we now see a folder called view. This folder contains our file for MainView.js: Ext.define('MyApp.view.MainView', { extend: 'Ext.tab.Panel', config: { items: [ { xtype: 'container', title: 'Tab 1' }, { xtype: 'container', title: 'Tab 2' } ] } }); Right now this file simply defines MainView as an extension of the standard Ext. tab.Panel function and sets the Config containing our two tabs. As we add additional pieces to the panel, they will appear here in the code. Let's head back to the designer and do just that. Chapter 1 [ 27 ] Configuring the Tab Panel The first thing we should probably do is rename the tabs. Select Tab 1 in the Project Inspector and then click on the value for Title in the Config section. Change the title from Tab 1 to Current and press Enter on your keyboard. Do the same thing for Tab 2, changing its title to Completed. One additional thing we should do is change the tabs to appear on the bottom. This will give our application a more classic iPhone look. To make this change, select mainView in Project Inspector and find the Tab Bar Config listing in the Config section. Click on the + button next to Tab Bar Config and a new Tab Bar option will appear below. Click on the arrow next to the new Tab Bar option and the configuration options for it will appear. A Simple Task List [ 28 ] Locate the docked Config area and change it from top to bottom. The tabs should drop down to the bottom and give us the large icons familiar to most iPhone users. You can change these icons by clicking on the Current or Completed tab in Project Inspector and changing the Config value for iconCls. Select the icons you like and save the project (I chose organize for the Current tab and delete for the Completed tab). Once you are finished, select MainView in Property Inspector and then click on Code in the upper-right side of the designer. This will change the designer over into code view, showing us the contents of our MainView.js file. Ext.define('MyApp.view.MainView', { extend: 'Ext.tab.Panel', config: { items: [ { xtype: 'container', title: 'Current', iconCls: 'organize' }, { xtype: 'container', title: 'Completed', iconCls: 'delete' } ], tabBar: { docked: 'bottom' } } }); You can now see that the tab panel and its two tabs have been added to our MainView.js file. We also see our tabBar configuration and the iconCls values we selected. Adding the lists and details Next, we want to add a list and a panel to each of our tabs. The lists will display the names for our current and completed tasks. The panels will display details for the task when we click on it in the list. Chapter 1 [ 29 ] Let's start by selecting each tab and setting the layout property in Config to card. This will let us easily switch between the List and Details sections for each tab. Next, take a List component from the Toolbox list and drop one on each tab in Property Inspector. Dropping items in Property Inspector While most components can be dropped directly onto the design area, it is often better to drop components into Project Inspector. It's much easier to ensure that you are putting the component within the correct container using Property Inspector. Next you will need to take a panel from Toolbox and drop one onto each tab, just like we did with the list. This panel will be our details container and it will not appear in the design view because the list is in front. We will create some code later on to swap the list and the container when the user clicks on a task in the list. Your Property Inspector area should now look something like this: Notice that the tabs (Current and Completed) are both indented under the mainView tab panel. Each tab also has a list and a panel beneath it. The tabs are children of the mainView tab panel and each tab has two child items; a list and a panel. A Simple Task List [ 30 ] Since we will need to address the lists and the panels in our code, we should probably name them something a bit more descriptive than MyList and MyPanel. In order to do this, you will need to select each of these items and change the id property in Config. Let's rename them as follows: In the Current tab, we will call them CurrentList and CurrentDetails and in the Completed tab, we will call them CompletedList and CompletedDetails. Setting up the templates Next we need to set up the templates (called itemTpl in the Config area) for our lists and our details. These templates control how our data will be laid out on the screen. Remember that we have the following data items: • id • name • description • created • completed • priority We can use any of these values by including them in our template by placing them in curly braces like so:
{name}
We can also use any HTML styling or tags as part of our template. This gives us a great deal of flexibility in controlling the layout of our application. To edit the template for our CurrentList component, select it in Project Inspector and a gear icon will appear next to the list in our Design view. Click on the gear and you will see a pop-up window with a few configuration options including Edit Template at the bottom of the pop-up window. When you click on Edit Template, a text area will appear over the list with the following text:
List Item {string}
Change this text to:
{name}
Chapter 1 [ 31 ] Click on Done Editing when you are finished. The list items will appear empty for now, but we will fix that a bit later. Next, click on the CurrentDetails panel in Project Inspector and edit the template the same way you did for CurrentList. Set the template for the CurrentDetails to:
{name}
{description}
Created: {created}
Click on Done Editing when you are finished. When you are finished with the CurrentDetails, we want to follow the same steps for CompletedList and CompletedDetails. You can keep the list template the same but we should include the completed date on our details page like so:
{name}
{description}
Created: {created}
Completed: {completed}
Testing with starter data You will notice that since we have no records, we have nothing to display. This can make testing our application difficult, so we are going to add a few test records to our application using the launch method. Select Application in Project Inspector and then locate launch down in the Config section. Click on the + button next to launch to add a new launch function, and then click on the arrow next to our new launch function to open it. This will open the code editor with: launch: function() { } Add the following code inside the launch function: var TaskStore = Ext.data.StoreManager.lookup('TaskStore'); var CompletedStore = Ext.data.StoreManager.lookup('CompletedStore'); if(CompletedStore.getCount()+TaskStore.getCount() === 0) { console.log('we have no records'); TaskStore.add({name: 'Here Is A Task', description: 'You can mark the task complete by clicking the Completed button below.', priority: 1, created: Date.now(), completed: '', isComplete: false}); A Simple Task List [ 32 ] TaskStore.add({name: 'How To Edit A Task', description: 'You can edit the task by clicking the Edit button below.', priority: 2, created: Date.now(), completed: '', isCompleted: false}); TaskStore.add({name: 'How To Add A Task', description: 'Add a task by clicking the Add button in the upper right corner.', priority: 3, created: Date.now(), completed: '', isComplete: false}); TaskStore.sync(); } else { console.log('we have records'); } This code will grab our two stores, TaskStore and Completed Store, and check to see if there are any records. If there are no records, the function will add three new records, and then sync the store to save the records. These task records can also serve as a set of three instructions to new users opening the application for the first time. Console logs The console.log command is your best friend when programming. It will print text and objects to the error console in Safari or Chrome. This is critical for debugging any issues you have. In the previous code, the console logs will print based on whether we get back records or not. We could also use console.log(TaskStore) to get a display of every attribute of that store object. This is really handy for making sure you actually have the object you think you have. Now when you open up the app.html file in your browser, you should see the following in the TaskMaster application: Chapter 1 [ 33 ] We now have tasks to view, but we still can't get to the details. We need to add a function to switch the view between our list and our details. Back in Sencha Architect, click on CurrentList in Project Inspector and then look for Events at the top of the Config section. Click on the + button next to Events to add a new event listener. Use the menu that appears to choose the select event. Click on the arrow next the new select event to edit the code. The code editor will appear with the following code: onCurrentListSelect: function(dataview, record, options) { } Add the following code to the select event: var currentTab = this.getActiveItem(); var currentDetails = currentTab.down('panel'); currentDetails.setRecord(record); currentTab.setActiveItem(currentDetails); The first line grabs our Current tab and the second grabs our CurrentDetails panel. We then set the record on the details panel to the record we are passed as part of the select function (the record from our list). Finally, we switch the card layout of our current tab to the CurrentDetails panel, hiding CurrentList. We need to do the same thing with our CompletedList component. Add the new select event and set the code to: var completedTab = this.getActiveItem(); var completedDetails = completedTab.down('panel'); completedDetails.setRecord(record); completedTab.setActiveItem(completedDetails); If we test this in the browser we should be able to see our details panel when we click on an item in the list. This also brings us to our next challenge; we need a way to get back to our list. In this chapter, we will create the back button manually and in the next chapter we will highlight a different approach to this same problem. For now, let's add a new button to our toolbar. A Simple Task List [ 34 ] Adding the back button Grab a toolbar object from Toolbox and drag it over onto the mainView tab panel. Grab a button object from Toolbox and drag it onto the new toolbar panel. Next, we want to give our toolbar a title. Let's select the toolbar and change the title in the Config section to TaskMaster. Next we need to change a few things with our button. Select the Button object and make the following changes to its Config section: • Change text to Back • Change the id to backButton • Change the ui to back (this will make it look like a typical back button) • Check the box next to the hidden property (we want the button hidden by default) I can't find one of the Config options If you are unable to find some of these properties, you may need to toggle the Config section between Show Common Configs and Show All Configs using the two buttons in the upper-right corner of the Config section. Now that we have a back button, we need to make it do something. Add a tap listener by clicking on the + button next to events in the button's Config section. Select tap and then click on the arrow next to the tap event that appears. Edit the back button code to look like this: onBackButtonTap: function(button, e, options) { var currentTab = this.getActiveItem(); currentTab.setActiveItem(0); button.hide(); } By grabbing this.getActiveItem() we grab the active tab in our MainView tab panel, which makes sure that the button will work correctly for both of our lists. We set the active item to the first item in the active tab. Finally, we hide the button so that it does not show up in our list view. The last part we need to take care of is showing the button when we click an item in the list. Click on the select event for our current panel and add the following to our select function: Chapter 1 [ 35 ] var backButton = Ext.getCmp('backButton'); backButton.show(); You will want to add the exact same button code to our select event in the CompletedList component. Just copy, open the select event for CompletedList, and paste. Our lists and details are now complete. Now we need to be able to add, edit, and mark tasks as complete. Creating the forms Before we start creating forms we need to add a button to our MainView toolbar that will display the form for adding new tasks. Drag a Button object out from the Toolbox list and drop it on the TaskMaster toolbar. The new button should appear next to backButton. We probably want to move that over to the other side of our title, so we need to drag a Spacer out of Toolbox and drop it between our new button and backButton. Spacer will push the new button to the right side of the screen. Next we need to change the following Config properties for the new button: • Set text to Add • Set itemId to addButton We will come back and add a tap function once we complete our forms. Add Task form To create our Add Task form we will add a new form panel to the Current tab. Drag a Form Panel from the toolbox and drop it on our Current tab. The panel should appear below our CurrentList and CurrentDetails panels. Next we need to drag some fields into our form so let's start with dropping a Text Field object on the MyFormPanel panel and changing the following Config properties: • Set name to name • Set label to Name A Simple Task List [ 36 ] • Set id to name • Set margin to 3 Next, add a Text Area object to our MyFormPanel panel and set the following Config properties: • Set name to description • Set label to Description • Set id to description • Set margin to 3 Now, we need to add a Select Field object to MyFormPanel and set the following Config properties: • Set name to priority • Set label to Priority • Set id to priority • Set margin to 3 We also need to add some options for Select Field. Locate Options in the Config properties and click to edit. The Options property expects an object as its value, in this case an array of name-value pairs like this: [{text: 'High', value: 1}, {text: 'Medium', value: 2}, {text: 'Low', value: 3}] By default, the Select Field uses text for display and value for the submitted value. You can change this by editing the displayField and valueFields in the Config properties, but we can leave these as the defaults for our application. We can save ourselves a lot of work if we use this form for both adding new tasks and editing existing ones. To do this we also need to add a hidden field to hold the ID value of any existing tasks that we edit. Add a Hidden Field object to MyFormPanel and set the properties for id and name to id in the Config section. We will use this later when saving the form. The last thing we need in our form is two buttons; one for save and one for cancel. Add the buttons and make the following changes: • Set text for button 1 to Save • Set itemID for button 1 to SaveButton Chapter 1 [ 37 ] • Set margin for button 1 to 10 • Set text for button 2 to Cancel • Set itemID for button 2 to CancelButton • Set margin for button 2 to 10 The structure and the form should look something like this: Next, we will add a tap event handler to each button using the Event section of the Config as before. For our Cancel button, set the event function to: var currentTab = this.getActiveItem(); currentTab.setActiveItem(0); This code grabs our Current tab and sets the active panel back to CurrentList. A Simple Task List [ 38 ] The Save button is a bit more complex. As we mentioned earlier, we want to use this form for both adding new tasks and editing existing ones. This means we need to check to see if the Hidden Field value of our form is set and save the task correctly. Add the following code to the SaveButton tap event function: var currentTab = this.getActiveItem(); var formPanel = currentTab.getActiveItem(); var values = formPanel.getValues(); var store = Ext.data.StoreManager.lookup('TaskStore'); if(values.id === null) { var record = Ext.ModelMgr.create(values, 'MyApp.model.Task'); record.set('created', new Date()); store.add(record); } else { var record = store.getById(values.id); record.set('name', values.name); record.set('description', values.description); record.set('priority', values.priority); } store.sync(); formPanel.reset(); currentTab.setActiveItem(0); Our first two lines grab currentTab and formPanel. We then get the values from formPanel and store that we need to save our data to. We check the value of our hidden field to see if it has been set. This will be true if we are editing, but not if we are adding a new task. If we are adding a new task, we create a new record option using the values form in the form field, we set a create date, and add record to store. If we are editing an existing record, we use the id value from our store to get the record from the store. We then set the name, description, and priority values of record from our form values. Finally, we sync our store to save the record, clear out our form values and close the form by setting the active item back to our CurrentList view (0). Chapter 1 [ 39 ] Editing and completing a task For editing a task, we are going to use the form we just created, but we need to load it up with the currently selected record. For completing a task, we just need a button. To do this we will add a toolbar with two buttons to our CurrentDetails panel. We should probably add a Spacer object between the two buttons like we did with our previous toolbar. Each button also needs a tap event added to it in the Config section, under Events. For the Edit button, set the tap function to: var currentTab = this.getActiveItem(); var DetailsPanel = currentTab.getActiveItem(); currentTab.setActiveItem(2); var formPanel = currentTab.getActiveItem(); formPanel.setRecord(DetailsPanel.getRecord()); this.setActiveItem(currentTab); var backButton = Ext.getCmp('backButton'); backButton.hide(); This code grabs record from the DetailsPanel panel and loads this into formPanel. Setting this record on the form also sets the value of our hidden field to the correct id value for our record. We then display the form as before and hide the Back button. For our Complete Task button, we need to get record from DetailsPanel and set the values for completed (a date) and isCompleted (a Boolean). We do that by setting the tap event function to this: var currentTab = this.getActiveItem(); var detailsPanel = currentTab.getActiveItem(); var record = detailsPanel.getRecord(); record.set('completed', new Date()); record.set('isComplete', true); var store = Ext.data.StoreManager.lookup('TaskStore'); store.sync(); this.setActiveItem(1); var completedList = this.getActiveItem(); var completedStore = completedList.getActiveItem().getStore(); A Simple Task List [ 40 ] completedStore.add(record); completedList.getActiveItem().refresh(); currentTab.setActiveItem(0); var backButton = Ext.getCmp('backButton'); backButton.hide(); This gets our record as before, sets our two values, and syncs TaskStore. This sync will also cause the filter on TaskStore to prevent the record from displaying in our Current list. Next we add the record to CompletedStore and refresh the view for our completed list. We finish up by returning the user to the Current list and hiding the Back button. Testing the application If we did everything correctly, you should be able to open the app.html file in Safari (or Chrome) and test the application. Try putting the editing and marking tasks as completed. Be sure to use the JavaScript console in your browser to track down issues and view errors. Chapter 1 [ 41 ] Extra credit Task managing applications come in a variety of designs, with a wide variety of features. Everyone seems to have their own preference for tracking tasks. You can use this application as a base for your own personal task management application. These are a few ideas for taking the application to the next level: • Add styles in the CSS file based on the priority of the tasks in the list and details • Add a way to sort the tasks by date and priority • Customize the CurrentDetails and CompletedDetails templates to add icons for priority Summary In this chapter we discussed the basic setup for an application using local storage, including: • The basics of the Sencha Architect application • Creating data stores to use local storage and a task model • Creating lists and details for our data stores to use • Creating events to switch between the List and Details views • Creating buttons to control navigation and launch our forms • Creating forms for editing and adding new tasks In the next chapter we will take a look at using layouts and templates to create a more complex and visually appealing application. A Feed Reader In our first project, Task Manager, we explored some of the basics of Sencha Architect. We also covered some of the ways to store, edit, and display data in Sencha Touch. This chapter will explore three new areas: • The NavigationView • Loading remote data • Creating complex layouts with Sencha XTemplate In this chapter we will build an RSS reader to grab news feeds from a list of sites and display the contents of those feeds in a complex pattern of columns and rows: The newsreader will also build on the Sencha Touch NavigationView component to automate a number of helpful navigation elements for touch devices. A Feed Reader [ 44 ] The basic application Our basic application will start off much the same way as our previous application: • A NavigationView component to hold all our view components • A list to display feeds • A store to hold the list data • A model to describe the data • A form to add items to the list We will spend most of our time setting up NavigationView and a much briefer time covering the additional components, since these are very similar to what we did in Chapter 1, A Simple Task List. An overview of NavigationView In Chapter 1, A Simple Task List, we manually coded a Back button into our Details view. The Back button was hidden by default and only displayed when an item in the list was clicked on and the Details view appeared. The Back button returned our user to the main list of tasks and then hid itself until it was needed again. In our new Feed Reader application, we will use NavigationView, which handles all of this functionality automatically. NavigationView functions similar to a card layout where a number of panels can be hidden or displayed based on the active item for the view. However, unlike a card layout, where the items are typically declared when the layout is created, NavigationView typically adds items dynamically using the push() function to add panels and other items for display. For example, let's assume we have a NavigationView component called MainView that contains a list view with a list of items. We also have a details panel called Details. When the user clicks on an item in the list, we can call: var main = Ext.getCmp('MainView'); var details = Ext.create('MyApp.view.Details', { title: 'Something Cool' }); main.push(details); Chapter 2 [ 45 ] This code will push a copy of our Details view onto NavigationView (MainView). NavigationView will use an animation transition to slide the new Details panel into place, automatically create the Back button for us, and then set the title of our Navigation bar to Something Cool. It also handles all of the behind-the-scenes code for taking us back to our main list when the Back button is clicked. This makes NavigationView a very handy component to serve as the foundation for our application. Let's begin by creating a new Sencha Touch application in Sencha Architect. For this application we are going to target the iPad tablet sized screen. This will give us more room to create an interesting display. We will also show you how to adjust the screen for iPhone and mobile phone users on the fly. Use the Size menu at the bottom of the design area to select iPad as our screen size and drag a NavigationView object from the toolbox onto the iPad image in our display area. In the Config section, set userAlias to MainView so that we can reference it later. Next, we need to add a List view to MainView and give it a Store (with a LocalStorage proxy) and Model, just as we did in Chapter 1, A Simple Task List: 1. Start by adding a model and configure it as follows: °° Set userClassName to Feed °° Add three fields: °° id as int °° name as string °° url as string 2. Next, add the store and configure it as follows: °° A LocalStorage proxy °° Set userClassName to FeedStore °° Set storeId to FeedStore °° Set model to Feed A Feed Reader [ 46 ] 3. Finally configure the list: °° Set title to Feed Bag °° Set itemTpl to
{name}
°° Set id to FeedList °° Set store to FeedStore Adding the form Since our form is pretty simple this time (and to add a bit of variety) we are going to use a sheet to display our form. The sheet can be set to slide in from the top, bottom, or sides of the screen. It can also be set to pop in from the center. In this case, we do not want the sheet to be a child of our MainView container; we only want to create it when we need it, not when the application launches. To do this, we drag the Sheet object over to Project Inspector and drop it on the Views icon. This will create Sheet as a separate view from our MainView container. Configure the sheet as follows: • Set userClassName to AddSheet • Set enter to top • Set exit to bottom Chapter 2 [ 47 ] • Set stretch to true • Set stretchY to true Next we will add a Form Panel object to our sheet with the following items: a Container, two Text Field, and two Button objects. The container is simply a place to give the user some instructions. Set the html attribute to: 'Add an RSS feed URL and a Name. Feed URLs should be in the format: http://feedURL.com' Configure the two text fields as follows: • For Field 1: °° Set id to name °° Set name to name °° Set label to Name °° Set margin to 3 0 3 0 • For Field 2: °° Set id to url °° Set name to url °° Set label to URL °° Set margin to 3 0 3 0 Configure the two buttons like so: • For Button 1: °° Set id to SaveButton °° Set text to Save °° Set margin to 10 • For Button 2: °° Set id to CancelButton °° Set text to Cancel °° Set margin to 10 A Feed Reader [ 48 ] Next, we need to add tap listeners for our two buttons. In the Event section of Config for each button, click on the + button and select Basic Event Binding. When the menu appears, choose tap: For the Cancel button, our tap function is pretty simple: this.down('formpanel').reset(); this.hide(); Inside the function, this refers to our Sheet view. The code travels down into the sheet to find the form and then clears out all the field values. The sheet then hides itself. The Save button works much the same way as the one from our previous chapter: var formPanel = this.down('formpanel'); var values = formPanel.getValues(); var store = Ext.data.StoreManager.lookup('FeedStore'); var record = Ext.ModelMgr.create(values, 'MyApp.model.Feed'); store.add(record); store.sync(); this.hide(); Chapter 2 [ 49 ] We move down from the Sheet view (this) to get our form panel. We then get the values from our form and find our store. Next, a new Feed record is created using the model manager and populated with the values from our form. Finally, we add the record to the store, sync the store to save it, and then hide the sheet. Next, we need a way to show the sheet for adding Feed items. Back to the navigation view In our MainView component, we need to add a Navigation Bar object to the Config section using the + button next to Navigation Bar. This navigation bar will display our Back button and our titles. It will also give us a place to put the Add button that will show our sheet for adding feed items. We don't need to change any configuration options for the navigation bar, so just drag a new Button object onto it for our Add button. Set the button's configuration like so: • Set align to right • Set text to Add • Set id to addButton Then we need to add a tap event listener like we did with our other buttons. The code for our tap event needs to create a new instance of AddSheet and display it. It also has to do a bit of thinking before it creates the sheet to make sure that there is no sheet that is existing already. var sheet = Ext.getCmp('AddSheet'); if(!Ext.isDefined(sheet)) { sheet = Ext.create('MyApp.view.AddSheet'); Ext.Viewport.add(sheet); } sheet.show(); The first thing we do is call Ext.getCmp to see if there is already an existing sheet. This is because we set up our Save and Cancel buttons to hide the sheet, which does not destroy it. It's still a part of the application (still in memory), but it is not being displayed. If we have used the Add button previously, then Ext.getCmp will return a valid component. This is what we are checking on line two with !Ext.isDefined(sheet). If the sheet is not defined yet (not created), we use Ext.create('MyApp.view. AddSheet'); to make our sheet and then add it to the viewport. A Feed Reader [ 50 ] At this point, we should have a valid component for our sheet and then we can just call sheet.show();. Our application should now be able to add and display new Feed items to the list. Test the application to make sure everything is in working order by using Safari to open the app.html file in the folder where your project is saved: Next we need to add the logic for our MainView navigation view that will allow us to display a nice layout page for each of our feeds. Adding the controller Add a controller to the project by dragging it onto the Controllers section of Project Inspector. You can find it in Toolbox under Behaviors. Set the userClassName property of the controller to FeedController. We also want to add a Reference property in the controller for mainView. Click on the Add button next to Reference and set the ref property to mainView and Selector to MainView (the selector needs to match our userAlias instance for the main navigation view). Adding this reference will allow us to easily grab our MainView navigation by calling this.getMainView() anywhere inside the controller. Chapter 2 [ 51 ] Wait, shouldn't it be getmainView instead of getMainView? One of the things that should be pointed out in this example is that when the reference is created, Sencha automatically creates a "getter" function for the referenced component. Even though our reference has a lowercase m, the getMainView function changes this to uppercase, M. Given the case sensitive nature of JavaScript, this automatic case switch can lead to quite a bit of hair pulling and colorful language. Now that we have our reference, we need to add an action to perform when the user taps on the list of feeds. Click on the + button next to Actions and set the following information: • Set controlQuery to #FeedList • Set targetType to Ext.dataview.List • Set fn to onListItemTap • Set name to itemtap Next, we need to double-click on the itemtap action to bring up our code editor. This is the code that will be fired when the list is tapped. Notice that the function is already set up to pass us a number of useful items including the dataview itself and the record for the item the user tapped on. We will set this action to call another function like so: onListItemTap: function(dataview, index, target, record, e, options) { this.createFeedDetailsView(record.get('name'), record.get('url')); } We will pass along the record name and url to our new createFeedDetailsView function using record.get(). If we take a look at our Code view for the controller, it should look something like this: Ext.define('MyApp.controller.FeedController', { extend: 'Ext.app.Controller', config: { refs: { mainView: 'MainView' }, A Feed Reader [ 52 ] control: { "#FeedList": { itemtap: 'onListItemTap' } } }, onListItemTap: function(dataview, index, target, record, e, options) { this.createFeedDetailsView(record.get('name'), record. get('url')); } }); Here we see that Sencha has set up our FeedController function to extend the main Ext.app.Contoller component. This means it inherits all of the basic functions for the Ext.app.Contoller component. In the config section, we see our reference set up in the refs section. The controls section tells the controller which component to listen to (#FeedList), which event to listen for (itemtap), and which function to call when the event happens (onListItemTap). The last thing we need to do here is create the code for our createFeedDetailsView function. This code needs to use the URL to grab the RSS feed, create a new view, and push it onto the main navigation view. Before we do that, there are a few things that we need to consider: how do we get the data from the remote source and how can we format it in an easy-to-use structure (JSON)? To answer these questions, we need to have a better understanding of how Sencha Touch communicates with external servers, and some of the limitations involved in these types of transactions. Getting the data from a remote source For security reasons, JavaScript (and thus Sencha Touch) is not allowed to make AJAX requests to other domains. This means that if your application resides on myCoolApp.com and you make an AJAX request to the RSS feed at boingboing.net, it will be denied. Chapter 2 [ 53 ] The reason for this is the Same Origin Policy, which states that certain browser functions like cookies and AJAX requests can't be shared between different servers. The reasoning being that JavaScript executes within the browser on the end user's computer. This gives JavaScript some unique abilities to interact with the user without having to constantly be in contact with a web server. Once the web browser loads the initial JavaScript files, they are stored on the user's machine until the cache is cleared. This means the application can continue to function when offline. However, as we all know, with great power comes great responsibility. The ability to run remote code on a user's computer can lead to people doing very bad things. AJAX requests in particular are problematic because they can happen without any direct request from the user. For this reason, cross-domain AJAX requests in JavaScript are a very bad idea. While it may be easy enough to determine that your own code has honorable intentions, unchecked code from another domain can be potentially hostile. If you would like to learn more about same origin policy, this Wikipedia article is a good place to start: http://en.wikipedia.org/wiki/Same_origin_policy Enter the JSONP proxy We can get around the same origin policy by using Sencha's JSONP proxy component to send the request. This component injects This is the autoloader, which will automatically include the rest of the JavaScript we need. It also includes a loading indication in CSS. This will fire off while the application loads, to alert the user that things are happening behind the scenes. You shouldn't need to touch the index.html file. The app.js file has a few more interesting pieces included: Ext.Loader.setPath({ 'Ext': 'touch/src', 'TimeCop': 'app' }); Going Command Line [ 74 ] Ext.application({ name: 'TimeCop', requires: [ 'Ext.MessageBox' ], views: ['Main'], icon: { 57: 'resources/icons/Icon.png', 72: 'resources/icons/Icon~ipad.png', 114: 'resources/icons/Icon@2x.png', 144: 'resources/icons/Icon~ipad@2x.png' }, phoneStartupScreen: 'resources/loading/Homescreen.jpg', tabletStartupScreen: 'resources/loading/Homescreen~ipad.jpg', launch: function() { // Destroy the #appLoadingIndicator element Ext.fly('appLoadingIndicator').destroy(); // Initialize the main view Ext.Viewport.add(Ext.create('TimeCop.view.Main')); }, }); The Ext.Loader.setPath function at the top will point to our touch directory where all of our base Sencha Touch 2 library files are located. The next section sets up the name of our application, our required components, our views, our icons, and the startup screens. Chapter 3 [ 75 ] The launch section removes our loading indicator and then adds our TimeCop. view.Main to the viewport. If you navigate to the folder in Safari and look at the application, you will see something like this: Most of the actual display code for this application is contained in the TimeCop. view.Main file. This is the file we will modify to create our actual application. Going Command Line [ 76 ] Creating the TimeCop layout The layout for this application consists of a vbox layout on our main container. Inside the main container is a set of three containers, each with an hbox layout and containing three additional containers. This gives us a flexible 3 x 3 grid where we can place our components: Inside of containers 1, 3, 7, and 8 we need buttons with our four time increments. In container 5 we will place our start button. By using the vbox and hbox layouts as described later in this section we can keep our components centered regardless of the screen size. The containers for our buttons can be given a fixed width (in this case we choose 120). The empty containers in the row are then given a flex value of 1. This will cause them to take up the rest of the available space and maintain an even spacing between our buttons regardless of screen size. Chapter 3 [ 77 ] For example, our first row is laid out like so: { xtype: 'container', layout: { type: 'hbox' }, flex: 1, items: [ { xtype: 'container', width: 120, layout: { type: 'fit' }, items: [ { xtype: 'incrementButton', text: 5 } ] }, { xtype: 'container', flex: 1 }, { xtype: 'container', layout: { type: 'fit' }, width: 120, items: [ { xtype: 'incrementButton', text: 10 } ] } ] } Going Command Line [ 78 ] You will notice that each of our buttons has an xtype type of incrementButton, but different values for text (5 and 10). We will come back to that in just a moment, but we need to take a look at our second row first. The second hbox container row is a variation on the first row; a single fixed-width container in the center and a variable-width (flex:1) container on either side: { xtype: 'container', layout: { type: 'hbox' }, flex: 1, items: [ { xtype: 'container', flex: 1 }, { xtype: 'container', width: '', layout: { type: 'fit' }, width: 120, items: [ { xtype: 'button', hidden: true, id: 'startButton', ui: 'roundStart', text: 0 } ] }, { xtype: 'container', flex: 1 } ] } Chapter 3 [ 79 ] Our startButton in the center starts out hidden by default and it will only appear when the incrementButtons are tapped. We set ui to roundStart, which we will use later on to style the button, and we set the text value of the button to 0 (we will use this value later in our incrementButton functions). We also add a listener for our startButton instance's tap event. Our third row is simply a copy of our first row, with the button values set to 30 and 60 respectively. Both of these buttons will have xtype:incrementButton as in the first row. Creating the theme The base layout we currently have will give us a bunch of large, ugly, square buttons. We want something a bit cooler than that, so we are going to set a new theme for the application. Sencha Touch Themes use SASS and Compass to customize the user interface in a number of interesting ways. For more information on creating Sencha Touch themes, review the documentation and tutorial videos at: http://docs.sencha.com/ touch/2-0/#!/guide/theming. The first step in our theme is adding a ui configuration for our buttons. The four time increment buttons will have ui: 'round' added to their configuration options. This will give us a more pleasing circular button. The center button will have ui: 'roundStart' added to its configuration options. We will make ui inherit all of our qualities from our original round ui and add some color changes to give us a green start button. We can then add the following code to app.scss: .x-button-round, .x-button-roundStart { background-color: transparent; background-image: none; width: 120px; padding: 10px; height: 120px; overflow:hidden; border: none; span { color:#333; font-size:24px; line-height:68px; } Going Command Line [ 80 ] span.x-button-label { display: block; background: -webkit-gradient(linear, left top, left bottom, color- stop(0%,rgba(230,230,230,1)), color-stop(50%,rgba(168,168,168,1)), color-stop(50%,rgba(168,168,168,1)), color-stop(100%,rg ba(230,230,230,1))); /* Chrome,Safari4+ */ position:relative; height:100px; width:100px; text-align:center; cursor:pointer; border:16px solid #e8e8e8; -webkit-border-radius: 60px; font-weight: 900; -webkit-box-shadow: inset 0 0 10px#C7C7C7, 0 0 1px 2px #bababa; } } The key pieces of this are -webkit-border-radius: 60px (half of our button width/height), which makes the buttons circular, and background: -webkit- gradient, which creates the gradient background of the buttons. We do something similar with our start button, but we make the text white and the background green: .x-button-roundStart { span { color: white; } span.x-button-label { background-color: #0C0; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color- stop(0%, #1AFF1A), color-stop(50%, #00E600), color-stop(51%, #0C0), color-stop(100%, #00B300)); background-image: -webkit-linear-gradient(#1AFF1A, #00E600 50%, #0C0 51%, #00B300); background-image: linear-gradient(#1AFF1A, #00E600 50%, #0C0 51%, #00B300); } } Running compass compile will regenerate the app.css file with our new styles in it. Now that we have the basic look and feel of our application, we need to talk about using the native APIs in a Sencha Touch application. Chapter 3 [ 81 ] Creating the increment button Since each of our increment buttons will do much the same thing, this becomes an excellent opportunity to create a button class. To do this we will make a separate file on our view folder called incrementButton.js: Ext.define('TimeCop.view.incrementButton', { extend: 'Ext.Button', alias: 'widget.incrementButton', config: { itemId: 'mybutton', ui: 'round', text: 5, listeners: [ { fn: 'onMybuttonTap', event: 'tap' } ] } }); This code extends the standard Ext.Button class and sets defaults for itemID, text, and ui (we will use ui later on to style the buttons). We also add a tap listener for when the user presses the button. When our time increment button is tapped, we need to add the appropriate time to our start button in the center and display it (if it is hidden). We will do this by adding the following to our tap handler function after the Config section in our previous code: onMybuttonTap: function(button, e, options) { var increment = button.getText(); var start = Ext.getCmp('startButton'); var startInt = start.getText(); var total = parseInt(startInt, 10) + parseInt(increment, 10); start.setText(total); if(start.isHidden()) { start.show(); } } Going Command Line [ 82 ] This code grabs the text from our current button, which is 5, and then grabs the start button. We then add the values from the two buttons together and set this as the text on our start button. Since this function is now a part of the base incrementButton, each of our four buttons with an xtype of incrementButton will be able to use this same function. The only thing that will change is the text value of the button. This allows you to easily choose other time increments if you desire. Using the buttons works like this: when the user first starts the application, the start button is hidden and has a text value of 0. The user taps the 5 button and the start button appears, 5 is added to 0 and the start button's text is set to 5. The user then taps the 10 button, causing the start button text to be increased to 15 and so on. Creating the start button Our start button uses a separate function to begin the timer countdown. In this case, we will add a listener to our main view: listeners: [ { fn: 'onStartButtonTap', event: 'tap', delegate: '#startButton' } ] This will fire a function called onStartButtonTap. We add this new function after the Config section of our Main.js file. This is the function that starts the countdown for the timer: onStartButtonTap: function(button, e, options) { var delay = button.getText(); setTimeout(function() { Ext.Msg.alert('Back to work minion!', 'The boss needs a new villa!', Ext.emptyFn); },parseInt(delay)*1000); } This function grabs the text of the buttons, which is now set for the total amount of time we want to set on our timer. We then create a setTimeout function that will display a message box after the timer finishes. For the purposes of testing, we have set the delay for delay *1000, which will actually give us our delay time in seconds instead of minutes. When we want to set the delay to minutes, the last line can be changed to: Chapter 3 [ 83 ] setTimeout(function() { Ext.Msg.alert('Back to work minion!', 'The boss needs a new villa!', Ext.emptyFn); },parseInt(delay)*60000); For testing purposes let's leave the code as is for now and test the functions. As we can see from our example, the alert appears once the delay time expires. However this doesn't currently use any of the native alerts available on the device. In order to do this, we need to take a look at Ext.device. Going Command Line [ 84 ] Using native APIs with Ext.device By default, a Sencha Touch application is web-based. This means that a user on Android or iOS will use a web browser to access your application. You can add the web page to the desktop and it will look and behave very much like a compiled application. However, there are a number of features available on a mobile device that cannot be accessed through a web-based application; these include things like the camera, the device orientation, connection monitoring, native alerts, and some native geolocation features. Sencha Touch offers a way around this issue by using Ext.device. This component accepts JavaScript commands, which will then be translated into native functions when the application is compiled. It should be noted that one essential consideration when using Ext. device is that the application has to be compiled each time in order to actually test the native application features. If you make changes, or need to do debugging, you will have to recompile the application and reinstall it on your mobile device. Ext.device offers the following options: • Connection: This allows you to check if the user is online using Ext.device. Connection.isOnline(). You can also check the type of connection using Ext.device.Connection.getType(). • Notification: This allows you to access native notification windows and the vibrate device option. • Orientation: This provides the current orientation of the device tracked in three dimensions (alpha, beta, and gamma). These dimensions return values between 0 and 360 and can be used to calculate various device movements. • Camera: This lets your application take pictures or select existing images from the camera library (with your user's permission). For TimeCop, we will use a simple notification/vibrate alert. In later chapters, we will cover the other Ext.Device components. First, though, we will need to take a bit of a detour and explore some of the additional steps we need to take to test and run compiled applications under iOS. Chapter 3 [ 85 ] Testing and running native applications In order to run a native (compiled) application on iOS, you will need to take the following steps: 1. Register as an Apple iOS developer (cost is $99 annually as of this writing). 2. Enable your device for development. 3. Provision the application with Apple and create a P12 certificate. 4. Install the application on your device. This process is not always intuitive, and can often seem more laborious than coding the actual application. If you would prefer to create an Android application, we will discuss building native Android applications later in this chapter. Registering as a developer In order to publish your application to the Apple Store, or even to simply test compiled iOS applications, you are going to have to sign up for a developer account. There is a fee to become a developer (the cost is $99 annually as of this writing), and Apple will require quite a bit of information about you. They require this information for several reasons. First, they have to know who you are so that you can get paid for apps that you sell in their store. Second, they need to know how to contact you if there's a problem with your application. And last, they need to be able to track you down if you try to do something evil with your app. Not that you would, of course. Even if you are not yet ready to distribute your application, you will still need to register as a developer in order to install a compiled application on your iOS device. Your iOS device will also need to be registered for development with Apple. This will allow you to install and test your own personal compiled applications directly from your development computer instead of going through the Apple store. Becoming an Apple developer To become an Apple developer, first you must go to: http://developer.apple. com/programs/register/. Going Command Line [ 86 ] You will either need to supply your existing Apple ID or sign up for a new one, fill out some lengthy profile information, agree to some legal documents, and then perform an e-mail verification. From there you will have access to the Apple Developer center. The two points of most interest to us as mobile developers are the iOS Dev Center and the iOS Provisioning Portal. The iOS Dev Center is where you can download the iOS SDK (known as Xcode), as well as read documentation, see sample code and tutorials, and view some videos on iOS development. The iOS Provisioning Portal is where you add your application to the Apple store or publish test versions of your application. Note that in order to use Xcode, install a development certificate, or publish your application to the Apple store, you must have a computer running OSX. Windows and Linux computers cannot run Xcode or publish to the Apple store. The provisioning portal is the main area we are concerned with. Provisioning an application In order to run a compiled Sencha Touch application that you're developing for iPhone, iPad, or iPod touch, you must have a provisioning profile and a development certificate installed on your device and your Mac. (This is only true for compiled applications and not standard Sencha web applications.) While the provisioning process can seem a bit complex, Apple has a very nice set of "How To…" videos listed on the right side of the provisioning portal, as well as a handy Provisioning Assistant setup wizard. The Provisioning Assistant wizard will guide you through the steps to create and install your development provisioning profile and iOS development certificate. The first step in the process is to obtain a development certificate. The development certificate is an electronic document that links you as an Apple developer with your compiled applications. For testing purposes, the certificate gets loaded onto your iOS device and it lets the device know that it's okay to run your application. The provisioning profile is used when your application is compiled. It contains a separate set of development certificates, a device ID, and an app ID. This is checked against the original development certificate to authorize the application to run on your device. Chapter 3 [ 87 ] Meanwhile back in the code Now that we have our certificates properly set up, we can get back to the business of writing code. Using the native notifications To use the native notifications we need to replace our original onStartButtonTap function with a new function that uses Ext.device. Aside from that, the code for native notifications looks almost the same as our previous code: onStartButtonTap: function(button, e, options) { var delay = button.getText(); setTimeout(function() { Ext.device.Notification.vibrate(); Ext.device.Notification.show({ title: ' Back to work minion! ', message: 'The boss needs a new villa!' }); },parseInt(delay)*1000); } We still wrap the function in a setTimeout statement. We then call Ext.device. Notification.vibrate and Ext.device.Notification.show. This will cause the device to vibrate (if the device supports it) and then show our original message as before. Also, Ext.device is not loaded by default, so we need to add it to our app.js file, in the requires configuration: Ext.application({ name: 'TimeCop', requires: [ 'Ext.MessageBox', 'Ext.device.Notification' ], Going Command Line [ 88 ] Debugging problems with Ext.device is a difficult proposition. The Ext.device functionality is not available in desktop browsers, or when your app hasn't been compiled. However, if you need to debug an app running on your mobile device, there are some third-party solutions out there, one of the best being weinre, which stands for WEb INspector REmote. You can learn more about weinre at: http:// people.apache.org/~pmuellr/weinre/docs/latest/. For now, though, we need to compile the application for a native iPhone using Sencha Cmd. Compiling the application In order to compile an application you will need a few things from Apple first: • A developer certificate in P12 format (for a walkthrough on this process go to http://docs.sencha.com/touch/2-0/#!/guide/native_provisioning) • A provisioning profile (from Apple Provisioning Profile, launch the Development Provisioning Assistant: https://developer.apple.com/ ios/manage/overview/index.action) You will also need to know the application name, application ID, and bundle seed ID for your application. This information can be found by clicking on configure next to the application name in the App ID section of the portal. The format looks like this mockup: Once we have this information and the files, we need to set up our packager.json file. Chapter 3 [ 89 ] Setting up packager.json The packager.json file is in the root directory of our application folder and it is a template originally generated for us by Sencha Cmd. We need to change some of the default information in order to compile the application. The packager.json file has extensive comments, so we will just take a look at some of the more critical settings in the file: • "applicationName":"TimeCop" • "applicationId":"com.example.TimeCop" • "bundleSeedId":"D3THNXJT69" This is where we use the values from our previous example mockup. You will need to change these to reflect your own application information. Please note that we are using example information for our applicationID and bundleSeedId values. You will need to change these values to the ones you get from Apple. The next important section is: "configuration":"Debug", "platform":"iOSSimulator", "deviceType":"Universal", We can leave these as is for right now, but they control how the application is outputted and what devices it works on. The Configuration type should always be Debug until you are ready to distribute the application through the app store. This will help you track down any code errors that you might have. The platform options are: • iOSSimulator • iOS • Android • AndroidEmulator Using the iOSSimmulator or AndroidEmulator options allows you to test locally on your machine without an iOS or Android device. You will need to have Xcode and/ or the Android SDK kit installed on your machine to use this option. deviceType is an iOS-only option that declares the application as iPhone, iPad, or Universal (meaning both). Going Command Line [ 90 ] The last critical pieces of information are: "certificatePath":"/Users/12ftguru/Downloads/New_Cer/Certificates. p12", "provisionProfile":"/Users/12ftguru/Downloads/New_Cer/TimeCop. mobileprovision", Both certificatePath and provisionProfile should correspond to the correct paths to the converted P12 certificate mentioned earlier and the provisioning profile you downloaded from Apple. Once you have the information in place, we are ready to compile the application. In the command line, change to your application directory: cd /path/to/your/application And then type: sencha app build -run native This should compile your application into an executable file and launch the iOS simulator. The -run option is new in Sencha Cmd Version 3. Previous versions would launch the simulator by default. You can drop the -run option if you only want to build the application. Chapter 3 [ 91 ] This looks more like a native iPhone app should, and if the application is run on an iPhone, the phone will also vibrate when the notification occurs. The compiled application should now be in the build/native folder in your application directory. You can drag it onto iTunes to install it on your device. Installing a native application If your application fails to install, it is often helpful to try installing under Xcode. Connect your device and drop the application file on top of the Xcode application. This will launch Xcode and attempt to install the application on your mobile device. Xcode often gives back better error information than iTunes does. Building native Android applications Building compiled applications in Android follows a similar pattern as the one we used for iOS: 1. We create an Android signing certificate. 2. We create a package configuration file for Sencha Cmd to use. 3. We run the Sencha Cmd packager to create an application.apk file, which will run on Android devices or the Android emulator for testing. The nice part is that we can still use the exact same code, all we need is a new certificate and some configuration changes. Creating the Android signing certificate Generating the Android signing certificate is significantly less complex than its iOS counterpart. All of our keys can be generated on our local machine and there is no provisioning process for Android applications. The first thing we need to do is download the Android SDK from http:// developer.android.com/sdk/index.html. Once the ZIP file is downloaded, we need to extract it and save it in an appropriate location. For this example, we have chosen our home directory in a folder called development. When we create our configuration files, your file path information may vary depending on where you place the SDK. Going Command Line [ 92 ] The Android certificate is generated from the command line using the following command (all on one line): keytool -genkey -v -keystore time-cop.keystore -alias timecop -keyalg RSA -keysize 2048 -validity 10000 The important parts of this command are the keystore name, time-cop.keystore, and the alias, which is timecop. We will need to have these values in order to correctly set up our configuration file. When you execute this command, you will be prompted to create a password for the keystore. You will then be walked through a series of questions about your organization and location (these are optional, but probably a good idea). Once you have answered all of the questions, a file will be generated called time-cop.keystore (or whatever you named your keystore). Creating the Android configuration file As with our previous iOS configuration file, we create a JSON file called packager_ android.json. The format for this file will follow the same format as our previous iOS file: { "applicationName": "TimeCop", "applicationId": "com.12ftguru.TimeCop", "outputPath": "/Users/12ftguru/Development/compiled/", "iconName": "timecop.png", "versionCode": "1.0", "versionString": "1.0 Release 1", "inputPath": "/path/to/your/application", "configuration": "Debug", "platform": "AndroidEmulator", "certificatePath": "/Users/12ftguru/Development/time-cop.keystore", "certificateAlias": "timecop", "sdkPath": "/Users/12ftguru/Development/sdk", "orientations": [ "portrait", "landscapeLeft", "landscapeRight", "portraitUpsideDown" ], "deviceType": "" } Chapter 3 [ 93 ] applicationName will be the name of the .apk file that is created when we compile, which in this case, is TimeCop.apk. applicationId is a unique identifier for your application, we recommend using something like com.your_name.your_application_name. outputPath is where the .apk file will be saved and iconName is the file that will be used as the icon for your application. versionCode and versionString are up to you and should be used to differentiate which version of the software is being used. inputPath is the full path to your TimeCop files (or a path relative to this configuration file). configuration can be set to Release or Debug and platform can be set to Android or AndroidEmulator. These settings will typically be Debug + AndroidEmulator for testing and Release + Android for a finished application. certificatePath is the location of the keystore file that we generated in the previous section and certificateAlias is the alias we supplied as part of our command-line arguments when we created keystore. The orientations are the viewing positions available to your application. They will typically stay as the defaults listed before. The device type is ignored by Android, but the configuration manager will return errors if the configuration or value is left off. You can keep this value set as and it will be safely ignored. Compiling and launching the Android application Just like the previous iOS application, we will use the Sencha package command to compile the application. However, if you are testing in the Android emulator, you will need to start the emulator before issuing the command. Once the emulator is running, enter the following command on the command line: sencha package run packager_android.json This will execute our packagerAndroid.json file we created in the previous section. If you are creating a release version of the application, set configuration to Release and platform to Android in your packager_android.json configuration file. You can then execute the package command, but leave off the run command like so: sencha package packager_android.json Going Command Line [ 94 ] This will compile the application without running it in the emulator. The Android emulator is capable of emulating a wide variety of hardware. For more information on the Android emulator, go to http://developer.android.com/tools/devices/emulator. html. For information on setting up different hardware profiles (sometimes called Android Virtual Devices (ADVs) with the Android emulator, take a look at the documentation available at http://developer. android.com/tools/devices/managing-avds.html. Once the application is running, you can begin testing the different features and fixing any issues. Summary In this chapter we learned about: • Generating an application skeleton with the Sencha SDK command-line tools • Using Sencha's native Ext.device APIs • Provisioning iOS applications through the Apple developer portal • Compiling a Sencha Touch web application into a native iOS app • Compiling a Sencha Touch web application into a native Android app In the next chapter, we will take a look at the Sencha Touch Charts package. The Charts package is an add-on to Sencha Touch that will let us use charts and graphs in our applications. We will show you how to take a standard datastore instance and use it to feed data to your charts and graphs. Weight Weight In this chapter we will explore an optional add-on package to the Sencha Touch Framework. The package is called Sencha Charts and it enables us to create charts using a data store. In this chapter we will cover: • Building the basic application • Defining the data stores • Setting up the Sencha Charts package • Connecting the stores to Sencha Charts • Configuring and displaying the charts Sencha Charts overview The basic Sencha Touch Framework has a number of components for displaying data. However, business and other intensive software products often require something a bit more robust. By using Sencha Touch Charts, we can also display complex graphical data as part of our applications. Weight Weight [ 96 ] The following screenshot exemplifies an overview of chart and graph types for displaying data: These new components use data stores to display a wide range of chart and graph types including: • Pie • Bar • Line • Scatter • Area • Candlestick • Radar • Gauge We will be using a few of these charts to provide a more user-friendly display for our application. As of writing, the Sencha Charts package is only available as a part of Sencha Complete, or the open source version (GPL) of Sencha Touch 2.1 download. For this chapter, we will be using the open source version, which can be downloaded for free from the web page at http://www.sencha.com/products/touch/download/. Later on in the chapter, we will cover the basic setup for using Sencha Charts, but first, we will take a look at setting up the basic application. Chapter 4 [ 97 ] The basic application We will use the Sencha Charts package to create a program for tracking weight, exercise, calories, and water consumption. We will also allow the user to tag entries for adding additional information to the charts. The application consists of four basic pieces as follows: • A form for entering data • An overview that will provide a group of charts on a single page • A details section for viewing a specific chart in greater detail • A configuration section that will allow the user to set goals for our four categories, and define the units of measurement for weight and water consumption We will start by setting up the basic application and building our form. Setting up the application and building the form We will be using the Sencha Command SDK to create the application as described in the previous chapter. You will need to execute this command from the Sencha Touch directory. The basic command is as follows: sencha app create weightweight /Path/To/Your/New/Application If you prefer, you can create the initial directories and files yourself. Your file and directory structure should look something like this: Weight Weight [ 98 ] The previous screenshot shows the structure that is automatically generated with the sencha app create command. • The touch directory contains a copy of the Sencha Touch Framework including our chart functions. • The resources directory will contain our images and CSS files. • The app directory will contain the bulk of our code. To begin, we need to define our main view. This file will be called main.js and it belongs to the views folder. The main.js file is a simple tab panel with four items: Ext.define("WeightWeight.view.Main", { extend: 'Ext.tab.Panel', requires: ['Ext.TitleBar'], config: { tabBar: { docked: 'bottom' }, items: [ { xtype: 'dataentry'}, { xtype: 'overview'}, { xtype: 'details'}, { xtype: 'configform' } ] } }); We also need to make sure that this component is added into our app.js file in the views section of our Ext.application function: Ext.application({ name: 'WeightWeight', views: ['Main'], … Remember that the name we list under the view is not the file name (Main.js), it's the last part of the define statement at the top of our code: WeightWeight.view. Main. Once we have this setup, let's create four placeholder files, one for each panel in our tab view. We need to create a placeholder for dataentry, overview, details, and configform panels. These files will contain starter code for each panel or form in our application. This will let us test our application without getting errors for missing files. Chapter 4 [ 99 ] Let's have a look at how to test our application by using the starter code for each panel: 1. Create a dataentry.js file in the views directory. This will be a form panel so the starter code should be set as follows: Ext.define("WeightWeight.view.DataEntry", { extend:'Ext.form.Panel', alias:'widget.dataentry', config:{ title:'Enter Data', iconCls:'info', html: 'Data Entry' } }); 2. Next, we need to create an overview.js file with a simple panel in the views directory and set the code as follows: Ext.define("WeightWeight.view.OverviewChart", { extend:'Ext.Panel', alias:'widget.overview', config:{ title:'Overview', iconCls:'star', html: 'Overview' } }); 3. The view/details.js file is also a panel like the previous overview.js file. The code is as follows: Ext.define("WeightWeight.view.DetailChart", { extend:'Ext.Panel', alias:'widget.details', config:{ title:'Details', iconCls:'locate', html: 'Details' } }); 4. And finally, the views/config.js file, which is also a form panel like the dataentry.js file. The code is as follows: Ext.define("WeightWeight.view.Config", { extend:'Ext.form.Panel', alias:'widget.configform', Weight Weight [ 100 ] config:{ title:'Config', iconCls:'settings', html: 'Config' } }); 5. Once all the views are created, we need to remember to add them to the views section in our app.js file (where we added Main previously). In the app js file, set the views section as follows: views: ['Main', 'Config', 'AddTag', "OverviewChart", "DetailChart"] We should now be able to load the code and test our panels. Small steps Creating code can be a very involved process. It is often helpful to make small changes and then test, rather than changing a few hundred lines of code and then testing. By changing small amounts of code, you should be able to track down problems quicker when they occur. In this case, by creating these starter files, we can test to make sure that Sencha is locating the files correctly and that the application starts without errors. We can then work on one file at a time and limit the places where we need to look when things go boom. Chapter 4 [ 101 ] At this point, our application should simply start and allow us to switch between our views. This confirms that the application is working and then we can start creating our form. Creating the data entry form Our data entry form consists of: • Three fields: datepickerfield for setting the date, numberfield for each of our four categories (weight, water, calories, and exercise), and hiddenfield for storing our tag value for the entry. • Three buttons: One for adding tags, one for saving, and one for canceling and clearing the form. • We will also place the Cancel and Save buttons inside an Hbox layout container. This will let us display the buttons side by side. We will replace the line in view/DataEntry.js that says html: 'Data Entry' so that the code looks like this: Ext.define("WeightWeight.view.DataEntry", { extend:'Ext.form.Panel', alias:'widget.dataentry', config:{ title:'Enter Data', iconCls:'info', items:[ { xtype:'datepickerfield', label:'Date', placeHolder:'mm/dd/yyyy' }, { xtype:'numberfield', id:'weightField', margin:'10 0', label:'Weight' }, { xtype:'numberfield', id:'waterField', margin:'10 0', label:'Water' }, { Weight Weight [ 102 ] xtype:'numberfield', id:'calorieField', margin:'10 0', label:'Calories' }, { xtype:'numberfield', id:'exerciseField', label:'Exercise' }, { xtype:'hiddenfield', id:'hiddenTagField' }, { xtype:'button', margin:'25 0 25', text:'Add Tag', id: 'addTagButton' }, { xtype:'container', layout:{ type:'hbox' }, items:[ { xtype:'button', margin:'0 10 0 0', text:'Cancel', flex:1 }, { xtype:'button', margin:'0 0 0 10', text:'Save', flex:1 } ] } ] } }); Chapter 4 [ 103 ] We have also provided margins for each of our items to add spacing to the form, making it more readable. The end result should look something like this: The next view we need to create is the one for adding our tags. We will use a sheet to achieve this. Creating the AddTag view The AddTag view is embedded in an ActionSheet component. This view will allow us to add new tags or select from the previous ones, and the ActionSheet component will display the view as an overlay that slides up from the bottom of the screen. The form contains a single field called textfield, a list view, and two buttons. Create the file in the views directory and call it AddTag.js: Ext.define('WeightWeight.view.AddTag', { extend: 'Ext.ActionSheet', alias: 'widget.addtag', Weight Weight [ 104 ] config: { id: 'addTagSheet', items: [ { xtype: 'textfield', label: 'Enter a New Tag', placeHolder: 'or choose a tag from the list below.' }, { xtype: 'list', height: 300, itemTpl: [ '
List Item {string}
' ] }, { xtype: 'container', margin: 10, layout: { type: 'hbox' }, items: [ { xtype: 'button', margin: '0 10 0 0', text: 'Cancel', flex: 1 }, { xtype: 'button', margin: '0 0 0 10', text: 'Save', flex: 1 } ] } ] } }); We have used the alias configuration to give this component an xtype property. This will let us quickly create and remove it within our program. We have also given the component an id property so that we can reference it in our controller. Chapter 4 [ 105 ] The end result should look something like this: The list component is a placeholder for now. We will finish it later once we have our data stores set up. The next view we need to set up is the config form. This will be similar to our data entry form with a few different field types. Creating the config form We will start by editing the Config.js placeholder file that we set up earlier in the chapter. The code for it is as follows: Ext.define("WeightWeight.view.Config", { extend:'Ext.form.Panel', alias: 'widget.configform', config:{ title:'Config', iconCls:'settings', items:[] } }); The alias property allows us to call the panel by a custom xtype of config form. This is the xtype property we used in our Main.js file for the fourth panel. The title and iconCls properties control how the navigation for this panel appears in the main view. Weight Weight [ 106 ] Next, we need to add some items to our panel. We will start by adding number fields for Starting Weight and Target Weight. By using a numberfield component we make sure that the number keyboard will appear on most mobile devices. To keep the field organized, we will put them in a fieldset component. This will go in the empty items config: { xtype:'fieldset', title:'Weight Loss Goal', items:[ { xtype:'numberfield', id:'startingWeight', name:'startingWeight', label:'Starting Weight' }, { xtype:'numberfield', id:'targetWeight', name:'targetWeight', label:'Target Weight' } ] } Next, we will add a set of spinner fields. The spinnerfield component allows the user to increment the field values using + and - buttons. These will also be in a fieldset component like the previous ones: { xtype:'fieldset', title:'Daily Goals', items:[ { xtype:'spinnerfield', id:'exercisePerDay', label:'Exercise (minutes)', defaultValue:30, stepValue: 1 }, { xtype:'spinnerfield', id:'caloriesPerDay', label:'Caloric Intake', defaultValue:0, stepValue: 100 }, Chapter 4 [ 107 ] { xtype:'spinnerfield', id:'waterPerDay', label:'Water Consumption', defaultValue:8, stepValue: 1 } ] } Notice that the spinnerfield component also allows us to set a stepValue configuration, which controls how much the field will increase or decrease when the buttons are pressed. Lastly, we will add our units of measurement section with radio buttons for different selections as follows: { xtype:'fieldset', title:'Units of Measure', padding:25, items:[ { xtype:'fieldset', title:'Weight', items:[ { xtype:'radiofield', label:'Pounds', name:'weightUnits', value:'lbs', checked:true }, { xtype:'radiofield', label:'Kilograms', name:'weightUnits', value:'kg' } ] }, { xtype:'fieldset', title:'Water', items:[ Weight Weight [ 108 ] { xtype:'radiofield', label:'Glasses', name:'waterUnits', value:'glass', checked:true }, { xtype:'radiofield', label:'Ounces', name:'waterUnits', value:'oz' } ] } ] } The end form should look something like this: Now that we have our two forms, let's start working on the controllers for them. We'll start with the data entry controller. Chapter 4 [ 109 ] Creating the DataEntry controller Let's start off with a bare controller like so: Ext.define('WeightWeight.controller.DataEntry', { extend: 'Ext.app.Controller', config: { refs: { }, control: { } } }); We start off by extending the basic controller and then adding a config section that will contain the rest of our initial setup code. The refs section will contain references to other components we need, and the control section will assign functions to our buttons and other components. The refs section is where we will add a reference to our AddTag sheet: refs: { tagSheet: '#addTagSheet', } This is occasionally written out in a longer form as follows: refs: { tagSheet: { selector: '#addTagSheet' } } Both ways will work just fine. The reference looks for a component selector, in this case a component with an id value of addTagSheet. By creating this reference using the id configuration of our AddTag sheet, we can access it anywhere in the controller by typing the following code: var sheet = this.getTagSheet(); Notice that despite the fact that we use tagSheet as the reference, the get function capitalizes the first letter in our reference to getTagSheet. Since JavaScript is case sensitive, if you tried using gettagSheet, JavaScript will return an error. Weight Weight [ 110 ] Now that we have our reference, we need to add controls to the Add Tag button in our DataEntry form and the two buttons on our AddTag sheet. The code is as follows: control: { 'button#addTagButton': { tap: 'showAddTag' }, '#addTagSheet button[text="Cancel"]': { tap: 'cancelAddTag' }, '#addTagSheet button[text="Save"]': { tap: 'saveAddTag' } } Each of our controls has three parts: • A DOM selector that tells the program which component we want to bind to • The event we want it to listen for • The function to fire when the event occurs We will add additional controls later on when we create our data stores. For now, let's add in the functions that need to fire when these three buttons are clicked. The first is a showAddTag function. It calls our AddTag sheet and displays it. The function is added after the end of the config section and looks similar to the following code: showAddTag: function() { var sheet = this.getTagSheet(); if (typeof sheet == 'undefined') { sheet = Ext.widget('addtag'); Ext.Viewport.add(sheet); } sheet.show(); } First, we check to see if there is already a sheet in the memory (using the this. getTagSheet() function automatically created by our reference in the refs section), and if not, then we create a new one using the Ext.Widget() function to create a new component with an xtype property of addtag. We then add this sheet to the view port and show it. Chapter 4 [ 111 ] The Cancel button in our AddTag sheet has a very simple function: cancelAddTag: function() { this.getTagSheet().hide(); } This is also used as our autogenerated reference function to grab the open sheet and close it. For now, we will duplicate this function for our last saveAddTag function: saveAddTag: function() { this.getTagSheet().hide(); } This will simply hide that sheet as well. We will add the code to save our tag data once we get our stores created. For now, save and test the code to make sure that the sheet appears and hides as expected. The end result should look something like this: Now that we have the basic forms, we need to create our stores and models. This will provide us with places to store the data from our various forms. Weight Weight [ 112 ] Defining the models and stores For this project, we will be using the local storage offered by HTML5 to store our data. We will begin by defining the model for our data entry form. We will just call this one Entry.js and it goes in the models folder. The code is as follows: Ext.define('WeightWeight.model.Entry', { extend: 'Ext.data.Model', config: { idProperty: 'id', fields: [ {name: 'id', type: 'auto'}, {name: 'entryDate', type: 'date', dateFormat: 'm-d-Y'}, {name: 'weight', type:'float'}, {name: 'water', type:'int'}, {name: 'calories', type: 'int'}, {name: 'exercise', type: 'int'}, {name: 'tag', type: 'string'} ], proxy: { type: 'localstorage', id: 'weightweight-entry' } } }); The model is pretty straightforward, defining the various data types and names. One thing to be aware of is the entryDate field, which has a type field of date. When you use a date type in a model, you should always declare a dateFormat component. This tells the model how to store and retrieve the data. It also provides a common translation for the components that grab data from the model. Failure to set the dateFormat component often leads to foul language and extreme frustration. The next model we need is a model for the tags. The Tag.js file goes in the models folder and it is pretty simple. It only has an id field and a text field: Ext.define('WeightWeight.model.Tag', { extend: 'Ext.data.Model', config: { idProperty: 'id', fields: [ {name: 'id', type: 'auto'}, Chapter 4 [ 113 ] {name: 'text', type: 'string'} ], proxy: { type: 'localstorage', id: 'weightweight-tag' } } }); As before, we just use a localstorage proxy and give it a unique ID. This ID makes sure that the data is stored in its own separate table. The last model we need is our Config.js model. This model follows the same format as a local storage proxy and the fields from our config form. The code is as follows: Ext.define('WeightWeight.model.Config', { extend: 'Ext.data.Model', config: { fields: [ {name: 'id', type: 'int'}, {name: 'startingWeight', type: 'float'}, {name: 'targetWeight', type: 'float'}, {name: 'exercisePerDay', type: 'int', defaultValue: 30}, {name: 'caloriesPerDay', type: 'int'}, {name: 'waterPerDay', type: 'int', defaultValue: 8}, {name: 'weightUnits', type: 'string', defaultValue: 'lbs'}, {name: 'waterUnits', type: 'string', defaultValue: 'glass'} ], proxy: { type: 'localstorage', id : 'weightweight-config' } } }); We also include some default values as part of the model. These values will get pulled into the form when we create a new config record. Once we have our models, we need to create our data store. The EntryStore.js file is created and it goes into the stores folder. The code is as follows: Ext.define('WeightWeight.store.EntryStore', { extend: 'Ext.data.Store', config: { model: 'WeightWeight.model.Entry', autoLoad: true, storeId: 'EntryStore' } }); Weight Weight [ 114 ] This is a very basic store that we will expand later. For now, we will be using the model to do most of the heavy lifting. We give the store a storeId value of EntryStore, so that we can easily address it with our DataEntry controller. Next, we need a store for our tags. Since we only need very limited control over the tag store (it only feeds the list in our AddTag form), we are going to add the store as part of the component itself. Open the AddTag.js file and modify the list entry so that it looks similar to the following code: { xtype: 'list', height: 300, store: { model: 'WeightWeight.model.Tag', autoLoad: true }, itemTpl: [ '
{text}
' ] } This simple store format creates the store as part of the list entry and does not need to be added to our app.js file. Speaking of the app.js file, we should add other models and stores near the top of the Ext.Application function as follows: models: ["Tag", "Entry", "Config"] stores: ['EntryStore'] If a model or store is within its own file, then it needs to be added into the app.js file. But, since the simple store format for our list is part of the component itself, we don't need to add it to the app.js file. In the case of our Config model, there will only be one config record for the application. This means that we don't actually need a store to use it. We will take care of that back in our controllers. Meanwhile, back in the controllers Back in our controllers, it's time to put those stores to work for us, saving and displaying our data. Chapter 4 [ 115 ] Let's start with our DataEntry controller. First, we are going to add a few more references, so that we can get to our components easier. Update the DataEntry.js references as follows: refs: { tagSheet: '#addTagSheet', tagList: '#addTagSheet list', tagInput: '#addTagSheet textfield', tagButton: 'button#addTagButton', tagField: '#hiddenTagField', entrySaveButton: 'dataentry button[text="Save"]', entryCancelButton: 'dataentry button[text="Cancel"]', entryForm: 'dataentry' } This provides us with easy access to our tag adding sheet, the list of tags, the input and hidden fields, as well as the button that opens the sheet. We also add references to our data entry form and both of its buttons. Here, in the control section we need to assign events and functions to each of these items. We can also use our reference names here to address the controls as follows: control: { tagButton: { tap: 'showAddTag' }, tagInput: { clearicontap: 'deselectTag' }, tagList: { select: 'selectTag' }, '#addTagSheet button[text="Cancel"]': { tap: 'cancelAddTag' }, '#addTagSheet button[text="Save"]': { tap: 'saveAddTag' }, entrySaveButton: { tap: 'saveEntry' }, entryCancelButton: { tap: 'clearEntry' } } Weight Weight [ 116 ] Notice that we used the reference name for most of these. However, for the Save and Cancel buttons on our tagSheet, we used the component query reference. This is because we don't really need any additional control over those two pieces. They are basically single purpose components. For example, our showAddTag and cancelAddTag functions both need to be able to grab the sheet itself in order to show and hide it. Since we have a reference of TagSheet assigned to it, we can call it with the following code: var sheet = this.getTagSheet(); Since we don't modify the Save and Cancel buttons once they have been created, there is no need to create a reference for them. However, we will be making some modifications to our AddTagButton when we save our tag, so we created a reference for that one. Let's update our saveAddTag function and see how that's done. Change the function as follows: saveAddTag: function() { var tag = this.getTagInput().getValue(), store = this.getTagList().getStore(); if (tag != "") { this.getTagButton().setText('Tag: '+tag); this.getTagField().setValue(tag); if (store.findExact('text', tag) == -1) { store.add({text: tag}); store.sync(); } } else { this.getTagButton().setText('Add Tag'); this.getTagField().setValue(''); } this.getTagSheet().hide(); } Right from the start we begin using the get functions automatically created by our references. We get the value of the textfield in our form using this. getTagInput().getValue() and then we get the store we use for our tag list by calling the this.getTagList().getStore() function. Remember, the list store is the one we created as part of the component instead of a separate store.js file. However, since we can get to the list, and the list knows what store it is using, we have easy access to everything we need. The reference to the parent also gives us quick access to its children. Chapter 4 [ 117 ] Next, we check to see if the user entered anything into the field (if the value of tag != "") and if so, we set the text on our button to say Tag: and whatever the user entered. This provides the user with easy feedback as to what tag is on the current entry, so if we tag our entry as Tired, then the button will look like this: Next, we set the value of our hidden field to the same value. We do this because we will need to load our form into a record to save it. We can load values from a form field but we cannot load values from a button name. We use the hidden field to hold the value within the form for later use. Next, we need to find out if the tag is the one that we have entered previously, or if it is something new. To do this, we need to search the store using store. findExact('text', tag). This will return -1 if the value of tag is not found in the text field for any of the store's data. If we don't find the tag, we add it to our store using the following code: store.add({text: tag}); store.sync(); Lastly, if the user has cleared the textfield out leaving it blank, we remove the previous tag text from the button and clear out the value of the hidden field. Our next function controls when the user selects an existing tag from the list of tags in the sheet (instead of entering a new one): selectTag: function(list, record) { this.getTagInput().setValue(record.get('text')); } When the user selects an item in the list, we put the text of the item in the text field for saving. The saveAddTag function will take care of the rest. We have a similar function that deselects the items in the list: deselectTag: function() { this.getTagList().deselectAll(); } Our text field has a clear icon that removes the value of the field. We tie into the clearicontap event that we set up in our controllers section to fire this deselectTag function. Weight Weight [ 118 ] Now that we have our tags taken care of, we will be able to save the full entry. We do this by adding the following function: saveEntry: function() { var values = this.getEntryForm().getValues(), store = Ext.getStore('EntryStore'), entry = Ext.create('WeightWeight.model.Entry', values); store.add(entry); store.sync(); Ext.Msg.alert('Saved!', 'Your data has been saved.', this. clearEntry, this); } This function grabs the values from our form and creates a new entry for our store. Since the form names match the names of our model, we can use Ext.Create to create a new entry record and assign the values directly. We then add the new record to the store and sync. Finally, we alert the user that the new data has been saved. Our final function clears the fields in our form by using the following function: clearEntry: function() { this.getEntryForm().reset(); this.getTagButton().setText('Add Tag'); } This function resets our form and the text of the button. This function will be fired by the Cancel button in our data entry form. This wraps up the DataEntry.js controller. We can now move on to the Config.js controller. Config.js Create a new file in the controllers folder called Config.js (make sure to also add it to the app.js file in the list of controllers). We will start with just the basic controller: Ext.define('WeightWeight.controller.Config', { extend: 'Ext.app.Controller', config: { views:['Config'], models:['Config'], refs: { form: 'configform' Chapter 4 [ 119 ] }, control: { form: { initialize: 'getSavedConfig' } } } }); This sets up the controller with our views, models, and references. It also assigns a function to our form so that when it is initialized it calls getSavedConfig. This function is also the first one we need to create. Before we get started, we should keep in mind a few things about config. This will be like a set of preferences for the application. There will only be one record for config, which is why we don't need to create a store. We can use the Config.js model to create, load, and save the record directly. Let's take a look at how this gets done. Beneath the config section, we need to add the following code: getSavedConfig: function() { var config = Ext.ModelManager.getModel('WeightWeight.model. Config'); config.load(1, { scope: this, failure: this.createSavedConfig, success: this.bindRecordToForm }); } Here, we create an instance of our Config model and attempt to load the first record from the HTML5 local storage (remember this should also be the only record). There are two possible outcomes here: • If the load fails, it means that this is the first time the user has accessed the Config section and we have no record. In this case, we will call another function called createSavedConfig. • If the load succeeds then we need to load the data into our form for display. This will happen in the bindRecordToForm function. By setting the scope of the function to this (meaning the controller itself), we can make these two functions part of the controller and call them with this. createSavedConfig and this.bindRecordToForm respectively. Weight Weight [ 120 ] We'll start by adding our new functions beneath the previous getSavedConfig function: createSavedConfig: function() { var config = Ext.create('WeightWeight.model.Config', {id: 1}); config.save({ success: this.bindRecordToForm }, this); } This function creates a new empty record with the default values we defined in the config object and then saves the record. If this is successful, we call our next function, which binds the data record to our form: bindRecordToForm: function(record) { this.savedConfig = record; var form = this.getForm(); form.setRecord(this.savedConfig); form.on({ delegate: 'field', change: this.updateValue, spin: this.updateValue, check: function(field) { this.updateValue(field, field.getGroupValue()); }, scope: this }); } This function is called by both getSavedConfig and createSavedConfig, which pass along the data record automatically. We set this record to be our savedConfig, which allows us to get at the config data from anywhere in the controller. Next we grab the form and use setRecord to populate the form with our data. Once the form is populated, we also need a way to save the data. To do this, we are going to use an interesting technique called delegate. Delegate allows us to set listeners and functions on specific children within the form. In this case, we do form.on({ delegate: 'field', which lets us set a group of listeners on every field in our form: • The numberfield component understands the change event • The spinnerfield component understands the spin event • The checkboxfield component understands the check event Chapter 4 [ 121 ] Each of these events will call this.updateValue to save the data. While the other fields pass along both the field and value automatically, the checkboxes actually only pass the field when the check event fires. This means we do a tiny bit of extra work to get them to pass both field and value to our next function. Our updateValue function takes the field and value passed in our previous function, and saves the data for us: updateValue: function(field, newValue) { this.savedConfig.set(field.getName(), newValue); this.savedConfig.save(); } This saves our data to local storage. Now that we have a way to save data and our goals, we can start looking at the charting functions for displaying the data. Getting started with Sencha Touch Charts As we noted at the beginning of the chapter, Sencha Touch Charts is currently only available as part of Sencha Complete or the open source version of Sencha Touch 2.1. Previously, Sencha Touch Charts was a separate download, which had to be installed and configured as part of your application in order to function. This is no longer required. It should also be noted that if you are using the standalone commercial version of Sencha Touch 2.1 (which is not part of the Sencha Complete package), you will not be able to use the new Sencha Charts functions. While this standalone commercial version of Sencha Touch 2.1 includes an empty src/charts directory, it does not have any of the actual chart functionality. Creating the overview chart The overview chart is a single-line chart, tracking weight and exercise. Our chart will have three axes, with weight ranges displayed on the left, date ranges displayed across the bottom, and exercise time ranges along the right. Weight Weight [ 122 ] The following screenshot describes the preceding explanation in more detail: We will start with a few changes to our placeholder for the OverviewChart.js view: Ext.define("WeightWeight.view.OverviewChart", { extend:'Ext.Panel', alias:'widget.overview', config:{ title:'Overview', iconCls:'star', layout: 'fit', items:[{ xtype:'chart', store:'EntryStore', legend:{ position:'bottom' }] } } }); Chapter 4 [ 123 ] Here, we've replaced the html configuration and included a single chart item as part of our panel. Previous versions of the Sencha Touch Chart software used a chartPanel object, which automatically included the chart item as part of the panel. The current Version 2.1 treats the chart item as a separate object, which allows the chart item to be embedded in a panel or a container. We have given the chart item a store value to grab data from, and positioned the legend section at the bottom of the chart. Adding the axes The next piece we need to add is our axes. As we mentioned earlier, there are three for this graph. The code for them goes inside the chart section of the config definition (below our legend definition): axes:[ { type:'numeric', position:'left', fields:['weight'], title:{ text:'Weight', fontSize:14 } }, { type:'numeric', position:'right', fields:['exercise'], title:{ text:'Exercise', fontSize:14 } }, { type:'time', dateFormat:'m-d-Y', position:'bottom', fields:'entryDate', title:{ Weight Weight [ 124 ] text:'Date', fontSize:20 } } ] The first axis has a title section of weight and it's a numeric axis. We position it on the left-hand side and then tell the axis which fields we are tracking (in this case, weight). As you might have guessed from the name fields, this means we can have multiple items tracked along the same axis. This works well if you have multiple items with the same numeric range of data. In this case, we have too much variation in the range of exercise and weight, so we keep them on different axes. The exercise axis is set up in a similar fashion, but positioned on the right. The date axis is a bit different. It has a type of date and a dateFormat for display. Next, we need to set up the series. Creating the series The series section goes inside the chart configuration and beneath our axes section. The series section describes how the data points should align on the graph and how they should be formatted. Our overview graph is a line graph display, tracking weight and exercise over time. We need one entry for weight and a second one for exercise: series:[ { type:'line', xField:'entryDate', yField:'weight', title:'Weight', axis:'left', style:{ smooth:false, stroke:'#76AD86', miterLimit:3, lineCap:'miter', lineWidth:3 }, marker:{ type:'circle', r:6, Chapter 4 [ 125 ] fillStyle:'#76AD86' }, highlightCfg:{ scale:1.25 } }, { type:'line', xField:'entryDate', yField:'exercise', title:'Exercise', axis:'right', style:{ smooth:false, stroke:'#7681AD', lineWidth:3 }, marker:{ type:'circle', r:6, fillStyle:'#7681AD' }, highlightCfg:{ scale:1.25 } } ] This defines our two series (Weight and Exercise). The type configuration defines which kind of series we are using. The xField configuration determines which data field is tracked along the horizontal axis (entryDate for both) and the yField configuration determines which field is tracked along the vertical axis (weight for the first series and exercise for the second). The axis configuration tells the series which part of the graph to map its values to. The style section determines how the line for our series will appear. The marker section gives us the appearance of each data point along the line. The highlightCfg section uses scale to increase the size of a selected marker, so when the user clicks on a data point, the marker will increase to 1.25 times its normal size. Weight Weight [ 126 ] The marker section itself is actually a sprite reference, which means that we can use any of the available Sencha Touch sprite objects for our marker. These include things such as: • Circles • Ellipses • Images • Rectangles • Text A full list of available sprites and their configuration options can be found at http://docs.sencha.com/touch/2-1/ in the draw | sprite section of the API. To use these sprites, you just need to set the type configuration to the sprite name. The name for each sprite can be found at the top of the documentation as seen in the following screenshot: Once the type config is set for the marker section, you can use any of the sprite's configuration options to customize the marker's appearance. Now that the series configuration is complete, we can also add some interactions to the graph to make it more interesting. Chapter 4 [ 127 ] The interactions section The interactions section allows us to respond to the user's taps and gestures to expand the amount of information we provide. The current types of interactions include the following: • ItemCompare: This lets the user select two items and see a data comparison • ItemHightlight: This lets the user tap and highlight a series of data items in the chart • ItemInfo: This lets the user tap and get a detailed view of the data record • PanZoom: This lets the user pinch the chart to zoom in and out, or let them tap and drag to pan • PieGrouping: This lets the user select and merge consecutive pie slices • Rotate: This lets the user tap and drag around the center of the pie or radar charts to rotate the chart • ToggleStacked: This lets the user toggle between stacked and grouped orientations on a bar or column series chart For this application, we will allow the user to tap the data points and get back all of the details for that particular day. We set up an interaction with a type value of iteminfo and define a tpl tag, which is used to display the data in the panel. The interaction receives the entire data record for the tapped data point so the tpl tag can use any of our values for weight, exercise, water, calories, or tags: interactions:[ { type:'iteminfo', panel:{ tpl:[ '', '', '', '', '', '', '
Weight{weight} ({weightUnits})
Water{water} ({waterUnits})
Calories{calories}
Exercise{exercise} minutes
Tag{tag}
' ] } Weight Weight [ 128 ] This template will display our detailed item info. Next, we need to add the listener that will show the window when we click on one of the data points in our OverviewChart: listeners:{ show:function (interaction, item, panel) { var record = item.record; var dt = new Date(record.get('entryDate')); var config = Ext.ModelManager.getModel('WeightWeight.model. Config'); config.load(1, { scope:this, success:function (configRecord) { panel.setData(Ext.apply(record.getData(), configRecord. getData())); } }); panel.getDockedComponent(0).setTitle(Ext.Date.format(dt, 'm-d-Y')); } } } ] The listener starts by setting var record = item.record; and then getting the date out of the record so that we can format it properly for our setTitle function at the end of the listener. Next, we grab our single config record so that we can get the units of measurement for weight and water consumption. Then we set the data for the panel to the combined record and configRecord objects (using Ext.apply()). This gets both sets of data into our tpl for display. Chapter 4 [ 129 ] Lastly, since this is a special floating panel in Sencha Touch, it has no title attribute, but we can create one using the first docked component in the panel. We set this title to the formatted date we grabbed at the top of the function. You should be able to save your work now and click on any of the data points to see our new detailed item info. The last thing we want to cover is creating the details view. Weight Weight [ 130 ] Creating the details view For the details chart, we have decided to make something a little more reusable. Our overall details view will contain three similar charts and a radar chart. Since we don't want to create the same chart over and over, we need a view we can call up with a different configuration for each of our charts. This will be a simple bar style chart with two axes; one for the date and one for the amount. This reusable chart will be our goalChart view. We will create the goalChart view with its own xtype, which will allow us to reuse it with different configurations. Creating the goalChart view We start by creating a goalChart view and setting it up to load our config file when it initializes: Ext.define('WeightWeight.view.goalChart', { extend:'Ext.Panel', alias:'widget.goalchart', config: { layout: 'fit' }, Chapter 4 [ 131 ] constructor: function (config) { this.store = Ext.getStore('EntryStore'); Ext.apply(this, config); this.callParent([config]); var configRecord = Ext.ModelManager.getModel('WeightWeight. model.Config'); configRecord.load(1, { scope:this, success: this.createChart }); } }); Here, we set our panel's store to the EntryStore that contains all of our data (this gives us access to every record). Next, our constructor function will take whatever configuration options are passed to it, and applies them to the panel using Ext. apply(this, config);. This is where we will set an individual title, dataField, goalField, and colorSet for each chart. Once these options are set, the panel then loads the goals and measurements from our single configRecord in much the same way as our previous chart panel. This time when the Config successfully loads, we call a new function called createChart. The createChart function comes right after our constructor function: createChart: function(config) { this.configRecord = config; var goalStore = Ext.create('Ext.data.Store',{ fields: [ 'entryDate', {name: Ext.String.capitalize(this.dataField), type:'int'}, {name: 'goal', type: 'int'} ] } ); this.store.each(function(record) { if (record.get(this.dataField)) { var values = { entryDate: Ext.Date.format(dt,'m-d-Y'), goal: this.configRecord.get(this.goalField) }; values[Ext.String.capitalize(this.dataField)] = record.get(this. dataField); goalStore.add(values); } }, this); } Weight Weight [ 132 ] The createChart function starts by creating a second store called the goalStore and gives it three fields as follows: • entryDate: This is the date field from our store • goal: This is the the goal passed from our configRecord • this.dataField: This will be passed to us as one of our config options when we use the goalChart view We then loop through our data in the main store (EntryData) and look for any values in the field that match the value we received for this.dataField. As we find matches, we add them to our goalStore. The goalStore is the actual store that will feed the chart. For example, we could use the following code to create a goalChart view: { xtype: 'goalchart', chartTitle: 'Exercise', dataField: 'exercise', goalField: 'exercisePerDay', colorSet:['#a61120', '#ff0000'] } The goalChart view would use the dataField value to look for any data we have for exercise and create the chart. It would also use the goalField value of exercisePerDay to grab that number from our config record and add it to the display. The final part of our goalChart sets up the series and axes much like the previous one: this.chart = Ext.factory({ xtype: 'chart', store: goalStore, animate: true, legend: { position: 'right' }, axes: [{ type:'Numeric', position:'left', fields:[ Ext.String.capitalize(this.dataField), 'goal'], title: Ext.String.capitalize(this.dataField), decimals:0, minimum:0 }, Chapter 4 [ 133 ] { type:'category', position:'bottom', fields:['entryDate'], title:'Date' }], series: [ { type: 'bar', xField: 'entryDate', yField: Ext.String.capitalize(this.dataField), style: { fill: this.colorSet[0], shadowColor: 'rgba(0,0,0,0.3)', maxBarWidth: 50, minGapWidth: 3, shadowOffsetX: 3, shadowOffsetY: 3 } }, { type:'line', style: { smooth: false, stroke: this.colorSet[1], lineWidth: 3 }, axis:'left', xField:'entryDate', yField:'goal', showMarkers: false, title:'Goal' } ] }, 'Ext.char t.Chart'); The main difference from the previous charts is that we have some values that will be supplied by our config, and we use the Ext.factory function to create the chart object. Here, our use of Ext.factory is equivalent to Ext.create, but Ext.factory can also be used to update the configuration of existing objects. We chose to use Ext.factory here, rather than Ext. create, solely because most of the Sencha Charts examples refer to Ext.factory when creating charts, and we wanted to be consistent. Weight Weight [ 134 ] Now we can re-use the chart for our exercise, water, and weight charts just by setting different config values for: • dataField • goalField • chartTitle • colorSet Take a look at the DetailChart.js file in our example code to see how this works. The last chart we need to touch on is the word chart. Creating the word chart The wordChart view is set up much like our goalChart with its own constructor and createChart function. However, the goal chart uses our tags to create a different type of chart called a radar chart. Our wordChart.js file checks for the number of occurrences of specific words and uses the information to draw our radar chart. Chapter 4 [ 135 ] The beginning of the wordChart.js file looks almost the same as our goalChart: Ext.define('WeightWeight.view.wordChart', { extend:'Ext.Panel', alias:'widget.wordchart', config: { layout: 'fit' }, constructor: function (config) { this.store = Ext.getStore('EntryStore'); Ext.apply(this, config); this.callParent([config]); var configRecord = Ext.ModelManager.getModel('WeightWeight. model.Config'); configRecord.load(1, { scope:this, success: this.createChart }); } }); After the end of the constructor, we set up our createChart function: createChart: function(config) { this.configRecord = config; this.store.filterBy(function(record) { if (record.get('tag')) { return true; } else { return false; } }); this.store.setGroupField('tag'); this.store.setGroupDir('ASC'); var groups = this.store.getGroups(); this.store.setGroupField(''); this.store.clearFilter(); var wordStore = Ext.create('Ext.data.Store', { fields: ['name', {name: 'count', type: 'int'}]} ); Ext.each(groups, function(group) { wordStore.add({name: group.name, count: group.children. length}); }); Weight Weight [ 136 ] This grabs our configRecord like we did previously and then filters our store to find only the records that have tag data. We then group the fields by tag so that we can generate a count for each tag. Next we create a second store, much like in our goalCharts and we transfer our tag names and our counts into the second store. This one is called our wordStore. Now that we have a wordStore that consists only of the tag name and the number of times it occurs, we can use it to feed our new chart. Again, we use the Ext.Factory to create our store: this.chart = Ext.factory({ xtype: 'polar', store: wordStore, animate: { easing: "backInOut", duration: 500 }, series: [{ type: 'radar', xField: 'name', yField: 'count', labelField: 'name', marker:{ type:'circle', r:3, fillStyle:'#76AD86' }, style: { fillStyle: 'rgba(0,255,0,0.2)', strokeStyle: 'rgba(0,0,0,0.8)', lineWidth: 1 } }] The radar style chart uses an xtype value of polar as part of its chart configuration. Polar charts include circular chart systems such as the pie and radar style charts, whereas Cartesian charts are line-based charts such as the area and bar charts. Chapter 4 [ 137 ] In the series section, the type value for our chart is then set to radar, which gives us our specific chart appearance. As with our previous charts, we also set marker and style configurations. Finally, we finish our wordChart by setting up the axes, closing out the chart object, and adding it to our panel: axes: [{ type: 'numeric', position: 'radial', fields: 'count', grid: true, label: { fill: 'black' } },{ type: 'category', position: 'angular', fields: 'name', grid: true, label: { fill: 'black' }, style: { estStepSize: 1 } }] }, 'Ext.chart.Chart'); this.add(this.chart); We have two axes here: a numeric axis for our tag counts and a category axis for our tag names. We map these axes to the correct field and set grid to true. This will give us an underlying grid for our radar chart. The style setting of estStepSize: 1 ensures that all of our words will show up around the edge of our radar chart, without skipping any words. Weight Weight [ 138 ] Now that our wordChart is finished, we need to assemble all of our charts into a single page for our full details view: Back in our details.js placeholder file, we need to set up a new layout and add our four charts. As you can see in the screenshot, we have our four charts arranged in a square on the page with one chart in each corner. The easiest way to accomplish this is with a set of nested hbox and vbox layouts: Chapter 4 [ 139 ] Chart 1 Chart 2 vbox hbox Chart 3 Chart 4 vbox As you can see in the previous image, our details panel will have a layout section of hbox, with two containers inside, one on top of the other. In our config section, add the layout as follows: layout: { type: 'hbox', align: 'stretch', pack: 'center', flex: 1 } The stretch and center values ensure that our containers will expand to fill the available space and occupy the center of our details panel. The flex value makes the inner containers equal in size. These two containers will have a layout of vbox. We add these two containers in an items section within our config section: items: [ { xtype: 'container', layout: { type: 'vbox', align: 'stretch', pack: 'center', flex: 1 }, Weight Weight [ 140 ] items: [ {height: 300, width: 400, xtype: 'goalchart', chartTitle: 'Exercise', dataField: 'exercise', goalField: 'exercisePerDay', colorSet:['#a61120', '#ff0000'] }, {height: 300, width: 400, xtype: 'goalchart', chartTitle: 'Caloric Intake', dataField: 'calories', goalField: 'caloriesPerDay', colorSet:['#ffd13e', '#ff0000']} ] }, { xtype: 'container', layout: { type: 'vbox', align: 'stretch', pack: 'center', flex: 1 }, items: [ {height: 300, width: 400, xtype: 'goalchart', chartTitle: 'Water', dataField: 'water', goalField: 'waterPerDay', colorSet:['#115fa6', '#ff0000']}, {height: 300, width: 400, xtype: 'wordchart', chartTitle: 'Tags', dataField: 'tag'} ] } ] The two containers form a top and bottom layout with two charts each. The goal charts each have slightly different configurations so that they display exercise, calories, and water consumption. We also color them differently to provide more visual appeal. The wordchart uses a similar configuration to include only the data from our tags. With this last panel completed, you should be able to enter data into the application and test all of the charts. Homework Take some time to play around with the different types of charts and see what is available. The Sencha website has an excellent guide for using charts and interactions at http://docs.sencha.com/touch/2-1/#!/guide/drawing_and_charting. Chapter 4 [ 141 ] Summary In this chapter we talked about: • Setting up the basic application to create the different views for the application • Creating the stores that will hold the data and feed our charts • Setting up the controllers for the application • Creating the overview chart • Creating the details chart In the next chapter we will look at creating a simple application to work with an external API. On Deck: Using Sencha.io In our previous chapters we have typically used local storage for maintaining our data. This offers a number of advantages with its ease of use and simplicity. The store and the model do all of the heavy lifting for us. However, there are a number of disadvantages to local storage as well. First and foremost, it is very much local to the device. This means that if your user has more than one device (a phone, desktop, and a tablet computer), then they will have a separate set of data for each device. This can be confusing to the user and it negates the advantage of having a single application that is accessible from multiple devices. Additionally, the data can be deleted by the user when they clear the local browser data. This can make local storage a bit problematic for a robust application. In this chapter we are going to look at solving this issue with an external API called Sench.io. Here's what we will cover: • Setting up the basic application • Getting started with Sencha.io • Updating the basic application to work with Sencha.io On Deck: Using Sencha.io [ 144 ] The basic application Our basic application is designed to present a set of flash cards to the user in a random order. Each set of flash cards comprises a deck. The user can add new decks and new cards to each deck. The decks and the cards will reside in a remote storage service called Sencha.io. Using this service, the user will also be able to log in from any number of devices and access their cards and decks. We will start off our application with the models and stores. Creating the models and stores The model for our deck is very simple and only needs two pieces of information. We will use an ID to link cards to a specific deck and a name for display purposes: Ext.define('MyApp.model.Deck', { extend: 'Ext.data.Model', config: { fields: [ { name: 'id' }, Chapter 5 [ 145 ] { name: 'name' } ] } }); The card model needs an ID of its own so that we can uniquely identify it and a deckID value so that we know which deck it's a part of. We will also need the question and answer for each card: Ext.define('MyApp.model.Card', { extend: 'Ext.data.Model', config: { fields: [ { name: 'id' }, { name: 'deckID' }, { name: 'question' }, { name: 'answer' } ] } }); For the two stores, we will initially use a local storage proxy as we have in previous chapters. This will let us test our application before we start using the Sencha.io service. Our deck store looks like this: Ext.define('MyApp.store.DeckStore', { extend: 'Ext.data.Store', requires: [ 'MyApp.model.Deck' ], config: { autoLoad: true, model: 'MyApp.model.Deck', storeId: 'DeckStore', proxy: { On Deck: Using Sencha.io [ 146 ] type: 'localstorage', id: 'Decks' }, fields: [ { name: 'id', type: 'int' }, { name: 'name', type: 'string' } ] } }); If you have worked your way through the first chapter, this basic setup should look pretty familiar to you. We extend the basic store, require our model file, and then set up our configuration. The configuration sets the store to load when it is created, tells it which model to use, sets up our local storage proxy, and tells it which fields to expect. Our card store is almost an exact duplicate: Ext.define('MyApp.store.CardStore', { extend: 'Ext.data.Store', requires: [ 'MyApp.model.Card' ], config: { autoLoad: true, model: 'MyApp.model.Card', storeId: 'CardStore', proxy: { type: 'localstorage', id: 'Cards' }, fields: [ { name: 'id', type: 'int' }, { name: 'deckID', type: 'int' }, Chapter 5 [ 147 ] { name: 'question', type: 'string' }, { name: 'question', type: 'string' } ] } }); Here we have just changed the name from Deck to Card, and specified our card fields in place of our deck fields. If you configure your store with a model, you don't actually have to specify the fields. We are doing so here just for the sake of completeness. As mentioned before, we will be revisiting these stores once we get things set up with Sencha.io, but first we need to get our display together for our lists, cards, and editing. Creating the views For our main view, we will be using a tab panel with two containers, one for our decks and one for our cards. We will use sheets for editing and adding new decks and cards. Our initial main.js file looks like this: Ext.define('MyApp.view.Main', { extend: 'Ext.tab.Panel', config: { id: 'mainView', items: [], tabBar: { docked: 'bottom' } }); On Deck: Using Sencha.io [ 148 ] Remember to add this file into your app.js file and set the launch function to create a copy of the component when the application starts (if you are using Sencha Architect, then this should happen automatically). Your app.js file should look like this: Ext.Loader.setConfig({ enabled: true }); Ext.application({ models: [ 'Deck', 'Card' ], stores: [ 'DeckStore', 'CardStore' ], views: [ 'Main' ], name: 'MyApp', launch: function() { Ext.create('MyApp.view.Main', {fullscreen: true}); } }); Next we need to add the two containers to our main.js view. In the empty items section, add the following container: { xtype: 'container', layout: { type: 'fit' }, title: 'Decks', iconCls: 'info', items: [ { xtype: 'list', itemTpl: [ '
{name}
' Chapter 5 [ 149 ] ], store: 'DeckStore' }, { xtype: 'titlebar', docked: 'top', title: 'Decks', items: [ { xtype: 'button', itemId: 'mybutton', text: 'Add', align: 'right' } ] } ] } This will be the list for our decks. The overall container has a fit layout so the items will fill the entire width and height of the container. We have given the container a title and an iconCls value, which will be used to label the tab in our Main tab panel. The container has a list view that uses our DeckStore store and a simple itemTpl template that displays the name of each deck in a separate div tag. We have also added a title bar where we can display a button for adding new decks and a title to let the user know what they are looking at. Our second container follows the same pattern as our first, but instead of a list, we have a separate container with a carousel layout, as shown in the following code: { xtype: 'container', title: 'Cards', iconCls: 'info', items: [ { xtype: 'titlebar', docked: 'top', items: [ { xtype: 'button', itemId: 'mybutton1', text: 'Add', On Deck: Using Sencha.io [ 150 ] align: 'right' }, { xtype: 'button', text: 'Shuffle' } ] }, { xtype: 'carousel' } ] } This container has a titlebar control that will be set to display the name of the current deck at the top and pull the cards into our carousel layout. We also have a second button that will shuffle the current deck of cards. Next we need to set up the two sheets for adding cards and decks. The deck sheet is a simple sheet with a textfield element for naming the deck, a button element for saving, and another button element for canceling: Ext.define('MyApp.view.addDeckSheet', { extend: 'Ext.Sheet', alias: 'widget.addDeckSheet', config: { id: 'addDeckSheet', items: [ { xtype: 'textfield', margin: '0 0 10 0', label: 'Name' }, { xtype: 'button', ui: 'confirm', text: 'Save', itemID: 'saveDeckButton' }, { xtype: 'button', itemId: 'cancelDeckButton', ui: 'decline', text: 'Cancel' Chapter 5 [ 151 ] } ], listeners: [ { fn: 'hideDeckSheet', event: 'tap', delegate: '#cancelDeckButton' } ] }, hideDeckSheet: function(button, e, options) { button.up('sheet').hide(); } }); We also add a listener for the Cancel button that will hide the sheet without saving the values. The listener delegates the tap event to our cancelDeckButton delegate and calls the hideDeckSheet function when the tap event occurs. The hideDeckSheet function receives the button element as part of its arguments. We can then travel up the DOM structure from the button, find the sheet, and hide it. A note about using up and down The up and down functions in Sencha Touch are extremely useful when you have a component and you need to get to either a sub component or a parent component. However, it should be noted that both up and down only return the first component that matches. For example, if a button element is inside of a container element, which is itself inside another container element, then button.up('container') would return the first container and not the second, outer container. Our card sheet is a duplicate of the deck sheet, but with text fields for question and answer: Ext.define('MyApp.view.addCardSheet', { extend: 'Ext.Sheet', config: { id: 'addCardSheet', items: [ { xtype: 'container', html: 'Deck Name Here', On Deck: Using Sencha.io [ 152 ] style: 'color: #FFFFFF; text-align:center;' }, { xtype: 'textareafield', id: 'cardQuestion', margin: '0 0 10 0', label: 'Question' }, { xtype: 'textareafield', id: 'cardAnswer', margin: '0 0 10 0', label: 'Answer' }, { xtype: 'button', ui: 'confirm', itemId: 'saveCardButton', text: 'Save' }, { xtype: 'button', itemId: 'cancelCardButton', ui: 'decline', text: 'Cancel' } ], listeners: [ { fn: 'hideCardSheet', event: 'tap', delegate: '#cancelCardButton' } ] }, hideCardSheet: function(button, e, options) { button.up('sheet').hide(); } }); Chapter 5 [ 153 ] As before, we have our Save and Cancel buttons, with the Cancel button hiding the sheet when tapped. You should now be able to start the application and test the different views as shown: Before we can get things working further in the application, we need to get set up with Sencha.io. Getting started with Sencha.io The Sencha.io service will allow us to store our data using Sencha's cloud service. We will need to register a new account, add our application and user groups using the Sencha.io dashboard, and then configure our application to use the service. On Deck: Using Sencha.io [ 154 ] The sign-up process To register a new account, go to https://manage.sencha.io and click on the Register link at the bottom of the page. Fill out the forms with your information and submit. Once your account is created, log in to the Sencha.io dashboard at the same address you used for registration, and you will see something similar to the following screenshot: Downloading and installing the Sencha.io SDK Now that you have an account you can download and install the Sencha.io SDK. There is a download link in the first part of the Getting Started page (which should be where you first start when you log in). Download the SDK to your computer and unzip the file. Move it into your web directory (someplace where you can easily reference it from your application). Next we need to add these files to our application. You can begin by opening your main app.html file and adding the following lines in the head section of the file (with your other script includes):