Introduction to Cappuccino

    Cappuccino Framework is a unique technology that allows you to create desktop web applications. It abstracts the DOM and instead provides a Cocoa-like API. Instead of messing with CSS layout and cross-browser issues, you use interfaces specifically designed for application development, rather than static pages, interfaces taken from Mac OS X and iOS.

    I noticed that in Russian there are almost no training materials about Cappuccino, and decided to fill the gap. This essay is designed so that after reading it, you can immediately begin to develop your first Cappuccino application. I met the framework when I was looking for a tool to implement an online development environment for my Akshell project. I needed to make a fully functional IDE that worked in a browser window, and Cappuccino did an excellent job.

    History


    In 2008, Francisco Tolmaski, Tom Robinson, and Ross Boucher (Francisco and Ross are former Apple employees) created 280 North and released 280 Slides , a presentation presentation web application. It still impresses with its look and feel, and then it was just something unreal. Moreover, 280 Slides was not an independent product, it was just a demonstration of the capabilities of the new web framework, Cappuccino.

    On September 4th of that year, Cappuccino was published under the LGPL license and immediately became a hit on GitHub. Around the framework, a community of programmers has formed that use it in their products. Since then, this community has only grown and is taking an increasingly active part in the development of Cappuccino.

    In 2009, 280 North announced a new product, Atlas , and it also turned out to be a breakthrough technology. Atlas is an analogue of Interface Builder for web applications, in it you can draw interfaces with the mouse, dragging the necessary components from the library. A couple of minutes of work gives a more impressive result than a day of torment with CSS. Unlike Cappuccino, Atlas is a closed paid program, you need to pay $ 20 to download the beta version. This essay is dedicated to Cappuccino, so I will not describe Atlas in more detail. I can only say that it really saves a lot of time when creating interfaces, allows you to focus on usability, and not on the struggle with different versions of browsers.

    Cappuccino and Atlas developed rapidly when, in the summer of 2010, Motorola bought 280 North with all its assets, spending $ 20 million, according to rumors. The official purpose of the purchase is the development of development technologies on the Android platform. Many evil languages ​​began to talk about the death of Cappuccino, however, half a year has passed since then, the framework is being actively developed, and version 0.9 was released on February 23rd.

    So, Cappuccino is one of the most advanced technologies for creating web applications, its development is provided by a large corporation. It will be useful to every web programmer to study, or at least familiarize yourself with the framework, and let's get started.

    Theory


    The main feature of Cappuccino is the Objective-J language created specifically for it. It is the same add-on for JavaScript as Objective-C is for C. For me, as for many, at the beginning it was completely unclear why an object-oriented add-in was made over a language that is itself object-oriented. However, this makes a lot of sense.

    Firstly, thanks to Objective-J, Cappuccino completely repeats the Cocoa API. This is not a fantasy on the topic or even creative processing, it is just a repetition, right down to the signatures of functions. Due to this, programmers working on Mac OS X and iOS can easily switch to web development. The convenience of all APIs has been verified by long-term use on the desktop, starting with the NeXTSTEP OS. And finally, when using the framework, you can successfully useCocoa documentation , which is significantly superior in quality and sophistication to Doxygen Cappuccino documentation .

    Secondly, lack of flexibility is sometimes a plus. Most programmers will consider that I have now expressed a great heresy, so immediately begin to make excuses. Designers know that introducing nets and other artificial constraints can improve design. Cappuccino's Objective-J interfaces at first glance seem clumsy and antediluvian in comparison with what could be done on JavaScript. Instead of the Target-Action paradigm , closures could often be used, instead of explicit getters and setters, __defineGetter__and__defineSetter__, instead of square brackets - the usual points, after all. And the worst part: Objective-J methods are not first class objects !

    After working with Cappuccino, I realized the merits of this approach, and they are direct consequences of the shortcomings. A more rigid object model allows you to more clearly structure the code without drowning in the mess of functions embedded in each other. Long, meaningful names allow you to deal with Cocoa class hierarchies, in which one class usually has many dozens of methods. The ubiquitous getters and setters allow you to implement Key-Value Coding, Key-Value Observing and Key-Value Binding, techniques that revolutionize the idea of ​​creating complex user interfaces (unfortunately, this essay does not have enough space to describe them).

    I started using Cappuccino because I was attracted by the look of the applications created with it. Now I understand that the real strength lies in its API, which at first seemed awful to me. Therefore, I urge you to become aware of the philosophy of the framework before making a final judgment about it.

    Let's get started with a review of Objective-J. The main thing that he brings to JavaScript is the classes. They are defined using the keyword @implementation:

    @implementation Person: CPObject
    {
        CPString name @accessors;
    }
    (id) initWithName: (CPString) aName
    {
        if (self = [super init])
            name = aName;
        return self;
    }
    (Person) personWithName: (CPString) aName
    {
        return [[Person alloc] initWithName: aName];
    }
    @end

    Between @implementationand @endis a description of the class. A class is Personinherited from CPObject, the top of the Cappuccino class hierarchy. Then, class member variables are declared in braces. name- string member variable for which getter and setter are automatically generated (thanks to the indication @accessors). Naming accessor in Objective-J more unconventionally - it nameand setName:. This format should be adhered to, as Cappuccino internal mechanisms rely on it.

    Interaction between Objective-J objects occurs through message forwarding , the first time this technique appeared in the Smalltalk language. Syntactically, it looks like this:

    [object message]
    [message objectParameter: value]
    [message object with parameter1: value1 parameter2: value2]
    ...

    For example, calling getters and setters of a class object Personlooks like this:

    var name = [aPerson name];
    [aPerson setName: ”Vasya”];

    Methods are declared after member variables, before the keyword @end. Class methods start with a plus, class instance methods start with a minus.

    Creating an instance of a class in Objective-J takes place in two stages: first, the method of allocthis class creates an uninitialized object, and then the constructor (init method) initializes it. Class methods that simplify this process are often defined personWithName:— an example of such a method.

    The names of constructors in Objective-J are usually started with the word init. Each constructor must first take care of calling the superclass constructor, then initialize and return self. A design error is signaled by a return value.nil(yes, at the time of the creation of Objective-C, exceptions were not yet in fashion, so Cocoa and, as a result, Cappuccino do without them).

    For the sake of style unity, Objective-J defines three variables taken from Objective-C, this nil, YESand NO. They are identical null, trueand false, respectively. Cappuccino Coding Style Guidelines recommend using them.

    Like many readers of this essay, I never used Objective-C, so it was completely unobvious for me why a class is defined by a keyword @implementation, and not, for example,@class. Especially for the same curious: Objective-C, as an add-on over C, uses declaration files (* .h from headers) and definition files (* .m from messages), so classes must be declared ( @interface) and defined ( @implementation). Objective-J does not need declarations, so it is used only @implementation.

    Practice


    It's time to install Cappuccino. If you use Windows, you will first have to install Cygwin (an unpleasant circumstance). Everyone else and those who still decided on the previous step, just download the Cappuccino Starter Package and run the bootstrap.sh script in it.

    After installation, the capp utility should appear on the system. As an example, we will be creating (surprise!) An application for searching on Twitter. We generate it:

    capp gen "Twitter Search"

    The folder that appears contains a bunch of files, we are interested in index-debug.html and AppController.j (* .j is the standard file extension with Objective-J code).

    Open index-debug.html. The strict security policy of Google Chrome does not allow Cappuccino to download the files it needs when working with the file: // protocol, it is necessary to raise the web server, in all other browsers we will see the working Hello World application! Cappuccino compiles Objective-J code in JavaScript on the fly, right in the browser, so when developing, you can just refresh the page and not worry about rebuilding. The “combat” version of the application can be compiled in advance to speed up the download.

    We proceed to the development. A class is defined in the AppController.j fileAppController, in Objective-J, it is customary to place the code of each class in the same file. Cappuccino creates one instance AppControllerat startup, it must initialize the application and manage its further work.

    Before defining the class, we import Foundationand AppKit, the two main parts of Cappuccino. Foundationcontains classes for providing business logic, and AppKitis a user interface class library.

    @import 
    @import 

    The keyword @importincludes the specified file. As in C, when specifying the path in angle brackets, the file will be searched in system folders, when using double quotes, in the current folder.

    The method applicationDidFinishLaunching:is called on all objects immediately after the application starts. The substantial names of the Cappuccino methods make the code understandable even for those who are not familiar with the framework, so I will give explanations only in non-obvious cases. Cappuccino is unlikely to save you keystrokes, so at least save them to me.

    To initialize the application, we will create a window and a text field:

    var window = [[CPWindow alloc] initWithContentRect: CGRectMake (100, 100, 250, 70)
                                             styleMask: CPTitledWindowMask],
        contentView = [window contentView],
        textField = [[CPTextField alloc] initWithFrame: CGRectMake (25, 20, 200, 30)];

    The JavaScript function CGRectMake(x, y, width, height)describes the rectangle in which the control will be located. The method contentViewreturns a view of the inner area of ​​the window.

    I used to be an ardent supporter of the string length limit of 80 characters. Applying this rule in Cappuccino turns the code into an unreadable mess, so most developers do not limit themselves when working with both Objective-J and Objective-C. The only exceptions are Google guidelines , but he and Google.

    Now let's make the text field editable and add frames to it:

    [textField setEditable: YES];
    [textField setBezeled: YES];

    Set Target and Action, i.e. the object and the method that it should be called when the Enter button is pressed in the field:

    [textField setTarget: self];
    [textField setAction: @selector (didSubmitTextField :)];

    @selectorused to turn a method into a passed value.

    Add a field to the window and focus it:

    [contentView addSubview: textField];
    [window makeFirstResponder: textField];

    And finally, we show the window:

    [window center];
    [window setTitle: "Twitter Search"];
    [window orderFront: self];

    The initialization of the application is over, now we will determine its reaction to pressing Enter, i.e. methoddidSubmitTextField:

    - (void) didSubmitTextField: (CPTextField) textField
    {
        var searchString = [textField stringValue];
        if (! searchString)
            return
        [[[SearchWindowController alloc] initWithSearchString: searchString] showWindow: nil];
        [textField setStringValue: ""];
        [[textField window] makeKeyAndOrderFront: nil];
    }

    We extract the value of the text field and, if it is not empty, create an instance SearchWindowController, show its window, empty the text field and push its window forward. The point is small - to develop a class SearchWindowController.

    Create the file SearchWindowController.j and inherit our class from CPWindowController:

    @implementation SearchWindowController: CPWindowController
    {
    }
    @end

    Define a constructor that creates a window and makes a request to Twitter:

    - (id) initWithSearchString: (CPString) searchString
    {
        if (self = [super init]) {
            var window = [[CPWindow alloc] initWithContentRect: CGRectMake (10, 30, 300, 400)
                                                     styleMask: CPTitledWindowMask | CPClosableWindowMask | CPResizableWindowMask];
            [window setTitle: searchString];
            [self setWindow: window];
            var request = [CPURLRequest requestWithURL: "http://search.twitter.com/search.json?q=" + encodeURIComponent (searchString)];
            [CPJSONPConnection sendRequest: request callback: "callback" delegate: self];
        }
        return self;
    }

    And finally, write a response handler from Twitter:

    - (void) connection: (CPJSONPConnection) connection didReceiveData: (Object) data
    {
        var contentView = [[self window] contentView];
        if (data.results.length) {
            var bounds = [contentView bounds],
                collectionView = [[CPCollectionView alloc] initWithFrame: bounds];
            [collectionView setAutoresizingMask: CPViewWidthSizable];
            [collectionView setMaxNumberOfColumns: 1];
            [collectionView setMinItemSize: CGSizeMake (200, 100)];
            [collectionView setMaxItemSize: CGSizeMake (10000, 100)];
            var itemPrototype = [[CPCollectionViewItem alloc] init];
            [itemPrototype setView: [TweetView new]];
            [collectionView setItemPrototype: itemPrototype];
            [collectionView setContent: data.results];
            var scrollView = [[CPScrollView alloc] initWithFrame: bounds];
            [scrollView setAutoresizingMask: CPViewWidthSizable | CPViewHeightSizable]
            [scrollView setDocumentView: collectionView];
            [contentView addSubview: scrollView];
        } else {
            var label = [CPTextField labelWithTitle: "No tweets"],
                boundsSize = [contentView boundsSize];
            [label setCenter: CGPointMake (boundsSize.width / 2, boundsSize.height / 2)];
            [label setAutoresizingMask: CPViewMinXMargin | CPViewMaxXMargin | CPViewMinYMargin | CPViewMaxYMargin];
            [contentView addSubview: label];
        }
    }

    He uses library classes to display tweets CPCollectionView, CPCollectionViewItemand CPScrollView. But the class TweetViewwill have to be determined independently.

    You have probably noticed several method calls in your code setAutoresizingMask:. This method will force you to forever curse CSS positioning, unless, of course, you have already done so. The positioning of controls in Cappuccino is so simple and elegant that even a child can handle it.

    It remains to deal with the class TweetView:

    @implementation TweetView: CPView
    {
        CPImageView imageView;
        CPTextField userLabel;
        CPTextField tweetLabel;
    }
    - (void) setRepresentedObject: (id) tweet
    {
        if (! imageView) {
            imageView = [[CPImageView alloc] initWithFrame: CGRectMake (10, 5, 48, 48)];
            [self addSubview: imageView];
            userLabel = [[CPTextField alloc] initWithFrame: CGRectMake (65, 0, -60, 18)];
            [userLabel setAutoresizingMask: CPViewWidthSizable];
            [userLabel setFont: [CPFont boldSystemFontOfSize: 12]];
            [self addSubview: userLabel];
            tweetLabel = [[CPTextField alloc] initWithFrame: CGRectMake (65, 18, -60, 100)];
            [tweetLabel setAutoresizingMask: CPViewWidthSizable];
            [tweetLabel setLineBreakMode: CPLineBreakByWordWrapping];
            [self addSubview: tweetLabel];
        }
        [imageView setImage: [[CPImage alloc] initWithContentsOfFile: tweet.profile_image_url size: CGSizeMake (48, 48)]];
        [userLabel setStringValue: tweet.from_user];
        [tweetLabel setStringValue: tweet.text];
    }
    @end

    The method setRepresentedObject:creates an avatar image and two tags, with a username and tweet.

    Now open index-debug.html again and look for a few words. The result should be something like this:


    You can view the full code of the example and test it.

    What's next?


    Of course, in this essay it was impossible to describe a tenth of the Cappuccino functionality. However, I am sure that now you are ready to begin developing your applications, which is what I urge you to do. To deepen your knowledge, use the Cappuccino documentation , I also highly recommend a set of screencasts on the topic. I hope that the titanic work of Francisco, Tom and Ross, as well as my humble essay will help some of you to create a new interesting web application.

    Also popular now: