Реализация MVC в ExtendScript (+Collection.jsx +debug +пряники :) )

  • Автор темы Автор темы SlavaBuck
  • Дата начала Дата начала
Статус
Закрыто для дальнейших ответов.

SlavaBuck

Участник
Топикстартер
Сообщения
75
Реакции
30
Всех с наступающим!

Я сейчас работаю над одним, на мой взгляд, амбициозным проектом, скриншот которого я приложил, но речь не о нём :). Скажу только, что библиотеки, которые я разрабатываю в ходе работы над проектом, и которыми решил поделиться – уже достаточно самостоятельные и, на мой взгляд, действительно имеют определённую ценность и уже сейчас с лёгкостью могут быть использованы для облегчения работы над проектами разной степени сложности.
Пару слов о библиотеке MVC:
Начнём с MVC.jsx– это не что иное, как имплементация известного паттерна проектирования, разрабатываемая мной специально для AdobeExtendScript и ScriptUI. Что бы сильно не растекаться мыслями по бумаге – просто привожу пример небольшого полноценного приложения, использующего мою библиотеку:
Код:
SnpMVC_Simple.jsx
 
#include "../MVC.jsx"
 
// Создаём объект «приложение»:
var myApp = new MVC.Application();
 
// Создаём объект «модель»
var myData = myApp.addModel({
  id:"myData",
  value:{ txt:"Очень большой текст его можно редактировать" }
});
 
// Добавляем представления:
myApp.addView({ id:"st", view:"statictext" });
myApp.addView({ id:"et", view:"edittext { characters:30 }" });
myApp.addView({ id:"bt", view:"button { text:'Ok'}" });  // данное представление не связано ни с одной моделью, оно просто добавится к диалогу.
 
// Добавляем контролёры.
myApp.addController({ binding:"myData.value.txt:st.text" });
myApp.addController({ binding:"myData.value.txt:et.text" });
 
// Запуск приложения: Application.run()
myApp.run();
Данный файл и ещё один, чуть более интересный находятся в архиве с библиотекой. Стоит сказать, что главная идея библиотеки – не только максимальное разделение моделей и их представлений в логике приложения но и максимально простая реализация данной парадигмы MVC с точки зрения программиста.
Первое требование в MVC.jsx реализуется таким образом, что мои модели абсолютно ничего не знают (и не обязаны знать) не только про свои представления, но и про контролёры. Представления, в свою очередь, тоже ничего не знают о моделях (могут узнать, если вам это понадобиться). Им известно только чуть-чуть о контролёрах, самую малость, так, что это может быть совсем незаметно…

Что касается других файлов из архива, скажу пару слов:
Collection.jsx — содержит класс, реализующий коллекции. Не буду здесь много про него рассказывать, скажу только что он мне был крайне необходим для реализации MVC, а в папке test есть отличный вводный пример.
Monkeys.jsx — ну куда же без него. Это небольшой сборник вспомогательных методов, например помогающих реализовать нужное мне наследование. Содержимое его навеяно известными библиотеками underscore.js, prototype.js и личными изысканиями «правильного» пути. Хочу только предупредить – это не тот Monkeys.jsx, который сопровождает многие js–проекты в мире Web.
_debug.jsx — сюда я поместил свои реализации методов _debug() и log() – ценность которых в отладке скриптов я просто не могу передать :). Ну ещё там есть немного мусора… в любом случае всё это в фазе активной разработки.
Вот собственно и вся презентация – пользуйтесь! :)

П.С. Работа над MVC сейчас активно продолжается, сейчас я обдумываю добавление событийной модели взаимодействий между моделями и объектами приложения (будем имплементировать Observer)… так что ещё много чего планирую сделать.
Тоже касается и Collection– не паханное поле для рефакторинга :).
Библиотеки выложены в обменнике:
http://forum.rudtp.ru/resources/biblioteka-mvc-dlja-es.362/history
 

Вложения

  • DialogBuilder2.png
    DialogBuilder2.png
    37.7 КБ · Просм.: 1 063
Последнее редактирование:
  • Спасибо
Реакции: jeth
Нет желания C++ SDK для InD поковырять? Там использованы почти все паттерны из небезызвестного труда )
 
Сейчас скорее нет чем да. Потому, что С++ для меня второй государственный :), а javascript — был совершенно новым. Впервые для себя я открыл его только этим летом и стремление основательно в нём разобраться толкает меня на всевозможные подвиги... К тому же было сложно писать скрипты для Indesign прекрасно понимая его DOM и при этом совершенно не понимая как работает JavaScript. На сегодняшний день я уже вполне в состоянии на глаз определить, что возвращает подобная конструкция : )
Код:
(function pewpew(Infinity, length, __proto__) {
  return [,,Number(~0)[0|0]][pewpew.__proto__.length && Infinity, -~String(this).length >> __proto__] << (0. === .0) + Infinity;
}).apply(typeof pewpew, [,,2])
Да и со ScriptUI хватало вопросов. Так что мой текущий проект — это как бы квинтэссенция всех моих потуг в получении чёрного пояса по JavaScript и ExtendScript в частности.
Думаю через пару тройку месяцев я закончу то что начал и вот тогда непременно посмотрим...
 
Последнее редактирование:
_debug(obj, "all" || "prop" || "func" ) - выводит в консоль описание obj, отображает все его методы, свойства их тип и значение (используется ReflectionInfo интерфейс). Второй необязательный параметр указывает что выводить, по умолчанию выводится всё ("all"). Может объединиться в цепочки или привязываться к контексту...
log(arg1, arg2, ..., argn) - это просто удобное сокращение для $.writeln().
Вместе с мини-таймером все эти методы используются в файле Snp_Collection.jsx из архива.
Отдельно конечно хочется сказать за таймер - никогда прежде не видел, чтобы среда настолько негарантированно обеспечивала выполнение программного кода (я про ESTK). Замерять производительность методов просто бессмысленно - разбросы между запусками иногда несколько сотен процентов...
 
Я наблюдаю, что есть небольшое кол-во людей, заинтересовавшихся библиотекой. Я вчера выложил обновление, которое, по сути, делает эту библиотеку полноценным Фреймворком, но сегодня опять скачали старую версию. Поэтому сообщаю здесь, что доступна новая версия MVC (v1.05).
В новой версии:
- динамическое создание и удаление моделей и представлений (в старой только добавлялись, удалять нужно было в ручном режиме, напрямую работая со ScriptUI или соответствующими коллекциями приложения);
- связывание представлений с моделями напрямую (в одностороннем порядке представление -> модель), что позволяет реализовать т.н. адаптеры, которые легко переключать между различными моделями для их редактирования, что хорошо продемонстрировано в новом файле примера SnpMVC_SimpleApplication.jsx, в котором EditBox выступает в качестве адаптера для редактирования моделей, представляющих заголовки вкладок элемента TabbedPanel. В этом же примере продемонстрирован способ динамического создания и удаления моделей, представлений и их контролёров и, заодно, общая архитектура полноценного приложения в рамках MVC;
- в целом произошло почти двух-кратное расширение интерфейса библиотеки без малейшего ущерба для функциональности кода, основанного на предыдущей редакции;
- подчищен код и комментарии, добавлена шапка с самым общим обзором по содержимому;

П.С.
- на подходе масштабное расширение библиотеки новым компонентом MVC.DOM
В связи с этим мне пришлось провести небольшой рефакторинг конструкторов MVCApplication и других. На способы создания объектов это никак не отразилось, единственное - теперь следует внимательно относиться к использованию new при создании соответствующих объектов. Оно обязательно при создании MVC объектов (MVCApplication, MVCModel, MVCView, MVCController). Хотя библиотека построена так, что, как правило, для создания всех объектов (кроме MVCApplication) вам достаточно использовать интерфейс приложения addXXXX ( { . } ); а создавать MVC объекты напрямую, используя их конструкторы, почти никогда нет необходимости.
http://forum.rudtp.ru/resources/biblioteka-mvc-dlja-es.362/
 
Код:
var Collection = function Collection(arg) {
В чем смысл такой записи вместо просто
Код:
var Collection = function(arg) {
 
На самом деле это очень важно, разница существенная:

В втором случае переменная Collection будет указывать на неименованную/анонимную функцию, а в первом на функцию с именем Collection. Таким образом в первом случае будет создана функция со свойством constructor.name == 'Collection', в во втором == anonimus.
Разница проявляется когда, например, необходимо получить контроль над использованием слова new перед именем функции (когда функция выполняет роль конструктора при создании объекта). В первом случае я могу в теле функции выполнить подобную проверку и вернуть новый объект независимо от использования new:
Код:
if (this.constructor.name != 'Collection') return new Collection();
во втором случае такой возможности уже не будет. Кроме того, во втором случае мы лишаемся возможности проверок instanceof с экземплярами объектов созданных с помощью анонимных конструкторов и т.п.
Уголок Маньяка:
Кроме того, можно было бы взять первый вариант и избавиться от переменной var Collection оставив только function Collection(arg). Но в этом случае мы не сможем произвести правильное наследование для Collection, так как ниже я использую inherit (Collection, Array) и без этого присвоения var Collection = ... на момент вызова inherit(...) мой Collection не будет обладать прототипом, поскольку в таком случае это будет не объект, а именованный идентификатор... хотя в выражениях его можно использовать абсолютно равнозначно с "нормальными" JavaScript-объектами, но, повторюсь, между объектами (в т.ч. объектами-функциями) и идентификаторами есть тонкая но существенная разница.
 
Последнее редактирование:
Да уж попроще чем у некоторых в scriptLib ;)
 
Кроме того, во втором случае мы лишаемся возможности проверок instanceof с экземплярами объектов созданных с помощью анонимных конструкторов и т.п.
У Адоба в запасе есть масса тузов для того, чтобы в корне поломать и не такие мудреные построения.
Один из тезисов статьи, которая у меня все еще в черновиках за недостатком времени на правку: любой пользовательский объект, возвращенный через app.doScript(), превращается в простой Object. И вообще, любой пользовательский объект, прогнанный через связку eval(userobject.toSource()) дает в результате ивана-не-помнящего-родства, а именно Object, для которого нет возможности получить исходный constructor.name или правильно определить instanceof.
Без учета такого специфичного поведения пользовательских объектов достаточно легко получить трудно вылавливаемую ошибку.
 
Видимо есть некая особенность в возврате объекта из динамической области видимости, создаваемой такими конструкциями как eval(...), doScript(...), with() {...} и try catch {...} в отличии от возврата того же объекта из области замыкания, созданной обычной функцией, или, может быть, дело просто в toSource()...
Слава богу - я пока не сталкивался с такими проблемами, так как до сих пор старался избегать eval. Хотя чувствую, что в задачах сериализации/десериализации объектов это может стать проблемой.
 
Последнее редактирование:
Хотя чувствую, что в задачах сериализации/десериализации объектов это может стать проблемой.
Обязательно станет. Ибо сериализация объектов (JSON-совместимая), собственно, и дает такой эффект. А на основе сериализации/десериализации сделано некоторое количество штатных средств и немало скриптов.
Опять же из неопубликованной пока статьи о том, что на хитрый Адоб все равно найдется скрипт с левой резьбой: в определение объекта добавляется строка
Код:
this.generator = {name: this.constructor.name};
которая позволяет получить от объекта userobject.generator.name идентичный исходному userobject.constructor.name.
Заодно в generator можно засунуть еще разной служебной информации, например о версии или еще чего полезного.
 
doScript
Да уж попроще чем у некоторых в scriptLib
Слав, это не в твою сторону было сказано, я вообще о JS - наворотили в своем prototype черти-чего, элементарные вещи нужно делать с какими-то выкрутасами.

любой пользовательский объект, возвращенный через app.doScript(), превращается в простой Object
да, странно, ведь doScript отрабатывает в одном engine:
Код:
var z = 'zzz';
app.doScript( 'z;' ); //--> zzz
Впрочем, извернувшись '))', можно получить желаемое:
Код:
var Collection = function Collection(arg) {};
var obj = new Collection();
app.doScript( 'arguments[0].constructor = arguments[1]; arguments[0].constructor.name', undefined, [obj, Collection] ); //--> Collection
 
to LeshikSan:
Я понял, что это была у тебя "мысль вслух" в отношении JavaScript. По стечению обстоятельств я как раз копался в одной из твоих работ, пытаясь решить проблему динамического выделения некоторых непослушных ListBox-ов... и пребывал под некоторым впечатлением в хорошем смысле этого слова. Подчерпнул много чего интересного для себя. Так что это был - слегка спонтанный, и потому - может немного неудачный, комплимент ;]= (сорри :) )
doScript
К LeshikSan добавить пока просто ничего не могу. Но кажется мне, что должны быть ещё варианты...
 
Разбирать чужой код - самое неблагодарное занятие, так что сочувствую '))'
 
Впрочем, извернувшись, можно получить желаемое
Много удивительного и загадочного таит в себе подмена constructor! :)
Например, вместо Collection можно передать Array, что даст constructor.name == Array, но не даст возможности работать с объектом как с массивом.
 
Ну на практике свойство constructor играет роль только в контексте самого объекта (оно даже не используется в instanceof для идентификации объектов)
даже если покалечить его извне, например так:
Array.__proto__.constructor = Date;
var arr = new Array();
arr - всё равно получит корректный Array. И его свойство constructor будет по прежнему указывать на Array
теперь попробуем покалечить уже сам arr:
arr.__proto__.constructor = Date; // arr.constructor.name ---> 'Date'
arr.push(10); // прекрасно работает хотя связь с конструктором (и всеми его статическими свойствами) для объекта навсегда утеряна
 
В связи с публикацией предварительной версии своего дизайнера диалогов Dialog Builder обновил архив с библиотекой MVC. Эта библиотека (вместе с другими расширениями, включёнными в архив) стала основой для моего дизайнера. Думаю, я бы свихнулся, работая над дизайнером и не имея чего-то, подобного на MVC и MVC.DOM :)
Следует сказать, что в архив вошло расширение DOM основной библиотеки. Данный модуль вносит поддержку многодокументного интерфейса для приложений, использующих MVC.
Библиотека значительно доработана, добавлены новые примеры. В файлах примеров и самой библиотеки очень много длинных комментариев :) так что если кому-то пригодится — буду рад!
 
Статус
Закрыто для дальнейших ответов.