- PVSM.RU - https://www.pvsm.ru -

Мне часто приходилось сталкиваться с нодовыми интерфейсами в программах. Начиная с музыкальных модульных приложений, заканчивая пакетами для создания трехмерной графики.
Идея графического управления логикой программы мне всегда казалась очень элегантным, а в некоторых случаях, единственным удачным решением. Позже, когда помимо музыки и видео я увлекся программированием (в основном PHP, так уж сложилось), мне захотелось попробовать, пусть даже в качестве эксперимента, создать графическую оболочку для выполнения тех нехитрых задач, с которыми я сталкивался в своей работе.
Мое знакомство с нодовым интерфейсом (или графическим программированием) началось с программы Plogue Bidule [1]. Это приложение относится к семейству модульных программ для управления звуком в реальном времени. В свое время эта программа помогала мне решать достаточно нетривиальные задачи, касающиеся обработки звука и синхронизации сценического оборудования на живых выступлениях нескольких моих музыкальных проектов.
Интерфейс программы Plogue Bidule выглядит следующим образом:

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

Plogue Bidule включает в себя много интересных возможностей, которые не ограничены только работой со звуком, но для данной статьи этого описания будет достаточно.
Мне повезло впервые познакомиться с нодовым интерфейсом именно в музыкальной программе, поскольку поток звука, на мой взгляд, отличная аналогия для потока выполнения процедурной программы.
Нодовые редакторы широко используются в графических пакетах для создания 3D графики. В таких программах «черные ящики» описывают сложнейшие структуры и функции, а интерфейс позволяет сохранить свободу и гибкость в построении логики выполнения. Самая интересная, на мой взгляд, реализация нодовой системы в программе Houdini [2].

Так а что же с веб-программированием? Поиск похожих систем не дал результатов. Я подумал, что, возможно, в такой технологии просто отсутствует необходимость. И что идея моя родилась скорее от некомпетентности и наивности. Но желание попробовать создать такой инструмент оказалось сильнее моих сомнений.
Так родился проект Mooha [3].
Каким же образом можно соединить эти три технологии в одной нодовой системе, чтобы получить удобный графический инструмент? В программах для создания 3D графики в нодовых редакторах часто используется разделение на контексты, которые обычно плохо совместимы друг с другом. Но в нашем случае структуры данных не настолько сложны и разнородны, чтобы плодить отдельные редакторы. Поэтому я решил создать единое пространство, разделив ноды на категории по технологиям:
Идея состояла в том, чтобы переход между технологиями был незаметен для пользователя. Чтобы соединение нод разных категорий между собой создавало единый поток выполнения программы.
Рассмотрим по порядку все три категории нод.

Соединяя такие ноды между собой мы получаем простейшую структуру HTML документа. По-умолчанию HTML/XML нода имеет по одному входу и одному выходу. Вход (IN) — то, что помещается внутрь тэга, выход (OUT) — результирующая строка. Таким образом из последнего выхода ноды «HTML» мы получим строку:
<html>
<head>
<meta />
<script />
</head>
<body>
<div>
<div />
<div />
</div>
</body>
</html>
Служебная нода «Merge» используется для возможности повлиять на сортировку входящих соединений. Сортировка доступна в свойствах самой ноды. В последней ноде «HTML» два входящих соединения конкатенируются в том порядке, в котором произошло соединение. Т.е. если сначала подключили «HEAD», а потом «BODY», то в такой последовательности они и появятся в конечной строке.
Атрибуты тэга мы можем прописать в свойствах ноды, но при желании можем выводить их в виде коннекторов для входящих соединений. Например, добавив ноде «my div» коннекторы для атрибутов id и class мы получим такой вид:

<div id="my-id" class="my-class">
<div>my text</div>
</div>
Переменные со строковыми значениями «my-id» и «my-class» попадают в наши атрибуты. Переменная «my text» становится содержимым первого тэга div. Переменные — это уже PHP ноды, о которых речь пойдет чуть ниже.
В качестве коннекторов мы можем выводить и шаблоны для замены текста внутри тэга. Например, если у нас имеется следующий тэг с прописанным содержимым:
<h1>Hello, %user%</h1>
И если шаблон %user% выведен как коннектор, то теперь мы можем заменить этот шаблон переменной следующим образом:

В первом случае текст внутри ноды прописан в ее свойствах и коннектор для данных выключен, а выведен только коннектор для замены шаблона. Во втором случае текст с шаблоном тоже приходит из переменной. И в том и в другом случае на выходе мы получим:
<h1>Hello, Mooha</h1>
Конечно, нет особого смысла заниматься построением HTML кода именно таким образом. В редакторе есть возможность использовать ноды не только с отдельными тэгами, а с целыми кусками кода и даже подключать внешние файлы.
Например, у нас есть файл template.tpl, который содержит два шаблона для замены: %title% и %content%. Содержимое файла следующее:
<html>
<head>
</head>
<body>
<h1>%title%</h1>
<div>%content%</div>
</body>
</html>
Чтобы подключить файл и заменить шаблоны переменными, достаточно построить вот такую схему:

В результате получим:
<html>
<head>
</head>
<body>
<h1>My article title</h1>
<div>My article content</div>
</body>
</html>
Так как мы уже вышли за рамки описания только HTML нод, перейдем к следующему типу — к PHP нодам.

Это простейший пример арифметических вычислений и вызова встроенной функции abs() с помощью PHP нод. Названия нод произвольные и могут меняться в свойствах каждой ноды. Для удобства я переименовал их так, чтобы были видны значения переменных и названия арифметических операций.
Первые три ноды «1», «2» и «5» — это переменные, значения которых соответствуют названиям.
Т.е. в результате мы получим такой скрипт:
<?php
print abs((1+2)-5);
?>
А на выходе просто число 2.
Я постарался реализовать необходимые логические конструкции, обычно используемые в нодовых системах. Например, вот так выглядит сравнение двух переменных (или результатов двух потоков):

Результат будет true (1!=2).
А вот так можно делать выбор потока, исходя из условий:

Здесь каждому красному коннектору в свойствах ноды присвоены значения, с которыми сравнивается значение, поступившее в коннектор оранжевого цвета. Если значения совпадают, то переключатель Switch пропускает соответствующий поток. В этом примере красные коннекторы имеют значения 1, 2 и 3. То есть результат такой конструкции будет «it was 2».
Во всех цепочках в соединениях передаются конечные значения всех предыдущих вычислений потока. Таким образом в следующем примере у нас результат будет тоже «it was 2», но результирующая строка собирается из нескольких действий:

Генерируется примерно следующий код:
$data = 2;
switch ($data)
{
case "1": // заданное в первом коннекторе значение для сравнения
$result = "it was 1";
break;
case "2":// заданное во втором коннекторе значение для сравнения
$result = str_replace("foo", "2", "in was foo");
break;
case "3": // заданное в третьем коннекторе значение для сравнения
$result = "it was 3";
break;
default:
$result=false;
}
var_dump($result);
Отдельного внимания заслуживает нода Copy, которая по значениям из первого соединения размножает шаблон из второго. Это полезно, например, в том случае, когда нам необходимо размножить HTML код и заменить в нем шаблоны значениями из массива. В самом простом случае с одномерным массивом такая схема выглядит следующим образом:

Для наглядности я назвал ноды значениями их переменных. В первый коннектор (для значений) мы запускаем JSON массив, во второй — HTML код с шаблоном для замены. Все ноды (или цепочки нод), подключенные к коннектору шаблона будут скопированы столько раз, сколько имеется элементов в массиве, подключенному к первому коннектору. При этом у каждой ноды слева от такого соединения появится специальная настройка, какие именно данные в ней заменять элементом из массива. В нашем случае в ноде с параграфом настроена замена шаблона %number%. Т.е. результатом этой схемы будет следующий код:
<p>first paragraph</p>
<p>second paragraph</p>
<p>third paragraph</p>
Вот пример сложнее, с массивом объектов и с несколькими нодами:

В переменной «JSON» следующий массив объектов:
[
{"title":"first title","content":"first content"},
{"title":"second title","content":"second content"}
]
В ноде c тэгом <h1> шаблону %title% в качестве источника для копирования указан объект «title» массива. В ноде с тэгом <p> шаблону %content% в качестве источника для копирования указан объект «content» массива. В результате генерируется следующий код:
<div>
<h1>first title</h1>
<p>first content</p>
</div>
<div>
<h1>second title</h1>
<p>second content</p>
</div>
По умолчанию нода Copy имеет тип foreach и работает так, как я описал в двух примерах выше. Но мы можем выбрать тип for, после чего появится возможность настроить стартовое значение счетчика, конечное значение, шаг и тип инкремента (возрастающий или убывающий).

Базовая нода для запросов — это таблица, а точнее — оператор SELECT, который выполняется по-умолчанию при подключении к любому полю. Присоединившись к последнему выходу, обозначенному символом "*", мы выполним запрос:
SELECT * FROM `authors`
Результат получим в виде массива объектов в формате JSON. Это подключение «поймает» следующий набор:
[
{ "id": "1", "Name": "Iain Menzies Banks", "Birthday": "1954-02-16" },
{ "id": "2", "Name": "Charles Michael Palahniuk", "Birthday": "1962-02-21" }
]
Чтобы получить массив только имен авторов, соединение будет выглядеть следующим образом:

На выходе мы получим обычный одномерный массив:
["Iain Menzies Banks", "Charles Michael Palahniuk"]
На данный момент в редакторе есть несколько служебных SQL нод, с помощью которых можно выполнять несложные выборки. Например:

В первом случае (слева) мы выполним запрос:
SELECT * FROM `authors` WHERE id=1
И получим массив с одним объектом:
[
{ "id": "1", "Name": "Iain Menzies Banks", "Birthday": "1954-02-16" }
]
Во втором случае (справа) запрос будет таким:
SELECT * FROM `authors` WHERE Name LIKE '%Banks%'
А вот запрос с несколькими условиями и сортировкой:

SELECT * FROM `authors` WHERE (`Birthday` < '1990-01-01' AND `Birthday` > '1920-01-01') ORDER BY `Name` ASC
Служебная нода AND выступает здесь не только в качестве логического оператора в условии WHERE, но и в качестве соединения команд в одном запросе (в нашем случае, добавление к запросу команды ORDER BY)
Запросы INSERT, UPDATE и DELETE вызываются подключениями к таким же нодам-таблицам, но только с соответствующими настройками.
Простой INSERT выглядит так:

INSERT INTO `authors` (`id`, `Name`, `Birthday`) VALUES ('1', 'Iain Menzies Banks', '1954-02-16')
UPDATE:

UPDATE `authors` SET `Name`='Iain Banks', `Birthday`='1954-02-17' WHERE `id`='1'
DELETE:

DELETE FROM `authors` WHERE `Birthday`<'1954-02-17'
В процессе построения генерируются запросы с использованием PDO.
С помощью нодовых соединений можно построить почти любые типы запросов, в том числе организовать JOIN объединения. Однако не для всех сложных запросов я нашел идеальные графические решения, поэтому оставил возможность вписать текст SQL инструкции в специальную ноду. Немного забегая вперед, скажу, что работу с базой данных можно построить в системе и другими способами, не только с помощью MySQL нод.
И, наконец, пример объединения всех трех технологий (MySQL, PHP и HTML) в одной цепочке, может выглядеть следующим образом:

Здесь у нас имеется заготовленный шаблон library.tpl для вывода книг из домашней библиотеки. Содержимое примерно следующее:
<div>
<select>
<option value=''>all authors</option>
%author-list%
</select>
</div>
<br />
<div>%book-list%</div>
У нас есть два шаблона для замены: %author-list% и %book-list%. В первый нам нужно вывести тэги option, соответствующие всем авторам. Во второй — все книги, а именно — размножить блок «book block», в котором содержится HTML код вывода одной книги. Первый список нам нужен для того, чтобы фильтровать книги по автору (для этого действия мы будем использовать функцию jquery в шаблоне library.tpl).
Для первого шаблона %author-list% мы добавляем ноду Copy («Author Copy»), которая по данным из таблицы «authors» клонирует тэг «option», при этом внутрь тэга помещает имя автора, а атрибуту «value» присваивает значение id. На выходе ноды «Author Copy» мы получим следующий код:
<option value='1'>Iain Menzies Banks</option>
<option value='2'>Charles Michael Palahniuk</option>
<option value='3'>Mikhail Bulgakov</option>
Нода «book block» содержит следующий код:
<div class='author-%author-id%'>
<img width='100' src='images/%image%' align='left' />
<b>%title%</b> by <b>%author%</b>
<br />
ISBN: %isbn%<br />
<small>%description%</small>
<br clear='all'/>
</div>
Шаблоны в этом блоке заменяются данными книги из объединенной выборки двух таблиц «books» и «authors». Таблица «authors» здесь нужна для вывода в книге имени ее автора. Сам блок клонируется столько раз, сколько у нас записей в таблице «books».
Вот что у нас получится в результате:
В этой статье я постарался описать основную идею работы системы. В проекте реализовано много других возможностей. Некоторые из них — типичные для нодовых интерфейсов, некоторые мне пришлось изобретать самому, с учетом специфики редактора. Вот неполный список доступных на данный момент инструментов:
Результат работы редактора — набор генерированных PHP файлов. Каждая сцена — это отдельный скрипт, сохраненный в специальной папке. Такой скрипт может выполняться без помощи самого редактора. Кроме того, в настройках системы можно выбрать стартовый сценарий, который будет включать в себя главную логику работы всего проекта, исходя из полученных данных (строка запроса или передаваемые параметры).
Цель написания этой статьи — понять, насколько такая система может быть интересна для разработчиков. Я слишком долго и увлеченно занимался этим проектом и у меня изменилось отношение к нему с того момента, как я начал над ним работу. Поэтому я буду крайне признателен за отзывы и комментарии, за вопросы и предложения, а также за участие в тестировании.
На данный момент проект почти закончен и идет процесс отладки. Совсем скоро я буду готов выложить бета-версию. Для того, чтобы быть в курсе процесса разработки и узнать о выходе проекта — нужно просто подписаться на получение новостей любым удобным способом на сайте Mooha.net [3].
В проекте использованы следующие инструменты:
Серверная часть тестировалась в таких условиях:
Автор: younnat
Источник [9]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/mysql/48037
Ссылки в тексте:
[1] Plogue Bidule: http://www.plogue.com/products/bidule/
[2] Houdini: http://www.sidefx.com/
[3] Mooha: http://mooha.net
[4] JQuery 2.0: http://jquery.com/
[5] Codemirror: http://codemirror.net/
[6] CKEditor: http://ckeditor.com/
[7] Spectrum Colorpicker: http://plugins.jquery.com/spectrum/
[8] jQuery File Tree Plugin: http://www.abeautifulsite.net/blog/2008/03/jquery-file-tree/
[9] Источник: http://habrahabr.ru/post/201646/
Нажмите здесь для печати.