Pull to refresh

Введение в Cappuccino

Reading time 10 min
Views 4.9K
Фреймворк Cappuccino – уникальная технология, позволяющая создавать веб-приложения десктопного качества. Он абстрагирует DOM и вместо него предоставляет Cocoa-подобный API. Вместо того, чтобы возиться с CSS-версткой и кроссбраузерными проблемами, вы используете интерфейсы, специально созданные для разработки приложений, а не статических страниц, интерфейсы, взятые с платформ Mac OS X и iOS.

Я заметил, что на русском языке почти нет обучающих материалов о Cappuccino, и решил восполнить пробел. Этот очерк рассчитан на то, чтобы прочитав его, можно было сразу приступать к разработке своего первого Cappuccino приложения. Я познакомился с фреймворком, когда искал средство для реализации онлайновой среды разработки для своего проекта Akshell. Мне нужно было сделать полнофункциональную IDE, работающую в окне браузера, и Cappuccino отлично справился с поставленной задачей.

История


В 2008 году Франциско Толмаски, Том Робинсон и Росс Баучер (Франциско и Росс – бывшие сотрудники Apple) создали компанию 280 North и выпустили 280 Slides, веб-приложение для подготовки презентаций. Оно и сейчас впечатляет своим look and feel’ом, а тогда это было просто что-то нереальное. Причем 280 Slides не был самостоятельным продуктом, это была лишь демонстрация возможностей нового веб-фреймворка, Cappuccino.

4-го сентября того же года Cappuccino был опубликован под лицензией LGPL и тут же стал хитом на GitHub. Вокруг фреймворка образовалось сообщество программистов, использующих его в своих продуктах. С тех пор это сообщество только увеличивается и принимает все более активное участие в разработке Cappuccino.

В 2009 году 280 North анонсировала новый продукт, Atlas, и он также оказался прорывной технологией. Atlas – это аналог Interface Builder для веб-приложений, в нем вы можете рисовать интерфейсы мышкой, перетаскивая нужные компоненты из библиотеки. Пара минут работы дает более впечатляющий результат, чем день мучений с CSS. В отличие от Cappuccino, Atlas – закрытая платная программа, для скачивания бета-версии нужно заплатить $20. Этот очерк посвящен Cappuccino, поэтому я не буду описывать Atlas более подробно. Скажу только, что он действительно экономит кучу времени при создании интерфейсов, позволяет сосредоточиться на юзабилити, а не на борьбе с различными версиями браузеров.

Cappuccino и Atlas активно развивались, когда летом 2010 года Motorola купила 280 North со всеми ее активами, потратив на это, по слухам, 20 миллионов долларов. Официальная цель покупки – развитие технологий разработки на платформе Android. Многие злые языки начали поговаривать о смерти Cappuccino, однако с тех пор прошло уже полгода, фреймворк активно разрабатывается, а 23-го февраля состоялся релиз версии 0.9.

Итак, Cappuccino является одной из самых продвинутых технологий создания веб-приложений, его развитие обеспечивает большая корпорация. Изучить или хотя бы ознакомиться с фреймворком будет полезно каждому веб-программисту, к чему и приступим.

Теория


Главная особенность Cappuccino – это созданный специально для него язык Objective-J. Он является такой же надстройкой над JavaScript, как Objective-C – над C. Для меня, как и для многих, вначале было совершенно неочевидно, зачем делать объектно-ориентированную надстройку над языком, который сам по себе является объектно-ориентированным. Однако в этом есть большой смысл.

Во-первых, благодаря Objective-J Cappuccino полностью повторяет Cocoa API. Это не фантазия на тему и даже не творческая переработка, это именно повторение, вплоть до сигнатур функций. За счет этого программисты, работавшие на Mac OS X и iOS могут безболезненно перейти на веб-разработку. Удобство всех API проверено долговременными использованием на десктопе, начиная с ОС NeXTSTEP. И наконец, при использовании фреймворка можно успешно пользоваться документацией Cocoa, которая значительно превосходит по качеству и проработанности Doxygen документацию Cappuccino.

Во-вторых, отсутствие гибкости иногда является плюсом. Большинство программистов посчитает, что я сейчас высказал большую ересь, поэтому сразу начну оправдываться. Дизайнеры знают, что введение сеток и других искусственных ограничений может улучшить дизайн. Objective-J интерфейсы Cappuccino на первый взгляд кажутся неповоротливыми и допотопными по сравнению с тем, что можно было бы сделать на JavaScript. Вместо парадигмы Target-Action зачастую можно было бы использовать замыкания, вместо явных геттеров и сеттеров – __defineGetter__ и __defineSetter__, вместо квадратных скобок – привычные точки, в конце концов. И самое ужасное: Objective-J методы не являются объектами первого класса!

Поработав с Cappuccino, я понял достоинства такого подхода, и они являются прямыми следствиями недостатков. Более жесткая объектная модель позволяет четче структурировать код, не потонуть в каше вложенных друг в друга функций. Длинные содержательные имена позволяют справляться с иерархиями классов Cocoa, в который один класс обычно имеет многие десятки методов. Вездесущие геттеры и сеттеры позволяют реализовать Key-Value Coding, Key-Value Observing и Key-Value Binding, техники переворачивающие представление о создании сложных интерфейсов пользователя (к сожалению, в этом очерке не хватит места для их описания).

Я начал пользоваться Cappuccino потому, что меня привлек внешний вид приложений, созданных с его помощью. Теперь я понимаю, что настоящая сила заключается в его API, которые сначала казались мне ужасными. Поэтому я призываю вас осознать философию фреймворка прежде, чем сделать окончательное суждение о нем.

Приступим к обзору Objective-J. Главное, что он вносит в JavaScript – это классы. Они определяются с помощью ключевого слова @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

Между @implementation и @end находится описание класса. Класс Person наследуется от CPObject, вершины иерархии классов Cappuccino. Затем в фигурных скобках объявляются переменные-члены класса. name – строковая переменная-член, для которой автоматически генерируются геттер и сеттер (благодаря указанию @accessors). Правило именования аксессоров в Objective-J несколько нестандартно – это name и setName:. Этого формата стоит придерживаться, т.к. на него полагаются внутренние механизмы Cappuccino.

Взаимодействие между Objective-J объектами происходит с помощью пересылки сообщений, впервые эта техника появилась в языке Smalltalk. Синтаксически это выглядит так:

[объект сообщение]
[объект сообщениеСПараметром:значение]
[объект сообщениеСПараметром1:значение1 параметром2:значение2]
...

Например, вызов геттеров и сеттеров объекта класса Person выглядит так:

var name = [aPerson name];
[aPerson setName:”Вася”];

Методы объявляются после переменных-членов, до ключевого слова @end. Методы класса начинаются на плюс, методы экземпляра класса – на минус.

Создание экземпляра класса в Objective-J происходит в два этапа: сначала метод alloc этого класса создает неинициализированный объект, а затем конструктор (init-метод) инициализирует его. Часто определяют методы класса, упрощающие этот процесс, personWithName: – пример такого метода.

Названия конструкторов в Objective-J принято начинать со слова init. Каждый конструктор должен сначала позаботится о вызове конструктора суперкласса, затем выполнить инициализацию и вернуть self. Ошибка конструирования сигнализируется возвратом значения nil (да, во времена создания Objective-C исключения еще не вошли в моду, поэтому Cocoa и, как следствие, Cappuccino обходятся без них).

Ради единства стиля Objective-J определяет три переменных, взятые из Objective-C, это nil, YES и NO. Они идентичны null, true и false, соответственно. Cappuccino Coding Style Guidelines рекомендуют использовать именно их.

Как и многие читающие это очерк, я никогда не использовал Objective-C, поэтому для меня было совершенно неочевидно, почему класс определяется ключевым словом @implementation, а не, например, @class. Специально для таких же любопытных: Objective-C, как надстройка над C, использует файлы объявлений (*.h от headers) и файлы определений (*.m от messages), поэтому классы должны объявляться (@interface) и определяться (@implementation). В Objective-J объявления не нужны, поэтому используется только @implementation.

Практика


Пора установить Cappuccino. Если вы используете Windows, сначала придется установить Cygwin (неприятное обстоятельство). Все остальные и те, кто все-таки решился на предыдущий шаг, просто скачивают Cappuccino Starter Package и запускают в нем скрипт bootstrap.sh.

После установки в системе должна появиться утилита capp. В качестве примера мы будем создавать (сюрприз!) приложение для поиска в твиттере. Сгенерируем его:

capp gen "Twitter Search"

В появившейся папке лежит куча файлов, нас интересуют index-debug.html и AppController.j (*.j – стандартное расширение файлов с Objective-J кодом).

Откроем index-debug.html. Строгая политика безопасности Google Chrome не позволяет Cappuccino загружать нужные ей файлы при работе с протоколом file://, требуется поднимать веб-сервер, во всех же остальных браузерах мы увидим работающее приложение Hello World! Cappuccino компилирует Objective-J код в JavaScript на лету, прямо в браузере, поэтому при разработке можно просто обновлять страницу и не заботиться о пересборке. «Боевую» версию приложения можно скомпилировать заранее, чтобы ускорить загрузку.

Приступим к разработке. В файле AppController.j определен класс AppController, в Objective-J принято размещать код каждого класса в одноименном файле. Cappuccino создает один экземпляр AppController при запуске, он должен инициализировать приложение и управлять его дальнейшей работой.

Перед определением класса импортируем Foundation и AppKit, две основных части Cappuccino. Foundation содержит классы для обеспечения бизнес-логики, а AppKit – это библиотека классов пользовательского интерфейса.

@import <Foundation/Foundation.j>
@import <AppKit/AppKit.j>

Ключевое слово @import подключает указанный файл. Как и в C, при указании пути в угловых скобках файл будет искаться в системных папах, при использовании двойных кавычек – в текущей папке.

Метод applicationDidFinishLaunching: вызывается у всех объектов непосредственно после запуска приложения. Содержательные имена методов Cappuccino делают код понятным даже для тех, кто не знаком с фреймворком, поэтому я буду давать пояснения только в неочевидных случаях. Cappuccino вряд ли сэкономит вам нажатия клавиш, так пусть хоть сэкономит их мне.

Для инициализации приложения мы создадим окно и текстовое поле:

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

JavaScript функция CGRectMake(x, y, width, height) описывает прямоугольник, в котором будет располагаться элемент управления. Метод contentView возвращает представление внутренней области окна.

Раньше я был ярым сторонником ограничения длины строки 80-ю символами. Применение этого правила в Cappuccino превращает код в нечитаемую кашу, поэтому большинство разработчиков не ограничивают себя при работе как с Objective-J, так и с Objective-C. Единственным исключением являются гайдлайны Google, но на то он и Google.

Теперь сделаем текстовое поле редактируемым и добавим ему рамки:

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

Установим Target и Action, т.е. объект и метод, который должен у него вызваться при нажатии кнопки Enter в поле:

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

@selector используется, чтобы превратить метод в передаваемое значение.

Добавляем поле к окну и фокусируем его:

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

И наконец, показываем окно:

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

С инициализацией приложения покончено, теперь определим его реакцию на нажатие Enter, т.е. метод didSubmitTextField:

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

Мы извлекаем значение текстового поля и, если оно не пусто, создаем экземпляр SearchWindowController, показываем его окно, опустошаем текстовое поле и выдвигаем его окно вперед. Дело за малым – разработать класс SearchWindowController.

Создадим файл SearchWindowController.j и отнаследуем наш класс от CPWindowController:

@implementation SearchWindowController : CPWindowController
{
}

@end

Определим конструктор, создающий окно и делающий запрос к твиттеру:

- (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;
}

И наконец, напишем обработчик ответа от твиттера:

- (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];
    }
}

Для отображения твитов он использует библиотечные классы CPCollectionView, CPCollectionViewItem и CPScrollView. А вот класс TweetView придется определить самостоятельно.

Наверное, вы заметили в коде несколько вызовов метода setAutoresizingMask:. Этот метод заставит вас навсегда проклясть позиционирование CSS, если, конечно, вы этого еще не сделали. Позиционирование элементов управления в Cappuccino настолько просто и элегантно, что с ним справится даже ребенок.

Осталось разобраться с классом 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

Метод setRepresentedObject: создает изображение аватара и две метки, с именем пользователя и твитом.

Теперь снова откроем index-debug.html и поищем несколько слов. Результат должен получиться примерно такой:


Вы можете просмотреть полный код примера и протестировать его.

Что дальше?


Конечно, в этом очерке невозможно было описать и десятую долю функционала Cappuccino. Однако я уверен, что сейчас вы уже готовы начать разработку своих приложений, к чему вас и призываю. Для углубления знаний используйте документацию Cappuccino, также очень рекомендую набор скринкастов по теме. Надеюсь, что титанический труд Франциско, Тома и Росса, а также мой скромный очерк помогут кому-то из вас создать новое интересное веб-приложение.
Tags:
Hubs:
+90
Comments 53
Comments Comments 53

Articles