Фреймворк
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, также очень рекомендую
набор скринкастов по теме. Надеюсь, что титанический труд Франциско, Тома и Росса, а также мой скромный очерк помогут кому-то из вас создать новое интересное веб-приложение.
комментарии (53)