Common method in Swift


1. Check for any field empty in NSDictionary

class func checkforEmptyValueinDictioanty(dic:NSDictionary)-> Bool{

for (keyVal, dataVal) in dic {

  if (dataVal.length()==0){

                println(\(keyVal): \(dataVal.length()))

                return false

            }

            

        }

        return true

    }

2.Email validation

class func isValidEmail(testStr:String) -> Bool {

let fullNameArr = testStr.componentsSeparatedByString(“@”)

        var firstPart: String = fullNameArr[0]

        if let range = firstPart.rangeOfCharacterFromSet(NSCharacterSet.letterCharacterSet()){

 }else{

            return false

        }

      let emailRegEx = “[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}”

 var emailTest = NSPredicate(format:“SELF MATCHES %@”, emailRegEx)

        let result = emailTest.evaluateWithObject(testStr)

        return result

    }

3. Alert in Swift

class func commonAlert(title:String,msg:String,curView:UIViewController){

        var device : UIDevice = UIDevice.currentDevice();

        var systemVersion = device.systemVersion;

        var iosVerion : Float = (systemVersion as NSString).floatValue

        if(iosVerion >= 8.0) {

    

        var alert = UIAlertController(title: title, message: msg, preferredStyle: UIAlertControllerStyle.Alert)

            alert.addAction(UIAlertAction(title: “Ok”, style: UIAlertActionStyle.Default, handler: nil))

                  // return alert

            curView.presentViewController(alert, animated: true, completion: nil)

        }else{

            let alert=UIAlertView(title: title, message: msg, delegate: self, cancelButtonTitle: “ok”)

            alert.show()

           

        }

    }

4. NSUserDefaults in swift as common function

class func saveToUserDefault(value:AnyObject, key:String)

    {

        NSUserDefaults.standardUserDefaults().setObject(value, forKey:key)

        NSUserDefaults.standardUserDefaults().synchronize()

    }

    

    class func userDefaultForKey(key:String) -> String

    {

        return NSUserDefaults.standardUserDefaults().objectForKey(key) as NSString

        

    }

    class func userDefaultForAny(key:String) -> AnyObject

    {

        return NSUserDefaults.standardUserDefaults().objectForKey(key) as AnyObject!

    }

    

    class func userdefaultForArray(key:String) -> Array<AnyObject>

    {

        return NSUserDefaults.standardUserDefaults().objectForKey(key) as Array

    }

    

    class func removeFromUserDefaultForKey(key:String)

    {

        NSUserDefaults.standardUserDefaults().removeObjectForKey(key)

        NSUserDefaults.standardUserDefaults().synchronize()

        

    }

5.Get screen height and width

let _screenWidth=UIScreen.mainScreen().bounds.size.width

let _screenHeight=UIScreen.mainScreen().bounds.size.height

Advertisements

Use different font text in a Label in swift


let secondLabel=UILabel(frame: CGRectMake(0, 16, _screenWidth2, 20))

        secondLabel.textColor=UIColor.whiteColor()

        secondLabel.textAlignment=NSTextAlignment.Center

        secondLabel.font=UIFont(name: “Arial”, size: 12)

        bottomView.addSubview(secondLabel)

        

        let attrSting=NSMutableAttributedString(string: “I agree to the Terms of Service and Privacy Policy.”)

        

        NSLog(“text length is %d”, attrSting.length)

        

        attrSting.addAttribute(NSFontAttributeName, value: UIFont(name: “Helvetica-bold”, size: 14), range: NSMakeRange(15, 16))

        

        attrSting.addAttribute(NSFontAttributeName, value: UIFont(name: “Helvetica”, size: 12), range: NSMakeRange(31,4 ))

        

        attrSting.addAttribute(NSFontAttributeName, value: UIFont(name: “Helvetica-bold”, size: 14), range: NSMakeRange(35, 15))

        

        secondLabel.attributedText=attrSting

Use HexColor in Swift as a color


Add it in your class 

extension UIColor {

   convenience init(red: Int, green: Int, blue: Int) {

       assert(red >= 0 && red <= 255, “Invalid red component”)

       assert(green >= 0 && green <= 255, “Invalid green component”)

       assert(blue >= 0 && blue <= 255, “Invalid blue component”)

       self.init(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: 1.0)

   }

   convenience init(netHex:Int) {

       self.init(red:(netHex >> 16) & 0xff, green:(netHex >> 8) & 0xff, blue:netHex & 0xff)

   }

}

Use it where you need…….

var color = UIColor(red: 0xFF, blue: 0xFF, green: 0xFF)

var color2 = UIColor(netHex:0xFFFFFF)

Lineage Technology


Lineage technology is a Mobile application based software company established in 2012. we are the fastest growing company in lucknow . We offer mobile application ( iOS , Android , BlackBerry ) development with Quality in affordable cast . Our motive is to attain client faith on us. Customer satisfaction is most important for us. we develop application in 6 step

      Step 1:: Analyze requirement .

      Step 2:: Discuss with Customer with Suggestion

      Step 3:: Develop in milestone.

      Step 4:: Application Testing

      Step 5:: Submit to server

      Step 6:: Customer Feedback

Key Area :: 

  • iOS app development
  • iPhone app development
  • iPad app development
  • Android app development
  • BlackBerry app development
  • Website development

Fore more detail Contact us :: msy.shani@gmail.com

                           Contact No:: +919935523781

                            website     ::  http://www.lineagetechnology.com/

Fotor01015113226

AirDrop in iOS 7


AirDrop Overview

Before we step into the implementation, let’s have a quick look at AirDrop. It’s very simple to use AirDrop. Simply bring up Control Center and tap AirDrop to enable it. You can either select “Contact Only” or “Everyone” depending on whom you want to share the data with. If you choose the Contact Only option, your device will only discovered by people listed in your contacts. Obviously, your device can be discovered by anyone for the Everyone option.

Airdrop Overview

AirDrop uses Bluetooth to scan for nearby devices. When a connection is established via Bluetooth, it’ll create an ad-hoc Wi-Fi network to link the two devices together, allowing for faster data transmission. It doesn’t mean you need to connect the devices to a Wi-Fi network in order to use AirDrop. Your WiFi simply needs to be on for the data transfer.

Say you want to share a photo in the Photos app from one iPhone to another. Assuming you’ve enabled AirDrop on both devices, to share the photos with another device, tap the Share button (the one with an arrow pointing up) at the lower-left of the screen.

Airdrop Overview Receiving Side

In the AirDrop area, you should see the name of the devices that are eligible for sharing. AirDrop is not available when the screen is turned off. So make sure the device on the receiving side is switched on. You can then select the device to share the photo. On the other device, you’ll see a preview of the photo and a confirmation request. The recipient can accept or decline to receive the image. If you choose the accept option, the photo is then transferred and automatically saved in the camera roll.

AirDrop doesn’t just work with the Photos app. You can also find the share option in most of the built-in apps such as Contacts, iTunes, App Store, Safari, to name a few. If you’re new to AirDrop, you should now have a better idea.

Let’s move on and see how we can incorporate AirDrop feature in your app to share various types of data.

A Quick Look at UIActivityViewController

You may think it’ll take a lot of efforts to implement the AirDrop feature. Conversely, you just need a few lines of code to add AirDrop support. The UIActivityViewController class available in iOS 7 SDK makes it super easy to integrate the feature. The AirDrop has been built into the class.

The UIActivityViewController class is a standard view controller that provides several standard services, such as copying items to the clipboard, sharing content to social media sites, sending items via Messages, etc. In iOS 7 SDK, the class comes with the AirDrop feature built-in.

Say, you have an array of objects to share using AirDrop. All you need to do is to initiate a UIActivityViewController with the array of objects and present it on screen:

1
2
UIActivityViewController *controller = [[UIActivityViewController alloc] initWithActivityItems:objectsToShare applicationActivities:nil];
[self presentViewController:controller animated:YES completion:nil];

With just two lines of code, you can bring up the activity view with AirDrop option. Whenever there is a nearby device detected, the activity controller automatically shows the device and handles the data transfer if you choose to.

UIActivityViewController AirDrop

Optionally, you can exclude certain types of activities. Say, you can just display the AirDrop activity by excluding all other activities. Use the following code:

1
2
3
4
5
6
7
8
9
10
11
12
    UIActivityViewController *controller = [[UIActivityViewController alloc] initWithActivityItems:objectsToShare applicationActivities:nil];
    
    NSArray *excludedActivities = @[UIActivityTypePostToTwitter, UIActivityTypePostToFacebook,
                                    UIActivityTypePostToWeibo,
                                    UIActivityTypeMessage, UIActivityTypeMail,
                                    UIActivityTypePrint, UIActivityTypeCopyToPasteboard,
                                    UIActivityTypeAssignToContact, UIActivityTypeSaveToCameraRoll,
                                    UIActivityTypeAddToReadingList, UIActivityTypePostToFlickr,
                                    UIActivityTypePostToVimeo, UIActivityTypePostToTencentWeibo];
    controller.excludedActivityTypes = excludedActivities;
    
    [self presentViewController:controller animated:YES completion:nil];

The activity view controller now only shows the AirDrop option:

UIActivityViewController AirDrop Only

You can use UIActivityViewController to share different types of data including NSString, UIImage and NSURL. Not only you can use NSURL to share a link, it allows developers to transfer any types of files by using file URL.

On the receiving side, when the other device receives the data, it’ll automatically open an app based on the data type. Say, if an UIImage is transferred, the received image will be displayed in Photos app. When you transfer a PDF file, the other device will open it in Safari. If you just share a NSString object, the data will be presented in Notes app.

A Glance at the AirDrop Demo App

To give you a better idea about UIActivityViewController and AirDrop, we’ll build a AirDrop demo app. The app is very simple. When it is first launched, you’ll see a table view listing a few files including an image file, a PDF file and a text file. You can tap the file and view the content. In the content view, there is an action button on the top-right corner of screen. Tapping it will bring up the AirDrop option and you can share the image or document with nearby device.

AirDrop Demo App Workflow

You’re encouraged to build the demo app from scratch. But to save your time, you can download this project template to start with. When you open Xcode project, you should find the following Storyboard:

AirDrop Demo Storyboard

I have already implemented the ListTableViewController and DocumentViewController for you. If you compile and run the app, you’ll be presented with a list of files. When you tap any of the file, the image or document content will be displayed. But the share button is not yet implemented and that is what we’re going to talk about.

Adding AirDrop Feature

In the project template, the ListTableViewController is used to displayed the list of files in a table view, while the DocumentViewController presents the document content via a web view. The action button in the document view is associated with the share: method of the DocumentViewController class. Edit the method with the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
– (IBAction)share:(id)sender {
    NSURL *url = [self fileToURL:self.documentName];
    NSArray *objectsToShare = @[url];

    UIActivityViewController *controller = [[UIActivityViewController alloc] initWithActivityItems:objectsToShare applicationActivities:nil];
    
    // Exclude all activities except AirDrop.
    NSArray *excludedActivities = @[UIActivityTypePostToTwitter, UIActivityTypePostToFacebook,
                                    UIActivityTypePostToWeibo,
                                    UIActivityTypeMessage, UIActivityTypeMail,
                                    UIActivityTypePrint, UIActivityTypeCopyToPasteboard,
                                    UIActivityTypeAssignToContact, UIActivityTypeSaveToCameraRoll,
                                    UIActivityTypeAddToReadingList, UIActivityTypePostToFlickr,
                                    UIActivityTypePostToVimeo, UIActivityTypePostToTencentWeibo];
    controller.excludedActivityTypes = excludedActivities;
    
    // Present the controller
    [self presentViewController:controller animated:YES completion:nil];

}

If you’re not forgetful, the above code should be very familiar to you as we’ve discussed it at the very beginning. The above code simply creates a UIActivityViewController, excludes all activities except AirDrop and presents the controller as a modal view. The tricky part is how you define the objects to share. Here we turn the file to share into a NSURL object and pass the file URL as an array to AirDrop.

The first two lines of code are responsible for the file URL conversion. The documentName property stores the current file (e.g. ios-game-kit-sample.pdf) displaying in document view. We simply call up the fileToURL: method with the document name and it returns the corresponding file URL. The fileToURL: method is bundled in the project template and here is the code:

1
2
3
4
5
6
7
– (NSURL *) fileToURL:(NSString*)filename
{
    NSArray *fileComponents = [filename componentsSeparatedByString:@”.”];
    NSString *filePath = [[NSBundle mainBundle] pathForResource:[fileComponents objectAtIndex:0] ofType:[fileComponents objectAtIndex:1]];

    return [NSURL fileURLWithPath:filePath];
}

The code is very straightforward. For example, the ios-game-kit-sample.pdf will be transformed to file:///Users/simon/Library/Application%20Support/iPhone%20Simulator/7.0.3/Applications/A5321493-318A-4A3B-8B37-E56B8B4405FC/AirDropDemo.app/ios-game-kit-sample.pdf. The file URL varies depending on the device you’re running. But the URL should begin with the “file://” protocol. With the file URL object, we create the corresponding array and pass it to UIActivityViewController for AirDrop sharing.

Build and Run the AirDrop Demo

You’re done. That’s what you need to implement AirDrop sharing. Compile and run the app on a real iPhone. Yes, you need a real device to test AirDrop sharing. The sharing feature won’t work on the Simulator.

AirDrop Demo App Share PDF

Uniform Type Identifiers (UTIs)

When you share an image to another iOS device, the receiving side automatically opens Photos app and loads the image. If you transfer a PDF file, the receiving device may prompt you to pick an app for opening the file or open it directly in iBooks. How can iOS know which app to use for the type of data?

UTIs (short for Uniform Type Identifiers) is Apple’s answer to identify data handled within the system. In brief, a uniform type identifier is a unique identifier for a particular type of data or file. For instance, com.adobe.pdf represents a PDF document and public.png represents a PNG image. You can find the full list of registered UTIs here. Application that is capable of opening a specific type of file has registered to handle that UTI with the iOS. So whenever that type of file is opened, iOS hands off that file to the specific app.

The system allows multiple apps to register the same UTI. In this case, iOS will prompt user with the list of capable apps for opening the file. For example, when you share a PDF document, you may experience the following screen in the receiving device:

UTIs PDF Apps

Wrap Up

AirDrop is a very cool feature introduced in iOS 7. It offers a great way to share data between devices. Best of all, the built-in UIActivityViewController has made it easy for developers to add AirDrop support in their apps. As you can see from the demo app, it just needs a few lines of code to implement the feature. I highly recommend you to put this sharing feature in your app.

For your complete reference, you can download the full source code of the Xcode project from here.

As always, leave us comment and share your thought about the tutorial. We love to hear your feedback.

Sencha Navigation Tutorial


1. Main panel issues are resolved now.
2. There are multiple viewport files in the development version. Keep only one while creating production or testing build with Sencha. Because Sencha by default includes all the classes of same name in the build.

——————————————————————————————

Slide navigation in mobile apps is a huge hit now-a-days. Starting from major players like Facebook, Google+ and Youtube, I see most of the dashboard or navigation style apps are using it. And not only it is a new concept, you should find it pretty useful if you have already used it.

I was looking for a similar ready-made component with Sencha Touch and found a number of them (check other example links at the bottom of this post). While all these examples serve the same functionality, I found these are bit complex for a newbie to understand and implement freely in their apps. So, I came up with a dead simple Slide Navigation Menu mostly with CSS3 and a very small chunk of Javascript code. The same navigation can work as leftright and top menu. Follow me.

Lets first create a Sencha Touch project. I used the latest Sencha 2.2 and Sencha CMD 3.1. By default the generated app has a Main view which is the main container for all other panes. But in our case, I created a Viewport view which actually holds the Main and Navigation panels. This Navigation view is the collapsible panel which can be Ext.List or Ext.Dataview or any other view component.

Take a look at the Main and Navigation panels. Both are simple panels with least amount of items inside.

Sencha views

Navigation.js 

1234567891011121314151617
Ext.define(‘SlideNav.view.Navigation’, {
extend: ‘Ext.List’,
xtype: ‘navigation’,
requires: [‘Ext.data.Store’],
config: {
cls: ‘nav-list’,
itemTpl: ‘{title}’,
data: [{
title: ‘Item 1’
}, {
title: ‘Item 2’
}, {
title: ‘Item 3’
}
]
}
});
view rawgistfile1.js hosted with ❤ by GitHub

Main.js

Ext.define(‘SlideNav.view.Main’, {
extend: ‘Ext.TabPanel’,
xtype: ‘main’,
config: {
tabBarPosition: ‘bottom’,
items: [{
title: ‘Home’,
iconCls: ‘home’,
html: [‘This is a very simple example of Facebook style slide navigation. ‘,
‘The component is mostly developed with CSS3 animations and a small bit of javascript code. ‘,
‘Use it freely in your Sencha Touch application’
],
styleHtmlContent: true
}, {
xtype: ‘titlebar’,
title: ‘Slide Nav’,
docked: ‘top’,
items: [{
align: ‘left’,
name: ‘nav_btn’,
iconCls: ‘list’,
ui: ‘plain’
}
]
}
]
}
});
view rawgistfile1.js hosted with ❤ by GitHub

Portfolio_Page_1

A navigation menu at left is the most popular one – lets start with that. At first we will give the Viewport container a hbox layout. The navigation menu will have a width of 250px and instead of giving Main panel flex:1, we will make it 100% width. In css, we will give the navigation menu a position:absolute and left:0; Now, if Main view’s z-index is greater than Slide nav, it will hide the slidenav behind it (check the figure). One point to remember, if your main panel is having a transparent background, do not forget to add a background to it. Else, the nav menu will be seen behind it.

On clicking nav button, we just set  translateX:250px for Main menu, and the Nav menu is now revealed – hence we get what we want. The concept is that simple.

Same concept can be applied to both right and top navigation too. So, here is our Viewport container:

Viewport.js

Ext.define(‘SlideNav.view.Viewport’, {
extend: ‘Ext.Container’,
xtype: ‘app_viewport’,
requires: [
‘Ext.TitleBar’
],
config: {
fullscreen: true,
layout: ‘hbox’,
items: [{
xtype: ‘main’,
cls: ‘slide’,
// Needed to fit the whole content
width: ‘100%’
}, {
xtype: ‘navigation’,
width: 250
}
]
}
});
view rawgistfile1.js hosted with ❤ by GitHub

All the views are done. We now need to add a handler for the nav button inside the controller where we will add or remove the css classes. We gave the Main panel a class “.slide”, which we will add animations with “.in” and “.out” classes.

CSS for left navigation

/* For left side navigation */
.slide ,
.nav-list {
-webkit-animation-duration: .200s;
-webkit-transition-timing-function: cubic-bezier(0.275, 0.080, 0.425, 0.855);
}
.slide {
background: #f1f1f1;
z-index: 1;
}
/* Main view */
@-webkit-keyframes slideout {
from {
-webkit-transform: translateX(0px);
}
to {
-webkit-transform: translateX(250px);
};
}
@-webkit-keyframes slidein {
from {
-webkit-transform: translateX(250px);
}
to {
-webkit-transform: translateX(0px);
};
}
.slide.out {
-webkit-animation-name: slideout;
-webkit-transform: translateX(250px);
}
.slide.in {
-webkit-animation-name: slidein;
-webkit-transform: translateX(0px);
}
/* Main view ENDS */
/* Nav menu view */
.nav-list {
height: 100%;
background: #222;
-webkit-box-shadow: inset -15px 0px 30px -15px rgba(0,0,0,1);
position: absolute !important;
left: 0;
}
.nav-list .x-list-item {
color: rgb(223, 223, 223);
border-bottom: 1px solid #000 !important;
border-top: 1px solid #333 !important;
font-size: 15px;
}
/* Nav menu view ENDS */
view rawgistfile1.css hosted with ❤ by GitHub

We add/remove these css classes in controller’s toggleNav() method. The controller at this point has only this method. You can use this method inside any of the views also. However, because we maintain the functional part in a controller, I preferred to put it there.

Sencha Controller

App.js

Ext.define(‘SlideNav.controller.App’, {
extend: ‘Ext.app.Controller’,
config: {
refs: {
main: ‘main’,
navigation: ‘navigation’,
navBtn: ‘button[name=”nav_btn”]’
},
control: {
navBtn: {
tap: ‘toggleNav’
},
navigation: {
itemtap: function (list, index, target, record) {
this.toggleNav();
}
}
}
},
/**
* Toggle the slide navogation view
*/
toggleNav: function () {
var me = this,
mainEl = me.getMain().element;
if (mainEl.hasCls(‘out’)) {
mainEl.removeCls(‘out’).addCls(‘in’);
} else {
mainEl.removeCls(‘in’).addCls(‘out’);
}
}
});
view rawgistfile1.js hosted with ❤ by GitHub

all-screens
And here is how the left navigation look. We can move the menu to right and top too. For right nav, we just need to change nav button align property to “right”. In css, we now have to make the initial position of Navigation view at “right 0;” which will fix it to extreme right of window. For main panel, we have the positions just opposite to what we have earlier. Find the code for this below.

Css for right navigation:

/* For right side navigation */
.slide ,
.nav-list {
-webkit-animation-duration: .200s;
-webkit-transition-timing-function: cubic-bezier(0.275, 0.080, 0.425, 0.855);
}
.slide {
background: #f1f1f1;
z-index: 1;
}
/* Main view */
.slide.out {
-webkit-animation-name: slideout;
-webkit-transform: translateX(-250px);
}
.slide.in {
-webkit-animation-name: slidein;
-webkit-transform: translateX(0px);
}
@-webkit-keyframes slideout {
from {
-webkit-transform: translateX(0px);
}
to {
-webkit-transform: translateX(-250px);
};
}
@-webkit-keyframes slidein {
from {
-webkit-transform: translateX(-250px);
}
to {
-webkit-transform: translateX(0px);
};
}
/* Main view ENDS */
/* Nav menu view */
.nav-list {
position: absolute !important;
height: 100%;
right: 0;
-webkit-box-shadow: inset 15px 0px 30px -15px rgba(0,0,0,1);
background: #222;
}
.nav-list .x-list-item {
color: rgb(223, 223, 223);
border-bottom: 1px solid #000 !important;
border-top: 1px solid #333 !important;
font-size: 15px;
}
/* Nav menu view ENDS */
/* For right side navigation ENDS */
view rawgistfile1.css hosted with ❤ by GitHub

For top navigation, we need to do following changes at Viewport.js:

  • Make Viewport layout “vbox
  • Change navigation panel’s width:250 to height:250
  • Change main panel’s width:100% to height:100%, and add width:100% to Main panel’s style

Css for top navigation:

/* For top navigation */
.slide,
.nav-list {
-webkit-animation-duration: .200s;
-webkit-transition-timing-function: cubic-bezier(0.275, 0.080, 0.425, 0.855);
}
.slide {
background: #f1f1f1;
z-index: 1;
}
/* Main view */
.slide.out {
-webkit-animation-name: mainSlideOut;
-webkit-transform: translateY(250px);
}
.slide.in {
-webkit-animation-name: mainSlideIn;
-webkit-transform: translateY(0px);
}
@-webkit-keyframes mainSlideOut {
from {
-webkit-transform: translateY(0px);
}
to {
-webkit-transform: translateY(250px);
};
}
@-webkit-keyframes mainSlideIn {
from {
-webkit-transform: translateY(250px);
}
to {
-webkit-transform: translateY(0px);
};
}
/* Main view ENDS */
/* Nav menu view */
.nav-list {
width: 100%;
background: #222;
position: absolute;
top: 0;
-webkit-box-shadow: inset 0px -15px 30px -15px rgba(0,0,0,1);
}
.nav-list .x-list-item {
color: rgb(223, 223, 223);
border-bottom: 1px solid #000 !important;
border-top: 1px solid #333 !important;
font-size: 15px;
}
/* Nav menu view ENDS */
/* For top navigation ENDS */
view rawgistfile1.css hosted with ❤ by GitHub

This is it. Hope you can use this functionality in your Sencha Touch apps. If you get any issue implementing this, do not hesitate to add a comment here. Whole source code of the extension is available for download at github.

Other resources:

  1. Slide navigation by Weston Nielson
  2. Draggable slide navigation

– See more at: http://innofied.com/simplest-slide-navigation-with-sencha-touch-2-2/#sthash.BrRPz1hJ.dpuf

Sencha Tutorial Part -4


In this article, we are going to complete the following tasks:

  • Add the delete note feature to the Note Editor View.
  • Implement the navigation back to the Notes List Container View when the Home button in the Note Editor View is tapped.
  • Modify the Notes List Container View so it renders the notes grouped by date.

Deleting Records from a Sencha Touch Data Store

The Delete Note workflow begins when a user taps the Delete Button on the Note Editor View:

This Button needs a tap handler, which we will add to the NoteEditor Class, in the NoteEditor.js file:

1 var deleteButton = {
2     xtype: "button",
3     iconCls: "trash",
4     iconMask: true,
5     handler: this.onDeleteButtonTap,
6     scope: this
7 };

As we did with the Save Button, we are using the handler and scope configs to map the function that will handle tap events on the Button, as well as to pass the View as the scope for the handler function.

Of course, we need to add the onDeleteButtonTap() function to the NoteEditor Class:

1 onDeleteButtonTap: function () {
2     console.log("deleteNoteCommand");
3     this.fireEvent("deleteNoteCommand"this);
4 }

This is the same pattern we’ve used to emit events from the Views throughout the application. We capture the event triggered from a control in the View, and create a View event that is in turn captured by the Controller.

Over in the Notes Controller, we are going to map a handler function to the deleteNoteCommand event fired by the Note Editor View. We will do this in the control config, under the noteEditor key:

1 control: {
2     notesListContainer: {
3         // The commands fired by the notes list container.
4         newNoteCommand: "onNewNoteCommand",
5         editNoteCommand: "onEditNoteCommand"
6     },
7     noteEditor: {
8         // The commands fired by the note editor.
9         saveNoteCommand: "onSaveNoteCommand",
10         deleteNoteCommand: "onDeleteNoteCommand"
11     }
12 }

Now we can implement the onDeleteNoteCommand() function like so:

1 onDeleteNoteCommand: function () {
2  
3     console.log("onDeleteNoteCommand");
4  
5     var noteEditor = this.getNoteEditor();
6     var currentNote = noteEditor.getRecord();
7     var notesStore = Ext.getStore("Notes");
8  
9     notesStore.remove(currentNote);
10     notesStore.sync();
11  
12     this.activateNotesList();
13 }

Here, we acquire references to the Note Editor, the note loaded into the editor, and the Notes Store. Remember that the getNoteEditor() function is a routine the framework created for us when we declared the editor in the refs config:

1 refs: {
2     // We're going to lookup our views by xtype.
3     notesListContainer: "noteslistcontainer",
4     noteEditor: "noteeditor"
5 }

Our next steps in onDeleteNoteCommand() are to remove the current note from the store and make the changes permanent:

1 notesStore.remove(currentNote);
2 notesStore.sync();

Finally, we activate the Notes List Container View:

1 this.activateNotesList();

Another quick check on the emulator should confirm that at this point we are able to delete notes.

Navigating Back To the Main View

In order to navigate from the Note Editor View back to the Notes List Container View without making any changes to a note, we need to add a tap handler for the Home Button in the Note Editor Class:

1 var backButton = {
2     xtype: "button",
3     ui: "back",
4     text: "Home",
5     handler: this.onBackButtonTap,
6     scope: this
7 };

We will define the onBackButtonTap() function as follows:

1 onBackButtonTap: function () {
2     console.log("backToHomeCommand");
3     this.fireEvent("backToHomeCommand"this);
4 }

In the Controller, we will map this event to the onBackToHomeCommand() handler function:

1 control: {
2     notesListContainer: {
3         // The commands fired by the notes list container.
4         newNoteCommand: "onNewNoteCommand",
5         editNoteCommand: "onEditNoteCommand"
6     },
7     noteEditor: {
8         // The commands fired by the note editor.
9         saveNoteCommand: "onSaveNoteCommand",
10         deleteNoteCommand: "onDeleteNoteCommand",
11         backToHomeCommand: "onBackToHomeCommand"
12     }
13 }

And the onBackToHomeCommand() function will look like this:

1 onBackToHomeCommand: function () {
2  
3 console.log("onBackToHomeCommand");
4 this.activateNotesList();
5 }

At this point, we can use the emulator to check that a tap on the Home Button activates the Notes List Container View.

Setting Up Grouping in a Sencha Touch List

One important usability detail we are missing is the ability to render the cached notes grouped by date. It’s amazing how easily we can accomplish this in Sencha Touch. Let’s first define a grouper config for the Notes Store:

1 Ext.define("NotesApp.store.Notes", {
2     extend: "Ext.data.Store",
3     requires:"Ext.data.proxy.LocalStorage",
4     config: {
5         model: "NotesApp.model.Note",
6         proxy: {
7             type: 'localstorage',
8             id: 'notes-app-store'
9         },
10         sorters: [{ property: 'dateCreated', direction:'DESC'}],
11         grouper: {
12             sortProperty: "dateCreated",
13             direction: "DESC",
14             groupFn: function (record) {
15  
16                 if (record && record.data.dateCreated) {
17                     returnrecord.data.dateCreated.toDateString();
18                 else {
19                     return '';
20                 }
21             }
22         }
23     }
24 });

As of this writing, groupers are not explained very well in Sencha Touch’s documentation. However, it is not difficult to make sense of this config’s properties. The groupFn config is the function used to generate the label for the group. In our case, the label will be the date the notes were taken:

The sortProperty config defines the value that will be used to sort the groups. If you do not include this config, the fields will be sorted based on the value returned by the function defined with the groupFn config. The direction config specifies the direction to sort the groups.

The last change needed to implement grouping consists of adding the grouped config to the NotesList Class:

1 Ext.define("NotesApp.view.NotesList", {
2     extend: "Ext.dataview.List",
3     alias: "widget.noteslist",
4     config:{
5         scrollable:'vertical'
6     },
7     config: {
8         loadingText: "Loading Notes...",
9         emptyText: "</pre>
10 <div class="\&quot;notes-list-empty-text\&quot;">No notes found.</div>
11 <pre>
12 ",
13         onItemDisclosure: true,
14         grouped: true,
15         itemTpl: "</pre>
16 <div class="\&quot;list-item-title\&quot;">{title}</div>
17 <div class="\&quot;list-item-narrative\&quot;">{narrative}</div>
18 <pre>
19 "
20     }
21 });

When we set the grouped config to true, the List will use the groups defined in its store, through the grouper config, to render its items appropriately.

Let’s check how the list looks after we turned on grouping. Start the emulator and confirm that the notes list has date groups similar to the following screenshot:

 

Sencha Tutorial part -3


we are going to create the Note Editor View. This is the View that will allow our users to create, edit and delete notes.

When we finish this article, our application will have the ability to create notes, and edit existing notes. Let’s get started building the Note Editor View.

 

Creating a Form Panel in Sencha Touch

We will place the View’s source code in the  NoteEditor.js file, which we will create in the view directory:

In the file, we will define an empty NoteEditor Class like so:

1 Ext.define("NotesApp.view.NoteEditor", {
2     extend: "Ext.form.Panel",
3     requires: "Ext.form.FieldSet",
4     alias: "widget.noteeditor",
5     config:{
6         scrollable:'vertical'
7     },
8     initialize: function () {
9  
10         this.callParent(arguments);
11  
12     }
13 });

The Note Editor is an extension of the Ext.form.Panel Class. As we will use a FieldSet Class instance in the View, we are asking the loader to download its source by using the requires config. We are also using the scrollable config to allow the contents of the Panel to scroll vertically. This stops the form from being cropped when its height is larger than the screen height of the device.

As we did with the Notes ListContainer Class, we are going to use the initialize() function to define the Note Editor’s components:

1 Ext.define("NotesApp.view.NoteEditor", {
2     extend: "Ext.form.Panel",
3     requires: "Ext.form.FieldSet",
4     alias: "widget.noteeditor",
5     config:{
6         scrollable:'vertical'
7     },
8     initialize: function () {
9  
10         this.callParent(arguments);
11  
12         var backButton = {
13             xtype: "button",
14             ui: "back",
15             text: "Home"
16         };
17  
18         var saveButton = {
19             xtype: "button",
20             ui: "action",
21             text: "Save"
22         };
23  
24         var topToolbar = {
25             xtype: "toolbar",
26             docked: "top",
27             title: "Edit Note",
28             items: [
29                 backButton,
30                 { xtype: "spacer" },
31                 saveButton
32             ]
33         };
34  
35         var deleteButton = {
36             xtype: "button",
37             iconCls: "trash",
38             iconMask: true,
39             scope: this
40         };
41  
42         var bottomToolbar = {
43             xtype: "toolbar",
44             docked: "bottom",
45             items: [
46                 deleteButton
47             ]
48         };
49  
50         var noteTitleEditor = {
51             xtype: 'textfield',
52             name: 'title',
53             label: 'Title',
54             required: true
55         };
56  
57         var noteNarrativeEditor = {
58             xtype: 'textareafield',
59             name: 'narrative',
60             label: 'Narrative'
61         };
62  
63         this.add([
64             topToolbar,
65             { xtype: "fieldset",
66                 items: [noteTitleEditor, noteNarrativeEditor]
67             },
68             bottomToolbar
69         ]);
70     }
71  
72 });

Within initialize(), after invoking callParent(), we proceed to define the top Toolbar, along with its two buttons, the Home Button and the Save Button. We then define the bottom Toolbar and the Delete Button.

The noteTitleEditor and noteNarrativeEditor are the fields we will use to edit the note’s title and narrative. They are instances of the Ext.form.Text and Ext.form.TextArea Classes.

Once all the View’s components are defined, we proceed to add them to the View. This is where we also add an Ext.form.FieldSet instance to enhance the appearance of the form. The title and narrative editors will render within the FieldSet:

1 this.add([
2     topToolbar,
3     { xtype: "fieldset",
4         items: [noteTitleEditor, noteNarrativeEditor]
5     },
6     bottomToolbar
7 ]);

Rendering a View In Sencha Touch

Before we start developing the features of the Note Editor, we are going to work on the code that will render this View when the New Button in the Notes List Container is tapped.

In the previous chapter of this tutorial we created the tap handler for the New Button, which fires the newNoteCommand event. We also added the onNewNoteCommand() listener to the Notes Controller. We will use this function to activate the Note Editor View.

To activate the Note Editor View in the Controller, we first need to acquire a reference to the View. We will use the noteeditor ref for this purpose:

1 Ext.define("NotesApp.controller.Notes", {
2  
3     extend: "Ext.app.Controller",
4     config: {
5         refs: {
6             // We're going to lookup our views by xtype.
7             notesListContainer: "noteslistcontainer",
8             noteEditor: "noteeditor"
9         },
10  
11    // Remainder of the controller’s code omitted for brevity.
12  
13 });

Remember, this ref automatically creates a getNoteEditor() function in the controller, which we can use to refer to the NoteEditor instance and make it the active View in the application.

Next, we need to modify the onNewNoteCommand() function:

1 onNewNoteCommand: function () {
2  
3     console.log("onNewNoteCommand");
4  
5     var now = new Date();
6     var noteId = (now.getTime()).toString() + (this.getRandomInt(0, 100)).toString();
7  
8     var newNote = Ext.create("NotesApp.model.Note", {
9         id: noteId,
10         dateCreated: now,
11         title: "",
12         narrative: ""
13     });
14  
15     this.activateNoteEditor(newNote);
16 }

Here things get more interesting. We are creating a new note, and passing it to the activateNoteEditor() function.

We are going to use the getRamdomInt() helper function to generate the unique id for a note:

1 getRandomInt: function (min, max) {
2     return Math.floor(Math.random() * (max - min + 1)) + min;
3 }

The activateNoteEditor() function will load the new note into the Note Editor, and make the Editor active:

1 activateNoteEditor: function (record) {
2  
3     var noteEditor = this.getNoteEditor();
4     noteEditor.setRecord(record); // load() is deprecated.
5     Ext.Viewport.animateActiveItem(noteEditor,this.slideLeftTransition);
6 }

Here we are taking advantage of the Ext.form.Panel’s setRecord() function, which allows us to load the values of a model instance into the form’s fields whose names match those of the model’s fields.

This function also uses the slideLeftTransition variable, which we need to define like so:

1 slideLeftTransition: { type: 'slide', direction: 'left' }

The transition will bring the Note Editor into view with a slide motion from the right to the left.

This is all the Controller needs to do in order to activate the Note Editor View when the New Button is tapped. However, we need to make the application aware of the NoteEditor Class.

Making The Application Aware Of A View

In the app.js file, we will add the new View to the views config:

1 views: ["NotesList""NotesListContainer""NoteEditor"]

We are also going to instantiate the View in the Application’s launch function:

1 Ext.application({
2     name: "NotesApp",
3  
4     models: ["Note"],
5     stores: ["Notes"],
6     controllers: ["Notes"],
7     views: ["NotesList""NotesListContainer","NoteEditor"],
8  
9     launch: function () {
10  
11         var notesListContainer = {
12             xtype: "noteslistcontainer"
13         };
14         var noteEditor = {
15             xtype: "noteeditor"
16         };
17  
18         Ext.Viewport.add([notesListContainer,noteEditor]);
19     }
20 });

Ready to check it out? Well, start the emulator. :-)

A tap on the New Button should render the Note Editor View:

Editing A Record Rendered In A Sencha Touch List

We also want to activate the Note Editor View when a note’s disclosure button is tapped:

In order to accomplish this, we need to revisit the onEditNoteCommand() function in the Controller, and add the code that will activate the NoteEditor:

1 onEditNoteCommand: function (list, record) {
2  
3     console.log("onEditNoteCommand");
4  
5     this.activateNoteEditor(record);
6 }

Incredibly simple thanks to the fact that the disclose event supplies the selected Note model instance to the handler through the record argument. All we need to do here is call activateNoteEditor(), which we created a few minutes ago.

Back in the emulator, we should be able to see the Note Editor render the selected note:

Now it is time to save the note. We need to take care of a few things in order for this to happen.

Using A LocalStorage Proxy In Sencha Touch

First, we need to stop using hard-coded notes as the data for the Notes store.

Up to this point, we have been using the data config to define a few hard-coded records in the store:

1 Ext.define("NotesApp.store.Notes", {
2     extend: "Ext.data.Store",
3     config: {
4         model: "NotesApp.model.Note",
5         data: [
6             { title: "Note 1", narrative: "narrative 1" },
7             { title: "Note 2", narrative: "narrative 2" },
8             { title: "Note 3", narrative: "narrative 3" },
9             { title: "Note 4", narrative: "narrative 4" },
10             { title: "Note 5", narrative: "narrative 5" },
11             { title: "Note 6", narrative: "narrative 6" }
12         ],
13         sorters: [{ property: 'dateCreated', direction:'DESC'}]
14     }
15 });

As we intend to cache notes on the device that runs the app, we are going to discontinue the data config in the store, and define a LocalStorage proxy as follows:

1 Ext.define("NotesApp.store.Notes", {
2     extend: "Ext.data.Store",
3     requires:"Ext.data.proxy.LocalStorage",
4     config: {
5         model: "NotesApp.model.Note",
6         proxy: {
7             type: 'localstorage',
8             id: 'notes-app-store'
9         },
10         sorters: [{ property: 'dateCreated', direction:'DESC'}]
11     }
12 });

LocalStorageProxy uses the HTML5 localStorage API to save Model data on the client browser. This proxy is ideal for storing multiple records of similar data. And it requires that we provide the id config, which is the key that will identify our data in the localStorage object.

Now the Notes store has the ability to save and read data from localStorage, and we can go back to the Views and Controller to take care of the functions that will save the data.

Adding Event Listeners To A Sencha Touch Controller

In the NoteEditor Class, let’s modify the saveButton declaration in the initialize() function like so:

1 var saveButton = {
2     xtype: "button",
3     ui: "action",
4     text: "Save",
5     handler: this.onSaveButtonTap,
6     scope: this
7 };

The handler config defines the handler function that will run when a user taps the button. To make sure we run the handler in the scope of the NoteEditor, we set the scope config to this.

Now we can define onSaveButtonTap() as follows:

1 onSaveButtonTap: function () {
2     console.log("saveNoteCommand");
3     this.fireEvent("saveNoteCommand"this);
4 }

No surprises here, right? As we’ve done with other handlers, we are capturing the Button tap event within the View and defining a View event, saveNoteCommand.

As we already know, the fact that the NoteEditor View broadcasts the saveNoteCommand event is not enough for the Controller to be able to listen to it. We need to tell the Controller where this event is coming from, which we did when we added the noteEditor entry in the refs config of the Controller:

1 refs: {
2     // We're going to lookup our views by xtype.
3     notesListContainer: "noteslistcontainer",
4     noteEditor: "noteeditor"
5 }

We will use the Controller’s control config to map the saveNoteCommand event to a handler function. Therefore, need an entry for the Note Editor in the control config:

1 control: {
2     notesListContainer: {
3         // The commands fired by the notes list container.
4         newNoteCommand: "onNewNoteCommand",
5         editNoteCommand: "onEditNoteCommand"
6     },
7     noteEditor: {
8         // The commands fired by the note editor.
9         saveNoteCommand: "onSaveNoteCommand"
10     }
11 }

Finally, we will define the onSaveNoteCommand() function like so:

1 onSaveNoteCommand: function () {
2  
3     console.log("onSaveNoteCommand");
4  
5     var noteEditor = this.getNoteEditor();
6  
7     var currentNote = noteEditor.getRecord();
8     var newValues = noteEditor.getValues();
9  
10     // Update the current note's fields with form values.
11     currentNote.set("title", newValues.title);
12     currentNote.set("narrative", newValues.narrative);
13  
14     var errors = currentNote.validate();
15  
16     if (!errors.isValid()) {
17         Ext.Msg.alert('Wait!', errors.getByField("title")[0].getMessage(), Ext.emptyFn);
18         currentNote.reject();
19         return;
20     }
21  
22     var notesStore = Ext.getStore("Notes");
23  
24     if (null == notesStore.findRecord('id', currentNote.data.id)) {
25         notesStore.add(currentNote);
26     }
27  
28     notesStore.sync();
29  
30     notesStore.sort([{ property: 'dateCreated', direction:'DESC'}]);
31  
32     this.activateNotesList();
33 }

We begin onSaveNoteCommand() acquiring references to the note being edited and the values in the form’s fields:

1 var noteEditor = this.getNoteEditor();
2 var currentNote = noteEditor.getRecord();
3 var newValues = noteEditor.getValues();

Then, we transfer the new values to the loaded note:

1 currentNote.set("title", newValues.title);
2 currentNote.set("narrative", newValues.narrative);

Model Validation In Sencha Touch

Next comes an important part, which is validation. To validate the new values we loaded into the model instance, we first call the model’s validate() function, and then call the isValid() function on the errors object returned by validate():

1 var errors = currentNote.validate();
2  
3 if (!errors.isValid()) {
4     Ext.Msg.alert('Wait!', errors.getByField("title")[0].getMessage(), Ext.emptyFn);
5     currentNote.reject();
6     return;
7 }

The Ext.data.Model’s validate() function iterates over the validations defined for the model, and returns an Ext.data.Errors instance containing Ext.data.Error instances for each model field that is invalid.

In our case, the only field with an attached validator is the note’s title. When we find that the model is invalid, we first display an alert using the validator’s configured message, and then call the model’s reject() function. This function reverts the modified fields back to their original values before we exit the onSaveNoteCommand() handler.

Saving Data Using LocalStorageProxy

In onSaveNoteCommand(), after confirming that the modified note is valid, we move on to save it on the device:

1 var notesStore = Ext.getStore("Notes");
2  
3 if (null == notesStore.findRecord('id', currentNote.data.id)) {
4     notesStore.add(currentNote);
5 }
6  
7 notesStore.sync();

As this routine works for new or edited notes, we need to find out if the note is new by searching the store using its findRecrod() function. If the note is new, we add it to the store.

The store’s sync() function asks the store’s proxy to process all the changes, effectively saving new or edited notes, and removing deleting notes from localStorage.

After the store’s records have been updated, we sort them by date:

1 notesStore.sort([{ property: 'dateCreated', direction:'DESC'}]);

Returning To The Main View

Our last step in onSaveNoteCommand() consists of invoking the activateNotesList() function. This is a helper function, similar to activateNoteEditor(), that will make the app’s main View active:

1 activateNotesList: function () {
2     Ext.Viewport.animateActiveItem(this.getNotesListContainer(),this.slideRightTransition);
3 }

In this case we are using a right-slide transition, which we will define like so:

1 slideRightTransition: { type: 'slide', direction: 'right' }

OK. What do you think about doing another quick check? This time we should be able to save a note:

Sencha Tutorial Part 2


we will continue building a small application that allows people to save notes on the device running the app.

So far, we have been working on the View that renders the list of notes cached on the device:

 

While building this View, we defined the NotesListContainer Class. We are going to start this article with a modification to this Class. This modification will promote encapsulation, and make the app easier to change and maintain.

We had previously defined the NotesListContainer Class as follows:

1 Ext.define("NotesApp.view.NotesListContainer", {
2     extend: "Ext.Container",
3     config: {
4         items: [{
5             xtype: "toolbar",
6             docked: "top",
7             title: "My Notes",
8             items: [{
9                 xtype: "spacer"
10             }, {
11                 xtype: "button",
12                 text: "New",
13                 ui: "action",
14                 id:"new-note-btn"
15             }]
16         }]
17     }
18 });

The changes we will make to this View consist of using the Class’s initialize() function to define its components. We will begin creating a new Class definition like so:

1 Ext.define("NotesApp.view.NotesListContainer", {
2     extend: "Ext.Container",
3     alias: "widget.noteslistcontainer",
4  
5     initialize: function () {
6  
7         this.callParent(arguments);
8  
9     }
10 });

Notice how we are using the alias config. This config is very helpful, as it effectively defines an xtype for our Class. Thanks to the alias, we can now refer to the NotesListContainer Class with the xtype=”noteslistcontainer” config. We will use this construct to instantiate the Class later in the article,

In Sencha Touch 2, every Class has an initialize() function. Initialize() can be used to perform logic right after the Class is instantiated, it replaces the initComponent() function that exists in Sencha Touch 1, and we can use it to add the toolbar with the New button:

1 Ext.define("NotesApp.view.NotesListContainer", {
2     extend: "Ext.Container",
3     alias: "widget.noteslistcontainer",
4  
5     initialize: function () {
6  
7         this.callParent(arguments);
8  
9         var newButton = {
10             xtype: "button",
11             text: 'New',
12             ui: 'action',
13             handler: this.onNewButtonTap,
14             scope: this
15         };
16  
17         var topToolbar = {
18             xtype: "toolbar",
19             title: 'My Notes',
20             docked: "top",
21             items: [
22                 { xtype: 'spacer' },
23                 newButton
24             ]
25         };
26  
27         this.add([topToolbar]);
28     },
29     onNewButtonTap: function () {
30         console.log("newNoteCommand");
31         this.fireEvent("newNoteCommand"this);
32     },
33     config: {
34         layout: {
35             type: 'fit'
36         }
37     }
38 });

In the initialize() function, after invoking callParent(), we define the New button variable, along with the Toolbar. As you already know from the previous chapter, the Toolbar’s items are the spacer and the Button.

Our last step within initialize() consists of adding the Toolbar to the View’s items via a call to the add() function.

If you go back to the newButton definition, you will notice that we’ve added a tap handler using the handler config:

1 var newButton = {
2     xtype: "button",
3     text: 'New',
4     ui: 'action',
5     handler: this.onNewButtonTap,
6     scope: this
7 };

This function will capture tap events on the button, and transform them into an event that is more specific and descriptive of the application’s business logic. We will call this event newNoteCommand. Here’s the handler’s implementation:

1 onNewButtonTap: function () {
2     console.log("newNoteCommand");
3     this.fireEvent("newNoteCommand"this);
4 }

This is one of the important changes we are making to the NotesListContainer Class. In the first article of the series, the tap event on the button was captured by the Controller:

1 Ext.define("NotesApp.controller.Notes", {
2     extend: "Ext.app.Controller",
3     config: {
4         refs: {
5             newNoteBtn: "#new-note-btn"
6         },
7         control: {
8             newNoteBtn: {
9                 tap: "onNewNote"
10             }
11         }
12     },
13     onNewNote: function () {
14         console.log("onNewNote");
15     }
16  
17     // Rest of the controller's code omitted for brevity.
18 });

Now, we’re capturing the event within the View, and broadcasting a new event, newNoteCommand, which will be captured by the Controller:

1 onNewButtonTap: function () {
2     console.log("newNoteCommand");
3     this.fireEvent("newNoteCommand"this);
4 }

Although both approaches are valid, there are important benefits derived from the second approach:

  • The View’s interface is cleaner. It now fires events that are more in line with the business logic of the application.
  • The View is easier to modify and maintain, as the Controller does not need intimate knowledge of the View’s inner workings.

As long as the View’s public events remain the same, the Controller will be able to work with the View. For example, we can change the elements used to trigger the creation of a new note in the View without affecting the Controller. The Controller only needs to listen to the newNoteCommand event fired from the view.

The next step of this refactoring will take place in the app.js file, where we will modify the application() function so we create our NoteListContainer instance using the Class’s alias:

1 Ext.application({
2     name: "NotesApp",
3  
4     controllers: ["Notes"],
5     views: ["NotesListContainer"],
6  
7     launch: function () {
8  
9         var notesListContainer = {
10             xtype: "noteslistcontainer"
11         };
12  
13         Ext.Viewport.add(notesListContainer);
14     }
15 });

And finally, we will switch over to the controller and modify the refs section just so we lookup our ref by xtype instead of by id:

1 Ext.define("NotesApp.controller.Notes", {
2  
3     extend: "Ext.app.Controller",
4     config: {
5         refs: {
6             // We're going to lookup our views by xtype.
7             notesListContainer: "noteslistcontainer"
8         },
9         control: {
10             notesListContainer: {
11                 // The commands fired by the notes list container.
12                 newNoteCommand: "onNewNoteCommand",
13                 editNoteCommand: "onEditNoteCommand"
14             }
15         }
16     },
17  
18     // Commands.
19     onNewNoteCommand: function () {
20  
21         console.log("onNewNoteCommand");
22     },
23     onEditNoteCommand: function (list, record) {
24  
25         console.log("onEditNoteCommand");
26     },
27     // Base Class functions.
28     launch: function () {
29         this.callParent(arguments);
30         console.log("launch");
31     },
32     init: function () {
33         this.callParent(arguments);
34         console.log("init");
35     }
36 });

Notice that we also added the onEditNoteCommand event and editNoteCommand handler to the controller. We will define the onEditNoteCommand in the next section of the tutorial, when we create the Notes List View.

After these changes, we can open the index.html page in our favorite WebKit browser, to confirm that everything is working as expected. Tapping the New button should produce the console message we added to the onNewButtonTap function:

Creating The Notes List View

The Notes List View is the component that will render the cached notes. Its file is NotesList.js, which we will place in the view folder. To create this component, we will extend the Sencha Touch’s Ext.dataview.List Class:

1 Ext.define("NotesApp.view.NotesList", {
2     extend: "Ext.dataview.List",
3     alias: "widget.noteslist",
4     config: {
5         loadingText: "Loading Notes...",
6         emptyText: '</pre>
7 <div class="notes-list-empty-text">No notes found.</div>
8 <pre>',
9         onItemDisclosure: true,
10         itemTpl: '</pre>
11 <div class="list-item-title">{title}</div>
12 <div class="list-item-narrative">{narrative}</div>
13 <pre>',
14     }
15 });

In this definition, we are setting the onItemDisclosure config to true, which means that we want the list to display a disclosure button next to each item:

A tap on the disclosure button will trigger the Note editing feature of the application. We will create the disclosure handler function in a few minutes.

In the NotesList Class, pay attention to the different CSS classes we use in the itemTpl and emptyText configs. They will allow us to nicely format both the list items, and the message the list will show when there are no items to display.

Before creating these styles we need to create the app.css file. We will place the file in the resources/css directory:

Here are the styles we need:

1 /* Increase height of list item so title and narrative lines fit */
2 .x-list .x-list-item .x-list-item-label
3 {
4      min-height3.5em!important;
5 }
6 /* Move up the disclosure button to account for the list item height increase */
7 .x-list .x-list-disclosure {
8 positionabsolute;
9 bottom0.85em;
10 right0.44em;
11 }
12 .list-item-title
13 {
14     float:left;
15     width:100%;
16     font-size:90%;
17     white-spacenowrap;
18     overflowhidden;
19     text-overflow: ellipsis;
20     padding-right:25px;
21     line-height:150%;
22 }
23 .list-item-narrative
24 {
25     float:left;
26     width:95%;
27     color:#666666;
28     font-size:80%;
29     white-spacenowrap;
30     overflowhidden;
31     text-overflow: ellipsis;
32     padding-right:25px;
33 }
34 .x-item-selected .list-item-title
35 {
36     color:#ffffff;
37 }
38 .x-item-selected .list-item-narrative
39 {
40     color:#ffffff;
41 }
42 .notes-list-empty-text
43 {
44     padding:10px;
45 }

In order to render the NotesList instance, we first need to add the Class name to the views config of the application:

1 views: ["NotesList""NotesListContainer"]

Then, we need to add it to the NotesListContainer Class. Back in the NotesListContainer.js file, we will add the notesList variable to the initialize() function:

1 Ext.define("NotesApp.view.NotesListContainer", {
2     extend: "Ext.Container",
3     alias: "widget.noteslistcontainer",
4  
5     initialize: function () {
6  
7         this.callParent(arguments);
8  
9         var newButton = {
10             xtype: "button",
11             text: 'New',
12             ui: 'action',
13             handler: this.onNewButtonTap,
14             scope: this
15         };
16  
17         var topToolbar = {
18             xtype: "toolbar",
19             title: 'My Notes',
20             docked: "top",
21             items: [
22                 { xtype: 'spacer' },
23                 newButton
24             ]
25         };
26  
27         var notesList = {
28             xtype: "noteslist",
29             listeners: {
30                 disclose: { fn: this.onNotesListDisclose, scope: this }
31             }
32         };
33  
34         this.add([topToolbar, notesList]);
35     },
36     onNewButtonTap: function () {
37         console.log("newNoteCommand");
38         this.fireEvent("newNoteCommand"this);
39     },
40     config: {
41         layout: {
42             type: 'fit'
43         }
44     }
45 });

Notice how we are setting a listener for the disclose event of the list:

1 var notesList = {
2     xtype: "noteslist",
3     listeners: {
4         disclose: { fn: this.onNotesListDisclose, scope:this }
5     }
6 };

Now we can define the onNotesListDisclose() function as follows:

1 onNotesListDisclose: function (list, record, target, index, evt, options) {
2     console.log("editNoteCommand");
3     this.fireEvent('editNoteCommand'this, record);
4 }

Here we are taking the approach we followed earlier with the New button. Instead of having the Controller listen to the disclose event of the List, we are hiding this event, and creating the editNoteCommand event, which we will expose to the Controller. This is another step towards making the application more flexible and easier to maintain.

Creating a Sencha Touch Data Model To Represent a Note

The Notes List requires a data store, which will supply the information for its list items. In order to create this store, we first need to define a data model that will represent a note.

Let’s go ahead and define the Note Class. We will place this Class in the Note.js file, which we will save in the model directory:

A note will have four fields: id, date created, title and narrative. We will start with the following definition:

1 Ext.define("NotesApp.model.Note", {
2     extend: "Ext.data.Model",
3     config: {
4         idProperty: 'id',
5         fields: [
6             { name: 'id', type: 'int' },
7             { name: 'dateCreated', type: 'date', dateFormat: 'c' },
8             { name: 'title', type: 'string' },
9             { name: 'narrative', type: 'string' }
10         ]
11     }
12 });

We will use the idProperty config to establish that the id field is actually the field the framework can use to uniquely identify a note. This seems trivial in our case because we have total control over the names of the fields of the data model. However, you might encounter cases where, for example, the data model’s fields are tightly coupled to column names in an existing database, and the name of the column that uniquely identifies a record is not “id”. This is why the idProperty config is important.

Setting Up Model Validation In Sencha Touch

The id, dateCreated and title fields in our Note Model are mandatory. We will express this requirement using the validations config:

1 Ext.define("NotesApp.model.Note", {
2     extend: "Ext.data.Model",
3     config: {
4         idProperty: 'id',
5         fields: [
6             { name: 'id', type: 'int' },
7             { name: 'dateCreated', type: 'date', dateFormat: 'c' },
8             { name: 'title', type: 'string' },
9             { name: 'narrative', type: 'string' }
10         ],
11         validations: [
12             { type: 'presence', field: 'id' },
13             { type: 'presence', field: 'dateCreated' },
14             { type: 'presence', field: 'title', message:'Please enter a title for this note.' }
15         ]
16     }
17 });

For the title field, we are taking advantage of the message config to define the message the user will see when she tries to save a note without typing in its title.

Before moving on to create the data store, we need to add the model to the models config of the application:

1 Ext.application({
2     name: "NotesApp",
3  
4     models: ["Note"],
5  
6     // Rest of the app's definition omitted for brevity...
7 });

Creating a Sencha Touch Data Store

This is all we need for our data model at this point. Now we can focus on creating the data store that will feed the List. The Notes store goes in a new file. We will place this file in the store directory:

For now, the store will simply contain a few hard-coded records:

1 Ext.define("NotesApp.store.Notes", {
2     extend: "Ext.data.Store",
3     config: {
4         model: "NotesApp.model.Note",
5         data: [
6             { title: "Note 1", narrative: "narrative 1" },
7             { title: "Note 2", narrative: "narrative 2" },
8             { title: "Note 3", narrative: "narrative 3" },
9             { title: "Note 4", narrative: "narrative 4" },
10             { title: "Note 5", narrative: "narrative 5" },
11             { title: "Note 6", narrative: "narrative 6" }
12         ]
13     }
14 });

Worth highlighting here is the model config, which we use to establish that this store will contain instances of the Note model.

We want the Notes List to render the notes sorted by creation date. This is why we will add the sorters config to the store’s definition:

1 Ext.define("NotesApp.store.Notes", {
2     extend: "Ext.data.Store",
3     requires: "Ext.data.proxy.LocalStorage",
4     config: {
5         model: "NotesApp.model.Note",
6         data: [
7             { title: "Note 1", narrative: "narrative 1" },
8             { title: "Note 2", narrative: "narrative 2" },
9             { title: "Note 3", narrative: "narrative 3" },
10             { title: "Note 4", narrative: "narrative 4" },
11             { title: "Note 5", narrative: "narrative 5" },
12             { title: "Note 6", narrative: "narrative 6" }
13         ],
14         sorters: [{ property: 'dateCreated', direction:'DESC'}]
15     }
16 });

Now we can jump back to the NotesListContainer.js file, and add the store to the notesList declaration in the NotesListContainer Class:

1 var notesList = {
2     xtype: "noteslist",
3     store: Ext.getStore("Notes"),
4     listeners: {
5         disclose: { fn: this.onNotesListDisclose, scope:this }
6     }
7 };

Before we check how we are doing, let’s quickly switch over to the Controller’s definition in the controller/Notes.js file, locate the launch() function, and invoke the store’s load() function as follows:

1 launch: function () {
2     this.callParent(arguments);
3     Ext.getStore("Notes").load();
4     console.log("launch");
5 }

We really don’t need this call now – remember that the data is hard-coded – but we are adding in preparation for the next chapter of the tutorial, where we will discontinue the use of hard-coded data, and begin using data stored in the browser’s cache.

What we need to do, though, is add a reference to the store in the app.js file:

1 Ext.application({
2     name: "NotesApp",
3  
4     models: ["Note"],
5     stores: ["Notes"],
6     controllers: ["Notes"],
7     views: ["NotesList""NotesListContainer"],
8  
9     launch: function () {
10  
11         var notesListContainer = {
12             xtype: "noteslistcontainer"
13         };      
14  
15         Ext.Viewport.add(notesListContainer);
16     }
17 });

Excellent! At this point we should be able to see the hard-coded notes rendered on the screen. Let’s start our emulator, where we should see something like this:

Sencha Tutorial 1


In this series we will create the Sencha Touch 2 version of the Notes Application, an application that allows its users to take notes and store them on the device running the app. Along the way, we will dive into the following areas:

  • The building blocks of a Sencha Touch application.
  • How to implement navigation in an application with multiple views.
  • How to edit data using Sencha Touch form elements.
  • How to render information using lists views.
  • How Sencha Touch stores data with HTML5 local storage.

Note: An expanded version of this tutorial that covers creating a production build of the application is available in my Sencha Touch book.

In the first part of the series, we are going to define the features of the application, its look and feel, and we will start building its main screen.

The Features Of The Notes App

We want our app to give its users the following abilities:

  • Create notes.
  • Edit existing notes.
  • Delete notes.
  • Render a list of the notes currently saved on the device.
  • Persist notes on the device running the application, across browser sessions.
 

The Application’s User Interface

The main screen of the Notes App will render a list of the existing notes. We will name this View NotesListContainer. Here is its mockup, depicting the Sencha Touch Components we will use to build it:

As you can see, the NoteListContainer View is an Ext.Container Component that hosts a Toolbar Component and a List Component. We will treat the Ext.Lit as a separate View – the NotesList View.

A second screen, the NoteEditor View, will allow users to create, edit and delete notes. This view will look just like the following mockup:

The NoteEditor View is an Ext.form.Panel Component that contains a couple of Toolbars and the form elements needed to edit a note’s properties.

We also need a mechanism for our users to navigate to these screens. As each Sencha Touch Application gets a Viewport instance upon launch, we will use the Viewport to take care of rendering the NotesListContainer and NoteEditor Views, as well as manage the navigation between them:

The Viewport is ideal for this task. It extends from the Container Class, and it defaults to a full screen Card layout, which is what we need in the Notes App.

Organizing A Sencha Touch Application In Directories And Files

With the application features and user interface already defined, we can begin writing the source code. We will organize our source code in a directories tree like this:

We will place our files under the NotesApp directory, with the Application’s setup code in the app.js file.

We will also have an app directory, in which we will have the controller, model, profile, store, and view directories. The controllers, models, profiles, stores and views used in the Application will reside in these directories.

The index.html file will launch the app. In it, we will include the Sencha Touch framework, along with our application:

1 <!--<!DOCTYPE html>
2 <html>
3 <head>--> <!--Remove comments!-->
4     My Notes
5 <script type="text/javascript" src="../Lib/ST2/sencha-touch-debug.js"></script><script type="text/javascript"src="app.js"></script>
6 <!--</head>
7 <body>
8 </body>
9 </html>--> <!--Remove comments!-->

Note that we are keeping the framework files in the Lib/ST2 folder:

Creating a Sencha Touch Application Instance

Our first step in the app.js file will consist of creating an instance of the Sencha Touch Application Class. We are going to define an Application instance as follows:

1 Ext.application({
2     name: "NotesApp",
3     launch: function () {
4  
5         console.log("App launch");
6     }
7 });

The application() method loads an Application instance after the page is ready, and it uses the supplied config object to configure the app. In our case, we are simply giving the app a name, and defining the launch() function.

One important role of the application() function is to trigger the load of any Models, Views, Controllers, Stores and Profiles the application depends on. This happens automatically when we define these dependencies using the models, views, controllers, stores and profiles config objects. We will see this feature in detail in this tutorial.

The launch() function is invoked as soon as the app loads its dependencies and instantiates its Controllers.

If we navigate to this page on the emulator, we should see something like this:

Extending Classes In Sencha Touch

We already know that the main screen of our application will render the list of notes currently cached on the device. To build this screen, we will use an instance of the Container Class, which will host a Toolbar and a List:

First, we will create the NotesListContainer.js file in the view directory:

In the NotesListContainer.js file, we will then define the NotesListContainer View like so:

1 Ext.define("NotesApp.view.NotesListContainer", {
2     extend: "Ext.Container",
3     config: {
4         items: [{
5             xtype: "toolbar",
6             docked: "top",
7             title: "My Notes",
8             items: [{
9                 xtype: "spacer"
10             }, {
11                 xtype: "button",
12                 text: "New",
13                 ui: "action",
14                 id:"new-note-btn"
15             }]
16         }]
17     }
18 });

Here we are using Ext.define() to define an extension to the Ext.Container class. We have added a Toolbar instance to this view, docked to the top. The Toolbar will in turn contain one Button, the New button, which will allow our users to activate the NoteEditor View and create a new note.

Pay attention to how we included a Spacer Component before the button. This will place the New button on the right end of the Toolbar. Also, how the ui=”action” config allows us to give the button a distinctive look, indicating that it represents the default button on the view.

Specifying Application Dependencies In Sencha Touch

With the NotesListContainer Class defined, it is time to make the Application aware of it. Back in the app.js file, we are going to add the views config to the application() function:

1 Ext.application({
2     name: "NotesApp",
3     views: ["NotesListContainer"],
4  
5     launch: function () {
6  
7         var notesListContainer = Ext.create("NotesApp.view.NotesListContainer");
8         Ext.Viewport.add(notesListContainer);
9     }
10 });

We inform the Application that it has a dependency on the NoteListContainer View by using the views config:

1 views: ["NotesListContainer"]

The Application Class expects its models, views, controllers, stores and profiles to exist in the app/model, app/view, app/controller, app/store, and app/profile directories. With this convention, we can define models, views, controllers, stores and profiles using the last part of their names, as we do here. If we use a different directory structure, we will need to refer to them using their fully qualified names.

Time to check out how the View looks. On the emulator, you should see something similar to this:

Very nice, right? Let’s move on to the Controller. We need a Controller Class to be in charge of handling user input, modifying the models, and governing the transitions between views.

Creating Sencha Touch Controllers

Let’s define a very simple Notes Controller Class. We will place this Class in the Notes.js file, which we will add to the controller directory:

And this is the Controller’s definition:

1 Ext.define("NotesApp.controller.Notes", {
2     extend: "Ext.app.Controller",
3     launch: function () {
4         this.callParent();
5         console.log("launch");
6     },
7     init: function () {
8         this.callParent();
9         console.log("init");
10     }
11 });

Controllers contain a couple of functions, init() and launch(), that run at different moments during the Application’s startup process. The init() function is invoked by the framework before the Application’s launch() function. The Controller’s launch() function is invoked after the Application’s launch() function runs.

We will make the Application aware of the Notes Controller Class by adding this Controller to the app’s controllers config:

1 Ext.application({
2     name: "NotesApp",
3     controllers: ["Notes"],
4     views: ["NotesListContainer"],
5  
6     launch: function () {
7  
8         var notesListContainer = Ext.create("NotesApp.view.NotesListContainer");
9         Ext.Viewport.add(notesListContainer);
10     }
11 });

As we already know, having the Notes Controller Class listed in the controllers config will cause the Application to automatically instantiate and keep a reference to this Controller.

How To Handle View Events In a Sencha Touch Controller

At this point we have our Controller set up, and we are ready to start adding features to it. The first thing the Notes Controller needs to do is handle taps on the New button. We can accomplish this by making the following modification to the Controller’s definition:

1 Ext.define("NotesApp.controller.Notes", {
2     extend: "Ext.app.Controller",
3     config: {
4         refs: {
5             newNoteBtn: "#new-note-btn"
6         },
7         control: {
8             newNoteBtn: {
9                 tap: "onNewNote"
10             }
11         }
12     },
13     onNewNote: function () {
14         console.log("onNewNote");
15     }
16  
17     // init and launch functions omitted.
18 });

Notice the change? Exactly – the refs and control configurations. They are the foundation of the mechanism by which a Controller acquires references to the Components in the app, and defines event handlers for them.

Refs give the Controller the ability to find Components in the app. They use the ComponentQuery Class, which retrieves components using a CSS selector-like syntax. I will not go into the details of ComponentQuery in this article, but I highly recommend that you take some time to study it.

In the Notes Controller, we use the newNoteBtn ref to create a reference to the Component whose id is #new-note-btn. This is the New button.

1 refs: {
2     newNoteBtn: "#new-note-btn"
3 }

Based on this ref, the framework generates a getter function that we can use to work with the New button reference if we need to. Ref-derived getter functions are named following a simple format, consisting of the word get and the capitalized name of the ref in question. In our case, the name of the function will be getNewNoteBtn().

Refs also give us the ability to instantiate the components they reference if such components do not already exist. Although we are not taking this approach in our app, I recommend that you study these features. They are important, especially if you will be building large applications where you cannot afford or do not need to instantiate all the application’s components upon application launch.

The control config allows us to define event handlers for any of the Application’s Components. We can use refs or ComponentQuery selectors to define event handlers within control. In the Notes Controller, we are using the newNoteBtn ref to define a tap handler for the New button:

1 control: {
2     newNoteBtn: {
3         tap: "onNewNote"
4     }
5 }

Obviously, we also need to define the handler function, which we do further down in the Controller’s source:

1 onNewNote: function () {
2     console.log("onNewNote");
3 }

The onNewNote() function is simply writing a message to the console, but we will soon add to it the code that will open the NoteEditor View.

Let’s take a moment to check the application on the emulator. In particular, what happens when we tap the New button. If a tap occurs, the console output should display the message we just added in the onNewNote() handler:

One important takeaway from this tutorial is that when you need your Controller to handle a particular event on a Component, you can follow these steps:

  1. Create a ref for the Component.
  2. Create an entry for the ref in the Controller’s control config.
  3. Use the entry in the control config to specify the handler for the events in which you are interested.