Гаджеты отличное дополнение к веб-сайту или сервису, они могут сообщать пользователю о новых публикациях, сообщениях, письмах, показывать постоянно изменяющуюся информацию (курсы валют, погоду), ускорять регулярные действия пользователя. Гаджет — это небольшая веб-страница, поэтому его создание не требует от веб-разработчика значительных усилий или освоения новых технологий.

Содержание статьи:

Hello World!

По старой традиции начнем с создания гаджета Hello World!

Создайте два файла:

gadget.xml — манифест гаджета

<?xml version="1.0" encoding="utf-8" ?>
<gadget>
  <name>Hello World!</name>
  <version>1.0</version>
  <hosts>
    <host name="sidebar">
    <base type="HTML" apiVersion="1.0.0" src="gadget.html" />
    <permissions>full</permissions>
    <platform minPlatformVersion="0.3" />
    </host>
  </hosts>
</gadget>

gadget.html — основной файл гаджета

<!DOCTYPE html>
<html>
  <head>
    <title></title>
    <style>
      body {
        width: 130px;
        height: 30px;
      }
    </style>
  </head>
  <body>
    <p>Hello World!</p>
  </body>
</html>

Запакуйте их в zip архив с расширением gadget.

Это и есть дистрибутив гаджета. Устанавливайте.

После установки гаджет автоматически добавляется на сайдбар.

Убедившись в простоте создания гаджета, перейдем к деталям.

Пример гаджета

Все описанное в статье собрано воедино в примере ExampleGadget.

Example Gadget

Файлы гаджета

Пользовательские гаджеты находится в директории %USERPROFILE%AppDataLocalMicrosoftWindows SidebarGadgets

Простейший гаджет состоит из двух файлов, манифеста и основного, находящихся в поддиректории GadgetName.gadget.

Подробный манифест включает информацию об авторе, описание, иконку и изображение, видимое при его перетаскивании на сайдбар:

<?xml version="1.0" encoding="utf-8" ?>
<gadget>
  <name>Пример гаджета</name>
  <version>1.0</version>
  <author name="Евгений Абашкин">
    <info url="designformasters.info" />
  </author>
  <copyright>2009</copyright>
  <description>Гаджет для Windows Sidebar</description>
  <icons>
    <!-- Иконка гаджета -->
    <icon width="128" height="128" src="icon.png" />
  </icons>
  <hosts>
    <host name="sidebar">
      <!-- имя и тип стартового файла гаджета -->
      <base type="HTML" apiVersion="1.0.0" src="main.html" />

      <permissions>full</permissions>
      <platform minPlatformVersion="0.3" />

      <!-- Изображение видимое при перетаскивании гаджета на сайдбар -->
      <defaultImage src="images/dockedBg.png" />
    </host>
  </hosts>
</gadget>

Описание элементов манифеста

HTML в основном файле гаджета практически ничем не отличается, от кода обычной веб-страницы.

<!DOCTYPE html>
<html>
  <head>
    <title></title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

    <link rel="stylesheet" type="text/css" href="main.css">

    <script type="text/javascript" src="jquery.js"></script>
    <script type="text/javascript" src="locale.js"></script>
    <script type="text/javascript" src="localize.js"></script>
    <script type="text/javascript" src="main.js"></script>
  </head>
  
  <body id="main">
    <g:background id="background" style="position:absolute;z-index:-1;top:0;left:0;"></g:background>
    
    <div id="gadgetContent">
      <p><a id="toggle-flyout" href="#"></a></p>
      
      <div id="systemData">
        <p><span id="CPU"></span>%</p>
        <p><span id="memory"></span> <span localize="megabyte"></span></p>
      </div>
    </div>
  </body>
</html>

Состояния гаджета: docked и undocked

У гаджетов есть два состояния docked и undocked. Максимальная ширина docked гаджета 130px, для undocked нет ограничений, рекомендуется не превышать 400px. Текущее состояние гаджета отражает переменная System.Gadget.docked, а при его изменении происходит событие System.Gadget.onUndock или System.Gadget.onDock. В обработчике изменения состояния нужно задать размеры body. Изменение класса body позволяет удобно управлять отображением внутренних элементов.

function dockStateChanged() {
  if (System.Gadget.docked) {
    $(document.body).removeClass('undocked')
                    .addClass('docked')
                    .css('width', '130px')
                    .css('height', '145px');
    // ...
  } else {
    $(document.body).removeClass('docked')
                    .addClass('undocked')
                    .css('width', '260px')
                    .css('height', '240px');
    // ...
  }
}

Фон

Фон гаджета задается с помощью элемента g:background:

<body>
  <g:background id="background" style="position:absolute;z-index:-1;top:0;left:0;">
  </g:background>
  <div id="gadgetContent">
  </div>  
</body>

Существует альтернативный способ задать фоновое изображение, через свойство System.Gadget.background, но тогда объект g:background не будет представлен в DOM.

Большинство гаджетов используют разное фоновое изображение для docked и undocked состояний, изменение изображения и размеров фона происходит в функции dockStateChanged.

function dockStateChanged() {
  if (System.Gadget.docked) {
    // ...
    $('#background').css('width', '130px')
                    .css('height', '145px')
                    .attr('src', 'images/dockedBg.png');
  } else {
    // ...
    $('#background').css('width', '260px')
                    .css('height', '240px')
                    .attr('src', 'images/undockedBg.png');
  }
}

К фону могут быть применены эффекты, например, тень:

Эффект тени

$('#background').get(0).addShadow("black", 20, 80, 5, 5);

Эффекты несколько замедляют работы гаджета, но иногда могут быть полезны.

Фоновый текст и изображения: g:text, g:image

Элементы g:text и g:image добавляют текст или изображение к фоновому слою гаджета. Создать их можно с помощью методов addImageObject и  addTextObject объекта g:background:

var background = $('#background').get(0);
var text = background.addTextObject('rotation', 'Arial', 25, 'white', 10, 10);
var image = background.addImageObject('images/arrow.png', 10, 10);

Также как и к фону, к этим элементам можно применить эффекты, например, blur и rotation:

Несмотря на то, что их можно создать и в HTML, такие объекты не будут обладать всей присущей им функциональностью.

Настройки

Сохранить и получить параметры гаджета можно с помощью методов System.Gadget.Settings.writeString, System.Gadget.Settings.readString.

System.Gadget.Settings.writeString('uri', 'http://designformasters.info/');
var uri = System.Gadget.Settings.readString('uri');

Ключи должны быть короче 1024 символов, а значения 2048 символов, значения длиннее урезаются.

Параметры сохраняются при перезапуске сайдбара или компьютера, но удаляются при закрытии гаджета. Чтобы сохранять настройки гаджета при его удалении с сайдбара или хранить больше данных, чем позволяет сайдбар, используйте файлы (библиотека Persistent Settings упрощает работу с файлами настроек).

Кнопка настроек гаджета

Страница настроек

Чтобы включить страницу настроек, нужно указать файл с ее разметкой:

System.Gadget.settingsUI = 'settings.html';

После этого на тулбаре гаджета появиться пиктограмма, клик на которой открывает настройки гаджета.

При закрытии окна настроек вызывается обработчик события System.Gadget.onSettingsClosed:

System.Gadget.onSettingsClosed = settingsClosed;

function settingsClosed(e) {
  if (e.closeAction == e.Action.commit) {
    //читаем и применяем настройки
  }
}

Пример скрипта для окна настроек:

function settingsClosing(event) {
  if (event.closeAction == event.Action.commit) {
  
    // убираем выделение ошибок
    $('.error').removeClass('error');
  
    var backgroundOpacity =  parseInt($('#backgroundOpacity').val());
    if(isNaN(backgroundOpacity) || backgroundOpacity > 100 || backgroundOpacity < 0) {
      // если backgroundOpacity не число от 0 до 100
      // отменяем закрытие окна настроек
      event.cancel = true;
      // показываем ошибку
      $('#backgroundOpacity').parent().addClass('error');
    }
    
    // если не было ошибок сохраняем значения
    if(!event.cancel) {  
      System.Gadget.Settings.writeString('backgroundOpacity', backgroundOpacity);
      // ...
    }
  }
}

function main() {
  System.Gadget.onSettingsClosing = settingsClosing;
  
  // считываем старые значения настроек и показываем их в форме
  $('#backgroundOpacity').val(System.Gadget.Settings.readString('backgroundOpacity'));
  // ...
}

$(document).ready(main);

Ширина страницы настроек не более 300px, а высота не ограничена (рекомендуется не превышать размеры 278x400px).

Flyout

Флайаут это дополнительная страница позволяющая расширить интерфейс гаджета. За работу с флайаутом отвечает объект System.Gadget.Flyout.

Флайаут для состояния undocked:

Флайаут для состояния docked:

Так же как и для страницы настроек, для флайаута нужно указать файл разметки:

System.Gadget.Flyout.file = 'flyout.html';

Отображением управляет свойство System.Gadget.Flyout.show. При открытии и закрытии флайаута происходят события System.Gadget.Flyout.onShow и System.Gadget.Flyout.onHide соответственно. Документ флайаута доступен через свойство System.Gadget.Flyout.document, основной документ гаджета доступен из флайаута через свойство System.Gadget.document. Поскольку флайаут может исчезнуть в любой момент, работа с ним из основного документа гаджета может привести к ошибкам.

Пример работы с флайаутом:

function onFlyoutShow() {
  $('#toggle-flyout').text(strings.hideFlyout);

  try {
    var fd = System.Gadget.Flyout.document;
    fd.getElementById('header').innerHTML = strings.flyoutHeader;
    fd.getElementById('description').innerHTML = strings.flyoutDescription;
  } catch (e) {
  
  }
}

function onFlyoutHide() {
  $('#toggle-flyout').text(strings.showFlyout);
}

function initFlyout() {
  // указываем файл флайаута
  System.Gadget.Flyout.file = 'flyout.html';
  System.Gadget.Flyout.onShow = onFlyoutShow;
  System.Gadget.Flyout.onHide = onFlyoutHide;
  
  $('#toggle-flyout').text(strings.showFlyout);

  // открываем и закрываем флайаут
  $('#toggle-flyout').get(0).onclick = function() {
    System.Gadget.Flyout.show = !System.Gadget.Flyout.show;
    return false;
  }
}

Чтобы изменять вид флайаута в зависимости от состояния гаджета меняем класс элемента body, сделать это нужно до того как флайаут отрендерится (фон и рамка не обновляются если изменены после отображения флайаута).

<body id="flyout" class="undocked">
  <script type="text/javascript">
  document.body.className = System.Gadget.document.body.className;
  </script>
  <div id="content"><div>
</body>

Протокол gimage

Протокол gimage позволяет эффективно получать превью изображений (пытается использовать кеш превью Windows для изображений меньше 256x256px).

Желаемый размер превью должен быть указан в запросе с помощью параметров width и height, если параметры не указаны, то они считаются равными 120, пропорции изображения сохраняются.

<img src="gimage:///c:garden.jpg?width=80&height=80">

Для файлов не являющихся изображениями gimage позволяет получить иконки.

function addIcons() {
  var files = [
    System.Gadget.path + '\\gadget.xml',
    System.Gadget.path + '\\main.html',
    System.Gadget.path + '\\main.css',
    System.Gadget.path + '\\main.js',
    'C:\\Windows\\System32\\calc.exe',
    'C:\\Program Files\\Windows Sidebar\\sidebar.exe'
  ];
  
  var icons = $('#icons').get(0);
  
  for(var i in files) {
    var icon = new Image();
    icon.src = 'gimage:///' + files[i] + '?width=32&height=32';
    icons.appendChild(icon);
  }
}

Несмотря на схожесть в названиях протокол gimage никак не связан с элементом g:image.

Локализация

Гаджеты поддерживают механизм локализации по папкам, т.е. при открытии любого ресурса (манифест, html, css, js, image) сайдбар ищет его в папках в следующем порядке:

  • Локаль полностью (en-us, es-us, ja-jp)
  • Языковая часть локали (en, es, ja)
  • Корневая папка гаджета

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

locale.js

var strings = {
  about: 'Gadget page',
  aboutTitle: 'En',
  aboutHref: 'http://designformasters.info/'
}

В скрипте вместо строк используются соответствующие элементы strings:

if(html == '') throw strings.dataError;

В html элементам требующим замены, зададим атрибут localize с именем строки из массива strings, а для локализации атрибутов будем использовать формат attrName:stringName:

<div id="about"><p><a localize="about,href:aboutHref,title:aboutTitle"></a></p></div>
В результате преобразования нам нужно получить:
<p><a href="http://designformasters.info/" title="En">Gadget page</a></p>

JQuery позволяет быстро реализовать такую локализацию:

$(document).ready(function localize() {
  $('*[localize]').each(function() {
    var localize = $(this).attr('localize');
    if(localize != '') {
      var list = localize.split(',');
      for(var i = 0, c = list.length; i < c; i++) {
        var parts = list[i].split(':');
        if(parts.length == 1) {
          $(this).html(strings[parts[0]]);
        } else {
          $(this).attr(parts[0], strings[parts[1]]);
        }
      }
    }
  });
});

Сохраним этот код в localize.js и подключим после скрипта locale.js в файлах требующих локализации.

<script type="text/javascript" src="locale.js"></script>
<script type="text/javascript" src="localize.js"></script>

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

Особенности

alarm и confirm

В гаджетах эти функции не работают, но хороший интерфейс и не предполагает их использования.

onclick

Обработчик click не добавляется методами:

element.attachEvent('onclick', function(){})
$(element).bind('click', function(){})

используйте свойство onclick:

element.onclick = function(){}
$(element).get(0).onclick = function(){}

Скорее всего, это связано с обработчиком перетаскивания гаджета. Если задан element.onclick то за этот элемент гаджет уже не перетаскивается.

Отладка

Гаджет можно отлаживать с помощью Visual Studio. Для этого нужно разрешить использование отладчиков в настройках Internet Explorer.

Разрешение отладки скриптов в IE

Добавить в нужном месте кода гаджета строку:

debugger;

и перезапустить гаджет.

Отладка гаджета в Visual Studio

Юзабилити гаджетов

Гаджет стоит использовать для отображения важной и регулярно обновляющейся информации, или для функционала, который часто требуется пользователю.

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

Не нужно стремиться к универсальности лучше оптимизировать гаджет для единственной задачи.

Графическое оформление должно быть привлекательным и отражать задачу гаджета.

Интерфейс гаджета должен быть привычным для Windows пользователя.

Юзабилити гайдлайн по гаджетам

Безопасность

Гаджеты могут использовать любые установленные ActiveX объекты (но не могут их устанавливать) и получать доступ к любому домену. К файлам и приложениям гаджет получает доступ с правами пользователя, от имени которого запущен (за исключением администратора в Windows Vista). К установке гаджетов следует относиться не менее ответственно, чем к установке приложений.

Gadgets for Windows Sidebar Security

Распространение и установка

Установочный файл гаджета, это zip архив с расширением gadget. При запуске такого файла сайдбар распакует его в папку пользовательских гаджетов %USERPROFILE%AppDataLocalMicrosoftWindows SidebarGadgets. Гаджеты, доступные всем пользователям компьютера, находятся в папке %SYSTEM_ROOT%Program FilesWindows SidebarGadgets.

Кроме zip, можно использовать форматы cab и msi.

Чтобы гаджет устанавливался кликом по ссылке, mime-тип файлов .gadget должен быть application/x-windows-gadget:

AddType application/x-windows-gadget .gadget