Pull to refresh

Создаём простейший виджет для Mac OS X Dashboard

Reading time 6 min
Views 10K
Здравствуйте, хабравчане-маководы!
Картинка поста
Сегодня мы с вами попробуем разобраться в азах создания виджета для Dashboard в Mac OS X. Нам понадобится программа Dashcode, предназначенная как раз для этого.

Для начала немного теории. Виджет в Dashboard — это специально сформировання веб-страничка, упакованная в бандл вместе со всем ресурсами. Ну, и немного служебной информации в довесок. Соответственно, используемый язык программирования — JavaScript. Если Вы уже знакомы с ним, а так же с HTML/CSS (хотя это вряд ли понадобится), то Вы уже способны написать простенький виджет. Если же нет, то не стоит расстраиваться, этот язык очень прост и интуитивно понятен, разобраться с ним можно достаточно быстро. Далее я буду считать, что с JS читатель более-менее знаком. Сама же статья рассчитана на новичков, так что прошу не ругать за «слишком простое изложение и детальное разжёвывание элементарных вещей». Кроме того, за дизайн тоже прошу не пинать — ну не дизайнер я, не дизайнер! Если кто хочет помочь с этим делом — welcome =)

Для удобства, все исходники (а так же готовый к использованию виджет) выложены на гитхаб, ссылка в конце статьи. Но не спешите просто скачивать их! Лучше потратить немного времени и разобраться, как создать это всё самому.

Итак, приступим. В качестве цели для экспериментов я, разумеется, выбрал наш любимый хабр. Мы будем шаг за шагом делать виджет, отображающий карму, рейтинг и позицию в рейтинге хабралюдей выбранного хабраюзера.
Картинка для привлечения внимания
Такой виджет (ну, очень похожий) уже был создан хабратоварищем neoromantic аж в 2007 году, но ссылки на скачивание не рабочие, а кроме того, та статья не содержала практического руководства по созданию подобных виджетов.

Восстановим справеВосполним эти недостатки.

Создадим пустой проект. Для этого запустим Dashcode и кликнем в нужные места. Процесс тривиален.

Что же мы видим? Базовый виджет имеет основное и вспомогательное состояния (соответственно, front и back в левой панели). Первое отображается в обычном режиме работы, второе — для настройки параметров виджета. Переключаться между ними можно выбирая соответствующие пункты в списке компонент слева. Мы можем смело удалить всё лишнее, кроме кнопок «info» и «Done», которые служат для переключения между основным и вспомогательным состояниями. Далее, для простоты, будем называть это лицевой и тыльной сторонами виджета.

Теперь на лицевую сторону нашего виджета (без единой строчки кода!) кидаем нужные компоненты: несколько надписей. Для этого открываем библиотеку комонент — кнопка Library справа вверху — и перетаскиваем на виджет компоненты типа «Text». Теперь открываем Inspector (так же кнопка справа вверху) и с его помощью настраиваем размеры, цвета и так далее для нашего виджета. С его же помощью зададим осмысленные имена нашим надписям — для более удобного доступа из кода.

На тыльную сторону кинем надпись и поле ввода. Ну, и ещё картинку — для красоты. И в итоге получаем примерно следующее:



Что ж, неплохо, наш GUI уже готов! Можем жать Cmd+R и потыкать на кнопки (i) и Done, любуясь эффектом переворота виджета.

Но одного GUI нам мало, так что переходим к логике. Для этого слева вверху жмём на кнопку View и выбираем в выпадающем списке Source Code. И можем уже лицезреть наш автоматически сгенерированный JavaScript-код. И смело начинаем его править!

Для начала определимся с «архитектурой» нашего виджета. Мы будем по таймеру запрашивать через API хабра данные о пользователе, парсить их и отображать карму и рейтинг на лицевой стороне виджета. Для этого объявляем глобальную переменную updateTimer в начале файла main.js, создаём функции startTimer(msec) и stopTimer(), которые будут с этим таймером работать. Так же создадим функцию updateStats(), которая будет вызываться по таймеру.

function startTimer(msec)
{
    updateTimer = setTimeout("updateStats()", msec);
}

function stopTimer()
{
    clearTimeout(updateTimer);
}

function updateStats()
{
    alert("It works!");
    startTimer(updateInterval);
}

В функцию show() вставим вызов startTimer(5000) для запуска таймера при показе виджета, а в функцию hide(), соответственно, вставим stopTimer() для экономии ресурсов когда виджет не показан (Dashboard не активна). Теперь мы можем запустить наш виджет и увидеть в консоли (Cmd+Alt+1) вывод «It works!» каждые 5 секунд.

Но нас ведь не интересует такой вздор, мы хотим по таймеру дёргать карму и рейтинг! Так что в функции updateStatus() вместо алерта мы будем вызывать функцию execStatsRequest() (API хабра советует не дёргать данные о пользователе чаще, чем раз в минуту, так что увеличим заодно интервал).

Теперь дело за HTTP-запросами к API хабрахабра. Создаём новые функции — execStatsRequest() и processStatsRequest(), которые будут служить для запуска и обработки запросов. Вот как они выглядят у меня:
function execStatsRequest()
{
    if (userName().length > 0)
    {
        var Url = "http://habrahabr.ru/api/profile/" + userName() + "/";
        alert("User: " + userName() + "\nURL: " + Url);

        xmlHttp = new XMLHttpRequest(); 
        xmlHttp.onreadystatechange = processStatsRequest;
        xmlHttp.overrideMimeType('text/xml');
        xmlHttp.open("GET", Url, true);
        xmlHttp.send();
    }
    else
    {
        resetStats();
    }
}

function processStatsRequest()
{
    if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
    {
        alert("xml is " + xmlHttp.responseXML);
        if (xmlHttp.responseXML == null)
        {
            resetStats();
        }
        else
        {
            alert(xmlHttp.responseText);
            var error = xmlHttp.responseXML.getElementsByTagName("error")[0];
            if (error != null)
            {
                alert("Some error occured!");
                resetStats();
                setLogin("<" + userName() + " not found>");
                return;
            }
            var login = xmlHttp.responseXML.getElementsByTagName("login")[0].firstChild.nodeValue;
            var karma = xmlHttp.responseXML.getElementsByTagName("karma")[0].firstChild.nodeValue;
            var rating = xmlHttp.responseXML.getElementsByTagName("rating")[0].firstChild.nodeValue;
            var position = xmlHttp.responseXML.getElementsByTagName("ratingPosition")[0].firstChild.nodeValue;
            setLogin(login);
            setKarma(karma);
            setRating(rating);
            setPosition(position);
        }                    
    }
}

Здесь мы формируем URL запроса, создаём объект типа XMLHttpRequest, и с его помощью запрашиваем методом GET наши данные. Что примечательно, приходится насильно ставить ответу MIME-тип «text/xml», ибо хабра-апи возвращает почему-то «text/html». А в функции processStatsRequest() мы парсим полученный в XML ответ. При этом, мы проверяем его на наличие ошибки — и уведомляем об этом пользователя.

Тут стоит отвлечься от кода и настроить сам виджет — разрешить ему работать с сетью. Для этого в левой панели прокручиваем список элементов вниз и видим пункт Widget Attributes. Здесь просто ставим галочку «Allow Network Access». Так же можно настроить id виджета и его версию. Теперь вернёмся к коду.

Функции setLogin(), setKarma() и иже с ними отображают передаваемую в них строку в нужных полях лицевой стороны. Они были созданы для удобства и выглядят однотипно, вроде того:

function setLogin(login)
{
    document.getElementById("userName").innerText = login;
}

Функция же resetStats() устанавливает дефолтные значения для всех полей. А функции setUserName() и userName() служат обёрткой над полем ввода имени хабраюзера на тыльной стороне виджета:

function userName()
{
    return document.getElementById("nameEdit").value;
}

function setUserName(name)
{
    document.getElementById("nameEdit").value = name;
}

Что ж, виджет почти готов. Почему почти? Да потому что нам надо бы ещё сохранять в настройках введённое имя пользователя. Для этого пишем функции loadPrefs() и savePrefs().

var preferenceKey = "habraUserName";

function loadPrefs()
{
    var name = widget.preferenceForKey(widget.identifier + "-" + preferenceKey);
    alert(widget.identifier + "-" + preferenceKey);
    alert("name from preferences: " + name);
    if (name != null)
        setUserName(name);

}

function savePrefs()
{
    widget.setPreferenceForKey(userName(), widget.identifier + "-" + preferenceKey);
}

Эти функции целесообразно вызывать соответственно в функциях show() и hide(). Настройка будет уникальна для каждого виджета, что позволяет накидать на Dashboard виджеты с информацией по нескольким пользователям.

Ну, теперь уж точно виджет готов к использованию. Но нет предела совершенству! Локализуем теперь наш виджет, дабы иметь русскую и английскую версии. Вы так же можете сделать (как домашнее задание) локализацию на французский и японский языки.

Переходм к нашей лицевой стороне, вызываем инспектор. Теперь поочерёдно выделяем наши надписи и в разделе Localization инспектора выставляем в поле Value значение на английском языке. Они, вероятно, будут совпадать с предустановленными уже значениями поля Key. Эти значения будут внесены в дефолтную (английскую) локализацию, что можно увидеть в файле en.lproj/localizedStrings.js.

Теперь добавим русскую локализацию. Переходм в Widget Attributes и в разделе Localization добавляем (в левом списке) русский язык. Выбираем его, и теперь в правом списке можем вводить локализованные строки.

Эти значения, соответственно, будут прописаны в ru.lproj/localizedStrings.js.

Собственно, вот и всё, можем запускать наш виджет и любоваться своей (или чужой) кармой! Чтобы установить виджет в Dashboard, нужно выбрать Run & Share в левой панели и выбрать Save to Disk или Deploy to Dashboard — в зависимости от наших потребностей.

Если хочется скачать уже готовый виджет, то милости прошу: вот он! Исходный код проекта для Dashcode можно взять на гитхабе.

Надеюсь, кому-то эта статья оказалась полезной и список виджетов для Dashboard будет пополняться замечательными вещами!
Tags:
Hubs:
+41
Comments 14
Comments Comments 14

Articles