Руководство для плагинов

Languages:
English •
a Plugin 日本語
Русский

(Add your language)

Contents

  • 1 Введение
    • 1.1 Ресурсы
  • 2 Создание плагина
    • 2.1 Имена, файлы и местоположения
      • 2.1.1 Имя плагина
      • 2.1.2 Файлы плагина
      • 2.1.3 Файл Readme
      • 2.1.4 Домашняя страница
    • 2.2 Заголовки файла
      • 2.2.1 Стандартная информация о плагине
      • 2.2.2 Лицензия
    • 2.3 Программирование плагина
      • 2.3.1 Зацепки (Hook) плагина
      • 2.3.2 Теги шаблонов
      • 2.3.3 Сохранение данных плагина в базе
      • 2.3.4 Механизм настроек WordPress
      • 2.3.5 Панели администирования
    • 2.4 Интернационализация плагина
  • 3 Советы по разработке плагина
  • 4 Внешние ресурсы

Введение

Вплоть до версии WordPress 1.2 возможность изменения его функционала «под свои потребности» или расширение возможностей достигались путем редактирования исходного кода ядра платформы WordPress (грубо говоря, «хакинга» ядра). Но это создавало различные неудобства (например, при обновлении версий), и от такой практики вскоре отказались. Разработчики внедрили достаточно удобную, понятную и легкую в использовании программистами систему расширения функционала с помощью «плагинов». Основная идея использования новой системы расширения возможностей состояла в том, чтобы сохранять ядро целостным и неизменяемым и в то же время дать PHP-программистам возможность изменять его поведение с помощью специальных легко подключаемых (и отключаемых) скриптов-плагинов. Итак, что такое плагин WordPress?

Плагин WordPress 
Плагин WordPress — это программа или набор функций, написанных на PHP, добавляющих определенный набор возможностей или сервисов к блогу на WordPress, которые легко объединяются с системой управления и функционалом WordPress при помощи Plugin Application Program Interface (API).

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

Эта статья подразумевает, что вы уже знакомы с основами функциональности WordPress, а также с языком программирования PHP.

Ресурсы

  • Plugin Resources — всеобъемлющий список статей и средств для разработчиков плагинов, включающий в себя развернутые статьи по написанию плагинов и статьи на специфические «узкие» темы.
  • Другой хороший путь изучить устройство плагинов — смотреть в исходные PHP-коды хорошо написанных плагинов, таких как Hello Dolly (плагин, входящий в базовую поставку WordPress).
  • Если вы написали плагин к WordPress, прочитайте Plugin Submission and Promotion, чтобы узнать, как распространить ваш плагин.

Создание плагина

Эта часть статьи даст вам понять, какие шаги вы должны предпринять для создания хорошего плагина.

Имена, файлы и местоположения

Имя плагина

Первая задача при создании плагина — подумать, что плагин будет делать, и придумать для него имя (желательно уникальное). Проверьте «Плагины» и другие хранилища, чтобы убедиться в том, что придуманное вами имя — уникальное; вы можете также погуглить по выбранному вами имени. Большинство разработчиков плагинов выбирают имена, которые отражают функциональность их плагина; например, плагин для отображения погоды может иметь в названии слово «погода». Название может состоять из нескольких слов.

Файлы плагина

Следующий шаг — создание файла PHP с именем, производным от названия плагина. Например, если ваш плагин будет называться Fabulous Functionality, вы можете назвать ваш файл fabfunc.php. Опять же, попробуйте создать уникальное имя. Люди, которые установят ваш плагин, положат этот файл в свою директорию для плагинов, wp-content/plugins/, и никакая пара используемых плагинов не должна иметь одинаковое имя файла.

Другой вариант — разбить ваш плагин на несколько файлов. Ваш плагин должен иметь как минимум один файл PHP; он также может содержать файлы JavaScript, CSS, изображения, языковые файлы и т.п. Если ваш плагин состоит из нескольких файлов, задайте уникальное имя для директории, в которой они лежат, и для главного файла PHP, такие как fabfunc и fabfunc.php в нашем примере, положите ваши файлы в эту директорию и дайте пользователям возможность устанавливать целую директорию в wp-content/plugins/.

В этой статье «PHP-файл плагина» означает главный PHP-файл, который находится в директории wp-content/plugins/ или в ее поддиректории.

Файл Readme

Если вы хотите разместить ваш плагин на http://wordpress.org/extend/plugins/, вам необходимо создать файл readme.txt в стандартном формате и включить его в свой плагин. Смотрите http://wordpress.org/extend/plugins/about/readme.txt для получения разъяснений по формату.

Домашняя страница

Также очень удобно создать веб-страницу, играющую роль «домашней страницы» вашего плагина. Эта страница должна объяснять, как установить плагин, что он делает, с какими версиями WordPress совместим, что менялось от версии к версии вашего плагина, и как его использовать.

Заголовки файла

Самое время внести некоторую информацию в ваш главный файл PHP.

Стандартная информация о плагине

Начало вашего файла должно содержать стандартный информационный заголовок. Этот заголовок позволяет WordPress понять, что ваш плагин существует, добавить его в панель управления плагинами, где он может быть активирован, загрузить его и запустить его функции; без заголовка ваш плагин никогда не будет активирован и запущен. Вот формат заголовка:

<?php
/*
Plugin Name: Название плагина
Plugin URI: http://страница_с_описанием_плагина_и_его_обновлений
Description: Краткое описание плагина.
Version: Номер версии плагина, например: 1.0
Author: Имя автора плагина
Author URI: http://страница_автора_плагина
*/
?>

Минимальная информация, которая нужна WordPress, чтобы обнаружить ваш плагин — его название (Plugin Name). Остальная информация (если она есть) используется для создания таблицы плагинов на странице управления плагинами. Порядок строк неважен.

Лицензия

За стандартным заголовком обычно следует информация о лицензии на плагин. Большинство плагинов используют лицензию GPL или лицензию, совместимую с GPL. Для указания лицензии GPL добавьте следующие строки в файл вашего плагина:

<?php
/*  Copyright ГОД  ИМЯ_АВТОРА_ПЛАГИНА  (email: E-MAIL_АВТОРА)

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/
?>

Программирование плагина

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

Зацепки (Hook) плагина


Итак, как же взаимодействуют компоненты системы плагин + ядро WordPress? Для того, чтобы плагины имели возможность влиять на работу ядра WordPress или на конечный результат его действий, была придумана система так называемых зацепок (часто их без перевода так и называют «хуками» от англ. hook — крючок, зацепка). Принцип ее действия состоит в том, что каждая более или менее важная элементарная функция в ядре WordPress перед тем как вернуть какой-то результат своей работы или совершить какое-то важное действие (например вывести содержимое записи на странице, или произвести запрос к базе данных) «пытается» исполнить дополнительные инструкции (строки кода), предназначенные именно для нее в файлах плагина. Такую попытку она делает с помощью зацепок, которые прописаны в теле этой функции. Вот пример вызова зацепок плагинов из ядра WordPress:

<?php
function get_the_title( $id = 0 ) {
…
…
 return apply_filters( 'the_title', $title, $post->ID ); //Пример зацепки для функции get_the_title();
}
?>

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

Например, перед тем как WordPress добавляет заголовок к записи, он сначала проверяет, имеет ли какой-либо плагин зарегистрированные функции для зацепки под названием «the_title».

<?php
…
 add_filter( 'the_title', 'my_own_function_for_title' ); //Так выглядит регистрация в файле плагина новой функции my_own_function_for_title(); с дополнительным инструкциями для зацепки 'the_title'.
…
?>

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

<?php
…
/* Так в плагине может выглядеть функция, изменяющая заголовки WordPress. В данном случае она заставляет выводить каждое слово заголовка с большой буквы. 
*/
 my_own_function_for_title( $title ){
      $title = ucwords($title);
      return $title;
} 
…
?>

Таким образом, если ваш плагин должен добавлять некую информацию к заголовку записи или изменять ее, в нем должна быть зарегистрирована зацепка-фильтр для «the_title» и в нем должна быть фукнция, которая делает все нужные изменения с заголовками.

Другой пример — существует зацепка под названием «wp_footer». Перед концом HTML-страницы, которую генерирует WordPress, он проверяет, имеют ли какие-нибудь плагины зарегистрированную функцию для «wp_footer», и запускает ее в случае обнаружения таковой.

Все зацепки в WordPress делятся на две категории — Фильтры и Действия. (filters и actions соответственно). Фильтры (filters) действительно предназначены для «фильтрования» (изменения) любых данных, перед тем как они будут выведены на странице или добавлены для хранения в базу данных. Это фильтрация спама, ошибок или просто ошибочного ввода в формах, откуда, собственно, и произошло английское название. А вторые (действия, actions) предназначены для замены различных действий ядра вашими действиями (например изменения строки запроса к базе данных), в программировании такое изменение действий базового функционала ещё называют перегрузкой.

Вы можете узнать больше о том, как регистрировать функции для Фильтров и Действий, и какие действия ядра можно изменить в WordPress, в Plugin API. Если вы нашли место в коде WordPress, где вы хотели бы иметь Действие или Фильтр, но в WordPress его нет, вы можете предложить новые зацепки (предложения в основном принимаются); как это сделать, вы можете узнать в Reporting Bugs.

Теги шаблонов

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

Чтобы объявить тег шаблона, просто напишите функцию PHP, и задокументируйте ее для пользователей плагина на вашей странице, посвященной плагину и/или в главном файле плагина. Хорошая идея, документируя функцию, приводить пример выполнения, содержащий <?php и ?>, который нужно добавить в тему для получения результата..

Сохранение данных плагина в базе

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

  1. Используйте механизм настроек WordPress (о нем рассказывается ниже). Этот метод предназначен для хранения относительно небольшого количества именованной статической информации — данных, которые владелец блога вводит при первом запуске плагина, и затем редко изменяет.
  2. Создайте новую отдельную таблицу в базе данных. Этот метод предназначен для данных, связанных с определенными записями, страницами, приложениями или комментариями — данных, объем которых растет с течением времени, и которые не имеют индивидуальных имен. Смотрите Creating Tables with Plugins для получения информации, как создать таблицу плагина.

Механизм настроек WordPress

Информацию о том, как создать страницу, автоматически сохраняющую ваши настройки, можно получить в статье «Создание страниц настройки».

WordPress имеет механизм для сохранения, обновления и извлечения отдельных именованных данных, хранящихся в базе WordPress. Значения настроек могут быть строками, массивами или объектами PHP (они будут сериализованы или сконвертированы в строку перед записью, и десериализованы перед извлечением). Названия настроек — строки, и они должны быть уникальными, чтобы не конфликтовать с WordPress или другими плагинами.

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

add_option($name, $value, $deprecated, $autoload);
Создает новую настройку; не делает ничего, если опция уже существует.
$name
Обязательный (строка). Имя настройки.
$value
Необязательный (строка), по умолчанию — пустая строка. Значение настройки.
$deprecated
Необязательный (строка), больше не используется WordPress. Можно передать пустую строку или null, чтобы использовать следующий за ним параметр $autoload.
$autoload
Необязательный, по умолчанию — «да» (enum: «да» или «нет»). Если установлено «да», настройки автоматически извлекаются функцией get_alloptions.
get_option($option);
Извлекает значение настройки из базы.
$option
Обязательный (строка). Имя настройки, значение которой нужно получить. Список настроек по умолчанию, созданных при установке WordPress, можно найти в Option Reference.
update_option($option_name, $newvalue);
Обновляет или создает значение настройки в базе (примечание: можно не вызывать add_option, если вам не нужен параметр $autoload).
$option_name
Обязательный (строка). Имя настройки для обновления.
$newvalue
Обязательный. Новое значение настройки.

Панели администирования

При условии, что ваш плагин имеет некие опции, хранящиеся в базе WordPress (см. раздел выше), вы, вероятно, захотите иметь административную панель, которая позволит пользователям смотреть и редактировать настройки вашего плагина. Методы создания панелей описаны в статье «Добавление административных меню».

Интернационализация плагина

После того, как вы закончили писать ваш плагин, его необходимо интернационализировать (при условии, что вы планируете распространять ваш плагин). Интернационализация — это процесс настройки программного обеспечения под локализацию; локализация — это процесс перевода на различные языки отображаемого программой текста. WordPress используется по всему миру, и интернационализация и локализация встроены в его структуру, в том числе, и локализация плагинов. Подробности использования GNU gettext для локализации WordPress можно узнать в Translating WordPress.

Настоятельно рекомендуется интернационализировать ваш плагин, чтобы люди из разных стран могли локализовать его. Процесс прост:

  • Выберите имя для пространства перевода вашего плагина. Обычно оно такое же, как имя главного файла вашего плагина (только без .php). Имя должно быть уникальным.
  • Везде, где ваш плагин использует строки текста, которые будут показаны пользователю (известны как «сообщения»), заключите их в одну из двух gettext-функций WordPress. Заметьте, что в вашем плагине вы должны использовать второй аргумент — имя пространства перевода, которое вы выбрали (в ядре WordPress аргумент $domain остается пустым).
__($message, $domain) 
Переводит $message, используя текущую локаль для $domain. Оберните строки, которые собираетесь использовать в расчетах, в эту функцию.
_e($message, $domain) 
Переводит $message, используя текущую локаль для $domain, и выводит на экран. Оберните в эту функцию строки, которые собираетесь показывать пользователю.
  • Создайте для вашего плагина файл POT (каталог переводов для всех переводных сообщений) и распространяйте его вместе с плагином. Пользователям необходимо будет положить MO-файл перевода в директорию вашего плагина и назвать его domain-ll_CC.mo, где ll_CC — имя нужной локали. Для получении информации о файлах POT, MO и локалях см. Translating WordPress.
  • Загружайте перевод для текущей локали и ваше текстовое пространство с помощью функции load_plugin_textdomain до того, как вызываются функции gettext, но настолько поздно, насколько возможно в сессии (потому что некоторые многоязычные плагины меняют локаль при загрузке). Одна из возможных реализаций — объявление функции инициализации, которая вызывается выше всех функций вашего плагина. Например, ваше пространство текста называется «fabfunc»:
$fabfunc_domain = 'fabfunc';
$fabfunc_is_setup = 0;

function fabfunc_setup()
{
   global $fabfunc_domain, $fabfunc_is_setup;

   if($fabfunc_is_setup) {
      return;
   } 

   load_plugin_textdomain($fabfunc_domain, PLUGINDIR.'/'.dirname(plugin_basename(__FILE__)));
}

Если ваш плагин не находится в собственной поддиректории, второй аргумент функции load_plugin_textdomain можно опустить.

Если вы читаете эту секцию, чтобы интернационализировать тему, можно следовать приведенным выше рекомендациям, с некоторыми исключениями:

  • MO-файл нужно скопировать в каталог темы (рядом со style.css).
  • MO-файл нужно назвать ll_CC.mo, где ll_CC — имя локали (т.е. имя пространства не должно быть частью имени файла).
  • Чтобы загрузить имя пространства перевода, вставьте следующий код (с PHP-заголовком, если необходимо) в файл functions.php вашей темы:
load_theme_textdomain('имя_пространства');

Советы по разработке плагина

Это последняя часть статьи, включающая в себя различные советы по разработке плагина.

  • Код плагина должен соответствовать стандартам разработки WordPress. Пожалуйста, примите во внимание также стандарты Inline Documentation.
  • Все функции вашего плагина должны иметь уникальные имена, отличные от имен функций ядра WordPress, других плагинов или тем. По этой причине, хорошая идея — использовать уникальный префикс для имен функций вашего плагина. Другая возможность — объявлять ваши функции внутри класса (который тоже должен иметь уникальное имя).
  • Не используйте явно префикс базы данных WordPress (обычно «wp_») в вашем плагине. Вместо этого используйте переменную $wpdb->prefix.
  • Чтение базы — легкий процесс, а вот запись в базу — сложный. Базы исключительно хороши при сборке данных и их выдаче, эти операции обычно выполняются быстро. Внесение изменений в базу — более комплексный процесс, следовательно более ресурсоемкий. В результате, постарайтесь уменьшить количество записей в базу. Держите все готовым в коде, тогда вы сможете делать только те записи в базу, которые действительно нужны.
  • Выбирайте из базы при помощи SELECT только то, что вам нужно. Даже несмотря на то, что базы извлекают данные достаточно быстро, вы можете уменьшить нагрузку на базу, выбирая только те данные, которые вам нужны. Если вам нужно подсчитать количество строк в таблице, не используйте SELECT * FROM, потому что все данные всех строк будут занимать память. Подобно этому, если вам нужны только post_id и post_author в вашем плагине, выбирайте с помощью SELECT только эти конкретные поля, чтобы уменьшить нагрузку. Помните: сотни других процессов могут обращаться к базе одновременно с вами. База и сервер могут только распределять ресурсы между процессами. Изучите, как минимизировать обращения вашего плагина к базе, чтобы гарантировать, что ваш плагин не злоупотребляет ресурсами.

Внешние ресурсы

  • Simplified AJAX For WordPress Plugin Developers using Jquery(10APR08)
  • «Desenvolvendo Plugins para WordPress» by Rafael Dohms (in Brazilian Portuguese) (10MAR08)
  • 12 part «How to Write a WordPress Plugin» at DevLounge.net by Ronald Huereca (PDF)
  • How to create WordPress Plugin from a scratch (9AUG07)
  • Using AJAX with your WordPress Plugin, also at DevLounce.net (25MAY07)
  • How to Write a Simple WordPress Plugin at ATD (22FEB05)

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

Создание плагина

Чтобы создать плагин WordPress нужно:

  1. Создать папку плагина. В ней будут лежать файлы нашего плагина. Создается она в папке всех плагинов WordPress. Например, создаем папку /wp-content/plugins/my-plugin-name.

  2. Создать главный файл плагина. Рекомендуется чтобы название этого файла совпадало с названием папки плагина. Например, my-plugin-name.php/wp-content/plugins/my-plugin-name/my-plugin-name.php.

  3. Создать описание плагина — заголовки плагина. Они нужны чтобы wordpress распознал плагин как плагин, иначе он просто не будет работать. В самое начало главного файла плагина, нужно добавить php комментарий, где указать имя плагина:

    <?php
    
    /*
     * Plugin Name: Мой первый плагин
     */

Готово! Теперь наш плагин можно увидеть в разделе Плагины в админ-панели.

Если плагин является всего одним файлом как Hello Dolly, то его можно поместить прямо в папку плагинов — /plugins/hello.php и все будет работать. Однако, рекомендуется соблюдать стандарт: файлы плагина должны находиться в своей собственной папке, а главный файл плагина должен иметь название папки плагина.

Дополнительные данные в заголовке

Чтобы плагин начал работать достаточно указать только Plugin Name (название плагина). Но можно также указать другие параметры плагина — это улучшит отображение плагина в консоли WordPress.

Build In Post

<?php

/**
 * Plugin Name: Название плагина
 * Description: Описание плагина желательно не очень длинное (140 символов)
 * Plugin URI:  Ссылка на страницу плагина
 * Author URI:  Ссылка на автора
 * Author:      Имя автора
 * Version:     Версия плагина, например 1.0
 *
 * Text Domain: ID перевода, указывается в load_plugin_textdomain()
 * Domain Path: Путь до файла перевода.
 * Requires at least: 2.5
 * Requires PHP: 5.4
 *
 * License:     GPL2
 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
 *
 * Network:     Укажите "true" для возможности активировать плагин для сети Multisite.
 * Update URI: https://example.com/link_to_update
 */

// код плагина
Plugin Name:(обязательный)
Название плагина, которое отображается в списке плагинов в админке.
Description:
Краткое описание плагина, которое отображается в разделе Плагины в в админке. Рекомендуется не превышать 140 символов.
Version:

Номер текущей версии плагина, например, 1.0 или 1.0.3.

При установке версии имейте ввиду, что WP для сравнения версий использует функцию version_compare(). Поэтому при изменении версии убедитесь что новая версия будет выше. Например, 1.02 больше чем 1.1

Plugin URI:
Домашняя страница плагина, которая может быть на WordPress.org или на вашем собственном сайте.
Author:
Имя автора плагина. В списке может быть более одного автора.
Author URI:
Сайт автора или профиль на другом веб-сайте, например, WordPress.org.
Requires at least:
Самая низкая версия WordPress, на которой будет работать плагин. Например: 2.5.
Requires PHP:
Минимальная требуемая версия PHP. Например: 5.4.
License:
Короткое имя лицензии плагина, например GPL2. Более подробную информацию о лицензировании можно найти на WordPress.org.
License URI:
Ссылка на лицензию, например, https://www.gnu.org/licenses/gpl-2.0.html .
Text Domain:
Идентификатор перевода (домен локализации) используется в функциях локализации и в названии файла перевод mo. Подобрее смотрите цитату здесь.
Domain Path:
Нужен если файл перевода находится не в той же папке, в которой находится текущий файл. Например, .mo файл находится в папке /myplugin/languages, а файл плагина в /myplugin/myplugin.php, тогда тут указываем /languages.
Network:
Укажите «true» чтобы плагин обязательно активировался по всей сети сайтов в MU сборке WordPress. Это сделает невозможным активировать плагин для одного сайта, если активирована мультисеть.
Update URI:
URL для обновления сайта. Используется в функции wp_update_plugins(). Из домена будет создан хук update_plugins_(hostname).

Шаблон для создания плагина WordPress

Чтобы не создавать файлы и структуру с нуля, можно использовать шаблон для создания плагина:

  • WordPress Plugin Boilerplate — генератор шаблона, где указывается название плагина, которое будет использовано в названиях папок, классов и функций — WordPress Plugin Boilerplate Generator.

Шаблон представляет собой стандартную и организованную объектно-ориентированную основу.

Шаблон придерживаются стандартов PHP кода для WordPress.

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

Хуки в плагине

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

Существует два типа хуков в WordPress:

  • События (actions) — позволяют добавлять или изменять функционал WordPress.
  • Фильтры (filters) — позволяют изменять данные.

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

Непосредственно к плагинам относятся три функции:

  • register_activation_hook() — регистрирует функцию, которая будет срабатывать во время активации плагина.
    Используется для добавления настроек плагина и т.п.

  • register_deactivation_hook() — регистрирует функцию, которая должна запускаться после деактивации плагина.
    Используется для удаления временных данных плагина.

  • register_uninstall_hook() — регистрирует функцию, которая вызывается при удалении плагина.
    Используется при удалении плагин для удаления всех данных плагина: в настройках, в файлах, в базе данных и т.д.

Можно создавать свои собственные хуки в исходном коде плагина с помощью do_action() или apply_filters(). Они позволят пользователям вашего плагина расширить его возможности, точно также как WordPress позволяет вам расширять свои возможности.

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

PHP Функции и WordPress API

WordPress предоставляет ряд API. API могут значительно упростить написание кода. Т.е. не нужно изобретать колесо, когда оно уже есть.

Некоторые API WordPress:

  • API настроек — упрощает создание и управление опциями плагина, которые сохраняются в базу данных.
  • plugin_dir_url() — Получает URL папки (директории, каталога), где находится указанный файл плагина (со слэшем на конце).
  • register_activation_hook() — Регистрирует функцию, которая будет срабатывать во время активации плагина.
  • register_deactivation_hook() — Регистрирует функцию, которая будет запускаться после деактивации плагина.
  • register_uninstall_hook() — Регистрирует функцию, которая вызывается при удалении плагина, чтобы почистить все следы прибывания плагина в системе.
  • HTTP API — упрощает создание HTTP запросов в PHP. Отличная замена велосипедов на cURL.

Как WordPress загружает плагины

При активации плагина WordPress, записывает путь на его главный файл в опцию active_plugins. Далее при загрузке любой страницы (админки и фронта) WordPress просто подключает все файлы из опции active_plugins (пути на них там хранятся в виде массива). Смотрите как это выглядит:

$active_plugins = get_option( 'active_plugins' );

/* Получим в $active_plugins
Array
(
	[0] => hello-dolly/hello-dolly.php
	[1] => backupwordpress/backupwordpress.php
	[2] => democracy-poll/democracy.php
	[3] => disable-emojis/disable-emojis.php
)
*/

Из всего этого следует: просто своим присутствием плагины не влияют на скорость работы сайта (за исключением подключения файла плагина, а это супер быстрая операция). Неактивные плагины не влияют вообще никак. Подробнее читайте здесь.

Репозиторий плагинов WordPress

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

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

Если планируете отправлять плагин на WordPress.org, нужно следовать требованиям к заголовку плагина WordPress.

Лицензия сообщает пользователям, как они могут использовать код плагина в своих целях. Для поддержания совместимости с ядром WordPress рекомендуется выбрать лицензию, работающую с GNU General Public License (GPLv2+).

Содержание
  1. Введение
  2. Четыре простых шагов для создания WordPress плагина
    1. Шаг 1. Подберите имя для вашего плагина
    2. Шаг 2. Создайте папку с плагином и PHP файл
    3. Шаг 3. Создайте заголовок файла
    4. Шаг 4: Добавьте функционал созданному плагину
  3. Практические советы по созданию своих плагинов для WordPress
    1. Основные советы
    2. Структура и архитектура плагина
    3. Готовый шаблон WordPress плагина

🤖 Шаблон WordPress плагина (генератор)

Стандартизированная, организованная, объектно-ориентированная основа для создания высококачественных WordPress плагинов.

Сгенерировать свой плагин

Раз уж вы попали на эту страницу значит у вас есть сайт на WordPress и вам наверняка знакомы возможности различных плагинов. Однако использование плагинов на сайте и создание собственного плагина – это две разные вещи. Давайте разбираться как создать плагин для WordPress!

Введение

К счастью, создавать собственные плагины для WordPress проще, чем вы могли бы подумать. Первый ключевой шаг – получить четкое представление о том, как работают плагины и как они создаются. После того, как вы определились с основными концепциями, создание и установка собственных плагинов станет довольно простой задачей.

В этом посте мы объясним основы разработки плагинов для WordPress, включая необходимые элементы и то, как они работают вместе.

Давайте начнем!

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

Шаг 1. Подберите имя для вашего плагина

Первый шаг в разработке плагина для WordPress – придумать официальное название для вашего плагина.

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

Называя плагин, убедитесь, что он уникален, конкретен и легко сокращается. #WordPress

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

Шаг 2. Создайте папку с плагином и PHP файл

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

Для начала перейдите в папку wp-content/plugins в каталоге где установлен WordPress. Создайте новую папку и назовите ее, используя имя плагина, используя дефисы для разделения слов. Например, my-super-plugin.

Структура папок - Как создать плагин для WordPress

Структура папок – Как создать плагин для WordPress

После того, как вы создали папку с плагинами, следующим шагом будет создание внутри нее файла PHP.

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

Шаг 3. Создайте заголовок файла

После того как вы создадите основной файл плагина, пора добавить заголовок файла. По сути, это комментарий блока PHP, который включает метаданные о вашем плагине.

Внутри файла добавьте следующий код:

/**
* Plugin Name: Мой супер плагин
* Plugin URI: https://wordpresslab.ru/plugins/kak-sozdat-plagin-dlya-wordpress/
* Description: Этот плагин делает мир лучше!
* Version: 1.0.0
* Author: WordPress лаборатория
* Author URI: https://wordpresslab.ru/
* License: GPL2
*/

Не забудьте заменить приведенную выше информацию на сведения, относящиеся именно к вашему плагину. Кроме того, если в вашем каталоге плагинов есть несколько файлов PHP, убедитесь, что вы добили этот заголовок только к одному из них.

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

Когда вы закончите, сохраните изменения и ваш плагин будет добавлен на сайт. Чтобы убедиться, перейдите на панель администратора WordPress и перейдите в раздел «Плагины»:

Как создать плагин для WordPress - Активация плагина

Как создать плагин для WordPress – Активация плагина

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

Шаг 4: Добавьте функционал созданному плагину

Большинство плагинов работают с помощью хуков (hook), которые позволяют одному фрагменту кода взаимодействовать с другим. В WordPress есть два типа хуков: действия (actions) и фильтры (filters). Мы обсудим их более подробно в рамках отдельной статьи, пока лишь основы…

Actions (действия)

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

Пример действия в WordPress – save_post. Действия определяются функцией do_action. Для них требуется параметр $tag (имя действия) и в некоторых случаях $args (дополнительные аргументы, расширяющие то, что делает действие).

Ядро WordPress уже содержит десятками предопределенных действий. Однако вы также можете создать свои собственные. В любом случае, при создании плагина для WordPress вы будете использовать do_action для установки значений к подключенной функции. Затем функция add_action будет использоваться для подключения этой функции к определенному действию.

Подробнее про экшен-хуки читайте тут.

Filters (фильтры)

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

Фильтры в WordPress создаются с помощью функции apply_filters и определяются внутри функции. Для них требуются аргументы $tag (имя фильтра) и $value (отфильтрованное значение или переменная) с возможностью использования $var для дополнительных значений функции.

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

Подробнее про фильтр-хуки читайте тут.

Практические советы по созданию своих плагинов для WordPress

Важно понимать, что вы не первый, кто создает свой собственный WordPress плагин. До вас были десятки тысяч и столько же будет после вас. В связи с этим стоит обратить внимание на передовые методики. Это поможет обеспечить плавный процесс разработки и повысить эффективность вашего плагина.

Основные советы

  • Избегайте конфликтов имен.
  • Все переменные, функции и классы должны иметь префикс с уникальным идентификатором.
  • PHP предоставляет ряд функций для проверки существования переменных, функций, классов и констант. Используйте эту возможность!
  • Самый простой способ решить проблему коллизии имен – использовать классы для кода вашего плагина. Метод объектно-ориентированного программирования.
  • Корневой уровень каталога вашего плагина должен содержать ваш файл название-плагина.php и при желании, файл uninstall.php. Все остальные файлы по возможности должны находится во вложенных папках.

Структура и архитектура плагина

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

Архитектура или организация кода должна зависеть от размера вашего плагина.

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

Для больших плагинов с большим количеством кода создание классов является стандартом. Это поможет в организации кода и долгосрочном обслуживании плагина.

Готовый шаблон WordPress плагина

Вместо того, чтобы начинать с нуля для каждого нового плагина, вы можете начать с шаблона.

Одно из преимуществ использования шаблона – согласованность ваших собственных плагинов. Плагины, созданные по шаблонам, также облегчают жизнь другим людям!

🤖 Шаблон WordPress плагина (генератор)

Стандартизированная, организованная, объектно-ориентированная основа для создания высококачественных WordPress плагинов.

Сгенерировать свой плагин

Надеемся эта статья помогла вам чуть больше погрузиться в мир WordPress и вы разобрались как создать плагин для WordPress 😊

Если у вас есть вопросы – спрашивайте в комментариях.

Спасибо.

Это пошаговое руководство по разработке плагинов для WordPress. В нем описываются действия, которые я выполнял при разработке реального плагина.

  • Описание необходимого функционала плагина
    • Плагин для одного сайта
  • Пошаговое руководство по разработке плагина для WordPress
    • Начало
      • Примечания к шагу 1
    • Запрос последних записей всех типов
      • Примечания к шагу 2
    • Как запросить все записи, опубликованные в этом месяце
    • Объединение запроса даты с основным запросом
      • Примечания к шагу 4
    • Отладка
      • Примечания к шагу 5
    • Повторяем попытку
      • Примечания к шагу 6
    • Это не сработало — отладка
      • Примечания к шагу 7
    • Пользовательский интерфейс и шорткод
      • Примечания к шагу 8
    • THE_DATE() работает довольно странно?
      • Примечания к шагу 9
  • Общие мысли о разработке плагинов WordPress

Мы создадим плагин, который будет сообщать о том, какие посты (любого типа) были опубликованы в этом календарном месяце, и кто их авторы.

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

Последовательность моих шагов:

<?php 
/**
 * Plugin Name: WPShout Show Authorship this Month
 * Description: Show who's written what this month on WPShout
 * Version: 1.0
 * Author: WPShout
 * Author URI: https://wpshout.com
*/

add_action( 'init', 'wpshout_do_thing' );
function wpshout_do_thing() {
		if( ! isset( $_GET['wpsdt'] ) ) :
			return;
		endif;

		var_dump("Here");
		die;
}

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

Обратите внимание на строку if( ! isset( $_GET[‘wpsdt’] ) ). В ней вызывается суперглобальная переменная PHP $_GET для запуска плагина. Она задействуется тогда, когда в URL-адресе есть строка запроса wpsdt. Этот прием позволяет запускать плагин путем изменения параметров URL-адресов.

Я также подключаю событие WordPress init, чтобы плагин запускался раньше остальных. По этой же причине я назвал перехваченную функцию wpshout_do_thing(). Благодаря чему она не вызовет конфликта пространства имен.

Код var_dump(); die позволяет убедиться, что плагин выводит данные и прекращает другие процессы PHP.

Результат работы кода, приведенного выше:

Примечания к шагу 1

<?php 
/**
 * Plugin Name: WPShout Show Authorship this Month
 * Description: Show who's written what this month on WPShout
 * Version: 1.0
 * Author: WPShout
 * Author URI: https://wpshout.com
*/

add_action( 'init', 'wpshout_do_thing' );
function wpshout_do_thing() {
		if( ! isset( $_GET['wpsdt'] ) ) :
			return;
		endif;

		$args = array(
			'posts_per_page' => 5,
			'post_type' => 'any',
			'post_status' => 'publish',
			'orderby' => 'date',
			'order' => 'DESC',
		);

		$query = new WP_Query( $args );

		var_dump( $query );
		die;
}

На этом шаге мы создаем пользовательский запрос WP_Query и выполняем var_dump(), чтобы убедиться, что запрос работает. Он запрашивает последние пять опубликованных записей любого типа.

В результате мы получаем объект WP_Query posts, который представляет собой массив из пяти записей.

Примечания к шагу 2

Первоначально я не знал, как это реализовать. Но затем мне удалось найти пример, в котором показывается, как передать элемент date_query массива WP_Query:

'date_query' => array(
	'after' => array(
		'year'  => 2012,
		'month' => 3,
		'day'   => 1,
	),
),

Но как в PHP указать, что нам нужен текущий месяц? Поиск привел меня к следующему примеру:

'date_query' => array(
	'after' => array(
		'year'  => date( 'Y' ),
		'month' => date( 'M' ),
		'day'   => 1,
	),
),
<?php 
/**
 * Plugin Name: WPShout Show Authorship this Month
 * Description: Show who's written what this month on WPShout
 * Version: 1.0
 * Author: WPShout
 * Author URI: https://wpshout.com
*/

add_action( 'init', 'wpshout_do_thing' );
function wpshout_do_thing() {
		if( ! isset( $_GET['wpsdt'] ) ) :
			return;
		endif;

		$args = array(
			'posts_per_page' => -1,
			'post_type' => 'any',
			'post_status' => 'publish',
			'date_query' => array(
				'after' => array(
					'year' => date( 'Y' ),
					'month' => date( 'M' ),
				),
				'day' => 1,
			),
			'orderby' => 'date',
			'order' => 'DESC',
		);

		$query = new WP_Query( $args );

		var_dump( $query );
		die;
}

На этом шаге мы добавляем параметр date_query в существующий WP_Query. А также изменяем значение posts_per_page на -1, чтобы получить все интересующие записи.

Результат выполнения приведенного выше кода:

Примечания к шагу 4

Но этот код работает неправильно. Запрос возвращает не те записи, которые нам нужны.

<?php 
/**
 * Plugin Name: WPShout Show Authorship this Month
 * Description: Show who's written what this month on WPShout
 * Version: 1.0
 * Author: WPShout
 * Author URI: https://wpshout.com
*/

add_action( 'init', 'wpshout_do_thing' );
function wpshout_do_thing() {
		if( ! isset( $_GET['wpsdt'] ) ) :
			return;
		endif;

		// $args = array(
		// 	'posts_per_page' => -1,
		// 	'post_type' => 'any',
		// 	'post_status' => 'publish',
		// 	'date_query' => array(
		// 		'after' => array(
		// 			'year' => date( 'Y' ),
		// 			'month' => date( 'M' ),
		// 		),
		// 		'day' => 1,
		// 	),
		// 	'orderby' => 'date',
		// 	'order' => 'DESC',
		// );

		// $query = new WP_Query( $args );

		$after = array(
			'year' => date( 'Y' ),
			'month' => date( 'M' ),
		);

		var_dump($after );
		die;
}

На этом этапе мы выполняем следующие действия:

  1. Комментируем код, который ведет себя странно.
  2. Изучаем этот код.

Вся проблема в элементе after, который является частью массива date_query. Чтобы понять, что пошло не так, выведем информацию об этом элементе с помощью функции var_dump().

Примечания к шагу 5

Оказывается, месяц является строкой, «Oct». А date_query может принимать только числовое значение.

<?php 
/**
 * Plugin Name: WPShout Show Authorship this Month
 * Description: Show who's written what this month on WPShout
 * Version: 1.0
 * Author: WPShout
 * Author URI: https://wpshout.com
*/

add_action( 'init', 'wpshout_do_thing' );
function wpshout_do_thing() {
		if( ! isset( $_GET['wpsdt'] ) ) :
			return;
		endif;

		$args = array(
			'posts_per_page' => -1,
			'post_type' => 'any',
			'post_status' => 'publish',
			'date_query' => array(
				'after' => array(
					'year' => date( 'Y' ),
					'month' => date( 'm' ),
				),
				'day' => 1,
			),
			'orderby' => 'date',
			'order' => 'DESC',
		);

		$query = new WP_Query( $args );

		// $after = array(
		// 	'year' => date( 'Y' ),
		// 	'month' => date( 'M' ),
		// );

		var_dump( $query );
		die;
}

Замени значение ‘M’ на ‘m’ в date_query. Потому что ‘m’ дает нам числовое обозначение месяца, когда он передается в date().

Запуск этого кода дает следующее:

Примечания к шагу 6

Но это тоже не сработало. Запрос WP_Query возвратил пустой массив posts.

<?php 
/**
 * Plugin Name: WPShout Show Authorship this Month
 * Description: Show who's written what this month on WPShout
 * Version: 1.0
 * Author: WPShout
 * Author URI: https://wpshout.com
*/

add_action( 'init', 'wpshout_do_thing' );
function wpshout_do_thing() {
		if( ! isset( $_GET['wpsdt'] ) ) :
			return;
		endif;

		$args = array(
			'posts_per_page' => -1,
			'post_type' => 'any',
			'post_status' => 'publish',
			'date_query' => array(
				'after' => array(
					'year' => date( 'Y' ),
					'month' => date( 'm' ),
					'day' => 1,
				),
			),
			'orderby' => 'date',
			'order' => 'DESC',
		);

		$query = new WP_Query( $args );

		// $after = array(
		// 	'year' => date( 'Y' ),
		// 	'month' => date( 'M' ),
		// );

		var_dump( $query );
		die;
}

Ошибка заключалась в том, что я использовал day как отдельный элемент массива date_query. А нужно поместить day внутрь элемента after из date_query. Исправление и повторный запуск кода дает следующее:

Примечания к шагу 7

В результате мы получили десять записей текущего месяца!

<?php 
/**
 * Plugin Name: WPShout Show Authorship this Month
 * Description: Show who's written what this month on WPShout
 * Version: 1.0
 * Author: WPShout
 * Author URI: https://wpshout.com
*/

add_shortcode( 'wpshout_show_this_months_posts_by_author', 'wpshout_show_this_months_posts_by_author' );
function wpshout_show_this_months_posts_by_author() {
		if( ! current_user_can( 'administrator' ) ) :
			return;
		endif;

		$args = array(
			'posts_per_page' => -1,
			'post_type' => 'any',
			'post_status' => 'publish',
			'date_query' => array(
				'after' => array(
					'year' => date( 'Y' ),
					'month' => date( 'm' ),
					'day' => 1,
				),
			),
			'orderby' => 'date',
			'order' => 'DESC',
		);

		$query = new WP_Query( $args );

		ob_start();

		while( $query->have_posts() ) :
			$query->the_post(); ?>

			<h2><?php the_title(); ?></h2>
			By <?php the_author(); ?> on <?php the_date(); ?>

		<?php endwhile;

		wp_reset_postdata();

		return ob_get_clean();
}

Мы будем использовать шорткоды. Для этого меняем add_action() на add_shortcode() и называем функцию шорткода wpshout_show_this_months_posts_by_author(). С помощью буферизации вывода мы сможем контролировать то, какую разметку будет возвращать шорткод. Внутри цикла while() используются теги шаблонов the_author() и the_date().

Запустив плагин с помощью шорткода wpshout_show_this_months_posts_by_author, мы получаем следующее:

Примечания к шагу 8

Но почему в одном из выведенных постов отсутствует дата?

<?php 
/**
 * Plugin Name: WPShout Show Authorship this Month
 * Description: Show who's written what this month on WPShout
 * Version: 1.0
 * Author: WPShout
 * Author URI: https://wpshout.com
*/

add_shortcode( 'wpshout_show_this_months_posts_by_author', 'wpshout_show_this_months_posts_by_author' );
function wpshout_show_this_months_posts_by_author() {
		if( ! current_user_can( 'administrator' ) ) :
			return;
		endif;

		$args = array(
			'posts_per_page' => -1,
			'post_type' => 'any',
			'post_status' => 'publish',
			'date_query' => array(
				'after' => array(
					'year' => date( 'Y' ),
					'month' => date( 'm' ),
					'day' => 1,
				),
			),
			'orderby' => 'date',
			'order' => 'DESC',
		);

		$query = new WP_Query( $args );

		ob_start();

		while( $query->have_posts() ) :
			$query->the_post(); ?>

			<h2><?php the_title(); ?></h2>
			By <?php the_author(); ?> on <?php echo get_the_date( 'l, F d, Y' ); ?>

		<?php endwhile;

		wp_reset_postdata();

		return ob_get_clean();
}

Оказалось, что функция the_date() выводит только один пост за одну дату. Если существуют две записи с одинаковой датой публикации, она перестает работать.

Также мне удалось выяснить, что у get_the_date() этой проблемы нет. Поэтому используем данную функцию. Результат запуск обновленного кода:

Примечания к шагу 9

И это именно то, что нужно.

Разработка плагина для WordPress – это обучение, эксперименты, исследования с помощью метода проб и ошибок. Но главное использовать имеющиеся знания для выполнения простых задач и получать новые для выполнения более сложных.

Приветствую друзья! Меня зовут Антон, я развиваю сайт ux.pub посвященный графическому редактору Sketch. Очень часто мне на почту приходят вопросы о тонкостях разработки плагинов для Sketch. Я не разработчик и не специалист в создании плагинов, поэтому я решил сделать перевод самого подробного руководства по созданию плагинов от Mike Mariano.

Часть 1  — С чего начать?

Вы хотите начать писать Sketch-плагины и не знаете, с чего начать? Продолжайте читать, так как этот пост как раз для вас!

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

Конечно, это не мануал по написанию плагинов для продвинутых, так как я сам не разработчик. Я UI/UX-дизайнер, которому иногда приходится кодить на довольно неплохом уровне (по крайней мере, я так думаю). Тру-программисты откровенно плачут, видя мой код, но я думаю, что как раз такой код хорошо понятен новичкам.

Если вы — инженер, который ищет более сложные примеры, вам будет полезен этот пост, а также официальный сайт Sketch-разработчиков.

Зачем писать плагин?

Плагины идеальны для автоматизации повторяющихся задач, они значительно упрощают работу и продакшн. Есть все шансы, что уже существует плагин под ваши нужды, так что сначала поищите готовое решение перед написанием своего. Очень вероятно, что кто-то уже справился с задачей лучше вас. Но если у вас уникальный рабочий процесс создания UI (как у меня с дизайном игровых интерфейсов в Unity), скорее всего вам потребуется кастомное решение.

Начало

Перед тем, как вы приступите к коду, установите все нужные программы и закладки.

  1. Установите текстовый редактор, если у вас его еще нет. (Я использую Atom, но есть много других отличных редакторов, таких как Sublime или Textmate).
  2. Откройте Консоль для дебага, и добавьте ее в свою панель Dock, вы часто будете ею пользоваться.>

    1-8tsgn6bxwkezgshwyip8lw

  3. Консоль используется вашей машиной для ВСЕГО дебага, так что создайте новый фильтр журнала запросов Sketch: File > New System Query Log

Скопируйте эти настройки и нажмите Ok.

1-n8clsi0_jhe-4jsfx-txga

Фильтр Sketch появится в колонке слева.

1-p0oatbbn78yfdssskqrigg

4. Сделайте закладку папки Sketch Plugins для быстрого доступа, добавив ее в Избранное (Favorites) в окне Finder.

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

/Library/Application support/com.bohemiancoding.sketch3/Plugins

1-xermiwsh60b0osekwabugw

Вот и все, вы готовы к написанию своего первого плагина!

Создание плагина за 10 простых шагов

Плагины Sketch — это папки с расширением .sketchplugin, которыми легко обмениваться с другими пользователями.

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

Плагины Sketch пишутся на CocoaScript, который представляет собой смесь Objective-C/Cocoa и JavaScript. Я неплохо знаком с Javascript, так что тут сложностей не возникло. Не скажу, что я в CocoaScript, как рыба в воде, но моих знаний по JavaScript было достаточно, чтобы разобраться.

Итак, начнем!

1. Создайте новую папку в каталоге Sketch Plugins и назовите ее MyPlugin.sketchplugin

1-dw5qn1na_zd8dgzm6s141w

(как только вы добавите расширение .sketchplugin, двойной клик на ней запустит установку плагина вместо открытия папки. Чтобы открыть саму папку, кликните правой кнопкой мышки на плагине и выберите опцию Show Package Contents).

2. Внутри папки создайте еще одну папку и назовите ее Contents

3. Внутри Contents создайте папку Sketch

Конечная структура папки плагина будет выглядеть так:

1-mnhkmqkpjyg0taofttwtgw

Внутри папки Sketch вы и будете создавать сам плагин, который состоит минимум из 2 файлов – манифеста и скрипта.

1-8l57idwssf5tnot0x408tq

Манифест описывает плагин и может содержать разные горячие клавиши и дополнительные скрипты, он всегда называется manifest.json.

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

4. В текстовом редакторе создайте новый файл под названием manifest.json и сохраните его в MyPlugin.sketchplugin > Contents > Sketch

5. Скопируйте и вставьте этот код в manifest.json, и сохраните.

{
    "name" : "My Plugin",
    "identifier" : "my.plugin",
    "version" : "1.0",
    "description" : "My First Sketch Plugin",
    "authorEmail" : "your@email.com",
    "author" : "Your Name",

    "commands" : [
    {
      "script" : "MyScript.js",
      "handler" : "onRun",
      "shortcut" : "command shift y",
      "name" : "Get Page Names",
      "identifier" : "my.plugin.pagenames"
    }
  ]
}

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

Теперь создадим MyScript.js, на который ссылается manifest. Убедитесь, что название файла совпадает с названием в файле manifest!

6. Вернитесь в текстовый редактор и создайте новый файл под названием MyScript.js, и также сохраните его в папку MyPlugin.sketchplugin > Contents > Sketch folder

7. Скопируйте и вставьте этот код в MyScript.js

var onRun = function(context) {
  
  //reference the Sketch Document
  var doc = context.document;
  
  //reference all the pages in the document in an array
  var pages = [doc pages];
  
  //loop through the pages of the document
  for (var i = 0; i < pages.count(); i++){
    //reference each page
    var page = pages[i];
    
    //get the name of the page
    var pageName = [page name];
    
    //show the page name in the console
    log(pageName);
  }
  
}

Я детальнее поясню этот код в последующих частях. А пока что опирайтесь на комментарии в строках.

8. Перейдите в Sketch и откройте новый файл

9. В меню Plugins выберите MyPlugin > Get Page Names

1-uelcrvjw2vyxzgjdib58ya

10. Перейдите в консоль и внизу лога вы должны увидеть название страницы:

10:54:42 PM Get Page Names (Sketch Plugin): Page 1

Попробуйте изменить название страницы в Sketch-файле и перезапустите плагин. Лог должен показывать новое название. Добавьте еще одну страницу и переименуйте ее, а затем запустите плагин, консоль теперь покажет названия обеих страниц.

Вот и все!

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

Часть 2 — Пользовательские уведомления

Я открыл для себя отличный текстовый редактор Atom, на который и переключился. Не знаю, почему до сих пор не пользовался им, но теперь я попрощался со своим старым-добрым TextMate!

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

Есть два дополнительных способа уведомления пользователей внутри Sketch:

  1. Сообщение (Message) — короткое ненавязчивое сообщение, которое отображается внизу приложения, прячется через короткий промежуток времени
  2. Окно оповещения (Alert Box) — стандартное всплывающее окно, которое либо запрашивает от пользователя ввод данных, либо требует какое-то действие для продолжения.

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

Вернемся к скрипту плагина из первого примера и переделаем его.

var onRun = function(context) {
  
  //reference the Sketch Document
  var doc = context.document;
  
  //reference all the pages in the document in an array
  var pages = [doc pages];
  
  //loop through the pages of the document
  for (var i = 0; i < pages.count(); i++){
    //reference each page
    var page = pages[i];
    
    //get the name of the page
    var pageName = [page name];
    
    //show the page name in the console
    log(pageName);
  }
  
}

Если рассматривать этот фрагмент кода, pageName ссылается на название каждой страницы в массиве страниц документа. Это контейнер с данными, содержащий всю информацию о странице. Обычно для доступа к этим массивам используются цикл for, которые итерируют через каждый объект, чтобы извлечь или назначить определенные значения.

В этой строке переменная pageName отправляется в Консоль.

log(pageName);

Теперь добавим сообщение вниз скрипта, который будет отображать сообщение внутри Sketch, когда скрипт отработает.

Для этого мы добавим строку кода после скобки цикла for:

doc.showMessage(“MyPlugin Finished!”);

Doc – это переменная вверху скрипта, которая ссылается на документ, а showMessage – это встроенная функция, в которой мы можем передавать переменную или строку (String) для отображения сообщения.

Вот как выглядит ее добавление после цикла for в контексте всего скрипта:

var onRun = function(context) {
  
  //reference the Sketch Document
  var doc = context.document;
  
  //reference all the pages in the document in an array
  var pages = [doc pages];
  
  //loop through the pages of the document
  for (var i = 0; i < pages.count(); i++){
    //reference each page
    var page = pages[i];
    
    //get the name of the page
    var pageName = [page name];
    
    //show the page name in the console
    log(pageName);
  }
  //show a message at the bottom of Sketch
  doc.showMessage("MyPlugin Finished!");
}

Запустите MyPlugin через меню Plugins, чтобы увидеть результат. Внизу окна Sketch вы должны увидеть:

1-z9cmbfc9syuokwrzdsairq

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

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

Первая строка добавляется вверх, над объявлением переменной doc, чтобы обратиться к самому приложению:

var app = [NSApplication sharedApplication];

Вторая строка дает доступ к новой переменной для отправки сообщений в приложение.

[app displayDialog:"This is an alert box!" withTitle:"Alert Box Title"];

И вот как выглядит конечный код с новыми строками:

var onRun = function(context) {
  
  //reference the Application
  var app = [NSApplication sharedApplication];
  
  //reference the Sketch Document
  var doc = context.document;
  
  //reference all the pages in the document in an array
  var pages = [doc pages];
  
  //loop through the pages of the document
  for (var i = 0; i < pages.count(); i++){
    //reference each page
    var page = pages[i];
    
    //get the name of the page
    var pageName = [page name];
    
    //show the page name in the console
    log(pageName);
  }
  //show a message at the bottom of Sketch
  doc.showMessage("MyPlugin Finished!");
  
  //send an alert message to the application
  [app displayDialog:"This is an alert box!" withTitle:"Alert Box Title"];
}

Запустите MyPlugin с меню Plugin, и вы должны увидеть что-то такое:

1-1aqb4gdfoh7dxzdugdqdzq

Меняйте сообщения (те, что в кавычках) на что угодно и перезапустите плагин. Полная кастомизация!

Это самое начало отладки и отображения данных внутри Sketch. Вы можете попробовать изменить скрипты сообщений/уведомлений, добавить дополнительные скрипты для показа разных данных — переменных или счетчиков массивов.

Чтобы показать количество страниц в документе, вы можете создать сообщение с отображением pages.count().

[app displayDialog:”This document has “ + pages.count() + “ pages.” withTitle:”Alert Box Title”];

Вот таким будет результат:

1-ruhpur9ob2ns-kknochjyq

Мы разобрались с пользовательскими уведомлениями! Сначала подготовили среду для разработки, затем разобрались, как дебажить и отображать сообщения внутри Sketch. Далее мы сфокусируемся больше на самом коде, поиске конкретных данных и способах их обработки.

Текущую версию плагина можно скачать здесь.

Часть 3 — Написание кода для многоразового использования

Настало время развернуть мониторы вертикально, потому что мы приступаем к программированию!

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

Базовые концепты написания кода

Есть код, который мы уже написали, и он включает в себя переменные, массивы, функции и циклы for. Эти элементы, а также операторы if/else, и есть фундаментальные кирпичики всего, что я делаю. Как только я освоил эти элементы (особенно, цикл loop), мне стало гораздо проще понимать скрипты. Я пишу свои скрипты на Javascript и Objective-C, но эти концепты довольно универсальны для любого языка программирования. Вам просто нужно выработать определенный способ написания кода.

Я бегло поясню эти понятия:

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

var myName = “Mike”;
 var thisYear = 2016;
 var givesHighFives = true;

Если я задам для myName значение “Mike”, тогда я просто могу писать везде в скрипте myName, и эта переменная будет ссылаться на “Mike”. Но если я захочу, чтобы значением myName стал “Shawn”, мне нужно будет изменить его только один раз в объявлении переменной, и каждая ее сущность в коде также изменится.

Массив — это тоже переменная, но обычно он содержит несколько однотипных значений. Как если бы у вас был массив дней недели, вы бы увидели стринговое значение всех дней внутри такого массива.

var daysOfTheWeek = {“Sunday”, “Monday”, “Tuesday”, Wednesday”, “Thursday”, “Friday”, “Saturday”};

Вы также можете написать его так, будет проще увидеть количество элементов массива, которые начинаются с 0:

var daysOfTheWeek[];
 daysOfTheWeek[0] = “Sunday”;
 daysOfTheWeek[1] = “Monday”;
 daysOfTheWeek[2] = “Tuesday”;
 daysOfTheWeek[3] = “Wednesday”;
 daysOfTheWeek[4] = “Thursday”;
 daysOfTheWeek[5] = “Friday”;
 daysOfTheWeek[6] = “Saturday”;

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

var eyesOpen = true;
 function closeEyes(){
 eyesOpen = false;
 }
 function openEyes(){
 eyesOpen = true;
 }

В примере выше каждый раз, когда вы хотите закрыть глаза, вы можете вызывать функцию closeEyes(), затем для их открытия вызывать openEyes().

Оператор if/else делает именно то, что говорит, т.е. проверяет какое-то условие, производит определенное действие, если условие выполняется, в противном случае выполняет что-то другое.

if(daysOfTheWeek == “Sunday”){
 watchGameOfThrones();
 }else if(daysOfTheWeek == “Wednesday”){
 watchMrRobot();
 }else{
 watchTheNews();
 }

Цикл for используется для повтора определенного блока кода известное количество раз. Например, в массиве daysOfTheWeek, если бы вы хотели вывести значения массива в список, вы бы воспользовались для этого циклом for.

Без цикла вы бы писали так:

log(daysOfTheWeek[0]);
 log(daysOfTheWeek[1]);
 log(daysOfTheWeek[2]);
 log(daysOfTheWeek[3]);
 log(daysOfTheWeek[4]);
 log(daysOfTheWeek[5]);
 log(daysOfTheWeek[6]);

А с циклом вам бы пришлось написать всего лишь:

for(var i = 0; i < daysOfTheWeek.count(); i++){
 log(daysOfTheWeek[i];
 }

Гораздо проще, правда? Для цикла мы задали начальное значение i, равное 0 и считали, пока i не достигнет количества значений в массиве daysOfTheWeek, т.е. 7. То есть начиная с i=0, цикл логирует значение daysOfTheWeek, потом добавляет к i 1, и все повторяется. Всего это проделывается 7 раз, пока не достигается полный набор значений в массиве.

Создание функции окна оповещения (Alert Box)

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

[app displayDialog:”This is an alert box!” withTitle:”Alert Box Title”];

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

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

function alert(title, message){
  var app = [NSApplication sharedApplication];
  [app displayDialog:message withTitle:title];
}

Каждый раз, когда мы вызываем alert() и передаем заголовок и сообщение, будет отображаться окно оповещения.

Добавьте эту функцию в конец скрипта, после скобок функции onRun:

var onRun = function(context) {
  
  //reference the Application
  var app = [NSApplication sharedApplication];
  
  //reference the Sketch Document
  var doc = context.document;
  
  //reference all the pages in the document in an array
  var pages = [doc pages];
  [app displayDialog:"This document has " + pages.count() + " pages." withTitle:"Alert Box Title"];
  
  //loop through the pages of the document
  for (var i = 0; i < pages.count(); i++){
    
    //reference each page
    var page = pages[i];
    
    //get the name of the page
    var pageName = [page name];
    
    //show the page name in the console
    log(pageName);
    
  }
  
  //show a message in app
  doc.showMessage("MyPlugin Finished!");
  
  //send an alert message to the application
  [app displayDialog:"This is an alert box!" withTitle:"Alert Box Title"];
  
}

function alert(title, message){
  var app = [NSApplication sharedApplication];
  [app displayDialog:message withTitle:title];
}

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

var onRun = function(context) {
  
  //reference the Application
  var app = [NSApplication sharedApplication];
  
  //reference the Sketch Document
  var doc = context.document;
  
  //reference all the pages in the document in an array
  var pages = [doc pages];
  alert("Number of Pages", "This document has " + pages.count() + " pages.");
  
  //loop through the pages of the document
  for (var i = 0; i < pages.count(); i++){
    
    //reference each page
    var page = pages[i];
    
    //get the name of the page
    var pageName = [page name];
    
    //show the page name in the console
    log(pageName);
    
  }
  
  //show a message in app
  doc.showMessage("MyPlugin Finished!");
  
  //send an alert message to the application
  alert("Plugin Finished!", "This is a message saying the Plugin is finished.")
  
}

function alert(title, message){
  var app = [NSApplication sharedApplication];
  [app displayDialog:message withTitle:title];
}

Запустите плагин, и вы будете видеть такой же результат, как раньше, но теперь ваш код ссылается на функцию. Ее можно вызвать в любом месте скрипта, и не нужно будет переписывать код снова и снова. Но что если бы у нас был другой скрипт, в котором нужно использовать эту функцию?

Ссылка на скрипты из других скриптов

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

1. Сохраните новый JavaScript-файл в ту же папку, в которой хранится MyScript.js, и назовите его common.js. Теперь в папке будет 3 файла:

1-c9uz7wbojajdje_xvd0ziw

2. Вырежьте функцию alert из MyScript.js и вставьте ее в common.js и сохраните изменения.

В common.js должен быть только этот код:

function alert(title, message){
  var app = [NSApplication sharedApplication];
  [app displayDialog:message withTitle:title];
}

3. Чтобы запустить этот скрипт из MyScript.js, добавьте эту строку кода вверху скрипта и сохраните:

@import 'common.js'

Весь скрипт с новой строкой вверху и удаленной функцией alert() выглядит так:

@import 'common.js'

var onRun = function(context) {
  
  //reference the Application
  var app = [NSApplication sharedApplication];
  
  //reference the Sketch Document
  var doc = context.document;
  
  //reference all the pages in the document in an array
  var pages = [doc pages];
  alert("Number of Pages", "This document has " + pages.count() + " pages.");
  
  //loop through the pages of the document
  for (var i = 0; i < pages.count(); i++){
    
    //reference each page
    var page = pages[i];
    
    //get the name of the page
    var pageName = [page name];
    
    //show the page name in the console
    log(pageName);
    
  }
  
  //show a message in app
  doc.showMessage("MyPlugin Finished!");
  
  //send an alert message to the application
  alert("Plugin Finished!", "This is a message saying the Plugin is finished.")
  
}

Запустите плагин еще раз, он должен работать, как и раньше, но теперь вы запускаете функцию из общего скрипта! Оптимизация завершена!

Теперь у вас есть основы написания и понимания более сложных плагинов. Далее мы изучим доступ к данным документа Sketch через его переменные и массивы, способы прохождения по этим массивам для извлечения и назначения нужных нам значений.

Вы можете скачать текущую версию плагина здесь.

Часть 4 — Примеры из реального мира

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

К счастью, Bohemian Coding имеют документацию по своим классам:
http://developer.sketchapp.com/reference/class/

К сожалению, как пишется вначале этой страницы, она все еще “в стадии разработки”. Так что не все еще описано, и не так много примеров, что с этим всем делать.

Лично я лучше всего учусь на примерах. Понимание кода может вызывать большие трудности, но когда вы видите, как он используется применительно к вашим знаниям и целям, становится гораздо проще. Я приведу три примера, которые нашел, пока работал со Sketch, надеюсь, вам они окажутся полезными!

Пример 1: Переключение высоты и ширины артборда.

Странно, но в стандартном функционале Sketch нет смены ориентации артборда с портретной на альбомную и наоборот. Я поискал, и не нашел ни одного плагина, который решает эту проблему, так что самое время создать наш собственный!

Для ясности давайте определимся, что мы будем делать. Это помогает определить цель, способы ее достичь, а также выявить крайние случаи, с которыми придется столкнуться. Это почти как создание UX-сценария, правда?

Для этого плагина мы напишем скрипт, который выполняет следующее:

  • Сначала убеждается, что что-то выделено
  • Затем убеждается, что выделенный объект — это артборд
  • Затем берет исходную высоту и ширину выбранного артборда и сохраняет их в переменные
  • Затем задает новую высоту на основе его старой ширины, и новую ширину на основе старой высоты
  • В конечном итоге, уведомляет пользователя, что скрипт отработал

Добавление еще одного скрипта в MyPlugin

Вместо добавления новой порции кода в MyScript.js, давайте создадим новый скрипт под названием RotateArtboard.js, и сохраните его в папку MyPlugin.sketchplugin.

Добавьте этот код в RotateArtboard.js. Каждый основной скрипт должен быть в функции onRun, так что этот скрипт всегда является хорошей базой для старта:

@import 'common.js'
var onRun = function(context) {
  //reference the Sketch Document
  var doc = context.document;
}

Вы увидите, что мы импортируем файл common.js, чтобы использовать ту же функцию alert, которую мы уже создали.

На данный момент в папке плагина должны быть следующие файлы:

1-zq4ryj_udiwcr-biqkupkg

Теперь откройте manifest.json, чтобы добавить еще один скрипт в наш манифест.

Скопируйте фрагмент кода со всеми командами MyScript.js, добавьте запятую после блока MyScript, и вставьте перед закрывающейся скобкой commands, вот так:

{
    "name" : "My Plugin",
    "identifier" : "my.plugin",
    "version" : "1.0",
    "description" : "My First Sketch Plugin",
    "authorEmail" : "your@email.com",
    "author" : "Your Name",

    "commands" : [
    {
      "script" : "MyScript.js",
      "handler" : "onRun",
      "shortcut" : "command shift y",
      "name" : "Get Page Names",
      "identifier" : "my.plugin.pagenames"
    },
    {
      "script" : "RotateArtboard.js",
      "handler" : "onRun",
      "shortcut" : "command shift u",
      "name" : "Rotate Artboard",
      "identifier" : "my.plugin.rotateartboard"
    }
  ],
}

Теперь вы видите новый скрипт в меню MyPlugin (можете менять горячие клавиши, кстати):

1-hv9u76wafmq1rnjitphdg

Вернемся к коду! Чтобы узнать, что выделено, используем следующее:

var selection = context.selection;

Этот код создает переменную типа массив со всеми выделенными слоями. Теперь мы можем проверить этот массив, чтобы узнать, есть ли что-нибудь внутри него. Если там 0, тогда ничего не выделено, и мы скажем пользователю, что нужно что-то выделить.

if(selection.count() == 0){
 doc.showMessage(“Please select something.”);
 }

Но если счетчик не 0, тогда внутри массива что-то есть, как минимум, один слой должен быть выделен. Теперь мы можем пройтись циклом по выделенным слоям, чтобы определить, есть ли среди них артборды (MSArtboardGroup):

if(selection.count() == 0){
 doc.showMessage(“Please select something.”);
} else {
 for(var i = 0; i < selection.count(); i++){
  if(selection[i].class() == "MSArtboardGroup"){
     //do something
  }
 }
}

Вы можете использовать эту же технику для проверки, является ли выделение текстовым слоем (MSTextLayer), группой (MSGroupLayer), фигурой (MSShapeGroup / не задокументированная), импортированным изображением (MSBitmapLayer) или символом (MSSymbolInstance / не задокументированная).

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

Создайте переменную под названием artboard на основе элемента массива в цикле for, и так мы сможем получить текущие размеры. Использование цикла for позволяет работать с несколькими выделенными артбордами одновременно.

var artboard = selection[i];
 var artboardFrame = artboard.frame();
 var artboardWidth = artboardFrame.width();
 var artboardHeight = artboardFrame.height();

Теперь создайте две новые переменные со значениями, которые нам нужны, т.е. поменяйте местами ширину и высоту:

var newArtboardWidth = artboardHeight;
 var newArtboardHeight = artboardWidth;

Затем воспользуемся artboardFrame, чтобы задать высоту и ширину новыми переменными:

artboardFrame.setWidth(newArtboardWidth);
 artboardFrame.setHeight(newArtboardHeight);

И, наконец, мы воспользуемся нашим окном уведомлений, чтобы сообщить пользователю об отработке скрипта и отобразить новые значения:

var alertMessage = “New Height: “+newArtboardHeight+ “ | New Width: “+newArtboardWidth;
 alert(“Artboard Rotated!”, alertMessage)

Вот весь скрипт с комментариями:

@import 'common.js'

var onRun = function(context) {
    
    //reference the sketch document
    var doc = context.document;
    //reference what is selected
    var selection = context.selection;

    //make sure something is selected
    if(selection.count() == 0){
        doc.showMessage("Please select something.");
    }else{
        //loop through the selected layers
        for(var i = 0; i < selection.count(); i++){

            //checks to see if the layer is an artboard
            if(selection[i].class() == "MSArtboardGroup"){

                //reference the selection
                var artboard = selection[i];

                //get the artboard frame for dimensions
                var artboardFrame = artboard.frame();
                //get the width
                var artboardWidth = artboardFrame.width();
                //get the height
                var artboardHeight = artboardFrame.height();

                //set a new width variable to the old height
                var newArtboardWidth = artboardHeight;
                //set a new height variable to the old width
                var newArtboardHeight = artboardWidth;

                //set the artboard frame with the new dimensions
                artboardFrame.setWidth(newArtboardWidth);
                artboardFrame.setHeight(newArtboardHeight);

                //send an alert message with the new values
                var alertMessage = "New Height: "+newArtboardHeight+ " | New Width: "+newArtboardWidth;
                alert("Artboard Rotated!", alertMessage);
            }else{
                doc.showMessage("Please select an artboard.");
            }
        }
    }
}

Подытожим пример 1:

  • Проверяем, выделено ли что-то
  • Определяем тип слоя выделенного, и делаем кое-что, если это тот тип, который нам нужен
  • Извлекаем исходный фрейм нашего выделенного артборда, высоту и ширину, и сохраняем это в переменных
  • Создаем новые переменные c нужными нам значениями
  • Задаем для фрейма выделенного артборда эти значения

Пример 2: Выделение слоев и их переименование на имя символа

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

Для этого примера, скачайте файл Sketch для справок.

В файле Sketch на первом артборде вы увидите символ под названием Square, он находится по центру. Также в библиотеке есть еще один символ под названием Circle.

Выделите Square, и используя опции справа измените Square на Circle

1-ow6hbt63d3_y9d7hzdkd0a

Теперь посмотрите на слои слева — слой все еще называется Square! Я думаю, что мог бы изменить название вручную, но это не прикольно, особенно если изменить нужно множество сущностей.

Сценарий для этого плагина такой:

  • Сначала убедиться, что что-то выделено
  • Затем убедиться, что выделен именно символ
  • Получить название символа
  • Проверить, совпадает ли название символа с названием слоя
  • Если нет, изменить название слоя на название символа
  • И, наконец, уведомить пользователя, когда скрипт отработает

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

Следуя первым шагам из предыдущего примера, давайте добавим еще один скрипт в MyPlugin.sketchplugin, создав новый файл под названием SetLayerNameToSymbolName.js и добавим его в manifest.json. Чтобы сэкономить время, просто используйте RotateArtboard.js и “Save As”. Мы будем использовать этот скрипт очень часто, так что начнем отсюда и сделаем несколько изменений.

Наша папка сейчас содержит такие файлы:

1-wswvgjzkj299_nctanol8g

В этом скрипте, вместо проверки, является ли выделение MSArtboardGroup (артбордом), мы проверим, является ли оно MSSymbolInstance (символом). Если является, мы выясним название symbolMaster и сравним его с названием слоя.

И вот вам новый скрипт с парой измененных строк:

@import 'common.js'

var onRun = function(context) {

    //reference the sketch document
    var doc = context.document;
    //reference what is selected
    var selection = context.selection;

    //make sure something is selected
    if(selection.count() == 0){
        doc.showMessage("Please select something.");
    }else{
        //loop through the selected layers
        for(var i = 0; i < selection.count(); i++){

            //checks to see if the layer is a Symbol
            if(selection[i].class() == "MSSymbolInstance"){

                //reference the selection
                var layer = selection[i];
                //get the original layer name
                var layerName = layer.name();
                //get the name of the symbol on the layer
                var symbolName = layer.symbolMaster().name();

                //check if layer name is not already symbol name
                if(layerName != symbolName){
                    //set the layer name to the symbol name
                    layer.setName(symbolName);
                    
                    var alertMessage = "Layer Name Changed from: "+layerName+ " to: "+symbolName;
                    alert("Layer Name Changed!", alertMessage);

                }else{
                    doc.showMessage("Layer name is already Symbol Name.");
                }

            }else{
                doc.showMessage("Please select a Symbol Layer.");
            }
        }
    }
}

Подытожим пример 2

В этом примере мы усвоили:

  • Проверки, является ли выделение артбордом или символом, выполняются идентично.
  • Как получить название symbolMaster и название слоя
  • Как сравнить два названия и настроить название слоя, если они отличаются.

Пример 3: Задание названий символов в качестве названий слоев

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

  • Сначала получить все страницы документа
  • Затем получить все артборды на каждой странице
  • Затем получить все слои на каждом артборде
  • Затем проверить, является ли какой-то из этих слоев слоем символа
  • Затем проверить, не совпадает ли название слоя с названием символа
  • Если не совпадает, заменить название слоя и уведомить пользователя

Так как мы теперь не учитываем, что сейчас выделено, нам придется изменить скрипт, чтобы он прошелся по данным страниц документа. Затем мы воспользуемся рядом вложенных петель for, которые будут итерировать по массиву артбордов, затем по массиву слоев, и наконец, дойдут до каждого слоя. Цикл for внутри цикла for, которая внутри цикла for.

Наверное, для этой задачи мы создадим еще один новый скрипт. Начните с нового файла под названием SetAllLayerNamesToSymbolNames.js и также добавьте его в manifest.json, как мы делали раньше.

Теперь в папке MyPlugin.sketchplugin будет столько файлов:

1-rhsu-brcrkmcsyvbno0ckw

И плагин в вашем списке меню, вместе с предыдущими примерами должен отображать новую опцию, как только вы добавите новый файл в манифест:

1-zl8p9tmk09ocpqo0ujd7gw

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

var pages = [doc pages];

Чтобы получить артборды страниц, используйте цикл for:

var pages = [doc pages];

for (var i = 0; i < pages.count(); i++){
  var page = pages[i];
  var artboards = [page artboards];
}

Чтобы получить слои артборда, используйте цикл for внутри цикла for:

var pages = [doc pages];

for (var i = 0; i < pages.count(); i++){
  var page = pages[i];
  var artboards = [page artboards];
  
  for (var z = 0; z < artboards.count(); z++){
    var artboard = artboards[z];
    var layers = [artboard layers];
    
  }
}   

Далее, чтобы извлечь каждый отдельный слой, вам нужно пройтись по слоям внутри цикла for, и затем проверить, что это за слой:

var pages = [doc pages];

for (var i = 0; i < pages.count(); i++){
  var page = pages[i];
  var artboards = [page artboards];
  
  for (var z = 0; z < artboards.count(); z++){
    var artboard = artboards[z];
    var layers = [artboard layers];
    
    for(var k = 0; k < layers.count(); k++){
      var layer = layers[k];
      
      if(layer.class() == "MSSymbolInstance"){
        //do something
        }
    }
  }
}

Как только мы установили, что это слой символа, мы можем проверить его название и сравнить с названием слоя. Если они отличаются, меняем название слоя на название символа, и все!

Вот как выглядит весь скрипт, для простоты понимания почти каждая строка прокомментирована:

@import 'common.js'

var onRun = function(context) {
    //reference the sketch document
    var doc = context.document;
    //reference the pages array in the document
    var pages = [doc pages];
    //create a variable to hold how many symbol layers we changed
    var symbolCount = 0;

    //loop through the pages array
    for (var i = 0; i < pages.count(); i++){
        //reference each page
        var page = pages[i];
        //reference the artboards array of each page
        var artboards = [page artboards];

        //loop through the artboards of each page
        for (var z = 0; z < artboards.count(); z++){
            //reference each artboard of each page
            var artboard = artboards[z];
            //reference the layers array of each artboard
            var layers = [artboard layers];

            //loop through the layers array
            for(var k = 0; k < layers.count(); k++){
                //reference each layer of each artboard
                var layer = layers[k];

                //check to see if the layer is a Symbol
                if(layer.class() == "MSSymbolInstance"){

                    //get the original layer name
                    var layerName = layer.name();
                    //get the name of the symbol on the layer
                    var symbolName = layer.symbolMaster().name();

                    //only change the name of layers that don't match the symbol name
                    if(layerName != symbolName){
                        //set the layer name to the symbol name
                        layer.setName(symbolName);
                        symbolCount = symbolCount + 1;

                    }
                }
            }
        }
    }
    var alertMessage = symbolCount + " symbol layer name changed.";
    alert("Symbol Layer Names Reset!", alertMessage);
}

Подытожим пример 3, тут мы научились:

  • Проходиться по всем страницам документа, чтобы получить список артбордов
  • Проходиться по всем артбордам страницы, чтобы получить слои
  • Проходиться по всем слоям артборда и проверять их тип
  • Сравнивать имя слоя с именем символов
  • И, наконец, если имена отличаются, изменить название слоя на название символа

Вывод

Это всего три примера, как можно начать использовать базовые понятия доступных классов Sketch, изученных ранее. Тут еще многое можно сделать, но мы пока только начинаем! Далее мы рассмотрим еще больше способов доступа к дополнительным классам внутри Sketch, еще больше примеров из реального мира.

Вы можете скачать текущую версию плагина здесь.

Часть 5 — Класс MSLayer

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

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

Чтобы лучше понять терминологию, есть два типа данных, доступных через класс:

  1. Атрибуты — именованное свойство объекта (например, высота, ширина или название)
  2. Методы — функции, связанные с классами, обычно используется для изменения атрибутов (таких как изменение высоты, ширины или названия)

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

Получение атрибутов слоя

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

Предположим, что вы использовали массив выделения, чтобы получить переменную под названием layer:

var onRun = function(context) {
    var doc = context.document;
    var selection = context.selection;

    if(selection.count() == 0){
        doc.showMessage("Please select something.");
    }else{
        for(var i = 0; i < selection.count(); i++){
            var layer = selection[i];
        }
    }
};

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

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

Вот несколько атрибутов, которые доступны в классе MSLayer:

Класс (тип слоя):

var layerClass = layer.class();

Фрейм (размер и положение, относительно его артборда, сущность MSRect):

var layerFrame = layer.frame();

var layerWidth = layerFrame.width();
 var layerHeight = layerFrame.height();
 var layerXpos = layerFrame.x();
 var layerYpos = layerFrame.y();

Стиль (границы, заливки, тени, внутренние тени, сущность MSStyle):

var layerStyle = layer.style();

//получение массива цветов заливки
 var layerFills = layer.style().fills();

//получение каждого цвета заливки
 for(var z = 0; z < layerFills.count(); z++){
    var fillColor = layerFills[z].colorGeneric();
 }

Название слоя:

var layerName = layer.name();

Видимость:

var layerIsVisible = layer.isVisible();

Статус блокировки:

var layerIsLocked = layer.isLocked();

Отражение (горизонтальное или вертикальное):

//горизонтальное
 var layerIsFlippedHorizontal = layer.isFlippedHorizontal();

//вертикальное
 var layerIsFlippedVertical = layer.isFlippedVertical();

Поворот:

var layerRotation = layer.rotation();

Родительская группа (страница, артборд или группа):

var layerParent = layer.parentGroup();

Статус выделения:

var layerIsSelected = layer.isSelected();

Absolute Rect (глобальный размер и положение в целом документе, сущность MSAbsoluteRect):

var layerAbsoluteRect = layer.absoluteRect();

var layerAbsoluteWidth = layerAbsoluteRect.width();
 var layerAbsoluteHeight = layerAbsoluteRect.height();
 var layerAbsoluteXpos = layerAbsoluteRect.x();
 var layerAbsoluteYpos = layerAbsoluteRect.y();

CSSAttributeString:

var layerCSSAttributeString = layer.CSSAttributeString();

CSSAttributes:

var layerCSSAttributes = layer.CSSAttributes();

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

var onRun = function(context) {
    var doc = context.document;
    var selection = context.selection;

    if(selection.count() == 0){
        doc.showMessage("Please select something.");
    }else{
        for(var i = 0; i < selection.count(); i++){
            var layer = selection[i];
            var layerClass = layer.class();
            var layerFrame = layer.frame();
            var layerStyle = layer.style();
            var layerName = layer.name();
            var layerIsVisible = layer.isVisible();
            var layerIsLocked = layer.isLocked();
            var layerIsFlippedHorizontal = layer.isFlippedHorizontal();
            var layerIsVertical = layer.isFlippedVertical();
            var layerRotation = layer.rotation();
            var layerParent = layer.parentGroup();
            var layerIsSelected = layer.isSelected();
            var layerAbsoluteRect = layer.absoluteRect();
            var layerUserInfo = layer.userInfo();
            var layerCSSAttributeString = layer.CSSAttributeString();
            var layerCSSAttributes = layer.CSSAttributes();
        }
    }
};

Затем вы можете использовать консоль для логирования любых переменных, которых хотели бы видеть:

log(“Layer Rotation: “ + layerRotation);

Использование методов

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

setName

var newLayerName = "New Layer Name";
 layer.setName(newLayerName);

setIsVisible

//показать слой
 layer.setIsVisible(true);
 //спрятать слой
 layer.setIsVisible(false);
 //переключить
 layer.setIsVisible(!layer.isVisible())

setIsLocked

//заблокировать слой
 layer.setIsLocked(true);
 //разблокировать слой
 layer.setIsLocked(false);
 //переключить
 layer.setIsLocked(!layer.isLocked());

setRotation

var newLayerRotation = 180;
 layer.setRotation(newLayerRotation);

setIsFlippedHorizontal

//отразить по горизонтали
 layer.setIsFlippedHorizontal(true);
 //сбросить
 layer.setIsFlippedHorizontal(false);
 //переключить
 layer.setIsFlippedHorizontal(!layer.isFlippedHorizontal());

setIsFlippedVertical

//отразить по вертикали
 layer.setIsFlippedVertical(true);
 //сбросить
 layer.setIsFlippedVertical(false);
 //переключить
 layer.setIsFlippedVertical(!layer.isFlippedVertical());

setIsSelected

//выделить
 layer.setIsSelected(true);
 //снять выделение
 layer.setIsSelected(false);
 //переключить
 layer.setIsSelected(!layer.isSelected());

дублирование

layer.duplicate();

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

Например, если вы хотите разблокировать все слои, вы сначала можете проверить, заблокированы ли они, затем можете их разблокировать:

var onRun = function(context) {
    var doc = context.document;
    var selection = context.selection;

    if(selection.count() == 0){
        doc.showMessage("Please select something.");
    }else{
        for(var i = 0; i < selection.count(); i++){
          
            var layer = selection[i];
            var layerIsLocked = layer.isLocked();
            
            if(layerIsLocked == true){
              layer.setIsLocked(false);
            }
            
        }
    }
};

Или если бы вы хотели добавить префикс к названию слоя:

var onRun = function(context) {
    var doc = context.document;
    var selection = context.selection;

    if(selection.count() == 0){
        doc.showMessage("Please select something.");
    }else{
        for(var i = 0; i < selection.count(); i++){
          
            var layer = selection[i];
            var layerName = layer.name();
            var layerPrefix = "prefix_";
            
            layer.setName(layerPrefix + layerName);
            
        }
    }
}

И еще много всего, опции действительно бесконечны!

Методы атрибутов

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

Например, чтобы изменить ширину слоя до 100, вам понадобится использовать setWidth на переменной фрейма:

var onRun = function(context) {
    var doc = context.document;
    var selection = context.selection;

    if(selection.count() == 0){
        doc.showMessage("Please select something.");
    }else{
        for(var i = 0; i < selection.count(); i++){
          
            var layer = selection[i];
            var layerFrame = layer.frame();
            var newLayerWidth = 100;
            
            layerFrame.setWidth(newLayerWidth);
            
        }
    }
}

Если вы хотите добавить на слой заливку, вы можете использовать addStylePartOfType(0) на переменной стиля:

var onRun = function(context) {
    var doc = context.document;
    var selection = context.selection;

    if(selection.count() == 0){
        doc.showMessage("Please select something.");
    }else{
        for(var i = 0; i < selection.count(); i++){
          
            var layer = selection[i];
            var layerStyle = layer.style();
            
            layerStyle.addStylePartOfType(0);
            
        }
    }
}

Определение типа подкласса слоя Finding the Layer Subclass Type

Это все вы можете делать с любым MSLayer. Но что если, например, вы хотите считать строку текста из текстового поля? Вам нужно знать сначала, является ли этот объект текстовым полем, иначе код выбросит вам ошибку о несуществовании String.

Не все подклассы описаны в документации, но так как мы можем выяснить класс слоя из атрибута класса, мы можем легко с этим справиться. И чтобы еще больше упростить себе задачу, специально для этого я написал плагин!

Скачайте и установите плагин Show Layer Type Plugin здесь. Затем скачайте и откройте этот файл Sketch для тестирования.

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

1-4_g6dwvalei6aasionoktw

Выделите все слои и запустите плагин Show Layer Type. В консоли вы увидите такой вывод:

8/28/16 9:07:18.993 AM Show Layer Type (Sketch Plugin)[46515]: Background is a: MSShapeGroup
 8/28/16 9:07:18.997 AM Show Layer Type (Sketch Plugin)[46515]: Line is a: MSShapeGroup
 8/28/16 9:07:18.999 AM Show Layer Type (Sketch Plugin)[46515]: Author is a: MSTextLayer
 8/28/16 9:07:18.999 AM Show Layer Type (Sketch Plugin)[46515]: Icon is a: MSBitmapLayer
 8/28/16 9:07:19.000 AM Show Layer Type (Sketch Plugin)[46515]: Oval is a: MSSymbolInstance
 8/28/16 9:07:19.001 AM Show Layer Type (Sketch Plugin)[46515]: Title is a: MSTextLayer
 8/28/16 9:07:19.002 AM Show Layer Type (Sketch Plugin)[46515]: Line is a: MSShapeGroup

Если вы перейдете на страницу Symbols и выберите символ, снова запустите плагин, то увидите следующее:

8/28/16 9:10:08.600 AM Show Layer Type (Sketch Plugin)[46515]: Oval is a: MSSymbolMaster

Если вы выделите артборд и запустите плагин, вывод будет таким:

8/28/16 9:10:48.226 AM Show Layer Type (Sketch Plugin)[46515]: Artboard 1 is a: MSArtboardGroup

Если вы сгруппируете все слои на артборде, затем запустите плагин, увидите следующее:

8/28/16 9:11:24.234 AM Show Layer Type (Sketch Plugin)[46515]: Group is a: MSLayerGroup

Вот и все типы подклассов слоев! Возможно, есть и другие, но это все, которые мне удалось найти (документированные я сопроводил ссылками).

MSShapeGroup
MSBitmapLayer
MSTextLayer
MSSymbolInstance
MSSymbolMaster
MSArtboardGroup
MSLayerGroup

Теперь мы можем дифференцировать код в зависимости от того, какой этот класс, используя операторы if/else.

var onRun = function(context) {
    var doc = context.document;
    var selection = context.selection;

    if(selection.count() == 0){
        doc.showMessage("Please select something.");
    }else{
        for(var i = 0; i < selection.count(); i++){
            var layer = selection[i];
            var layerClass = layer.class();
            
            if(layerClass == "MSShapeGroup"){
              //do something
            } else if (layerClass == "MSBitmapLayer"){
              //do something
            } else if (layerClass == "MSTextLayer"){
              //do something
            } else if (layerClass == "MSSymbolInstance"){
              //do something
            } else if (layerClass == "MSSymbolMaster"){
              //do something
            } else if (layerClass == "MSArtboardGroup"){
              //do something
            } else if (layerClass == "MSLayerGroup"){
              //do something
            }
        }
    }
};

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

Выводы

В этой части мы научились, как:

  • Просматривать атрибуты слоя
  • Изменять атрибуты слоя с помощью методов
  • Получать доступ к атрибутам атрибутов и методов
  • Проверять тип класса слоя и различать их в коде

Часть 6 — Экспорт данных

Sketch предоставляет удобный доступ к выходному коду svg и css через клик правой кнопкой мыши на слое. Это очень полезная опция для веб-дизайнеров, но для тех, кто занимается разработкой приложений или игр пользы в этом мало. Также приходится копировать и вставлять значения из одного приложения в другое, что представляет собой огромное поле для ошибок.

Есть много сторонних инструментов-мостиков между дизайном и разработкой (Zeplin, Avocode, Sketch Measure, Sympli и др.), но эти инструменты предоставляют лишь справочное руководство для разработчиков. Было бы куда полезнее, если приложения для дизайна и разработки работали вместе напрямую. Представьте: вы делаете дизайн чего-то в Sketch, нажимаете “экспортировать”, считываете свой проект по разработке и воссоздаете макет. Это избавит от необходимости в попиксельных спецификациях, и даже сэкономит время инженера на воссоздании дизайна в коде.

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

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

Плагины Sketch — это инструменты, соединяющие дизайн с разработкой

Чтобы соединить дизайн и разработку, нужно найти общую основу двумя этими сферами. Так как нет родных способов их соединить, что мы можем использовать такое, что понятно обоим? Ответ — XML или JSON. С помощью плагинов Sketch может экспортировать данные в оба формата, и, скорее всего, среда разработки, в которой вы работаете, может их считывать. Так зачем слать разработчику пиксельные спецификации, из которых придется копировать значения вручную, если можно отослать их в файле XML и JSON и импортировать автоматически?

Это действительно одно из лучших решений, возможных для Sketch. В этой части я покажу базовый пример, как экспортировать атрибуты слоя в XML или JSON. По аналогии с предыдущими частями, давайте набросаем план. Наш плагин будет работать в таком порядке:

  1. Выделяем слой
  2. Создаем модальное окно для выбора папки пользователем и сохранения этого местоположения в переменной.
  3. Из выделенного поля создаются переменные для хранения базовой информации (Название, координата X, координата Y, высота и ширина).
  4. Сохраняем все эти переменные либо в XML, либо в JSON-объект.
  5. И, наконец, сохраняем этот объект в файл XML или JSON в предварительно выбранную папку.

Экспорт XML

Для начала мы начнем с создания нашего плагина. Создайте плагин под названием Export Layer to XML с основным скриптом под названием ExportLayerToXML.js. Также нужно добавить наш файл common.js для использования кастомного окна уведомлений, который создавали в предыдущих частях.

Мы уже знаем, как определить, выделен ли слой:

@import 'common.js'

var onRun = function(context) {
  var doc = context.document;
    var selection = context.selection;
    
    //make sure something is selected
    if(selection.count() == 0){
        doc.showMessage("Please select a layer.");
    }else{
      //do something
    }
};

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

//allow xml to be written to the folder
var fileTypes = [NSArray arrayWithObjects:@"xml", nil];

//create select folder window
var panel = [NSOpenPanel openPanel];
[panel setCanChooseDirectories:true];
[panel setCanCreateDirectories:true];
[panel setAllowedFileTypes:fileTypes];

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

//create variable to check if clicked
var clicked = [panel runModal];
//check if clicked
if (clicked == NSFileHandlingPanelOKButton) {
  
  var isDirectory = true;
  //get the folder path
  var firstURL = [[panel URLs] objectAtIndex:0];
  //format it to a string
  var file_path = [NSString stringWithFormat:@"%@", firstURL];
  
  //remove the file:// path from string
  if (0 === file_path.indexOf("file://")) {
    file_path = file_path.substring(7);
  }
}

Когда все будет сделано, пройдемся циклом по выделенным слоям и вызовем функцию, которая записывает данные в XML-файл. Получившийся код будет выглядеть так:

@import 'common.js'

var onRun = function(context) {
    var doc = context.document;
    var selection = context.selection;
    
    //make sure something is selected
    if(selection.count() == 0){
        doc.showMessage("Please select a layer.");
    }else{
        //allow xml to be written to the folder
        var fileTypes = [NSArray arrayWithObjects:@"xml", nil];
        
        //create select folder window
        var panel = [NSOpenPanel openPanel];
        [panel setCanChooseDirectories:true];
        [panel setCanCreateDirectories:true];
        [panel setAllowedFileTypes:fileTypes];
        
        //create variable to check if clicked
        var clicked = [panel runModal];
        //check if clicked
        if (clicked == NSFileHandlingPanelOKButton) {
            
            var isDirectory = true;
            //get the folder path
            var firstURL = [[panel URLs] objectAtIndex:0];
            //format it to a string
            var file_path = [NSString stringWithFormat:@"%@", firstURL];
            
            //remove the file:// path from string
            if (0 === file_path.indexOf("file://")) {
                file_path = file_path.substring(7);
            }
        }
        //loop through the selected layers and export the XML
        for(var i = 0; i < selection.count(); i++){
            var layer = selection[i];
            exportXML(layer, file_path);
        }
    }
};

Теперь для функции exportXML передадим 2 значения: выделенный слой и file_path.

Сначала мы настроим XML:

//initialize the root xml element
var root = [NSXMLElement elementWithName:@"document"];
//initialize the xml object with the root element
var xmlObj = [[NSXMLDocument document] initWithRootElement:root];

Затем из переданного слоя извлечем нужные переменные:

//create the variables
var layerName = layer.name();
var layerFrame = layer.absoluteRect();
var layerXpos = String(layerFrame.x());
var layerYpos = String(layerFrame.y());
var layerHeight = String(layerFrame.height());
var layerWidth = String(layerFrame.width());

Сохраним эти переменные в объект XML:

//create the first child element and add it to the root
var layerElement = [NSXMLElement elementWithName:@"layer"];
[root addChild:layerElement];

//add elements based on variables to the first child
var layerNameElement = [NSXMLElement elementWithName:@"name" stringValue:layerName];
[layerElement addChild:layerNameElement];

var layerXPosElement = [NSXMLElement elementWithName:@"xPos" stringValue:layerXpos];
[layerElement addChild:layerXPosElement];

var layerYPosElement = [NSXMLElement elementWithName:@"yPox" stringValue:layerYpos];
[layerElement addChild:layerYPosElement];

var layerHeightElement = [NSXMLElement elementWithName:@"height" stringValue:layerHeight];
[layerElement addChild:layerHeightElement];

var layerWidthElement = [NSXMLElement elementWithName:@"width" stringValue:layerWidth];
[layerElement addChild:layerWidthElement];

И наконец, запишем XML-файл по переданному пути файла и уведомим пользователя, когда все будет сделано:

//create the xml file
var xmlData = [xmlObj XMLDataWithOptions:NSXMLNodePrettyPrint];

//name the xml file the name of the layer and save it to the folder
[xmlData writeToFile:file_path+layerName+".xml"];
var alertMessage = layerName+".xml saved to: " + file_path;

alert("Layer XML Exported!", alertMessage);

Скрипт целиком выглядит так:

@import 'common.js'

var onRun = function(context) {
    var doc = context.document;
    var selection = context.selection;
    
    //make sure something is selected
    if(selection.count() == 0){
        doc.showMessage("Please select a layer.");
    }else{
        //allow xml to be written to the folder
        var fileTypes = [NSArray arrayWithObjects:@"xml", nil];
        
        //create select folder window
        var panel = [NSOpenPanel openPanel];
        [panel setCanChooseDirectories:true];
        [panel setCanCreateDirectories:true];
        [panel setAllowedFileTypes:fileTypes];
        
        //create variable to check if clicked
        var clicked = [panel runModal];
        //check if clicked
        if (clicked == NSFileHandlingPanelOKButton) {
            
            var isDirectory = true;
            //get the folder path
            var firstURL = [[panel URLs] objectAtIndex:0];
            //format it to a string
            var file_path = [NSString stringWithFormat:@"%@", firstURL];
            
            //remove the file:// path from string
            if (0 === file_path.indexOf("file://")) {
                file_path = file_path.substring(7);
            }
        }
        //loop through the selected layers and export the XML
        for(var i = 0; i < selection.count(); i++){
            var layer = selection[i];
            exportXML(layer, file_path);
        }
    }
};

function exportXML(layer, file_path){

  //initialize the root xml element
  var root = [NSXMLElement elementWithName:@"document"];
  //initialize the xml object with the root element
  var xmlObj = [[NSXMLDocument document] initWithRootElement:root];

  //create the variables
  var layerName = layer.name();
  var layerFrame = layer.absoluteRect();
  var layerXpos = String(layerFrame.x());
  var layerYpos = String(layerFrame.y());
  var layerHeight = String(layerFrame.height());
  var layerWidth = String(layerFrame.width());

  //create the first child element and add it to the root
  var layerElement = [NSXMLElement elementWithName:@"layer"];
  [root addChild:layerElement];

  //add elements based on variables to the first child
  var layerNameElement = [NSXMLElement elementWithName:@"name" stringValue:layerName];
  [layerElement addChild:layerNameElement];

  var layerXPosElement = [NSXMLElement elementWithName:@"xPos" stringValue:layerXpos];
  [layerElement addChild:layerXPosElement];

  var layerYPosElement = [NSXMLElement elementWithName:@"yPos" stringValue:layerYpos];
  [layerElement addChild:layerYPosElement];

  var layerHeightElement = [NSXMLElement elementWithName:@"height" stringValue:layerHeight];
  [layerElement addChild:layerHeightElement];

  var layerWidthElement = [NSXMLElement elementWithName:@"width" stringValue:layerWidth];
  [layerElement addChild:layerWidthElement];

  //create the xml file
  var xmlData = [xmlObj XMLDataWithOptions:NSXMLNodePrettyPrint];

  //name the xml file the name of the layer and save it to the folder
  [xmlData writeToFile:file_path+layerName+".xml"];
  var alertMessage = layerName+".xml saved to: " + file_path;

  alert("Layer XML Exported!", alertMessage);

}

Скачайте плагин или просмотрите его на GitHub, протестируйте. Если вы создадите прямоугольник на артборде и экспортируете в XML, получится примерно так:

<document>
 <layer>
 <name>Rectangle</name>
 <xPos>550</xPos>
 <yPos>258</yPos>
 <height>234</height>
 <width>235</width>
 </layer>
</document>

Экспорт JSON

Сохранение JSON-файла реализуется почти также, кроме пары моментов. Мы можем использовать первую часть скрипта и создать функцию exportJSON, которая выглядит так:

function exportJSON(layer, file_path){

  //initialize the layer array
  var layerArray = [];

  //create the variables
  var layerName = String(layer.name());
  var layerFrame = layer.absoluteRect();
  var layerXpos = String(layerFrame.x());
  var layerYpos = String(layerFrame.y());
  var layerHeight = String(layerFrame.height());
  var layerWidth = String(layerFrame.width());

  // add the strings to the array
  layerArray.push({
      name: layerName,
      xPos: layerXpos,
      yPos: layerYpos,
      height: layerHeight,
      width: layerWidth,
  });
  
  // Create the JSON object from the layer array
  var jsonObj = { "layer": layerArray };
  // Convert the object to a json string
  var file = NSString.stringWithString(JSON.stringify(jsonObj, null, "t"));
  // Save the file
  [file writeToFile:file_path+layerName+".json" atomically:true encoding:NSUTF8StringEncoding error:null];

  var alertMessage = layerName+".json saved to: " + file_path;
  alert("Layer JSON Exported!", alertMessage);

}

Скрипт целиком выглядит так:

@import 'common.js'

var onRun = function(context) {
    var doc = context.document;
    var selection = context.selection;
    
    //make sure something is selected
    if(selection.count() == 0){
        doc.showMessage("Please select a layer.");
    }else{
        //allow xml to be written to the folder
        var fileTypes = [NSArray arrayWithObjects:@"json", nil];
        
        //create select folder window
        var panel = [NSOpenPanel openPanel];
        [panel setCanChooseDirectories:true];
        [panel setCanCreateDirectories:true];
        [panel setAllowedFileTypes:fileTypes];
        
        var clicked = [panel runModal];
        //check if Ok has been clicked
        if (clicked == NSFileHandlingPanelOKButton) {
            var isDirectory = true;
            //get the folder path
            var firstURL = [[panel URLs] objectAtIndex:0];
            //format it to a string
            var file_path = [NSString stringWithFormat:@"%@", firstURL];
            
            //remove the file:// path from string
            if (0 === file_path.indexOf("file://")) {
                file_path = file_path.substring(7);
            }
        }
        //loop through the selected layers and export the XML
        for(var i = 0; i < selection.count(); i++){
            var layer = selection[i];
            exportJSON(layer, file_path);
        }
    }
};

function exportJSON(layer, file_path){

  //initialize the layer array
  var layerArray = [];

  //create the variables
  var layerName = String(layer.name());
  var layerFrame = layer.absoluteRect();
  var layerXpos = String(layerFrame.x());
  var layerYpos = String(layerFrame.y());
  var layerHeight = String(layerFrame.height());
  var layerWidth = String(layerFrame.width());

  // add the strings to the array
  layerArray.push({
      name: layerName,
      xPos: layerXpos,
      yPos: layerYpos,
      height: layerHeight,
      width: layerWidth,
  });

    // Create the JSON object from the layer array
  var jsonObj = { "layer": layerArray };
  // Convert the object to a json string
  var file = NSString.stringWithString(JSON.stringify(jsonObj, null, "t"));
  // Save the file
  [file writeToFile:file_path+layerName+".json" atomically:true encoding:NSUTF8StringEncoding error:null];

  var alertMessage = layerName+".json saved to: " + file_path;
  alert("Layer JSON Exported!", alertMessage);

}

Вы можете скачать плагин здесь или посмотреть его на GitHub. Если протестируете скрипт, получите JSON-файл с таким содержимым:

{“layer”:[{“name”:”Rectangle”,”xPos”:”550",”yPos”:”258",”height”:”234",”width”:”235"}]}

Заключение

По итогу этой части мы научились:

  • Создавать модальное окно для сохранения файла
  • Создавать объекты XML и JSON для хранения переменных
  • Сохранять файлы XML и JSON в определенную папку.

Это всего лишь подсказка, как работать с экспортом данных из Sketch. Я только добавил пару атрибутов для старта, но есть огромное количество данных, из которых можно выбрать нужные для ваших целей.

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

Удачи вам в создании собственных плагинов, делитесь достижениями под этим постом. Если есть вопросы, можете их задать тут.

Обо всех найденных ошибках, неточностях перевода и прочих подобных вещах просьба сообщать в личку.

Понравилась статья? Поделить с друзьями:
  • Флорисепт дезинфицирующее средство инструкция по применению
  • Руководство принуждает к увольнению
  • Холодная сварка mastix инструкция по применению отзывы
  • Седимин для животных инструкция по применению для ягнят
  • Руководство по голососбережению л б рудина скачать бесплатно